1mod av1;
40mod h264;
41mod h265;
42#[cfg(test)]
43pub(crate) mod mock;
44#[cfg(test)]
45mod session_tests;
46mod vp8;
47mod vp9;
48
49use std::collections::HashMap;
50use std::ffi::{CStr, CString, c_char, c_int, c_uint, c_void};
51use std::os::fd::{AsRawFd, FromRawFd, OwnedFd};
52use std::ptr;
53use std::sync::{Arc, OnceLock};
54
55use smithay::backend::allocator::{Buffer, dmabuf::Dmabuf};
56use va_sys::*;
57
58use super::codec::{
59 Codec, Hardware, VIDEO_HEADER_LEN, av1_is_key, frame_type_from_key, h264_frame_type,
60 h265_frame_type, push_video_header, vp8_is_key, vp9_is_key,
61};
62use super::dmabuf_identity::DmaBufIdentity;
63use super::frame_rate::FrameRate;
64use super::reference::{
65 ANCHORS, Invalidation, REFERENCE_FRAMES, Reference, ReferenceReport, ReferenceSlots,
66 ReferenceWindow, SlotPlan,
67};
68use super::session::{RateSettings, check_host_frame};
69use super::sps::{NoReorder, h264_frame_num_range};
70use crate::CaptureSettings;
71
72const SLICES: u32 = 4;
79const REFINE_BITS_PER_PIXEL: f64 = 0.04;
89
90const HELD_KEY_BITS_PER_PIXEL: f64 = 1.6;
98
99fn refines(bps: u32, fps: f64, width: u32, height: u32) -> bool {
102 let pixels = f64::from(width) * f64::from(height);
103 f64::from(bps) / fps.max(1.0) >= REFINE_BITS_PER_PIXEL * pixels.max(1.0)
104}
105
106fn held_key_bits(bps: u32, width: u32, height: u32) -> f64 {
110 let pixels = f64::from(width) * f64::from(height);
111 (f64::from(bps) * crate::encoders::HELD_KEY_BUDGET_S).max(HELD_KEY_BITS_PER_PIXEL * pixels)
112}
113
114fn coded_buffer_size(width: u32, height: u32) -> u32 {
115 3 * width * height + (1 << 16)
116}
117
118fn libva() -> Result<VaApi, String> {
120 static LIBVA: OnceLock<Result<Libva, String>> = OnceLock::new();
121 LIBVA
122 .get_or_init(|| unsafe { Libva::load() })
123 .as_ref()
124 .map(|lib| lib.api)
125 .map_err(Clone::clone)
126}
127
128#[derive(Clone, Copy, Debug, PartialEq, Eq)]
131pub enum Input {
132 Dmabuf,
133 Host { rgba: bool },
134}
135
136const FULLCOLOR_FOURCCS: [u32; 2] = [VA_FOURCC_444P, VA_FOURCC_XYUV];
140
141fn surface_fourccs(fullcolor: bool, bit_depth: u32) -> &'static [u32] {
143 match (fullcolor, bit_depth) {
144 (false, 10) => &[VA_FOURCC_P010],
145 (true, 10) => &[VA_FOURCC_Y410],
146 (true, _) => &FULLCOLOR_FOURCCS,
147 (false, _) => &[VA_FOURCC_NV12],
148 }
149}
150
151fn rt_format(fullcolor: bool, bit_depth: u32) -> u32 {
153 match (fullcolor, bit_depth) {
154 (false, 10) => VA_RT_FORMAT_YUV420_10,
155 (true, 10) => VA_RT_FORMAT_YUV444_10,
156 (true, _) => VA_RT_FORMAT_YUV444,
157 (false, _) => VA_RT_FORMAT_YUV420,
158 }
159}
160
161pub(crate) fn fourcc_name(fourcc: u32) -> String {
163 match fourcc {
164 VA_FOURCC_NV12 => "nv12".into(),
165 VA_FOURCC_P010 => "p010".into(),
166 VA_FOURCC_Y410 => "y410".into(),
167 VA_FOURCC_444P => "yuv444p".into(),
168 VA_FOURCC_XYUV => "vuyx".into(),
169 VA_FOURCC_BGRA => "bgra".into(),
170 VA_FOURCC_RGBA => "rgba".into(),
171 other => String::from_utf8_lossy(&other.to_le_bytes())
172 .trim()
173 .to_string(),
174 }
175}
176
177unsafe extern "C" fn log_error(_user: *mut c_void, message: *const c_char) {
178 if !message.is_null() {
179 eprintln!(
180 "[vaapi] {}",
181 unsafe { CStr::from_ptr(message) }
182 .to_string_lossy()
183 .trim_end()
184 );
185 }
186}
187
188unsafe extern "C" fn log_info(_user: *mut c_void, message: *const c_char) {
189 if !message.is_null() {
190 crate::log::debug!(
191 "[vaapi] {}",
192 unsafe { CStr::from_ptr(message) }
193 .to_string_lossy()
194 .trim_end()
195 );
196 }
197}
198
199#[derive(Clone, Copy, Debug, PartialEq)]
214enum ConstantRate {
215 Buffer,
217 Unbounded,
219 Seconds(f64),
221}
222
223const IHD_AV1_BUFFER_S: f64 = 0.1;
225
226const PSNR_ROW_STEP: usize = 8;
229
230const UNREADABLE_DB: f32 = 12.0;
233
234fn strip_zero_padding(out: &mut Vec<u8>, from: usize) {
238 let end = out[from..]
239 .iter()
240 .rposition(|&b| b != 0)
241 .map_or(from, |i| from + i + 1);
242 out.truncate(end);
243}
244
245pub(crate) struct Device {
247 api: VaApi,
248 display: VADisplay,
249 _fd: OwnedFd,
250 vendor: String,
251 vce: bool,
253 whole_picture_vdenc: bool,
256}
257
258fn whole_picture_vdenc(fd: c_int) -> bool {
264 let mut stat = unsafe { std::mem::zeroed::<libc::stat>() };
265 if unsafe { libc::fstat(fd, &mut stat) } != 0 {
266 return false;
267 }
268 let id = |name: &str| {
269 let path = format!(
270 "/sys/dev/char/{}:{}/device/{name}",
271 libc::major(stat.st_rdev),
272 libc::minor(stat.st_rdev)
273 );
274 let text = std::fs::read_to_string(path).ok()?;
275 u32::from_str_radix(text.trim().trim_start_matches("0x"), 16).ok()
276 };
277 id("vendor") == Some(0x8086) && id("device").is_some_and(skylake_or_broxton)
278}
279
280fn skylake_or_broxton(device: u32) -> bool {
282 matches!(
283 device,
284 0x1900..=0x19ff | 0x0a84 | 0x1a84 | 0x1a85 | 0x5a84 | 0x5a85
285 )
286}
287
288fn amd_vce(fd: c_int) -> bool {
293 #[repr(C)]
294 struct Version {
295 major: c_int,
296 minor: c_int,
297 patch: c_int,
298 name_len: usize,
299 name: *mut c_char,
300 date_len: usize,
301 date: *mut c_char,
302 desc_len: usize,
303 desc: *mut c_char,
304 }
305 #[repr(C)]
306 struct Info {
307 return_pointer: u64,
308 return_size: u32,
309 query: u32,
310 ip_type: u32,
311 ip_instance: u32,
312 reserved: [u32; 2],
313 }
314 const DRM_IOCTL_VERSION: u64 = 0xc040_6400;
315 const DRM_IOCTL_AMDGPU_INFO: u64 = 0x4020_6445;
316 const AMDGPU_INFO_HW_IP_COUNT: u32 = 0x03;
317 const AMDGPU_HW_IP_VCE: u32 = 4;
318 const AMDGPU_HW_IP_VCN_ENC: u32 = 7;
319 let mut name = [0u8; 16];
320 let mut version = Version {
321 major: 0,
322 minor: 0,
323 patch: 0,
324 name_len: name.len(),
325 name: name.as_mut_ptr() as *mut c_char,
326 date_len: 0,
327 date: ptr::null_mut(),
328 desc_len: 0,
329 desc: ptr::null_mut(),
330 };
331 if unsafe { libc::ioctl(fd, DRM_IOCTL_VERSION as _, &mut version as *mut Version) } != 0
332 || name[..version.name_len.min(name.len())] != *b"amdgpu"
333 {
334 return false;
335 }
336 let engines = |ip_type: u32| {
337 let mut count: u32 = 0;
338 let mut info = Info {
339 return_pointer: &mut count as *mut u32 as u64,
340 return_size: 4,
341 query: AMDGPU_INFO_HW_IP_COUNT,
342 ip_type,
343 ip_instance: 0,
344 reserved: [0; 2],
345 };
346 if unsafe { libc::ioctl(fd, DRM_IOCTL_AMDGPU_INFO as _, &mut info as *mut Info) } != 0 {
347 0
348 } else {
349 count
350 }
351 };
352 engines(AMDGPU_HW_IP_VCE) > 0 && engines(AMDGPU_HW_IP_VCN_ENC) == 0
353}
354
355unsafe impl Send for Device {}
357unsafe impl Sync for Device {}
358
359impl Drop for Device {
360 fn drop(&mut self) {
361 unsafe { (self.api.vaTerminate)(self.display) };
362 }
363}
364
365impl Device {
366 pub(crate) fn open(api: &VaApi, encode_node_index: i32) -> Result<Self, String> {
369 let render_node = format!("/dev/dri/renderD{}", 128 + encode_node_index.max(0));
370 if crate::get_gpu_driver(encode_node_index.max(0)).contains("nvidia") {
374 return Err(format!(
375 "{render_node} is NVIDIA's, whose VA driver encodes nothing"
376 ));
377 }
378 let path = CString::new(render_node.clone()).unwrap();
379 let raw = unsafe { libc::open(path.as_ptr(), libc::O_RDWR | libc::O_CLOEXEC) };
380 if raw < 0 {
381 return Err(format!(
382 "{render_node}: {}",
383 std::io::Error::last_os_error()
384 ));
385 }
386 Self::on(api, unsafe { OwnedFd::from_raw_fd(raw) }, &render_node)
387 }
388
389 pub(crate) fn on(api: &VaApi, fd: OwnedFd, render_node: &str) -> Result<Self, String> {
392 let display = unsafe { (api.vaGetDisplayDRM)(fd.as_raw_fd()) };
393 if display.is_null() {
394 return Err(format!("{render_node} carries no VA display"));
395 }
396 unsafe {
397 (api.vaSetErrorCallback)(display, Some(log_error), ptr::null_mut());
398 (api.vaSetInfoCallback)(display, Some(log_info), ptr::null_mut());
399 }
400 let (mut major, mut minor) = (0 as c_int, 0 as c_int);
401 let status = unsafe { (api.vaInitialize)(display, &mut major, &mut minor) };
402 if status != VA_STATUS_SUCCESS as VAStatus {
403 return Err(format!(
404 "{render_node}: no VA driver initialized ({})",
405 error_text(api, status)
406 ));
407 }
408 let vendor = unsafe {
409 let text = (api.vaQueryVendorString)(display);
410 if text.is_null() {
411 String::new()
412 } else {
413 CStr::from_ptr(text).to_string_lossy().into_owned()
414 }
415 };
416 let vce = amd_vce(fd.as_raw_fd());
417 let whole_picture_vdenc = whole_picture_vdenc(fd.as_raw_fd());
418 Ok(Self {
419 api: *api,
420 display,
421 _fd: fd,
422 vendor,
423 vce,
424 whole_picture_vdenc,
425 })
426 }
427
428 fn rounds_linear_pitch(&self) -> bool {
432 self.vendor.contains("Intel")
433 }
434
435 fn hardware(&self) -> Hardware {
438 if self.vendor.contains("Intel") {
439 Hardware::VaapiIntel
440 } else {
441 Hardware::Vaapi
442 }
443 }
444
445 fn one_slice_h264(&self, entrypoint: VAEntrypoint) -> bool {
448 self.vce || (self.whole_picture_vdenc && entrypoint == VAEntrypointEncSliceLP)
449 }
450
451 fn keeps_anchors(&self) -> bool {
457 self.vendor.contains("radeonsi")
458 }
459
460 fn starves_in_a_small_buffer(&self) -> bool {
463 self.vendor.contains("iHD")
464 }
465
466 fn h264_driver_level(&self) -> u32 {
474 if self.vendor.contains("iHD") {
475 52
476 } else {
477 u32::MAX
478 }
479 }
480
481 fn check(&self, status: VAStatus, what: &str) -> Result<(), String> {
482 if status == VA_STATUS_SUCCESS as VAStatus {
483 Ok(())
484 } else {
485 Err(format!("{what}: {}", error_text(&self.api, status)))
486 }
487 }
488
489 fn profiles(&self) -> Result<Vec<VAProfile>, String> {
491 let mut profiles = vec![
492 0 as VAProfile;
493 unsafe { (self.api.vaMaxNumProfiles)(self.display) }.max(0) as usize
494 ];
495 let mut listed: c_int = 0;
496 self.check(
497 unsafe {
498 (self.api.vaQueryConfigProfiles)(self.display, profiles.as_mut_ptr(), &mut listed)
499 },
500 "vaQueryConfigProfiles",
501 )?;
502 profiles.truncate(listed.max(0) as usize);
503 Ok(profiles)
504 }
505
506 fn encode_entrypoints(&self, profile: VAProfile) -> Vec<VAEntrypoint> {
513 let mut entrypoints = vec![
514 0 as VAEntrypoint;
515 unsafe { (self.api.vaMaxNumEntrypoints)(self.display) }.max(0)
516 as usize
517 ];
518 let mut listed: c_int = 0;
519 let status = unsafe {
520 (self.api.vaQueryConfigEntrypoints)(
521 self.display,
522 profile,
523 entrypoints.as_mut_ptr(),
524 &mut listed,
525 )
526 };
527 if status != VA_STATUS_SUCCESS as VAStatus {
528 return Vec::new();
529 }
530 let listed = &entrypoints[..listed.max(0) as usize];
531 [VAEntrypointEncSliceLP, VAEntrypointEncSlice]
532 .into_iter()
533 .filter(|e| listed.contains(e))
534 .collect()
535 }
536
537 fn attribute(
540 &self,
541 profile: VAProfile,
542 entrypoint: VAEntrypoint,
543 kind: VAConfigAttribType,
544 ) -> Option<u32> {
545 let mut attrib = VAConfigAttrib {
546 type_: kind,
547 value: 0,
548 };
549 let status = unsafe {
550 (self.api.vaGetConfigAttributes)(self.display, profile, entrypoint, &mut attrib, 1)
551 };
552 (status == VA_STATUS_SUCCESS as VAStatus && attrib.value != VA_ATTRIB_NOT_SUPPORTED)
553 .then_some(attrib.value)
554 }
555
556 fn surface_attributes(&self, config: VAConfigID) -> Vec<VASurfaceAttrib> {
559 let mut count: c_uint = 0;
560 let mut attribs: Vec<VASurfaceAttrib> = Vec::new();
561 for pass in 0..2 {
562 let list = if pass == 0 {
563 ptr::null_mut()
564 } else {
565 attribs.as_mut_ptr()
566 };
567 if unsafe {
568 (self.api.vaQuerySurfaceAttributes)(self.display, config, list, &mut count)
569 } != VA_STATUS_SUCCESS as VAStatus
570 {
571 return Vec::new();
572 }
573 attribs.resize(count as usize, unsafe { std::mem::zeroed() });
574 }
575 attribs
576 }
577
578 fn surface_fourccs(&self, config: VAConfigID) -> Vec<u32> {
580 self.surface_attributes(config)
581 .iter()
582 .filter(|a| a.type_ == VASurfaceAttribPixelFormat)
583 .map(|a| unsafe { a.value.value.i } as u32)
584 .collect()
585 }
586
587 fn surface_alignment(&self, config: VAConfigID) -> (u32, u32) {
590 self.surface_attributes(config)
591 .iter()
592 .find(|a| a.type_ == VASurfaceAttribAlignmentSize)
593 .map(|a| {
594 let v = unsafe { a.value.value.i } as u32;
595 (1 << (v & 0xf), 1 << ((v & 0xf0) >> 4))
596 })
597 .unwrap_or((1, 1))
598 }
599
600 fn create_surfaces(
602 &self,
603 rt_format: u32,
604 fourcc: u32,
605 width: u32,
606 height: u32,
607 count: usize,
608 ) -> Result<Vec<VASurfaceID>, String> {
609 let mut attrib: VASurfaceAttrib = unsafe { std::mem::zeroed() };
610 attrib.type_ = VASurfaceAttribPixelFormat;
611 attrib.flags = VA_SURFACE_ATTRIB_SETTABLE;
612 attrib.value.type_ = VAGenericValueTypeInteger;
613 attrib.value.value.i = fourcc as i32;
614 let mut surfaces = vec![VA_INVALID_SURFACE; count];
615 self.check(
616 unsafe {
617 (self.api.vaCreateSurfaces)(
618 self.display,
619 rt_format,
620 width,
621 height,
622 surfaces.as_mut_ptr(),
623 count as c_uint,
624 &mut attrib,
625 1,
626 )
627 },
628 &format!(
629 "this VA-API driver allocates no {} surfaces",
630 fourcc_name(fourcc)
631 ),
632 )?;
633 Ok(surfaces)
634 }
635
636 fn destroy_surfaces(&self, surfaces: &mut [VASurfaceID]) {
637 if !surfaces.is_empty() {
638 unsafe {
639 (self.api.vaDestroySurfaces)(
640 self.display,
641 surfaces.as_mut_ptr(),
642 surfaces.len() as c_int,
643 )
644 };
645 }
646 }
647
648 fn create_buffer(
649 &self,
650 context: VAContextID,
651 kind: VABufferType,
652 data: &[u8],
653 ) -> Result<VABufferID, String> {
654 self.create_buffer_raw(
655 context,
656 kind,
657 data.len() as c_uint,
658 data.as_ptr() as *mut c_void,
659 )
660 }
661
662 fn create_output_buffer(
665 &self,
666 context: VAContextID,
667 kind: VABufferType,
668 size: u32,
669 ) -> Result<VABufferID, String> {
670 self.create_buffer_raw(context, kind, size as c_uint, ptr::null_mut())
671 }
672
673 fn create_buffer_raw(
674 &self,
675 context: VAContextID,
676 kind: VABufferType,
677 size: c_uint,
678 data: *mut c_void,
679 ) -> Result<VABufferID, String> {
680 let mut id = VA_INVALID_ID;
681 self.check(
682 unsafe {
683 (self.api.vaCreateBuffer)(self.display, context, kind, size, 1, data, &mut id)
684 },
685 &format!("vaCreateBuffer(type {kind})"),
686 )?;
687 Ok(id)
688 }
689}
690
691fn error_text(api: &VaApi, status: VAStatus) -> String {
692 let text = unsafe { (api.vaErrorStr)(status) };
693 if text.is_null() {
694 format!("VA status {status}")
695 } else {
696 unsafe { CStr::from_ptr(text) }
697 .to_string_lossy()
698 .into_owned()
699 }
700}
701
702pub(crate) fn probe_codecs(encode_node_index: i32) -> Result<Vec<Served>, String> {
708 probe_codecs_on(&Device::open(&libva()?, encode_node_index)?)
709}
710
711pub(crate) type Served = (Codec, super::Formats);
713
714pub(crate) fn probe_codecs_on(device: &Device) -> Result<Vec<Served>, String> {
718 let profiles = device.profiles()?;
719 let entrypoints = |profile: VAProfile| {
720 if profiles.contains(&profile) {
721 device.encode_entrypoints(profile)
722 } else {
723 Vec::new()
724 }
725 };
726 Ok(Codec::VIDEO
727 .into_iter()
728 .filter(|&codec| {
729 profile_ladder(codec, false, 8)
730 .into_iter()
731 .any(|p| !entrypoints(p).is_empty())
732 })
733 .map(|codec| {
734 let carries = |fullcolor: bool, bit_depth: u32| {
735 profile_ladder(codec, fullcolor, bit_depth)
736 .into_iter()
737 .any(|p| {
738 entrypoints(p).into_iter().any(|e| {
739 device
740 .attribute(p, e, VAConfigAttribRTFormat)
741 .is_none_or(|f| f & rt_format(fullcolor, bit_depth) != 0)
742 })
743 })
744 };
745 (
746 codec,
747 super::Formats {
748 fullcolor: carries(true, 8),
749 ten_bit: [carries(false, 10), carries(true, 10)],
750 },
751 )
752 })
753 .collect())
754}
755
756fn profile_ladder(codec: Codec, fullcolor: bool, bit_depth: u32) -> Vec<VAProfile> {
761 match (codec, fullcolor, bit_depth) {
762 (Codec::H264, false, 8) => vec![
763 VAProfileH264High,
764 VAProfileH264Main,
765 VAProfileH264ConstrainedBaseline,
766 ],
767 (Codec::H265, false, 8) => vec![VAProfileHEVCMain],
768 (Codec::H265, false, 10) => vec![VAProfileHEVCMain10],
769 (Codec::H265, true, 8) => vec![VAProfileHEVCMain444],
770 (Codec::H265, true, 10) => vec![VAProfileHEVCMain444_10],
771 (Codec::Vp8, false, 8) => vec![VAProfileVP8Version0_3],
772 (Codec::Vp9, false, 8) => vec![VAProfileVP9Profile0],
773 (Codec::Vp9, true, 8) => vec![VAProfileVP9Profile1],
774 (Codec::Vp9, false, 10) => vec![VAProfileVP9Profile2],
775 (Codec::Vp9, true, 10) => vec![VAProfileVP9Profile3],
776 (Codec::Av1, false, 8 | 10) => vec![VAProfileAV1Profile0],
777 _ => Vec::new(),
778 }
779}
780
781pub(super) struct Buffers {
783 entries: Vec<(VABufferType, Vec<u8>)>,
784}
785
786impl Buffers {
787 fn new() -> Self {
788 Self {
789 entries: Vec::new(),
790 }
791 }
792
793 pub(super) fn push<T: Copy>(&mut self, kind: VABufferType, value: &T) {
795 let bytes = unsafe {
796 std::slice::from_raw_parts(value as *const T as *const u8, std::mem::size_of::<T>())
797 };
798 self.entries.push((kind, bytes.to_vec()));
799 }
800
801 pub(super) fn push_misc<T: Copy>(&mut self, kind: VAEncMiscParameterType, value: &T) {
803 let mut bytes = kind.to_ne_bytes().to_vec();
804 bytes.extend_from_slice(unsafe {
805 std::slice::from_raw_parts(value as *const T as *const u8, std::mem::size_of::<T>())
806 });
807 self.entries.push((VAEncMiscParameterBufferType, bytes));
808 }
809
810 pub(super) fn push_packed(&mut self, kind: u32, bytes: &[u8], bit_length: u32) {
812 let param = VAEncPackedHeaderParameterBuffer {
813 type_: kind,
814 bit_length,
815 has_emulation_bytes: 1,
816 va_reserved: [0; 4],
817 };
818 self.push(VAEncPackedHeaderParameterBufferType, ¶m);
819 self.entries
820 .push((VAEncPackedHeaderDataBufferType, bytes.to_vec()));
821 }
822
823 #[cfg(test)]
824 pub(crate) fn entries(&self) -> &[(VABufferType, Vec<u8>)] {
825 &self.entries
826 }
827}
828
829#[derive(Clone, Copy)]
831pub(super) struct Negotiated {
832 pub profile: VAProfile,
833 pub entrypoint: VAEntrypoint,
834 pub packed: u32,
836 pub rc_mode: u32,
838 pub width: u32,
839 pub height: u32,
840 pub fps: FrameRate,
841 pub bits_per_second: u32,
842 pub dpb: u32,
844 pub dpb_level: u32,
847 pub fullcolor: bool,
848 pub bit_depth: u32,
849 pub min_qp: u32,
851 pub max_qp: u32,
852 pub qp_floor: u32,
855 pub unit_qp_deltas: bool,
859 pub h264_driver_level: u32,
862 pub roi_regions: u32,
866}
867
868pub(super) struct Frame<'a> {
871 pub key: bool,
872 pub pts: u64,
874 pub key_pts: u64,
876 pub recon: VASurfaceID,
877 pub coded: VABufferID,
878 pub reference: Option<(u64, VASurfaceID)>,
880 pub held: &'a [(u64, VASurfaceID, bool)],
883 pub anchored: bool,
885 pub anchor: Option<u8>,
887 pub long_term: &'a [(u64, u8)],
889 pub qp: u32,
891 pub slots: SlotPlan,
893 pub slot_surfaces: [VASurfaceID; 3],
895}
896
897enum Arm {
899 H264(Box<h264::Arm>),
900 H265(h265::Arm),
901 Vp8(vp8::Arm),
902 Vp9(vp9::Arm),
903 Av1(av1::Arm),
904}
905
906impl Arm {
907 fn alignment(&self) -> (u32, u32) {
909 match self {
910 Arm::H264(_) | Arm::Vp8(_) => (16, 16),
911 Arm::H265(a) => a.alignment(),
912 Arm::Vp9(_) => (64, 64),
913 Arm::Av1(a) => a.alignment(),
914 }
915 }
916
917 fn wanted_packed(&self) -> u32 {
919 match self {
920 Arm::H264(_) | Arm::H265(_) => {
921 VA_ENC_PACKED_HEADER_SEQUENCE | VA_ENC_PACKED_HEADER_SLICE
922 }
923 Arm::Av1(_) => VA_ENC_PACKED_HEADER_SEQUENCE | VA_ENC_PACKED_HEADER_PICTURE,
924 Arm::Vp8(_) | Arm::Vp9(_) => 0,
925 }
926 }
927
928 fn tracks_references(&self, packed: u32) -> bool {
932 match self {
933 Arm::H264(_) | Arm::H265(_) => packed & VA_ENC_PACKED_HEADER_SLICE != 0,
934 Arm::Av1(_) => packed & VA_ENC_PACKED_HEADER_PICTURE != 0,
935 Arm::Vp8(_) | Arm::Vp9(_) => true,
936 }
937 }
938
939 fn sequence(&mut self, negotiated: &Negotiated, out: &mut Buffers) {
940 match self {
941 Arm::H264(a) => a.sequence(negotiated, out),
942 Arm::H265(a) => a.sequence(negotiated, out),
943 Arm::Vp8(a) => a.sequence(negotiated, out),
944 Arm::Vp9(a) => a.sequence(negotiated, out),
945 Arm::Av1(a) => a.sequence(negotiated, out),
946 }
947 }
948
949 fn level(&self) -> u32 {
952 match self {
953 Arm::H264(a) => a.level(),
954 Arm::H265(a) => a.level(),
955 Arm::Av1(a) => a.level(),
956 Arm::Vp8(_) | Arm::Vp9(_) => 0,
957 }
958 }
959
960 fn picture(
961 &mut self,
962 negotiated: &Negotiated,
963 frame: &Frame,
964 out: &mut Buffers,
965 ) -> Result<(), String> {
966 match self {
967 Arm::H264(a) => a.picture(negotiated, frame, out),
968 Arm::H265(a) => a.picture(negotiated, frame, out),
969 Arm::Vp8(a) => a.picture(negotiated, frame, out),
970 Arm::Vp9(a) => a.picture(negotiated, frame, out),
971 Arm::Av1(a) => a.picture(negotiated, frame, out),
972 }
973 }
974}
975
976enum References {
978 Window(ReferenceWindow),
979 Slots(ReferenceSlots),
980}
981
982struct HostUpload {
985 surface: VASurfaceID,
986 fourcc: u32,
987 derive: bool,
988 image: Option<VAImage>,
989}
990
991const DMABUF_IMPORTS: usize = 8;
993
994const REFINE_FINER: u32 = 3;
1000
1001const REFINE_REACH: u32 = 10;
1007
1008const INTEL_VDENC_NATIVE_ROI: u32 = 3;
1010
1011fn roi_regions(
1020 attribute: Option<u32>,
1021 hardware: Hardware,
1022 entrypoint: VAEntrypoint,
1023 cbr: bool,
1024) -> u32 {
1025 let Some(v) = attribute else {
1026 return 0;
1027 };
1028 let qp_deltas_under_rate_control = v & (1 << 9) != 0;
1029 if cbr && !qp_deltas_under_rate_control {
1030 return 0;
1031 }
1032 let regions = v & 0xff;
1033 if !cbr && hardware == Hardware::VaapiIntel && entrypoint == VAEntrypointEncSliceLP {
1034 regions.min(INTEL_VDENC_NATIVE_ROI)
1035 } else {
1036 regions
1037 }
1038}
1039
1040pub struct VaapiEncoder {
1042 codec: Codec,
1043 input: Input,
1044 device: Arc<Device>,
1045 config: VAConfigID,
1046 context: VAContextID,
1047 vpp_config: VAConfigID,
1048 vpp_context: VAContextID,
1049 vpp_standards: (VAProcColorStandardType, VAProcColorStandardType),
1051 negotiated: Negotiated,
1052 fourcc: u32,
1053 rt_format: u32,
1054 surface_width: u32,
1055 surface_height: u32,
1056 low_power: bool,
1057 recon: Vec<VASurfaceID>,
1059 converted: Vec<VASurfaceID>,
1061 host: Option<HostUpload>,
1062 imports: Vec<(i32, DmaBufIdentity, VASurfaceID)>,
1065 coded: VABufferID,
1066 arm: Arm,
1067 references: Option<References>,
1068 frame_count: u64,
1071 key_count: u64,
1072 surfaces_of: HashMap<u64, VASurfaceID>,
1074 last_reference: Reference,
1075 rate: RateSettings,
1076 qp: u32,
1077 held: Option<u32>,
1079 refinement: Option<crate::ledger::Refinement>,
1082 roi: Vec<VAEncROI>,
1083 frame_cap: bool,
1085 last_bytes: Option<usize>,
1087 cleanup_bps: Option<u32>,
1089 sent_bps: u32,
1091 last_quality: Option<u32>,
1093 measure: bool,
1096 last_psnr: Option<f32>,
1097 measures: bool,
1098 quality_level: Option<u32>,
1101 sequence_start: bool,
1104 declared_level: u32,
1107 omit_headers: bool,
1108 fresh: bool,
1109 reorder: NoReorder,
1114}
1115
1116unsafe impl Send for VaapiEncoder {}
1117
1118impl Drop for VaapiEncoder {
1119 fn drop(&mut self) {
1120 let api = self.device.api;
1121 let display = self.device.display;
1122 unsafe {
1123 if self.coded != VA_INVALID_ID {
1124 (api.vaDestroyBuffer)(display, self.coded);
1125 }
1126 if let Some(host) = &self.host
1127 && let Some(image) = host.image
1128 {
1129 (api.vaDestroyImage)(display, image.image_id);
1130 }
1131 if self.vpp_context != VA_INVALID_ID {
1132 (api.vaDestroyContext)(display, self.vpp_context);
1133 }
1134 if self.context != VA_INVALID_ID {
1135 (api.vaDestroyContext)(display, self.context);
1136 }
1137 let mut surfaces = self.recon.clone();
1138 surfaces.extend(&self.converted);
1139 surfaces.extend(self.host.iter().map(|h| h.surface));
1140 surfaces.extend(self.imports.iter().map(|i| i.2));
1141 self.device.destroy_surfaces(&mut surfaces);
1142 if self.vpp_config != VA_INVALID_ID {
1143 (api.vaDestroyConfig)(display, self.vpp_config);
1144 }
1145 if self.config != VA_INVALID_ID {
1146 (api.vaDestroyConfig)(display, self.config);
1147 }
1148 }
1149 }
1150}
1151
1152impl VaapiEncoder {
1153 pub fn new(settings: &CaptureSettings, codec: Codec, input: Input) -> Result<Self, String> {
1156 if !codec.is_video() {
1157 return Err("JPEG has no VA-API encoder".into());
1158 }
1159 Self::on_device(
1160 Arc::new(Device::open(&libva()?, settings.encode_node_index)?),
1161 settings,
1162 codec,
1163 input,
1164 )
1165 }
1166
1167 pub(crate) fn on_device(
1169 device: Arc<Device>,
1170 settings: &CaptureSettings,
1171 codec: Codec,
1172 input: Input,
1173 ) -> Result<Self, String> {
1174 let fullcolor = settings.video_fullcolor && codec.fullcolor();
1175 let depths: &[u32] = if settings.video_bit_depth >= 10 && codec.high_bit_depth() {
1176 &[10, 8]
1177 } else {
1178 &[8]
1179 };
1180 let listed = device.profiles()?;
1181 let mut last = None;
1182 for &bit_depth in depths {
1183 let served = profile_ladder(codec, fullcolor, bit_depth)
1184 .into_iter()
1185 .filter(|p| listed.contains(p))
1186 .map(|p| (p, device.encode_entrypoints(p)))
1187 .find(|(_, entrypoints)| !entrypoints.is_empty());
1188 let Some((profile, mut entrypoints)) = served else {
1189 last.get_or_insert_with(|| {
1190 format!(
1191 "this VA-API driver encodes no {} {} at {bit_depth} bits",
1192 codec.display(),
1193 super::chroma_name(fullcolor)
1194 )
1195 });
1196 continue;
1197 };
1198 if codec == Codec::H264 && device.whole_picture_vdenc {
1199 entrypoints.sort_by_key(|&e| e == VAEntrypointEncSliceLP);
1200 crate::log::debug!(
1201 "[vaapi] This device's low-power H.264 encoder codes one slice a picture: {}",
1202 if entrypoints[0] == VAEntrypointEncSliceLP {
1203 "the driver offers no full entry point, so the session codes one slice."
1204 } else {
1205 "H.264 tries the full entry point first."
1206 }
1207 );
1208 }
1209 for entrypoint in entrypoints {
1210 for &fourcc in surface_fourccs(fullcolor, bit_depth) {
1211 match Self::open(
1212 &device, settings, codec, input, profile, entrypoint, fourcc, fullcolor,
1213 bit_depth,
1214 ) {
1215 Ok(session) => return Ok(session),
1216 Err(e) => last = Some(e),
1217 }
1218 }
1219 }
1220 }
1221 Err(last.unwrap_or_else(|| "no VA-API surface format to try".into()))
1222 }
1223
1224 #[allow(clippy::too_many_arguments)]
1227 fn open(
1228 device: &Arc<Device>,
1229 settings: &CaptureSettings,
1230 codec: Codec,
1231 input: Input,
1232 profile: VAProfile,
1233 entrypoint: VAEntrypoint,
1234 fourcc: u32,
1235 fullcolor: bool,
1236 bit_depth: u32,
1237 ) -> Result<Self, String> {
1238 let api = device.api;
1239 let rate = RateSettings::new(settings).uncapped();
1240 let rc_mode = if rate.cbr { VA_RC_CBR } else { VA_RC_CQP };
1241 let rt_format = rt_format(fullcolor, bit_depth);
1242 let width = settings.width.max(1) as u32;
1243 let height = settings.height.max(1) as u32;
1244 let fps = rate.fps;
1245 let bits_per_second = if rate.cbr {
1246 rate.bps().min(u64::from(u32::MAX)) as u32
1247 } else {
1248 0
1249 };
1250
1251 let mut attribs = Vec::new();
1252 if let Some(formats) = device.attribute(profile, entrypoint, VAConfigAttribRTFormat) {
1253 if formats & rt_format == 0 {
1254 return Err(format!(
1255 "the {} entry point renders no {} {bit_depth}-bit surfaces",
1256 fourcc_name(fourcc),
1257 super::chroma_name(fullcolor)
1258 ));
1259 }
1260 attribs.push(VAConfigAttrib {
1261 type_: VAConfigAttribRTFormat,
1262 value: rt_format,
1263 });
1264 }
1265 match device.attribute(profile, entrypoint, VAConfigAttribRateControl) {
1266 Some(modes) if modes & rc_mode != 0 => attribs.push(VAConfigAttrib {
1267 type_: VAConfigAttribRateControl,
1268 value: rc_mode,
1269 }),
1270 Some(_) => {
1271 return Err(format!(
1272 "this VA-API driver has no {} rate control for {}",
1273 if rate.cbr {
1274 "constant-rate"
1275 } else {
1276 "constant-quantizer"
1277 },
1278 codec.display()
1279 ));
1280 }
1281 None if rate.cbr => {
1282 return Err("this VA-API driver reports no rate control modes".into());
1283 }
1284 None => {}
1285 }
1286 let ref_l0 = device
1287 .attribute(profile, entrypoint, VAConfigAttribEncMaxRefFrames)
1288 .map_or(0, |v| v & 0xffff);
1289 if ref_l0 < 1 {
1290 return Err("this VA-API driver takes no reference frames".into());
1291 }
1292 let level_bitrate = u64::from(bits_per_second);
1293 let dpb_level = match codec {
1294 Codec::H264 => super::codec::h264_level(width, height, fps.ceil(), level_bitrate),
1295 Codec::H265 => super::codec::h265_level(width, height, fps.ceil(), level_bitrate, true),
1296 _ => 0,
1297 };
1298 let dpb = match codec {
1299 Codec::H264 => super::codec::h264_dpb_frames(dpb_level, width, height)
1300 .min(super::reference_frames(settings)),
1301 Codec::H265 => super::codec::h265_dpb_frames(dpb_level, width, height)
1302 .min(super::reference_frames(settings)),
1303 _ => REFERENCE_FRAMES,
1304 };
1305 let unit_qp_deltas = codec == Codec::H265
1306 && device.hardware() == Hardware::VaapiIntel
1307 && entrypoint == VAEntrypointEncSlice;
1308 let mut arm = match codec {
1309 Codec::H264 => Arm::H264(Box::new(h264::Arm::new(profile))),
1310 Codec::H265 => Arm::H265(h265::Arm::new(
1311 profile,
1312 device.attribute(profile, entrypoint, VAConfigAttribEncHEVCFeatures),
1313 device.attribute(profile, entrypoint, VAConfigAttribEncHEVCBlockSizes),
1314 device.attribute(profile, entrypoint, VAConfigAttribPredictionDirection),
1315 unit_qp_deltas,
1316 )),
1317 Codec::Vp8 => Arm::Vp8(vp8::Arm::new()),
1318 Codec::Vp9 => Arm::Vp9(vp9::Arm::new()),
1319 Codec::Av1 => Arm::Av1(av1::Arm::new(
1320 device.attribute(profile, entrypoint, VAConfigAttribEncAV1),
1321 device.attribute(profile, entrypoint, VAConfigAttribEncAV1Ext1),
1322 device
1323 .attribute(profile, entrypoint, VAConfigAttribEncAV1Ext2)
1324 .ok_or("this VA-API driver reports no AV1 encoder attributes")?,
1325 )),
1326 Codec::Jpeg => unreachable!(),
1327 };
1328 let packed = device
1329 .attribute(profile, entrypoint, VAConfigAttribEncPackedHeaders)
1330 .map_or(0, |v| v & arm.wanted_packed());
1331 if packed != 0 {
1332 attribs.push(VAConfigAttrib {
1333 type_: VAConfigAttribEncPackedHeaders,
1334 value: packed,
1335 });
1336 }
1337 let dpb = if arm.tracks_references(packed) {
1338 dpb
1339 } else {
1340 1
1341 };
1342 let quality_range = device.attribute(profile, entrypoint, VAConfigAttribEncQualityRange);
1343 let slice_caps = (
1344 device.attribute(profile, entrypoint, VAConfigAttribEncMaxSlices),
1345 device.attribute(profile, entrypoint, VAConfigAttribEncSliceStructure),
1346 );
1347
1348 let mut config = VA_INVALID_ID;
1349 device.check(
1350 unsafe {
1351 (api.vaCreateConfig)(
1352 device.display,
1353 profile,
1354 entrypoint,
1355 attribs.as_mut_ptr(),
1356 attribs.len() as c_int,
1357 &mut config,
1358 )
1359 },
1360 &format!(
1361 "no VA-API encode configuration for {} on {} surfaces",
1362 codec.display(),
1363 fourcc_name(fourcc)
1364 ),
1365 )?;
1366 let mut me = Self {
1367 codec,
1368 input,
1369 device: Arc::clone(device),
1370 config,
1371 context: VA_INVALID_ID,
1372 vpp_config: VA_INVALID_ID,
1373 vpp_context: VA_INVALID_ID,
1374 vpp_standards: (VAProcColorStandardNone, VAProcColorStandardNone),
1375 negotiated: Negotiated {
1376 profile,
1377 entrypoint,
1378 packed,
1379 rc_mode,
1380 width,
1381 height,
1382 fps,
1383 bits_per_second,
1384 dpb,
1385 dpb_level,
1386 fullcolor,
1387 bit_depth,
1388 min_qp: codec.hardware_quantizer_bound(device.hardware(), rate.min_qp),
1389 max_qp: codec.hardware_quantizer_bound(device.hardware(), rate.max_qp),
1390 qp_floor: if codec == Codec::H265
1391 && device.hardware() == Hardware::VaapiIntel
1392 && entrypoint == VAEntrypointEncSliceLP
1393 {
1394 h265::INTEL_LOW_POWER_MIN_QP
1395 } else {
1396 1
1397 },
1398 unit_qp_deltas,
1399 h264_driver_level: device.h264_driver_level(),
1400 roi_regions: roi_regions(
1401 device.attribute(profile, entrypoint, VAConfigAttribEncROI),
1402 device.hardware(),
1403 entrypoint,
1404 rate.cbr,
1405 ),
1406 },
1407 fourcc,
1408 rt_format,
1409 surface_width: 0,
1410 surface_height: 0,
1411 low_power: entrypoint == VAEntrypointEncSliceLP,
1412 recon: Vec::new(),
1413 converted: Vec::new(),
1414 host: None,
1415 coded: VA_INVALID_ID,
1416 arm: Arm::Vp8(vp8::Arm::new()),
1417 references: None,
1418 frame_count: 0,
1419 key_count: 0,
1420 surfaces_of: HashMap::new(),
1421 imports: Vec::new(),
1422 last_reference: Reference::Untracked,
1423 rate,
1424 qp: codec.hardware_quantizer(device.hardware(), settings.video_crf),
1425 held: None,
1426 refinement: None,
1427 roi: Vec::new(),
1428 frame_cap: false,
1429 last_bytes: None,
1430 cleanup_bps: None,
1431 sent_bps: 0,
1432 last_quality: None,
1433 measure: false,
1434 last_psnr: None,
1435 measures: true,
1436 quality_level: None,
1437 sequence_start: true,
1438 declared_level: 0,
1439 omit_headers: settings.omit_stripe_headers,
1440 fresh: true,
1441 reorder: NoReorder::new("The VA-API driver", true),
1442 };
1443 let (align_w, align_h) = {
1444 let (aw, ah) = arm.alignment();
1445 let (dw, dh) = device.surface_alignment(config);
1446 (aw.max(dw), ah.max(dh))
1447 };
1448 me.surface_width = width.div_ceil(align_w) * align_w;
1449 me.surface_height = height.div_ceil(align_h) * align_h;
1450 let rendered = device.surface_fourccs(config);
1451 if !rendered.is_empty() && !rendered.contains(&fourcc) {
1452 return Err(format!(
1453 "the {} encoder takes no {} surfaces",
1454 codec.display(),
1455 fourcc_name(fourcc)
1456 ));
1457 }
1458 let wanted_slices = if matches!(arm, Arm::H264(_)) && device.one_slice_h264(entrypoint) {
1459 1
1460 } else {
1461 SLICES
1462 };
1463 let slices = match (&arm, slice_caps) {
1464 (Arm::H264(_) | Arm::H265(_), (Some(max), Some(structure))) => Some(slice_layout(
1465 structure,
1466 max,
1467 me.surface_height.div_ceil(arm_block(&arm)),
1468 wanted_slices,
1469 )?),
1470 (Arm::H264(_) | Arm::H265(_), _) => {
1471 Some((1, me.surface_height.div_ceil(arm_block(&arm))))
1472 }
1473 _ => None,
1474 };
1475 match &mut arm {
1476 Arm::H264(a) => a.configure(
1477 &me.negotiated,
1478 me.surface_width,
1479 me.surface_height,
1480 slices.unwrap(),
1481 ),
1482 Arm::H265(a) => a.configure(
1483 &me.negotiated,
1484 me.surface_width,
1485 me.surface_height,
1486 slices.unwrap(),
1487 ),
1488 Arm::Vp8(a) => a.configure(&me.negotiated),
1489 Arm::Vp9(a) => a.configure(&me.negotiated),
1490 Arm::Av1(a) => a.configure(&me.negotiated, me.surface_width, me.surface_height)?,
1491 }
1492 me.arm = arm;
1493 me.references = me.arm.tracks_references(packed).then(|| match me.arm {
1494 Arm::Vp8(_) => References::Slots(ReferenceSlots::new()),
1495 Arm::H264(_) if me.device.keeps_anchors() && dpb >= 3 => {
1496 let mut w = ReferenceWindow::with_anchors(dpb, 1);
1497 if settings.acknowledge_references {
1498 w.set_acknowledged();
1499 }
1500 References::Window(w)
1501 }
1502 Arm::H265(_) if me.device.keeps_anchors() && dpb as usize >= ANCHORS + 2 => {
1503 let mut w = ReferenceWindow::with_anchors(dpb, ANCHORS);
1504 if settings.acknowledge_references {
1505 w.set_acknowledged();
1506 }
1507 References::Window(w)
1508 }
1509 _ => References::Window(ReferenceWindow::new(dpb)),
1510 });
1511 if let Some(References::Window(w)) = &mut me.references
1512 && let Arm::H264(a) = &me.arm
1513 {
1514 w.set_frame_num_range(a.frame_num_range());
1515 }
1516 me.quality_level = quality_range;
1517 me.frame_cap = device
1518 .attribute(profile, entrypoint, VAConfigAttribMaxFrameSize)
1519 .is_some_and(|v| v & 1 != 0);
1520
1521 let recon_count = match me.arm {
1522 Arm::Vp8(_) => 4,
1523 _ => dpb as usize + 1,
1524 };
1525 me.recon = device.create_surfaces(
1526 rt_format,
1527 fourcc,
1528 me.surface_width,
1529 me.surface_height,
1530 recon_count,
1531 )?;
1532 me.converted =
1533 device.create_surfaces(rt_format, fourcc, me.surface_width, me.surface_height, 1)?;
1534 let mut render_targets = me.recon.clone();
1535 render_targets.extend(&me.converted);
1536 device.check(
1537 unsafe {
1538 (api.vaCreateContext)(
1539 device.display,
1540 config,
1541 me.surface_width as c_int,
1542 me.surface_height as c_int,
1543 VA_PROGRESSIVE as c_int,
1544 render_targets.as_mut_ptr(),
1545 render_targets.len() as c_int,
1546 &mut me.context,
1547 )
1548 },
1549 "no VA-API encode context",
1550 )?;
1551 me.coded = device.create_output_buffer(
1552 me.context,
1553 VAEncCodedBufferType,
1554 coded_buffer_size(me.surface_width, me.surface_height),
1555 )?;
1556 me.open_vpp(device, input)?;
1557 Ok(me)
1558 }
1559}
1560
1561fn arm_block(arm: &Arm) -> u32 {
1563 match arm {
1564 Arm::H265(a) => a.ctu_size(),
1565 _ => 16,
1566 }
1567}
1568
1569fn slice_layout(
1577 structure: u32,
1578 max_slices: u32,
1579 rows: u32,
1580 wanted: u32,
1581) -> Result<(u32, u32), String> {
1582 let max_slices = max_slices.max(1);
1583 let wanted = wanted.min(rows).min(max_slices).max(1);
1584 let (count, size) = if structure
1585 & (VA_ENC_SLICE_STRUCTURE_ARBITRARY_ROWS | VA_ENC_SLICE_STRUCTURE_ARBITRARY_MACROBLOCKS)
1586 != 0
1587 {
1588 (wanted, rows / wanted)
1589 } else if structure & VA_ENC_SLICE_STRUCTURE_POWER_OF_TWO_ROWS != 0 {
1590 let mut k = 1;
1591 while (wanted > 1 && 2 * k * (wanted - 1) + 1 < rows) || rows.div_ceil(k) > max_slices {
1592 k *= 2;
1593 }
1594 (rows.div_ceil(k), k)
1595 } else if structure & VA_ENC_SLICE_STRUCTURE_EQUAL_MULTI_ROWS != 0 {
1596 let size = rows.div_ceil(wanted);
1597 (rows.div_ceil(size), size)
1598 } else if structure & VA_ENC_SLICE_STRUCTURE_EQUAL_ROWS != 0 {
1599 (rows, 1)
1600 } else {
1601 return Err(format!(
1602 "this VA-API driver supports no usable slice structure ({structure:#x})"
1603 ));
1604 };
1605 if count > max_slices {
1606 return Err(format!(
1607 "this VA-API driver encodes at most {max_slices} slices, not {count}"
1608 ));
1609 }
1610 Ok((count, size))
1611}
1612
1613impl VaapiEncoder {
1614 fn open_vpp(&mut self, device: &Device, input: Input) -> Result<(), String> {
1619 let api = device.api;
1620 device.check(
1621 unsafe {
1622 (api.vaCreateConfig)(
1623 device.display,
1624 VAProfileNone,
1625 VAEntrypointVideoProc,
1626 ptr::null_mut(),
1627 0,
1628 &mut self.vpp_config,
1629 )
1630 },
1631 "no VA-API video processing configuration",
1632 )?;
1633 let rendered = device.surface_fourccs(self.vpp_config);
1634 if !rendered.is_empty() && !rendered.contains(&self.fourcc) {
1635 return Err(format!(
1636 "this VA-API driver's video processor renders no {} surfaces",
1637 fourcc_name(self.fourcc)
1638 ));
1639 }
1640 device.check(
1641 unsafe {
1642 (api.vaCreateContext)(
1643 device.display,
1644 self.vpp_config,
1645 self.surface_width as c_int,
1646 self.surface_height as c_int,
1647 VA_PROGRESSIVE as c_int,
1648 self.converted.as_mut_ptr(),
1649 self.converted.len() as c_int,
1650 &mut self.vpp_context,
1651 )
1652 },
1653 "no VA-API video processing context",
1654 )?;
1655 let mut caps: VAProcPipelineCaps = unsafe { std::mem::zeroed() };
1656 device.check(
1657 unsafe {
1658 (api.vaQueryVideoProcPipelineCaps)(
1659 device.display,
1660 self.vpp_context,
1661 ptr::null_mut(),
1662 0,
1663 &mut caps,
1664 )
1665 },
1666 "vaQueryVideoProcPipelineCaps",
1667 )?;
1668 let standards =
1669 |list: *mut VAProcColorStandardType, count: u32| -> Vec<VAProcColorStandardType> {
1670 if list.is_null() {
1671 Vec::new()
1672 } else {
1673 unsafe { std::slice::from_raw_parts(list, count as usize) }.to_vec()
1674 }
1675 };
1676 let input_standards = standards(caps.input_color_standards, caps.num_input_color_standards);
1677 let output_standards =
1678 standards(caps.output_color_standards, caps.num_output_color_standards);
1679 self.vpp_standards = (
1680 color_standard(&input_standards, ColorDescription::SRGB_SOURCE),
1681 color_standard(&output_standards, self.declared_color()),
1682 );
1683 if let Input::Host { rgba } = input {
1684 let fourcc = if rgba { VA_FOURCC_RGBA } else { VA_FOURCC_BGRA };
1685 let surface = device.create_surfaces(
1686 VA_RT_FORMAT_RGB32,
1687 fourcc,
1688 self.negotiated.width,
1689 self.negotiated.height,
1690 1,
1691 )?[0];
1692 let mut derived: VAImage = unsafe { std::mem::zeroed() };
1693 let derive = unsafe { (api.vaDeriveImage)(device.display, surface, &mut derived) }
1694 == VA_STATUS_SUCCESS as VAStatus
1695 && {
1696 let same = derived.format.fourcc == fourcc;
1697 unsafe { (api.vaDestroyImage)(device.display, derived.image_id) };
1698 same
1699 };
1700 let host = self.host.insert(HostUpload {
1701 surface,
1702 fourcc,
1703 derive,
1704 image: None,
1705 });
1706 if !derive {
1707 let mut format = image_format(device, fourcc).ok_or_else(|| {
1708 format!(
1709 "this VA-API driver has no {} image format",
1710 fourcc_name(fourcc)
1711 )
1712 })?;
1713 let mut image: VAImage = unsafe { std::mem::zeroed() };
1714 device.check(
1715 unsafe {
1716 (api.vaCreateImage)(
1717 device.display,
1718 &mut format,
1719 self.negotiated.width as c_int,
1720 self.negotiated.height as c_int,
1721 &mut image,
1722 )
1723 },
1724 "vaCreateImage",
1725 )?;
1726 host.image = Some(image);
1727 }
1728 }
1729 Ok(())
1730 }
1731
1732 fn declared_color(&self) -> ColorDescription {
1736 ColorDescription {
1737 primaries: 1,
1738 transfer: 1,
1739 matrix: if self.codec == Codec::Vp8 { 6 } else { 1 },
1740 full_range: false,
1741 rgb: false,
1742 }
1743 }
1744
1745 pub fn codec(&self) -> Codec {
1746 self.codec
1747 }
1748
1749 pub fn is_fullcolor(&self) -> bool {
1751 self.negotiated.fullcolor
1752 }
1753
1754 pub fn bit_depth(&self) -> u32 {
1756 self.negotiated.bit_depth
1757 }
1758
1759 pub fn surface_format_name(&self) -> String {
1761 fourcc_name(self.fourcc)
1762 }
1763
1764 pub fn is_full_range(&self) -> bool {
1766 false
1767 }
1768
1769 pub fn vendor(&self) -> &str {
1771 &self.device.vendor
1772 }
1773
1774 pub fn low_power(&self) -> bool {
1776 self.low_power
1777 }
1778
1779 pub fn last_reference(&self) -> Reference {
1781 self.last_reference
1782 }
1783
1784 pub fn take_report(&mut self, report: ReferenceReport) -> bool {
1787 let (frame_id, held) = match report {
1788 ReferenceReport::Lost(frame_id) | ReferenceReport::Withheld(frame_id) => {
1789 return self.invalidate_reference(frame_id);
1790 }
1791 ReferenceReport::Held(frame_id) => (frame_id, true),
1792 ReferenceReport::Sent(frame_id) => (frame_id, false),
1793 };
1794 match &mut self.references {
1795 Some(References::Window(w)) => {
1796 w.acknowledge(frame_id, held);
1797 }
1798 Some(References::Slots(s)) => {
1799 s.acknowledge(frame_id, held);
1800 }
1801 None => {}
1802 }
1803 true
1804 }
1805
1806 pub fn invalidate_reference(&mut self, frame_id: u16) -> bool {
1809 match &mut self.references {
1810 Some(References::Window(w)) => {
1811 w.invalidate(frame_id);
1812 true
1813 }
1814 Some(References::Slots(s)) => {
1815 s.invalidate(frame_id);
1816 true
1817 }
1818 None => false,
1819 }
1820 }
1821
1822 pub fn reconfigure_rate(&mut self, settings: &CaptureSettings) -> Result<(), String> {
1828 let Some(rate) = self.rate.changed(settings) else {
1829 return Ok(());
1830 };
1831 let target_only = rate.cbr
1832 && RateSettings {
1833 bitrate_kbps: rate.bitrate_kbps,
1834 ..self.rate
1835 } == rate;
1836 self.rate = rate;
1837 self.negotiated.fps = rate.fps;
1838 self.negotiated.bits_per_second = if rate.cbr {
1839 rate.bps().min(u64::from(u32::MAX)) as u32
1840 } else {
1841 0
1842 };
1843 self.negotiated.min_qp = self
1844 .codec
1845 .hardware_quantizer_bound(self.device.hardware(), rate.min_qp);
1846 self.negotiated.max_qp = self
1847 .codec
1848 .hardware_quantizer_bound(self.device.hardware(), rate.max_qp);
1849 match &mut self.arm {
1850 Arm::H264(a) => a.configure(
1851 &self.negotiated,
1852 self.surface_width,
1853 self.surface_height,
1854 a.slices(),
1855 ),
1856 Arm::H265(a) => a.configure(
1857 &self.negotiated,
1858 self.surface_width,
1859 self.surface_height,
1860 a.slices(),
1861 ),
1862 Arm::Vp8(a) => a.configure(&self.negotiated),
1863 Arm::Vp9(a) => a.configure(&self.negotiated),
1864 Arm::Av1(a) => {
1865 a.configure(&self.negotiated, self.surface_width, self.surface_height)?
1866 }
1867 }
1868 if !target_only || self.arm.level() > self.declared_level {
1869 self.sequence_start = true;
1870 }
1871 Ok(())
1872 }
1873
1874 pub fn is_cbr(&self) -> bool {
1876 self.rate.cbr
1877 }
1878
1879 pub fn last_size(&self) -> Option<usize> {
1882 self.last_bytes
1883 }
1884
1885 pub fn last_quality(&self) -> Option<u32> {
1891 self.last_quality
1892 }
1893
1894 pub fn measures(&self) -> bool {
1897 self.measures
1898 }
1899
1900 pub fn measure(&mut self) {
1902 self.measure = true;
1903 }
1904
1905 pub fn last_psnr(&self) -> Option<f32> {
1911 self.last_psnr
1912 }
1913
1914 fn luma_rows(&self, surface: VASurfaceID) -> Option<(u32, u16, Vec<u8>)> {
1917 let api = self.device.api;
1918 let display = self.device.display;
1919 let mut image: VAImage = unsafe { std::mem::zeroed() };
1920 if unsafe { (api.vaDeriveImage)(display, surface, &mut image) }
1921 != VA_STATUS_SUCCESS as VAStatus
1922 {
1923 return None;
1924 }
1925 let fourcc = image.format.fourcc;
1926 let (bytes, luma) = match fourcc {
1927 VA_FOURCC_NV12 | VA_FOURCC_444P => (1, 0),
1928 VA_FOURCC_P010 => (2, 1),
1929 VA_FOURCC_XYUV | VA_FOURCC_Y410 => (4, 2),
1930 _ => (0, 0),
1931 };
1932 let mut address: *mut c_void = ptr::null_mut();
1933 let mut rows = None;
1934 if bytes > 0
1935 && u32::from(image.height) >= self.negotiated.height
1936 && unsafe { (api.vaMapBuffer)(display, image.buf, &mut address) }
1937 == VA_STATUS_SUCCESS as VAStatus
1938 {
1939 let width = (self.negotiated.width as usize).min(image.width as usize);
1940 let height = self.negotiated.height as usize;
1941 let (offset, pitch) = (image.offsets[0] as usize, image.pitches[0] as usize);
1942 let mut out = Vec::with_capacity(width * height.div_ceil(PSNR_ROW_STEP));
1943 for y in (0..height).step_by(PSNR_ROW_STEP) {
1944 let row = unsafe {
1945 std::slice::from_raw_parts(
1946 (address as *const u8).add(offset + y * pitch),
1947 width * bytes,
1948 )
1949 };
1950 if fourcc == VA_FOURCC_Y410 {
1951 out.extend(
1952 row.as_chunks::<4>()
1953 .0
1954 .iter()
1955 .map(|p| ((u32::from_le_bytes(*p) >> 12) & 0xff) as u8),
1956 );
1957 } else {
1958 out.extend(row.iter().skip(luma).step_by(bytes));
1959 }
1960 }
1961 unsafe { (api.vaUnmapBuffer)(display, image.buf) };
1962 rows = Some((fourcc, image.height, out));
1963 }
1964 unsafe { (api.vaDestroyImage)(display, image.image_id) };
1965 rows
1966 }
1967
1968 fn picture_psnr(&self, recon: VASurfaceID) -> Option<f32> {
1973 let (format, rows, source) = self.luma_rows(self.converted[0])?;
1974 let (coded_format, coded_rows, coded) = self.luma_rows(recon)?;
1975 if (format, rows) != (coded_format, coded_rows) || source.is_empty() {
1976 return None;
1977 }
1978 let sse: u64 = source
1979 .iter()
1980 .zip(&coded)
1981 .map(|(&a, &b)| {
1982 let d = i64::from(a) - i64::from(b);
1983 (d * d) as u64
1984 })
1985 .sum();
1986 let mse = (sse as f64 / source.len() as f64).max(1e-3);
1987 Some((10.0 * (255.0 * 255.0 / mse).log10()) as f32).filter(|&db| db >= UNREADABLE_DB)
1988 }
1989
1990 fn constant_rate(&self) -> ConstantRate {
1992 if !self.device.starves_in_a_small_buffer() {
1993 return ConstantRate::Buffer;
1994 }
1995 match self.arm {
1996 Arm::H264(_) | Arm::H265(_) => ConstantRate::Unbounded,
1997 Arm::Av1(_) => ConstantRate::Seconds(IHD_AV1_BUFFER_S),
1998 Arm::Vp8(_) | Arm::Vp9(_) => ConstantRate::Buffer,
1999 }
2000 }
2001
2002 pub fn ends_cleanup_on_key(&self) -> bool {
2007 self.rate.cbr
2008 && matches!(self.arm, Arm::H264(_) | Arm::H265(_))
2009 && self.last_bytes.is_some()
2010 && self.last_quality.is_none()
2011 && !refines(
2012 self.negotiated.bits_per_second,
2013 self.negotiated.fps.fps(),
2014 self.negotiated.width,
2015 self.negotiated.height,
2016 )
2017 }
2018
2019 pub fn follows_cap(&self) -> bool {
2022 self.rate.cbr
2023 }
2024
2025 pub fn cap(&mut self, bytes: usize) {
2031 if self.rate.cbr {
2032 let bps = (bytes as f64 * 8.0 / crate::pipeline::CLEANUP_FRAME_S)
2033 .min(f64::from(u32::MAX)) as u32;
2034 self.cleanup_bps = Some(bps.max(self.negotiated.bits_per_second));
2035 }
2036 }
2037
2038 pub fn refines(&self) -> Option<crate::ledger::Refiner> {
2056 (self.codec == Codec::H264 && self.negotiated.roi_regions > 0).then_some(
2057 crate::ledger::Refiner {
2058 finer: REFINE_FINER,
2059 reach: REFINE_REACH,
2060 runs: self.negotiated.roi_regions as usize,
2061 },
2062 )
2063 }
2064
2065 pub fn refine_blocks(&mut self, refinement: &crate::ledger::Refinement) {
2068 self.refinement = self.refines().map(|_| refinement.clone());
2069 }
2070
2071 fn roi_parameter(&mut self, refinement: &crate::ledger::Refinement, out: &mut Buffers) {
2076 use crate::ledger::BLOCK;
2077 let hw = |index: i32| self.codec.hardware_quantizer(self.device.hardware(), index) as i32;
2078 let delta =
2079 (hw(refinement.target) - hw(refinement.from)).clamp(-(REFINE_REACH as i32), 0) as i8;
2080 let plan = &refinement.plan;
2081 let (width, height) = (self.negotiated.width as i32, self.negotiated.height as i32);
2082 self.roi.clear();
2083 for row in 0..plan.rows {
2084 let mut col = 0;
2085 while col < plan.cols {
2086 if !plan.refines(col, row) {
2087 col += 1;
2088 continue;
2089 }
2090 let start = col;
2091 while col < plan.cols && plan.refines(col, row) {
2092 col += 1;
2093 }
2094 let (x, y) = (start as i32 * BLOCK, row as i32 * BLOCK);
2095 self.roi.push(VAEncROI {
2096 roi_rectangle: VARectangle {
2097 x: x as i16,
2098 y: y as i16,
2099 width: ((col as i32 * BLOCK).min(width) - x) as u16,
2100 height: (BLOCK.min(height - y)) as u16,
2101 },
2102 roi_value: delta,
2103 });
2104 }
2105 }
2106 self.roi.truncate(self.negotiated.roi_regions as usize);
2107 let mut parameter: VAEncMiscParameterBufferROI = unsafe { std::mem::zeroed() };
2108 parameter.num_roi = self.roi.len() as u32;
2109 parameter.max_delta_qp = 51;
2110 parameter.min_delta_qp = -51;
2111 parameter.roi = self.roi.as_mut_ptr();
2112 unsafe { parameter.roi_flags.bits.set_roi_value_is_qp_delta(1) };
2113 out.push_misc(VAEncMiscParameterTypeROI, ¶meter);
2114 }
2115
2116 pub fn hold_quantizer(&mut self, crf: u32) {
2117 self.held = Some(
2118 self.codec
2119 .hardware_quantizer(self.device.hardware(), crf as i32),
2120 );
2121 }
2122
2123 fn rate_control_at(&self, out: &mut Buffers, bps: u32) {
2128 if self.rate.cbr {
2129 let vbv = (u64::from(self.rate.vbv()) * u64::from(bps)
2131 / u64::from(self.negotiated.bits_per_second.max(1)))
2132 .min(u64::from(u32::MAX)) as u32;
2133 let mut rc: VAEncMiscParameterRateControl = unsafe { std::mem::zeroed() };
2134 rc.bits_per_second = bps;
2135 rc.target_percentage = 100;
2136 rc.window_size = (u64::from(vbv) * 1000 / u64::from(bps.max(1))) as u32;
2137 rc.initial_qp = 0;
2138 rc.min_qp = if matches!(self.arm, Arm::H264(_)) {
2139 self.negotiated.min_qp.max(h264::MIN_QP)
2140 } else {
2141 self.negotiated.min_qp
2142 };
2143 rc.max_qp = if self.negotiated.unit_qp_deltas && self.negotiated.max_qp == 0 {
2146 51
2147 } else {
2148 self.negotiated.max_qp
2149 };
2150 rc.basic_unit_size = 0;
2151 rc.ICQ_quality_factor = 1;
2152 rc.quality_factor = 0;
2153 let bound = self.constant_rate();
2154 unsafe {
2155 rc.rc_flags.bits.set_mb_rate_control(2);
2156 rc.rc_flags.bits.set_disable_bit_stuffing(1);
2157 }
2158 out.push_misc(VAEncMiscParameterTypeRateControl, &rc);
2159 let buffer = match bound {
2160 ConstantRate::Buffer => Some(vbv),
2161 ConstantRate::Seconds(s) => Some((f64::from(bps) * s) as u32),
2162 ConstantRate::Unbounded => None,
2163 };
2164 if let Some(bits) = buffer {
2165 let hrd = VAEncMiscParameterHRD {
2166 initial_buffer_fullness: bits,
2167 buffer_size: bits,
2168 va_reserved: [0; 4],
2169 };
2170 out.push_misc(VAEncMiscParameterTypeHRD, &hrd);
2171 }
2172 if self.frame_cap && bound == ConstantRate::Buffer {
2173 let cap = VAEncMiscParameterBufferMaxFrameSize {
2174 type_: VAEncMiscParameterTypeMaxFrameSize,
2175 max_frame_size: vbv,
2176 va_reserved: [0; 4],
2177 };
2178 out.push_misc(VAEncMiscParameterTypeMaxFrameSize, &cap);
2179 }
2180 }
2181 let fps = self.negotiated.fps.within(0xffff);
2182 let mut frame_rate: VAEncMiscParameterFrameRate = unsafe { std::mem::zeroed() };
2183 frame_rate.framerate = (fps.den << 16) | fps.num;
2184 out.push_misc(VAEncMiscParameterTypeFrameRate, &frame_rate);
2185 if let Some(level) = self.quality_level {
2186 let quality = VAEncMiscParameterBufferQualityLevel {
2187 quality_level: level,
2188 va_reserved: [0; 4],
2189 };
2190 out.push_misc(VAEncMiscParameterTypeQualityLevel, &quality);
2191 }
2192 }
2193
2194 pub fn encode_host(
2198 &mut self,
2199 pixels: &[u8],
2200 stride: usize,
2201 rgba: bool,
2202 frame_number: u64,
2203 crf: u32,
2204 force_idr: bool,
2205 ) -> Result<Vec<u8>, String> {
2206 let Input::Host { rgba: built } = self.input else {
2207 return Err("this session takes dmabufs".into());
2208 };
2209 if built != rgba {
2210 return Err("this session was built for the other byte order".into());
2211 }
2212 let (width, height) = (
2213 self.negotiated.width as usize,
2214 self.negotiated.height as usize,
2215 );
2216 check_host_frame(pixels, stride, width, height)?;
2217 let host = self.host.as_ref().ok_or("no host upload surface")?;
2218 let api = self.device.api;
2219 let display = self.device.display;
2220 unsafe {
2221 let mut image: VAImage = std::mem::zeroed();
2222 if host.derive {
2223 self.device.check(
2224 (api.vaDeriveImage)(display, host.surface, &mut image),
2225 "vaDeriveImage",
2226 )?;
2227 } else {
2228 image = host.image.unwrap();
2229 }
2230 let mut address: *mut c_void = ptr::null_mut();
2231 let mapped = self.device.check(
2232 (api.vaMapBuffer)(display, image.buf, &mut address),
2233 "vaMapBuffer",
2234 );
2235 if let Err(e) = mapped {
2236 if host.derive {
2237 (api.vaDestroyImage)(display, image.image_id);
2238 }
2239 return Err(e);
2240 }
2241 let pitch = image.pitches[0] as usize;
2242 let dst = (address as *mut u8).add(image.offsets[0] as usize);
2243 for row in 0..height {
2244 ptr::copy_nonoverlapping(
2245 pixels.as_ptr().add(row * stride),
2246 dst.add(row * pitch),
2247 width * 4,
2248 );
2249 }
2250 (api.vaUnmapBuffer)(display, image.buf);
2251 if host.derive {
2252 (api.vaDestroyImage)(display, image.image_id);
2253 } else {
2254 self.device.check(
2255 (api.vaPutImage)(
2256 display,
2257 host.surface,
2258 image.image_id,
2259 0,
2260 0,
2261 width as c_uint,
2262 height as c_uint,
2263 0,
2264 0,
2265 width as c_uint,
2266 height as c_uint,
2267 ),
2268 "vaPutImage",
2269 )?;
2270 }
2271 }
2272 let surface = host.surface;
2273 self.encode_surface(surface, frame_number, crf, force_idr)
2274 }
2275
2276 pub fn encode_dmabuf(
2279 &mut self,
2280 dmabuf: &Dmabuf,
2281 frame_number: u64,
2282 crf: u32,
2283 force_idr: bool,
2284 ) -> Result<Vec<u8>, String> {
2285 if self.input != Input::Dmabuf {
2286 return Err("this session takes host frames".into());
2287 }
2288 let surface = self.dmabuf_surface(dmabuf)?;
2289 let result = self.encode_surface(surface, frame_number, crf, force_idr);
2290 if result.is_err() {
2291 self.forget_import(surface);
2292 }
2293 result
2294 }
2295
2296 fn dmabuf_surface(&mut self, dmabuf: &Dmabuf) -> Result<VASurfaceID, String> {
2303 let fd = dmabuf
2304 .handles()
2305 .next()
2306 .map(|h| h.as_raw_fd())
2307 .ok_or("the dmabuf carries no object")?;
2308 let identity = DmaBufIdentity::probe(
2309 fd,
2310 u64::from(dmabuf.format().modifier),
2311 dmabuf.width(),
2312 dmabuf.height(),
2313 );
2314 if let Some(at) = self.imports.iter().position(|i| i.0 == fd) {
2315 if self.imports[at].1 == identity {
2316 return Ok(self.imports[at].2);
2317 }
2318 let (_, _, stale) = self.imports.remove(at);
2319 self.device.destroy_surfaces(&mut [stale]);
2320 }
2321 let surface = self.import_dmabuf(dmabuf)?;
2322 if self.imports.len() == DMABUF_IMPORTS {
2323 let (_, _, oldest) = self.imports.remove(0);
2324 self.device.destroy_surfaces(&mut [oldest]);
2325 }
2326 self.imports.push((fd, identity, surface));
2327 Ok(surface)
2328 }
2329
2330 fn forget_import(&mut self, surface: VASurfaceID) {
2332 if let Some(at) = self.imports.iter().position(|i| i.2 == surface) {
2333 self.imports.remove(at);
2334 self.device.destroy_surfaces(&mut [surface]);
2335 }
2336 }
2337
2338 fn import_dmabuf(&mut self, dmabuf: &Dmabuf) -> Result<VASurfaceID, String> {
2341 let api = self.device.api;
2342 let display = self.device.display;
2343 let (width, height) = (self.negotiated.width, self.negotiated.height);
2344 let mut objects: Vec<(i32, (u64, u64), u32)> = Vec::new();
2348 let mut plane_object: Vec<u32> = Vec::new();
2349 for fd in dmabuf.handles().map(|h| h.as_raw_fd()) {
2350 let id = object_identity(fd).ok_or("a dmabuf plane's object cannot be read")?;
2351 let at = match objects.iter().position(|o| o.1 == id) {
2352 Some(at) => at,
2353 None => {
2354 let size = unsafe { libc::lseek(fd, 0, libc::SEEK_END) };
2355 if size <= 0 {
2356 return Err("the dmabuf object reports no size".into());
2357 }
2358 objects.push((fd, id, size as u32));
2359 objects.len() - 1
2360 }
2361 };
2362 plane_object.push(at as u32);
2363 }
2364 if plane_object.is_empty() || plane_object.len() > 4 {
2365 return Err(format!(
2366 "VA-API maps a dmabuf of one to four planes, not {}",
2367 plane_object.len()
2368 ));
2369 }
2370 let drm_format = dmabuf.format().code as u32;
2371 let fourcc = match drm_format {
2372 0x34325241 => VA_FOURCC_BGRA,
2373 0x34325258 => VA_FOURCC_BGRX,
2374 0x34324241 => VA_FOURCC_RGBA,
2375 0x34324258 => VA_FOURCC_RGBX,
2376 other => return Err(format!("DRM format {other:#x} is not one VA-API maps")),
2377 };
2378 let planes: Vec<(u32, u32)> = dmabuf.strides().zip(dmabuf.offsets()).collect();
2379 let modifier = u64::from(dmabuf.format().modifier);
2380 if modifier == 0
2381 && !self.negotiated.fullcolor
2382 && self.device.rounds_linear_pitch()
2383 && !planes[0].0.is_multiple_of(64)
2384 {
2385 return Err(format!(
2386 "this VA-API driver reads a linear surface at a 64-byte pitch, not the {} of this dmabuf",
2387 planes[0].0
2388 ));
2389 }
2390 let mut surface = VA_INVALID_SURFACE;
2391 let mut attribs: [VASurfaceAttrib; 2] = unsafe { std::mem::zeroed() };
2392 attribs[0].type_ = VASurfaceAttribMemoryType;
2393 attribs[0].flags = VA_SURFACE_ATTRIB_SETTABLE;
2394 attribs[0].value.type_ = VAGenericValueTypeInteger;
2395 attribs[1].type_ = VASurfaceAttribExternalBufferDescriptor;
2396 attribs[1].flags = VA_SURFACE_ATTRIB_SETTABLE;
2397 attribs[1].value.type_ = VAGenericValueTypePointer;
2398 if modifier != 0x00ff_ffff_ffff_ffff {
2399 let mut desc: VADRMPRIMESurfaceDescriptor = unsafe { std::mem::zeroed() };
2400 desc.fourcc = fourcc;
2401 desc.width = width;
2402 desc.height = height;
2403 desc.num_objects = objects.len() as u32;
2404 for (i, &(fd, _, size)) in objects.iter().enumerate() {
2405 desc.objects[i].fd = fd;
2406 desc.objects[i].size = size;
2407 desc.objects[i].drm_format_modifier = modifier;
2408 }
2409 desc.num_layers = 1;
2410 desc.layers[0].drm_format = drm_format;
2411 desc.layers[0].num_planes = planes.len() as u32;
2412 for (i, &(pitch, offset)) in planes.iter().enumerate().take(4) {
2413 desc.layers[0].object_index[i] = plane_object[i];
2414 desc.layers[0].offset[i] = offset;
2415 desc.layers[0].pitch[i] = pitch;
2416 }
2417 attribs[0].value.value.i = VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2 as i32;
2418 attribs[1].value.value.p = &mut desc as *mut VADRMPRIMESurfaceDescriptor as *mut c_void;
2419 let status = unsafe {
2420 (api.vaCreateSurfaces)(
2421 display,
2422 VA_RT_FORMAT_RGB32,
2423 width,
2424 height,
2425 &mut surface,
2426 1,
2427 attribs.as_mut_ptr(),
2428 2,
2429 )
2430 };
2431 if status == VA_STATUS_SUCCESS as VAStatus {
2432 return Ok(surface);
2433 }
2434 }
2435 if objects.len() != 1 {
2437 return Err("this VA-API driver maps no dmabuf of several objects".into());
2438 }
2439 let (fd, _, size) = objects[0];
2440 let mut handle = fd as usize;
2441 let mut desc: VASurfaceAttribExternalBuffers = unsafe { std::mem::zeroed() };
2442 desc.pixel_format = fourcc;
2443 desc.width = width;
2444 desc.height = height;
2445 desc.data_size = size;
2446 desc.buffers = &mut handle;
2447 desc.num_buffers = 1;
2448 desc.num_planes = planes.len() as u32;
2449 for (i, &(pitch, offset)) in planes.iter().enumerate().take(4) {
2450 desc.pitches[i] = pitch;
2451 desc.offsets[i] = offset;
2452 }
2453 attribs[0].value.value.i = VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME as i32;
2454 attribs[1].value.value.p = &mut desc as *mut VASurfaceAttribExternalBuffers as *mut c_void;
2455 self.device.check(
2456 unsafe {
2457 (api.vaCreateSurfaces)(
2458 display,
2459 VA_RT_FORMAT_RGB32,
2460 width,
2461 height,
2462 &mut surface,
2463 1,
2464 attribs.as_mut_ptr(),
2465 2,
2466 )
2467 },
2468 "no VA surface over the dmabuf",
2469 )?;
2470 Ok(surface)
2471 }
2472
2473 fn convert(&mut self, source: VASurfaceID) -> Result<(), String> {
2478 let api = self.device.api;
2479 let display = self.device.display;
2480 let region = VARectangle {
2481 x: 0,
2482 y: 0,
2483 width: self.negotiated.width as u16,
2484 height: self.negotiated.height as u16,
2485 };
2486 let declared = self.declared_color();
2487 let mut params: VAProcPipelineParameterBuffer = unsafe { std::mem::zeroed() };
2488 params.surface = source;
2489 params.surface_region = ®ion;
2490 params.output_region = ®ion;
2491 params.output_background_color = 0xff00_0000;
2492 params.pipeline_flags = 0;
2493 params.filter_flags = VA_FRAME_PICTURE;
2494 params.rotation_state = VA_ROTATION_NONE;
2495 params.mirror_state = VA_MIRROR_NONE;
2496 params.surface_color_standard = self.vpp_standards.0;
2497 params.output_color_standard = self.vpp_standards.1;
2498 params.input_color_properties = ColorDescription::SRGB_SOURCE.properties();
2499 params.output_color_properties = declared.properties();
2500 let buffer = self.device.create_buffer(
2501 self.vpp_context,
2502 VAProcPipelineParameterBufferType,
2503 unsafe {
2504 std::slice::from_raw_parts(
2505 ¶ms as *const VAProcPipelineParameterBuffer as *const u8,
2506 std::mem::size_of::<VAProcPipelineParameterBuffer>(),
2507 )
2508 },
2509 )?;
2510 let mut buffers = [buffer];
2511 let result = (|| {
2512 self.device.check(
2513 unsafe { (api.vaBeginPicture)(display, self.vpp_context, self.converted[0]) },
2514 "vaBeginPicture (video processing)",
2515 )?;
2516 let rendered = self.device.check(
2517 unsafe {
2518 (api.vaRenderPicture)(display, self.vpp_context, buffers.as_mut_ptr(), 1)
2519 },
2520 "vaRenderPicture (video processing)",
2521 );
2522 let ended = self.device.check(
2523 unsafe { (api.vaEndPicture)(display, self.vpp_context) },
2524 "vaEndPicture (video processing)",
2525 );
2526 rendered.and(ended)
2527 })();
2528 unsafe { (api.vaDestroyBuffer)(display, buffer) };
2529 result
2530 }
2531
2532 fn encode_surface(
2535 &mut self,
2536 source: VASurfaceID,
2537 frame_number: u64,
2538 crf: u32,
2539 force_idr: bool,
2540 ) -> Result<Vec<u8>, String> {
2541 if !self.rate.cbr {
2542 self.qp = self
2543 .codec
2544 .hardware_quantizer(self.device.hardware(), crf as i32);
2545 }
2546 let held_qp = self.held.take();
2547 self.convert(source)?;
2548 let frame_id = frame_number as u16;
2549 let has_reference = match &self.references {
2550 Some(References::Window(w)) => w.has_reference(),
2551 Some(References::Slots(s)) => s.has_reference(),
2552 None => !self.fresh,
2553 };
2554 let mut key = force_idr || self.sequence_start || !has_reference;
2555 let (mut anchor, mut shared) = (None, false);
2556 if let Some(References::Window(w)) = &mut self.references
2557 && w.anchored()
2558 {
2559 if !key && w.plan_anchor(false).is_some() && w.settle() == Some(Invalidation::KeyFrame)
2560 {
2561 key = true;
2562 }
2563 if !key && w.forget_stale() == Some(Invalidation::KeyFrame) {
2564 key = true;
2565 }
2566 anchor = w.plan_anchor(key);
2567 shared = w.acknowledging() && w.predicts_from_shared();
2568 }
2569 let pts = match &self.references {
2570 Some(References::Window(w)) => w.next_pts(),
2571 Some(References::Slots(s)) => s.next_pts(),
2572 None => self.frame_count,
2573 };
2574 let held: Vec<(u64, VASurfaceID, bool)> = match &self.references {
2575 Some(References::Window(w)) => w
2576 .held()
2577 .filter_map(|(_, p, lost)| self.surfaces_of.get(&p).map(|&s| (p, s, lost)))
2578 .collect(),
2579 _ => Vec::new(),
2580 };
2581 let (anchored, long_term): (bool, Vec<(u64, u8)>) = match &self.references {
2582 Some(References::Window(w)) if w.anchored() => (
2583 true,
2584 held.iter()
2585 .filter_map(|h| w.anchor_slot(h.0).map(|slot| (h.0, slot)))
2586 .collect(),
2587 ),
2588 _ => (false, Vec::new()),
2589 };
2590 let (reference, key_pts, slots, slot_surfaces) = match &self.references {
2591 Some(References::Window(w)) => (
2592 if key {
2593 None
2594 } else {
2595 w.newest_valid()
2596 .and_then(|(_, p)| self.surfaces_of.get(&p).map(|&s| (p, s)))
2597 },
2598 if key { pts } else { w.key_pts() },
2599 SlotPlan::KEY,
2600 [VA_INVALID_SURFACE; 3],
2601 ),
2602 Some(References::Slots(s)) => {
2603 let plan = s.plan(key);
2604 let surfaces = [1u8, 2, 4].map(|slot| {
2605 s.slot(slot)
2606 .and_then(|(_, p, _)| self.surfaces_of.get(&p).copied())
2607 .unwrap_or(VA_INVALID_SURFACE)
2608 });
2609 let reference = (plan.predict_from != 0)
2610 .then(|| s.slot(plan.predict_from))
2611 .flatten()
2612 .and_then(|(_, p, _)| self.surfaces_of.get(&p).map(|&surf| (p, surf)));
2613 (reference, 0, plan, surfaces)
2614 }
2615 None => (
2616 if key {
2617 None
2618 } else {
2619 self.surfaces_of.get(&(pts - 1)).map(|&s| (pts - 1, s))
2620 },
2621 if key { pts } else { self.key_count },
2622 SlotPlan::KEY,
2623 [VA_INVALID_SURFACE; 3],
2624 ),
2625 };
2626 let recon = match &self.references {
2627 Some(References::Slots(_)) => *self
2628 .recon
2629 .iter()
2630 .find(|s| !slot_surfaces.contains(s))
2631 .ok_or("no VP8 reconstruction surface is free")?,
2632 Some(References::Window(_)) => *self
2633 .recon
2634 .iter()
2635 .find(|s| !held.iter().any(|h| h.1 == **s))
2636 .ok_or("no reconstruction surface is free")?,
2637 None => self.recon[(pts % self.recon.len() as u64) as usize],
2638 };
2639 let qp = if self.rate.cbr {
2640 self.qp
2641 } else {
2642 held_qp.unwrap_or(self.qp)
2643 };
2644 let frame = Frame {
2645 key,
2646 pts,
2647 key_pts,
2648 recon,
2649 coded: self.coded,
2650 reference,
2651 held: &held,
2652 anchored,
2653 anchor,
2654 long_term: &long_term,
2655 qp,
2656 slots,
2657 slot_surfaces,
2658 };
2659 let mut out = Buffers::new();
2660 let mut bps = self
2661 .cleanup_bps
2662 .take()
2663 .unwrap_or(self.negotiated.bits_per_second);
2664 if self.rate.cbr && key && held_qp.is_some() {
2665 let fps = self.negotiated.fps.fps().max(1.0);
2667 let bits = held_key_bits(
2668 self.negotiated.bits_per_second,
2669 self.negotiated.width,
2670 self.negotiated.height,
2671 );
2672 bps = (bits * fps).min(f64::from(u32::MAX)) as u32;
2673 }
2674 if key {
2675 self.arm.sequence(&self.negotiated, &mut out);
2676 self.declared_level = self.arm.level();
2677 self.rate_control_at(&mut out, bps);
2678 self.sent_bps = bps;
2679 } else if self.rate.cbr && bps != self.sent_bps {
2680 self.rate_control_at(&mut out, bps);
2682 self.sent_bps = bps;
2683 }
2684 self.arm.picture(&self.negotiated, &frame, &mut out)?;
2685 let refinement = self.refinement.take().filter(|_| !key && held_qp.is_none());
2686 if let Some(refinement) = &refinement {
2687 self.roi_parameter(refinement, &mut out);
2688 }
2689 let header_len = if self.omit_headers {
2690 0
2691 } else {
2692 VIDEO_HEADER_LEN
2693 };
2694 let mut output = vec![0; header_len];
2695 self.issue(&out, &mut output)?;
2696 if std::mem::take(&mut self.measure) {
2697 self.last_psnr = self.picture_psnr(recon);
2698 self.measures = self.last_psnr.is_some();
2699 }
2700 if key
2701 && self.codec == Codec::H264
2702 && let Some(bounded) = self.reorder.apply(&output[header_len..])
2703 {
2704 output.truncate(header_len);
2705 output.extend_from_slice(&bounded);
2706 }
2707 self.fresh = false;
2708 self.sequence_start = false;
2709 self.surfaces_of.insert(pts, recon);
2710 let coded = &output[header_len..];
2711 let frame_type = match self.codec {
2712 Codec::H264 => h264_frame_type(coded),
2713 Codec::H265 => h265_frame_type(coded),
2714 Codec::Vp8 => frame_type_from_key(vp8_is_key(coded)),
2715 Codec::Vp9 => frame_type_from_key(vp9_is_key(coded)),
2716 _ => frame_type_from_key(av1_is_key(coded)),
2717 };
2718 let is_key = frame_type != super::codec::FRAME_DELTA;
2719 if is_key
2720 && self.codec == Codec::H264
2721 && let Some(References::Window(w)) = &mut self.references
2722 && let Some(range) = h264_frame_num_range(coded)
2723 {
2724 w.set_frame_num_range(range);
2725 }
2726 self.last_reference = match &mut self.references {
2727 Some(References::Window(w)) => w.record_marked(frame_id, is_key, anchor),
2728 Some(References::Slots(s)) => {
2729 let plan = if is_key { s.plan(true) } else { slots };
2730 s.record(frame_id, plan)
2731 }
2732 None => {
2733 self.frame_count = pts + 1;
2734 if is_key {
2735 self.key_count = pts;
2736 }
2737 Reference::Untracked
2738 }
2739 };
2740 match &self.references {
2741 Some(References::Slots(s)) => {
2742 let held: Vec<u64> = [1u8, 2, 4]
2743 .iter()
2744 .filter_map(|&slot| s.slot(slot).map(|(_, p, _)| p))
2745 .collect();
2746 self.surfaces_of.retain(|p, _| held.contains(p));
2747 }
2748 Some(References::Window(w)) => {
2749 let held: Vec<u64> = w.held().map(|(_, p, _)| p).collect();
2750 self.surfaces_of.retain(|p, _| held.contains(p));
2751 }
2752 None => self
2753 .surfaces_of
2754 .retain(|&p, _| p + self.recon.len() as u64 > pts),
2755 }
2756 if !self.omit_headers {
2757 let mut header = Vec::with_capacity(VIDEO_HEADER_LEN);
2758 let anchor_flag = if anchor.is_some() && !is_key && shared {
2759 super::codec::FRAME_ANCHOR
2760 } else {
2761 0
2762 };
2763 push_video_header(
2764 &mut header,
2765 self.codec,
2766 frame_type | anchor_flag,
2767 frame_id,
2768 0,
2769 self.negotiated.width as u16,
2770 self.negotiated.height as u16,
2771 self.last_reference,
2772 );
2773 output[..VIDEO_HEADER_LEN].copy_from_slice(&header);
2774 }
2775 Ok(output)
2776 }
2777
2778 fn issue(&mut self, buffers: &Buffers, out: &mut Vec<u8>) -> Result<(), String> {
2781 let api = self.device.api;
2782 let display = self.device.display;
2783 let mut ids = Vec::with_capacity(buffers.entries.len());
2784 let mut created = Ok(());
2785 for (kind, bytes) in &buffers.entries {
2786 match self.device.create_buffer(self.context, *kind, bytes) {
2787 Ok(id) => ids.push(id),
2788 Err(e) => {
2789 created = Err(e);
2790 break;
2791 }
2792 }
2793 }
2794 let result = created.and_then(|()| {
2795 self.device.check(
2796 unsafe { (api.vaBeginPicture)(display, self.context, self.converted[0]) },
2797 "vaBeginPicture",
2798 )?;
2799 let rendered = self.device.check(
2800 unsafe {
2801 (api.vaRenderPicture)(
2802 display,
2803 self.context,
2804 ids.as_mut_ptr(),
2805 ids.len() as c_int,
2806 )
2807 },
2808 "vaRenderPicture",
2809 );
2810 let ended = self.device.check(
2811 unsafe { (api.vaEndPicture)(display, self.context) },
2812 "vaEndPicture",
2813 );
2814 rendered.and(ended)
2815 });
2816 for id in ids {
2817 unsafe { (api.vaDestroyBuffer)(display, id) };
2818 }
2819 result?;
2820 let synced = match api.vaSyncBuffer {
2821 Some(sync) => {
2822 let status = unsafe { sync(display, self.coded, u64::MAX) };
2823 if status == VA_STATUS_ERROR_UNIMPLEMENTED as VAStatus {
2824 None
2825 } else {
2826 Some(self.device.check(status, "vaSyncBuffer"))
2827 }
2828 }
2829 None => None,
2830 };
2831 match synced {
2832 Some(result) => result?,
2833 None => self.device.check(
2834 unsafe { (api.vaSyncSurface)(display, self.converted[0]) },
2835 "vaSyncSurface",
2836 )?,
2837 }
2838 let mut list: *mut c_void = ptr::null_mut();
2839 self.device.check(
2840 unsafe { (api.vaMapBuffer)(display, self.coded, &mut list) },
2841 "vaMapBuffer (coded)",
2842 )?;
2843 let segments = || {
2844 std::iter::successors(
2845 unsafe { (list as *const VACodedBufferSegment).as_ref() },
2846 |s| unsafe { (s.next as *const VACodedBufferSegment).as_ref() },
2847 )
2848 .filter(|s| !s.buf.is_null())
2849 };
2850 let from = out.len();
2851 let average_qp = segments()
2852 .next()
2853 .map_or(0, |s| s.status & VA_CODED_BUF_STATUS_PICTURE_AVE_QP_MASK);
2854 out.reserve(segments().map(|s| s.size as usize).sum());
2855 for s in segments() {
2856 out.extend_from_slice(unsafe {
2857 std::slice::from_raw_parts(s.buf as *const u8, s.size as usize)
2858 });
2859 }
2860 unsafe { (api.vaUnmapBuffer)(display, self.coded) };
2861 if self.rate.cbr && matches!(self.arm, Arm::H264(_) | Arm::H265(_)) {
2862 strip_zero_padding(out, from);
2863 }
2864 self.last_bytes = Some(out.len() - from);
2865 let quantizer = match &self.arm {
2866 Arm::H264(_) | Arm::H265(_) => Some(average_qp).filter(|&q| q > 0),
2867 Arm::Av1(a) => a.coded_qindex(&out[from..]),
2868 Arm::Vp9(_) => super::codec::vp9_base_q_idx(&out[from..]),
2869 Arm::Vp8(_) => None,
2870 };
2871 self.last_quality = quantizer
2872 .filter(|_| self.rate.cbr)
2873 .map(|q| self.codec.hardware_quality_index(self.device.hardware(), q));
2874 Ok(())
2875 }
2876}
2877
2878#[derive(Clone, Copy)]
2881struct ColorDescription {
2882 primaries: u8,
2883 transfer: u8,
2884 matrix: u8,
2885 full_range: bool,
2886 rgb: bool,
2887}
2888
2889impl ColorDescription {
2890 const SRGB_SOURCE: Self = Self {
2893 primaries: 1,
2894 transfer: 1,
2895 matrix: 0,
2896 full_range: true,
2897 rgb: true,
2898 };
2899
2900 fn properties(self) -> VAProcColorProperties {
2901 VAProcColorProperties {
2902 chroma_sample_location: (VA_CHROMA_SITING_VERTICAL_CENTER
2903 | VA_CHROMA_SITING_HORIZONTAL_CENTER) as u8,
2904 color_range: if self.full_range {
2905 VA_SOURCE_RANGE_FULL
2906 } else {
2907 VA_SOURCE_RANGE_REDUCED
2908 } as u8,
2909 colour_primaries: self.primaries,
2910 transfer_characteristics: self.transfer,
2911 matrix_coefficients: self.matrix,
2912 reserved: [0; 3],
2913 }
2914 }
2915}
2916
2917fn color_standard(
2922 offered: &[VAProcColorStandardType],
2923 wanted: ColorDescription,
2924) -> VAProcColorStandardType {
2925 if offered.contains(&VAProcColorStandardExplicit) {
2926 return VAProcColorStandardExplicit;
2927 }
2928 const TABLE: [(VAProcColorStandardType, u8, u8, u8); 12] = [
2929 (VAProcColorStandardBT601, 5, 6, 5),
2930 (VAProcColorStandardBT601, 6, 6, 6),
2931 (VAProcColorStandardBT709, 1, 1, 1),
2932 (VAProcColorStandardBT470M, 4, 4, 4),
2933 (VAProcColorStandardBT470BG, 5, 5, 5),
2934 (VAProcColorStandardSMPTE170M, 6, 6, 6),
2935 (VAProcColorStandardSMPTE240M, 7, 7, 7),
2936 (VAProcColorStandardGenericFilm, 8, 1, 1),
2937 (VAProcColorStandardSRGB, 1, 13, 0),
2938 (VAProcColorStandardXVYCC601, 1, 11, 5),
2939 (VAProcColorStandardXVYCC709, 1, 11, 1),
2940 (VAProcColorStandardBT2020, 9, 14, 9),
2941 ];
2942 let matrix_counts = !wanted.rgb;
2943 let worst = 4 * u32::from(matrix_counts) + 2 + 1;
2944 let mut best = (worst, VAProcColorStandardNone);
2945 for &standard in offered {
2946 for &(_, primaries, transfer, matrix) in TABLE.iter().filter(|t| t.0 == standard) {
2947 let score = 4 * u32::from(matrix_counts && wanted.matrix != matrix)
2948 + 2 * u32::from(wanted.transfer != transfer)
2949 + u32::from(wanted.primaries != primaries);
2950 if score < best.0 {
2951 best = (score, standard);
2952 }
2953 }
2954 }
2955 best.1
2956}
2957
2958fn image_format(device: &Device, fourcc: u32) -> Option<VAImageFormat> {
2960 let api = device.api;
2961 let mut formats: Vec<VAImageFormat> = vec![
2962 unsafe { std::mem::zeroed() };
2963 unsafe { (api.vaMaxNumImageFormats)(device.display) }.max(0)
2964 as usize
2965 ];
2966 let mut count: c_int = 0;
2967 if unsafe { (api.vaQueryImageFormats)(device.display, formats.as_mut_ptr(), &mut count) }
2968 != VA_STATUS_SUCCESS as VAStatus
2969 {
2970 return None;
2971 }
2972 formats[..count.max(0) as usize]
2973 .iter()
2974 .find(|f| f.fourcc == fourcc)
2975 .copied()
2976}
2977
2978fn object_identity(fd: i32) -> Option<(u64, u64)> {
2980 let mut st: libc::stat = unsafe { std::mem::zeroed() };
2981 (unsafe { libc::fstat(fd, &mut st) } == 0).then_some((st.st_dev, st.st_ino))
2982}
2983
2984#[cfg(test)]
2985mod tests {
2986 use super::*;
2987
2988 #[test]
2991 fn radeonsi_refines_from_a_twenty_fifth_of_a_bit_a_pixel() {
2992 assert!(refines(5_000_000, 60.0, 1920, 1080));
2993 assert!(refines(700_000, 8.0, 1920, 1080));
2994 assert!(!refines(2_000_000, 60.0, 1920, 1080));
2995 assert!(!refines(500_000, 8.0, 1920, 1080));
2996 }
2997
2998 #[test]
3001 fn intel_low_power_constant_quantizer_names_at_most_three_regions() {
3002 let lp = VAEntrypointEncSliceLP;
3003 let full = VAEntrypointEncSlice;
3004 let intel = Hardware::VaapiIntel;
3005 assert_eq!(roi_regions(Some(0x210), intel, lp, false), 3);
3006 assert_eq!(roi_regions(Some(0x210), intel, lp, true), 16);
3007 assert_eq!(roi_regions(Some(0x010), intel, lp, true), 0);
3008 assert_eq!(roi_regions(Some(0x210), intel, full, false), 16);
3009 assert_eq!(roi_regions(Some(0x002), intel, lp, false), 2);
3010 assert_eq!(roi_regions(None, intel, lp, false), 0);
3011 }
3012
3013 #[test]
3015 fn a_held_key_frame_gets_a_second_of_the_rate_and_enough_for_the_picture() {
3016 assert_eq!(held_key_bits(500_000, 1920, 1080), 1.6 * 1920.0 * 1080.0);
3017 assert_eq!(held_key_bits(8_000_000, 1920, 1080), 8_000_000.0);
3018 }
3019
3020 #[test]
3024 fn surface_and_profile_ladders() {
3025 assert_eq!(FULLCOLOR_FOURCCS, [VA_FOURCC_444P, VA_FOURCC_XYUV]);
3026 assert_eq!(fourcc_name(VA_FOURCC_XYUV), "vuyx");
3027 assert_eq!(fourcc_name(VA_FOURCC_444P), "yuv444p");
3028 assert!(
3029 profile_ladder(Codec::H264, true, 8).is_empty(),
3030 "no 4:4:4 H.264 profile is served"
3031 );
3032 assert_eq!(profile_ladder(Codec::H265, true, 8), [VAProfileHEVCMain444]);
3033 assert_eq!(profile_ladder(Codec::Vp9, true, 8), [VAProfileVP9Profile1]);
3034 assert!(profile_ladder(Codec::Av1, true, 8).is_empty());
3035 assert_eq!(
3036 profile_ladder(Codec::H265, false, 10),
3037 [VAProfileHEVCMain10]
3038 );
3039 assert_eq!(
3040 profile_ladder(Codec::H265, true, 10),
3041 [VAProfileHEVCMain444_10]
3042 );
3043 assert_eq!(
3044 profile_ladder(Codec::Vp9, false, 10),
3045 [VAProfileVP9Profile2]
3046 );
3047 assert_eq!(profile_ladder(Codec::Vp9, true, 10), [VAProfileVP9Profile3]);
3048 assert_eq!(
3049 profile_ladder(Codec::Av1, false, 10),
3050 [VAProfileAV1Profile0]
3051 );
3052 assert!(profile_ladder(Codec::H264, false, 10).is_empty());
3053 assert!(profile_ladder(Codec::Vp8, false, 10).is_empty());
3054 for codec in Codec::VIDEO {
3055 assert!(!profile_ladder(codec, false, 8).is_empty(), "{codec:?}");
3056 }
3057 }
3058
3059 #[test]
3064 fn the_color_standard_is_the_nearest_the_driver_offers() {
3065 let explicit = [VAProcColorStandardBT601, VAProcColorStandardExplicit];
3066 assert_eq!(
3067 color_standard(&explicit, ColorDescription::SRGB_SOURCE),
3068 VAProcColorStandardExplicit
3069 );
3070 let classic = [
3071 VAProcColorStandardBT601,
3072 VAProcColorStandardBT709,
3073 VAProcColorStandardSMPTE170M,
3074 VAProcColorStandardSRGB,
3075 ];
3076 assert_eq!(
3077 color_standard(&classic, ColorDescription::SRGB_SOURCE),
3078 VAProcColorStandardBT709
3079 );
3080 let bt709 = ColorDescription {
3081 primaries: 1,
3082 transfer: 1,
3083 matrix: 1,
3084 full_range: false,
3085 rgb: false,
3086 };
3087 assert_eq!(color_standard(&classic, bt709), VAProcColorStandardBT709);
3088 let bt601 = ColorDescription { matrix: 6, ..bt709 };
3089 assert_eq!(
3090 color_standard(&classic, bt601),
3091 VAProcColorStandardBT601,
3092 "the driver's first of two equal matches"
3093 );
3094 assert_eq!(
3095 color_standard(&classic[2..], bt601),
3096 VAProcColorStandardSMPTE170M
3097 );
3098 assert_eq!(
3099 color_standard(&[VAProcColorStandardBT2020], bt709),
3100 VAProcColorStandardNone,
3101 "a total mismatch names no standard"
3102 );
3103 }
3104
3105 #[test]
3109 fn slice_layout_follows_the_driver() {
3110 assert_eq!(
3111 slice_layout(VA_ENC_SLICE_STRUCTURE_ARBITRARY_ROWS, 32, 68, 4),
3112 Ok((4, 17))
3113 );
3114 assert_eq!(
3115 slice_layout(VA_ENC_SLICE_STRUCTURE_POWER_OF_TWO_ROWS, 32, 68, 4),
3116 Ok((5, 16))
3117 );
3118 assert_eq!(
3119 slice_layout(VA_ENC_SLICE_STRUCTURE_EQUAL_ROWS, 128, 68, 4),
3120 Ok((68, 1))
3121 );
3122 assert!(
3123 slice_layout(VA_ENC_SLICE_STRUCTURE_EQUAL_ROWS, 32, 68, 4).is_err(),
3124 "more slices than the driver takes"
3125 );
3126 assert_eq!(
3127 slice_layout(
3128 VA_ENC_SLICE_STRUCTURE_EQUAL_ROWS | VA_ENC_SLICE_STRUCTURE_EQUAL_MULTI_ROWS,
3129 256,
3130 68,
3131 4
3132 ),
3133 Ok((4, 17)),
3134 "equal rows of more than one where the driver offers both"
3135 );
3136 assert_eq!(
3137 slice_layout(VA_ENC_SLICE_STRUCTURE_EQUAL_MULTI_ROWS, 32, 68, 4),
3138 Ok((4, 17))
3139 );
3140 assert_eq!(
3141 slice_layout(VA_ENC_SLICE_STRUCTURE_EQUAL_MULTI_ROWS, 32, 67, 4),
3142 Ok((4, 17)),
3143 "the last slice is the shorter one"
3144 );
3145 assert!(slice_layout(0, 32, 68, 4).is_err());
3146 assert_eq!(
3147 slice_layout(VA_ENC_SLICE_STRUCTURE_ARBITRARY_ROWS, 32, 2, 4),
3148 Ok((2, 1)),
3149 "no more slices than rows"
3150 );
3151 assert_eq!(
3152 slice_layout(VA_ENC_SLICE_STRUCTURE_ARBITRARY_ROWS, 2, 68, 4),
3153 Ok((2, 34)),
3154 "no more slices than the driver takes"
3155 );
3156 assert_eq!(
3157 slice_layout(VA_ENC_SLICE_STRUCTURE_ARBITRARY_ROWS, 1, 68, 4),
3158 Ok((1, 68))
3159 );
3160 assert_eq!(
3161 slice_layout(VA_ENC_SLICE_STRUCTURE_POWER_OF_TWO_ROWS, 4, 68, 4),
3162 Ok((3, 32))
3163 );
3164 assert_eq!(
3165 slice_layout(VA_ENC_SLICE_STRUCTURE_POWER_OF_TWO_ROWS, 1, 68, 4),
3166 Ok((1, 128))
3167 );
3168 }
3169
3170 #[test]
3174 fn construction_answers_or_refuses() {
3175 let mut settings = CaptureSettings {
3176 width: 128,
3177 height: 128,
3178 codec: Codec::H264,
3179 video_fullcolor: true,
3180 ..Default::default()
3181 };
3182 for codec in Codec::VIDEO {
3183 settings.codec = codec;
3184 match VaapiEncoder::new(&settings, codec, Input::Host { rgba: false }) {
3185 Ok(enc) => assert_eq!(
3186 enc.is_fullcolor(),
3187 matches!(codec, Codec::H265 | Codec::Vp9),
3188 "{codec:?}"
3189 ),
3190 Err(e) => assert!(!e.is_empty(), "refusal must carry a reason"),
3191 }
3192 }
3193 settings.video_fullcolor = false;
3194 match VaapiEncoder::new(&settings, Codec::H264, Input::Host { rgba: false }) {
3195 Ok(enc) => assert!(!enc.is_fullcolor(), "4:2:0 session reports 4:4:4"),
3196 Err(e) => assert!(!e.is_empty(), "refusal must carry a reason"),
3197 }
3198 assert!(VaapiEncoder::new(&settings, Codec::Jpeg, Input::Host { rgba: false }).is_err());
3199 }
3200
3201 #[test]
3205 #[ignore]
3206 fn vaapi_vce_h264_is_one_slice() {
3207 let (w, h) = (1920usize, 1080usize);
3208 let settings = CaptureSettings {
3209 width: w as i32,
3210 height: h as i32,
3211 codec: Codec::H264,
3212 omit_stripe_headers: true,
3213 ..Default::default()
3214 };
3215 let mut enc = VaapiEncoder::new(&settings, Codec::H264, Input::Host { rgba: false })
3216 .expect("a VA-API H.264 session");
3217 let frame: Vec<u8> = (0..w * h)
3218 .flat_map(|i| [(i % w) as u8, (i / w) as u8, 0x80, 0xff])
3219 .collect();
3220 let key = enc
3221 .encode_host(&frame, w * 4, false, 0, 25, true)
3222 .expect("encode");
3223 let slices = super::super::codec::annexb_nals(&key)
3224 .filter(|n| matches!(n[0] & 0x1f, 1 | 5))
3225 .count();
3226 println!(
3227 "{}: VCE {}, {slices} slice(s) in the key frame",
3228 enc.vendor(),
3229 enc.device.vce
3230 );
3231 let wanted = if enc.device.one_slice_h264(enc.negotiated.entrypoint) {
3232 1
3233 } else {
3234 SLICES as usize
3235 };
3236 assert_eq!(slices, wanted);
3237 }
3238
3239 fn moving_picture(t: usize) -> Vec<u8> {
3242 let (w, h) = (1280usize, 720usize);
3243 let noise = |n: usize| {
3244 let mut x = (n as u64 ^ 0x9e37_79b9_7f4a_7c15).wrapping_mul(0xbf58_476d_1ce4_e5b9);
3245 x ^= x >> 31;
3246 x as u8
3247 };
3248 let bar = (t * 12) % (w - 160);
3249 let mut f = vec![0u8; w * h * 4];
3250 for (i, px) in f.as_chunks_mut::<4>().0.iter_mut().enumerate() {
3251 let (x, y) = (i % w, i / w);
3252 let bgr: [u8; 3] = if y >= 480 {
3253 let n = (t % 4) * w * 240 + (y - 480) * w + x;
3254 [noise(3 * n), noise(3 * n + 1), noise(3 * n + 2)]
3255 } else if (96..160).contains(&y) {
3256 [if ((x + t * 4) / 16 + (y - 96) / 16).is_multiple_of(2) {
3257 255
3258 } else {
3259 0
3260 }; 3]
3261 } else if y >= 160 && (bar..bar + 160).contains(&x) {
3262 [0x28, 0x3c, 0xdc]
3263 } else {
3264 [0x78, 0x28, 0x1e]
3265 };
3266 px[..3].copy_from_slice(&bgr);
3267 px[3] = 0xff;
3268 }
3269 f
3270 }
3271
3272 fn predict_past_losses(
3279 codec: Codec,
3280 from: usize,
3281 every: usize,
3282 depth: usize,
3283 ) -> Option<(usize, usize, usize, f64, f64, bool)> {
3284 use crate::webcam::decode::{Decoder as _, VideoDecoder};
3285 let (w, h) = (1280usize, 720usize);
3286 let settings = CaptureSettings {
3287 width: w as i32,
3288 height: h as i32,
3289 codec,
3290 target_fps: 60.0,
3291 video_cbr_mode: true,
3292 video_bitrate_kbps: 8000,
3293 omit_stripe_headers: true,
3294 ..Default::default()
3295 };
3296 let mut enc = match VaapiEncoder::new(&settings, codec, Input::Host { rgba: false }) {
3297 Ok(enc) => enc,
3298 Err(e) => {
3299 println!("{codec:?}: {e}");
3300 return None;
3301 }
3302 };
3303 println!("{codec:?} on {}", enc.vendor());
3304 let (mut whole, mut lossy) = (
3305 VideoDecoder::new(codec).expect("decoder"),
3306 VideoDecoder::new(codec).expect("decoder"),
3307 );
3308 let reported = |r: usize| r >= from && (r - from).is_multiple_of(every);
3309 let (mut past, mut anchored, mut keys, mut apart, mut worst) = (0, 0, 0, 0f64, 0f64);
3310 for t in 0..120usize {
3311 if reported(t) {
3312 assert!(enc.invalidate_reference((t - depth) as u16), "{codec:?}");
3313 }
3314 let from_anchor = matches!(&enc.references,
3315 Some(References::Window(win)) if win.predicting_anchor().is_some());
3316 let src = moving_picture(t);
3317 let out = enc
3318 .encode_host(&src, w * 4, false, t as u64, 25, t == 0)
3319 .expect("encode");
3320 match enc.last_reference() {
3321 Reference::None if t > 0 => keys += 1,
3322 Reference::Frame(r) if r as usize + 1 != t => {
3323 past += 1;
3324 anchored += usize::from(from_anchor);
3325 }
3326 _ => {}
3327 }
3328 assert!(whole.decode(&out).expect("decode"), "{codec:?}: frame {t}");
3329 if (t + 1..=t + depth).any(reported) {
3330 continue;
3331 }
3332 assert!(
3333 lossy.decode(&out).expect("decode"),
3334 "{codec:?}: lossy frame {t}"
3335 );
3336 let (a, b) = (whole.frame().unwrap(), lossy.frame().unwrap());
3337 let mut sum = 0f64;
3338 for y in (0..480).step_by(2) {
3339 for x in (0..w).step_by(2) {
3340 let i = (y * w + x) * 4;
3341 let (bb, g, r) = (
3342 f64::from(src[i]),
3343 f64::from(src[i + 1]),
3344 f64::from(src[i + 2]),
3345 );
3346 let luma = 16.0 + (0.2126 * r + 0.7152 * g + 0.0722 * bb) * 219.0 / 255.0;
3347 let (pa, pb) = (
3348 f64::from(a.y[y * a.y_stride + x]),
3349 f64::from(b.y[y * b.y_stride + x]),
3350 );
3351 sum += (pb - luma).abs();
3352 apart = apart.max((pa - pb).abs());
3353 }
3354 }
3355 worst = worst.max(sum / (240.0 * (w / 2) as f64));
3356 }
3357 println!(
3358 "{codec:?}: {past} frames predicted past a loss ({anchored} from an anchor), {keys} key frames; the lossy decoder at most {apart:.0} levels from the whole one, {worst:.1} on average from the source at worst"
3359 );
3360 Some((
3361 past,
3362 anchored,
3363 keys,
3364 apart,
3365 worst,
3366 enc.device.keeps_anchors(),
3367 ))
3368 }
3369
3370 #[test]
3374 #[ignore]
3375 fn vaapi_predicts_past_a_lost_frame() {
3376 for codec in [Codec::H264, Codec::H265] {
3377 if let Some((past, _, keys, apart, worst, _)) = predict_past_losses(codec, 13, 13, 4) {
3378 assert!(past >= 5, "{codec:?}: {past} predicted past a loss");
3379 assert_eq!(keys, 0, "{codec:?}");
3380 assert!(
3381 apart < 2.0,
3382 "{codec:?}: the decoder without the lost frames is {apart} off"
3383 );
3384 assert!(worst < 20.0, "{codec:?}: {worst} off the source");
3385 }
3386 }
3387 }
3388
3389 #[test]
3399 #[ignore]
3400 fn vaapi_predicts_from_an_anchor() {
3401 for (codec, from, every, depth, want) in [
3402 (Codec::H264, 70, 40, 12, 2),
3403 (Codec::H264, 52, 1000, 4, 0),
3404 (Codec::H265, 70, 40, 12, 2),
3405 ] {
3406 if let Some((past, anchored, keys, apart, worst, anchors)) =
3407 predict_past_losses(codec, from, every, depth)
3408 {
3409 if anchors {
3410 assert!(
3411 past >= 1 && anchored == want.min(past),
3412 "{codec:?}: {anchored} of {past} from an anchor"
3413 );
3414 assert_eq!(keys, 0);
3415 } else if want > 0 {
3416 assert!(
3417 anchored == 0 && keys >= 1,
3418 "{codec:?}: a loss past the recent frames took {keys} key frames"
3419 );
3420 } else {
3421 assert!(
3422 past >= 1 && keys == 0,
3423 "{codec:?}: {past} predicted past the loss, {keys} key frames"
3424 );
3425 }
3426 assert!(
3427 apart < 2.0,
3428 "the decoder without the lost frames is {apart} off"
3429 );
3430 assert!(worst < 20.0, "{worst} off the source");
3431 }
3432 }
3433 }
3434
3435 #[test]
3437 fn a_node_that_is_not_amdgpu_is_not_vce() {
3438 let node = std::fs::File::open("/dev/null").unwrap();
3439 assert!(!amd_vce(node.as_raw_fd()));
3440 }
3441
3442 #[test]
3445 #[ignore]
3446 fn vaapi_h264_bounds_reordering_at_zero() {
3447 use crate::encoders::sps::fixtures::assert_no_reorder;
3448 use crate::webcam::decode::{Decoder as _, VideoDecoder};
3449 let (w, h) = (1280usize, 720usize);
3450 let settings = CaptureSettings {
3451 width: w as i32,
3452 height: h as i32,
3453 codec: Codec::H264,
3454 video_cbr_mode: true,
3455 video_bitrate_kbps: 4000,
3456 omit_stripe_headers: true,
3457 ..Default::default()
3458 };
3459 let mut enc = VaapiEncoder::new(&settings, Codec::H264, Input::Host { rgba: false })
3460 .expect("a VA-API H.264 session");
3461 let frame: Vec<u8> = (0..w * h)
3462 .flat_map(|i| [(i % w) as u8, (i / w) as u8, 0x80, 0xff])
3463 .collect();
3464 let key = enc
3465 .encode_host(&frame, w * 4, false, 0, 25, true)
3466 .expect("encode");
3467 assert_no_reorder(&key, enc.vendor());
3468 let mut dec = VideoDecoder::new(Codec::H264).expect("decoder");
3469 assert!(
3470 dec.decode(&key).expect("the bounded key frame decodes"),
3471 "no picture"
3472 );
3473 }
3474
3475 #[test]
3481 #[ignore]
3482 fn vaapi_h264_constant_rate_decodes_above_level_5_2() {
3483 use crate::webcam::decode::{Decoder as _, VideoDecoder};
3484 let (w, h, tile_w, tile_h) = (3840usize, 2160usize, 1280usize, 720usize);
3485 let mut settings = CaptureSettings {
3486 width: w as i32,
3487 height: h as i32,
3488 codec: Codec::H264,
3489 target_fps: 60.0,
3490 video_cbr_mode: true,
3491 video_bitrate_kbps: 39000,
3492 omit_stripe_headers: true,
3493 ..Default::default()
3494 };
3495 let mut enc = VaapiEncoder::new(&settings, Codec::H264, Input::Host { rgba: false })
3496 .expect("a VA-API H.264 session");
3497 let mut dec = VideoDecoder::new(Codec::H264).expect("decoder");
3498 let mut phases = Vec::new();
3499 for (phase, fps, level) in [(0usize, 60.0, 52u8), (1, 75.0, 60), (2, 60.0, 52)] {
3500 if phase > 0 {
3501 settings.target_fps = fps;
3502 enc.reconfigure_rate(&settings).expect("reconfigure");
3503 }
3504 let (mut declared, mut failed, mut worst) = (None, 0, 0f64);
3505 for t in phase * 30..phase * 30 + 30 {
3506 let tile = moving_picture(t);
3507 let src: Vec<u8> = (0..h)
3508 .flat_map(|y| {
3509 let row = &tile[(y % tile_h) * tile_w * 4..][..tile_w * 4];
3510 [row, row, row].concat()
3511 })
3512 .collect();
3513 let out = enc
3514 .encode_host(&src, w * 4, false, t as u64, 25, t == 0)
3515 .expect("encode");
3516 let sps = super::super::codec::annexb_nals(&out)
3517 .find(|n| n[0] & 0x1f == 7 && n.len() > 3)
3518 .map(|n| n.as_ptr() as usize - out.as_ptr() as usize + 3);
3519 let mut fed = out.clone();
3520 if let Some(at) = sps {
3521 declared = Some(out[at]);
3522 fed[at] = fed[at].min(52);
3523 }
3524 if !dec.decode(&fed).unwrap_or(false) {
3525 failed += 1;
3526 continue;
3527 }
3528 let p = dec.frame().unwrap();
3529 let mut sum = 0f64;
3530 for y in (0..h).step_by(4) {
3531 for x in (0..w).step_by(4) {
3532 let i = (y * w + x) * 4;
3533 let (b, g, r) = (
3534 f64::from(src[i]),
3535 f64::from(src[i + 1]),
3536 f64::from(src[i + 2]),
3537 );
3538 let luma = 16.0 + (0.2126 * r + 0.7152 * g + 0.0722 * b) * 219.0 / 255.0;
3539 sum += (f64::from(p.y[y * p.y_stride + x]) - luma).abs();
3540 }
3541 }
3542 worst = worst.max(sum / ((h / 4) * (w / 4)) as f64);
3543 }
3544 println!(
3545 "{} {fps} fps: level {declared:?}, {failed} frames undecodable, {worst:.1} on average from the source at worst",
3546 enc.vendor()
3547 );
3548 phases.push((fps, level, declared, failed, worst));
3549 }
3550 for (fps, level, declared, failed, worst) in phases {
3551 assert_eq!(declared, Some(level), "{fps} fps");
3552 assert_eq!(failed, 0, "{fps} fps");
3553 assert!(worst < 20.0, "{fps} fps: {worst} off the source");
3554 }
3555 }
3556
3557 #[test]
3563 #[ignore]
3564 fn vaapi_dmabuf_import_follows_the_buffer() {
3565 use crate::encoders::nvenc::gpu_tests::{
3566 assert_painted, block_rect, decoded_means, gpu_render, paint_dmabuf, painted_dmabuf,
3567 };
3568 use crate::webcam::decode::VideoDecoder;
3569 let (w, h) = (1280u32, 720u32);
3570 let Some((gbm, mut renderer)) = gpu_render() else {
3571 return;
3572 };
3573 let settings = CaptureSettings {
3574 width: w as i32,
3575 height: h as i32,
3576 codec: Codec::H264,
3577 target_fps: 60.0,
3578 video_crf: 25,
3579 ..Default::default()
3580 };
3581 let mut enc = VaapiEncoder::new(&settings, Codec::H264, Input::Dmabuf)
3582 .expect("a zero-copy VA-API H.264 session");
3583 let mut dec = VideoDecoder::new(Codec::H264).expect("decoder");
3584 let (_a_bo, mut a) = painted_dmabuf(&gbm, &mut renderer, w, h, 1);
3585 let (_b_bo, mut b) = painted_dmabuf(&gbm, &mut renderer, w, h, 2);
3586 for frame in 0..8u32 {
3587 let seed = frame + 1;
3588 let (name, buffer) = if frame % 2 == 0 {
3589 ("A", &mut a)
3590 } else {
3591 ("B", &mut b)
3592 };
3593 if frame >= 2 {
3594 paint_dmabuf(&mut renderer, buffer, w, h, seed);
3595 }
3596 let out = enc
3597 .encode_dmabuf(buffer, u64::from(frame), 25, frame == 0)
3598 .expect("encode");
3599 let (block, bg) = decoded_means(&mut dec, &out, block_rect(w, h, seed));
3600 println!(
3601 "{} frame {frame} (buffer {name}): block {block:.1?}, background {bg:.1?}",
3602 enc.vendor()
3603 );
3604 assert_painted(&format!("frame {frame}, buffer {name}"), block, bg, 6.0);
3605 }
3606 assert_eq!(enc.imports.len(), 2, "one import a buffer");
3607 }
3608}