1mod av1;
34mod h264;
35mod h265;
36#[cfg(test)]
37pub(crate) mod mock;
38#[cfg(test)]
39mod session_tests;
40mod vp8;
41mod vp9;
42
43use std::collections::HashMap;
44use std::ffi::{CStr, CString, c_char, c_int, c_uint, c_void};
45use std::os::fd::{AsRawFd, FromRawFd, OwnedFd};
46use std::ptr;
47use std::sync::{Arc, OnceLock};
48
49use smithay::backend::allocator::{Buffer, dmabuf::Dmabuf};
50use va_sys::*;
51
52use super::codec::{
53 Codec, Hardware, VIDEO_HEADER_LEN, av1_is_key, frame_type_from_key, h264_frame_type,
54 h265_frame_type, push_video_header, vp8_is_key, vp9_is_key,
55};
56use super::frame_rate::FrameRate;
57use super::reference::{
58 ANCHORS, Invalidation, REFERENCE_FRAMES, Reference, ReferenceReport, ReferenceSlots,
59 ReferenceWindow, SlotPlan,
60};
61use super::session::{RateSettings, check_host_frame};
62use super::sps::{NoReorder, h264_frame_num_range};
63use crate::RustCaptureSettings;
64
65const SLICES: u32 = 4;
72fn coded_buffer_size(width: u32, height: u32) -> u32 {
75 3 * width * height + (1 << 16)
76}
77
78fn libva() -> Result<VaApi, String> {
80 static LIBVA: OnceLock<Result<Libva, String>> = OnceLock::new();
81 LIBVA
82 .get_or_init(|| unsafe { Libva::load() })
83 .as_ref()
84 .map(|lib| lib.api)
85 .map_err(Clone::clone)
86}
87
88#[derive(Clone, Copy, Debug, PartialEq, Eq)]
91pub enum Input {
92 Dmabuf,
93 Host { rgba: bool },
94}
95
96const FULLCOLOR_FOURCCS: [u32; 2] = [VA_FOURCC_444P, VA_FOURCC_XYUV];
100
101fn surface_fourccs(fullcolor: bool, bit_depth: u32) -> &'static [u32] {
103 match (fullcolor, bit_depth) {
104 (false, 10) => &[VA_FOURCC_P010],
105 (true, 10) => &[VA_FOURCC_Y410],
106 (true, _) => &FULLCOLOR_FOURCCS,
107 (false, _) => &[VA_FOURCC_NV12],
108 }
109}
110
111fn rt_format(fullcolor: bool, bit_depth: u32) -> u32 {
113 match (fullcolor, bit_depth) {
114 (false, 10) => VA_RT_FORMAT_YUV420_10,
115 (true, 10) => VA_RT_FORMAT_YUV444_10,
116 (true, _) => VA_RT_FORMAT_YUV444,
117 (false, _) => VA_RT_FORMAT_YUV420,
118 }
119}
120
121pub(crate) fn fourcc_name(fourcc: u32) -> String {
123 match fourcc {
124 VA_FOURCC_NV12 => "nv12".into(),
125 VA_FOURCC_P010 => "p010".into(),
126 VA_FOURCC_Y410 => "y410".into(),
127 VA_FOURCC_444P => "yuv444p".into(),
128 VA_FOURCC_XYUV => "vuyx".into(),
129 VA_FOURCC_BGRA => "bgra".into(),
130 VA_FOURCC_RGBA => "rgba".into(),
131 other => String::from_utf8_lossy(&other.to_le_bytes())
132 .trim()
133 .to_string(),
134 }
135}
136
137unsafe extern "C" fn log_error(_user: *mut c_void, message: *const c_char) {
138 if !message.is_null() {
139 eprintln!(
140 "[vaapi] {}",
141 unsafe { CStr::from_ptr(message) }
142 .to_string_lossy()
143 .trim_end()
144 );
145 }
146}
147
148unsafe extern "C" fn log_info(_user: *mut c_void, message: *const c_char) {
149 if !message.is_null() {
150 crate::log::debug!(
151 "[vaapi] {}",
152 unsafe { CStr::from_ptr(message) }
153 .to_string_lossy()
154 .trim_end()
155 );
156 }
157}
158
159#[derive(Clone, Copy, Debug, PartialEq)]
174enum ConstantRate {
175 Buffer,
177 Unbounded,
179 Seconds(f64),
181}
182
183const IHD_AV1_BUFFER_S: f64 = 0.1;
185
186const PSNR_ROW_STEP: usize = 8;
189
190const UNREADABLE_DB: f32 = 12.0;
193
194fn strip_zero_padding(out: &mut Vec<u8>, from: usize) {
198 let end = out[from..]
199 .iter()
200 .rposition(|&b| b != 0)
201 .map_or(from, |i| from + i + 1);
202 out.truncate(end);
203}
204
205pub(crate) struct Device {
207 api: VaApi,
208 display: VADisplay,
209 _fd: OwnedFd,
210 vendor: String,
211 vce: bool,
213 whole_picture_vdenc: bool,
216}
217
218fn whole_picture_vdenc(fd: c_int) -> bool {
224 let mut stat = unsafe { std::mem::zeroed::<libc::stat>() };
225 if unsafe { libc::fstat(fd, &mut stat) } != 0 {
226 return false;
227 }
228 let id = |name: &str| {
229 let path = format!(
230 "/sys/dev/char/{}:{}/device/{name}",
231 libc::major(stat.st_rdev),
232 libc::minor(stat.st_rdev)
233 );
234 let text = std::fs::read_to_string(path).ok()?;
235 u32::from_str_radix(text.trim().trim_start_matches("0x"), 16).ok()
236 };
237 id("vendor") == Some(0x8086) && id("device").is_some_and(skylake_or_broxton)
238}
239
240fn skylake_or_broxton(device: u32) -> bool {
242 matches!(
243 device,
244 0x1900..=0x19ff | 0x0a84 | 0x1a84 | 0x1a85 | 0x5a84 | 0x5a85
245 )
246}
247
248fn amd_vce(fd: c_int) -> bool {
253 #[repr(C)]
254 struct Version {
255 major: c_int,
256 minor: c_int,
257 patch: c_int,
258 name_len: usize,
259 name: *mut c_char,
260 date_len: usize,
261 date: *mut c_char,
262 desc_len: usize,
263 desc: *mut c_char,
264 }
265 #[repr(C)]
266 struct Info {
267 return_pointer: u64,
268 return_size: u32,
269 query: u32,
270 ip_type: u32,
271 ip_instance: u32,
272 reserved: [u32; 2],
273 }
274 const DRM_IOCTL_VERSION: u64 = 0xc040_6400;
275 const DRM_IOCTL_AMDGPU_INFO: u64 = 0x4020_6445;
276 const AMDGPU_INFO_HW_IP_COUNT: u32 = 0x03;
277 const AMDGPU_HW_IP_VCE: u32 = 4;
278 const AMDGPU_HW_IP_VCN_ENC: u32 = 7;
279 let mut name = [0u8; 16];
280 let mut version = Version {
281 major: 0,
282 minor: 0,
283 patch: 0,
284 name_len: name.len(),
285 name: name.as_mut_ptr() as *mut c_char,
286 date_len: 0,
287 date: ptr::null_mut(),
288 desc_len: 0,
289 desc: ptr::null_mut(),
290 };
291 if unsafe { libc::ioctl(fd, DRM_IOCTL_VERSION as _, &mut version as *mut Version) } != 0
292 || name[..version.name_len.min(name.len())] != *b"amdgpu"
293 {
294 return false;
295 }
296 let engines = |ip_type: u32| {
297 let mut count: u32 = 0;
298 let mut info = Info {
299 return_pointer: &mut count as *mut u32 as u64,
300 return_size: 4,
301 query: AMDGPU_INFO_HW_IP_COUNT,
302 ip_type,
303 ip_instance: 0,
304 reserved: [0; 2],
305 };
306 if unsafe { libc::ioctl(fd, DRM_IOCTL_AMDGPU_INFO as _, &mut info as *mut Info) } != 0 {
307 0
308 } else {
309 count
310 }
311 };
312 engines(AMDGPU_HW_IP_VCE) > 0 && engines(AMDGPU_HW_IP_VCN_ENC) == 0
313}
314
315unsafe impl Send for Device {}
317unsafe impl Sync for Device {}
318
319impl Drop for Device {
320 fn drop(&mut self) {
321 unsafe { (self.api.vaTerminate)(self.display) };
322 }
323}
324
325impl Device {
326 pub(crate) fn open(api: VaApi, encode_node_index: i32) -> Result<Self, String> {
329 let render_node = format!("/dev/dri/renderD{}", 128 + encode_node_index.max(0));
330 if crate::get_gpu_driver(encode_node_index.max(0)).contains("nvidia") {
334 return Err(format!(
335 "{render_node} is NVIDIA's, whose VA driver encodes nothing"
336 ));
337 }
338 let path = CString::new(render_node.clone()).unwrap();
339 let raw = unsafe { libc::open(path.as_ptr(), libc::O_RDWR | libc::O_CLOEXEC) };
340 if raw < 0 {
341 return Err(format!(
342 "{render_node}: {}",
343 std::io::Error::last_os_error()
344 ));
345 }
346 Self::on(api, unsafe { OwnedFd::from_raw_fd(raw) }, &render_node)
347 }
348
349 pub(crate) fn on(api: VaApi, fd: OwnedFd, render_node: &str) -> Result<Self, String> {
352 let display = unsafe { (api.vaGetDisplayDRM)(fd.as_raw_fd()) };
353 if display.is_null() {
354 return Err(format!("{render_node} carries no VA display"));
355 }
356 unsafe {
357 (api.vaSetErrorCallback)(display, Some(log_error), ptr::null_mut());
358 (api.vaSetInfoCallback)(display, Some(log_info), ptr::null_mut());
359 }
360 let (mut major, mut minor) = (0 as c_int, 0 as c_int);
361 let status = unsafe { (api.vaInitialize)(display, &mut major, &mut minor) };
362 if status != VA_STATUS_SUCCESS as VAStatus {
363 return Err(format!(
364 "{render_node}: no VA driver initialized ({})",
365 error_text(&api, status)
366 ));
367 }
368 let vendor = unsafe {
369 let text = (api.vaQueryVendorString)(display);
370 if text.is_null() {
371 String::new()
372 } else {
373 CStr::from_ptr(text).to_string_lossy().into_owned()
374 }
375 };
376 let vce = amd_vce(fd.as_raw_fd());
377 let whole_picture_vdenc = whole_picture_vdenc(fd.as_raw_fd());
378 Ok(Self {
379 api,
380 display,
381 _fd: fd,
382 vendor,
383 vce,
384 whole_picture_vdenc,
385 })
386 }
387
388 fn rounds_linear_pitch(&self) -> bool {
392 self.vendor.contains("Intel")
393 }
394
395 fn hardware(&self) -> Hardware {
398 if self.vendor.contains("Intel") {
399 Hardware::VaapiIntel
400 } else {
401 Hardware::Vaapi
402 }
403 }
404
405 fn one_slice_h264(&self, entrypoint: VAEntrypoint) -> bool {
408 self.vce || (self.whole_picture_vdenc && entrypoint == VAEntrypointEncSliceLP)
409 }
410
411 fn keeps_anchors(&self) -> bool {
417 self.vendor.contains("radeonsi")
418 }
419
420 fn starves_in_a_small_buffer(&self) -> bool {
423 self.vendor.contains("iHD")
424 }
425
426 fn check(&self, status: VAStatus, what: &str) -> Result<(), String> {
427 if status == VA_STATUS_SUCCESS as VAStatus {
428 Ok(())
429 } else {
430 Err(format!("{what}: {}", error_text(&self.api, status)))
431 }
432 }
433
434 fn profiles(&self) -> Result<Vec<VAProfile>, String> {
436 let mut profiles = vec![
437 0 as VAProfile;
438 unsafe { (self.api.vaMaxNumProfiles)(self.display) }.max(0) as usize
439 ];
440 let mut listed: c_int = 0;
441 self.check(
442 unsafe {
443 (self.api.vaQueryConfigProfiles)(self.display, profiles.as_mut_ptr(), &mut listed)
444 },
445 "vaQueryConfigProfiles",
446 )?;
447 profiles.truncate(listed.max(0) as usize);
448 Ok(profiles)
449 }
450
451 fn encode_entrypoints(&self, profile: VAProfile) -> Vec<VAEntrypoint> {
458 let mut entrypoints = vec![
459 0 as VAEntrypoint;
460 unsafe { (self.api.vaMaxNumEntrypoints)(self.display) }.max(0)
461 as usize
462 ];
463 let mut listed: c_int = 0;
464 let status = unsafe {
465 (self.api.vaQueryConfigEntrypoints)(
466 self.display,
467 profile,
468 entrypoints.as_mut_ptr(),
469 &mut listed,
470 )
471 };
472 if status != VA_STATUS_SUCCESS as VAStatus {
473 return Vec::new();
474 }
475 let listed = &entrypoints[..listed.max(0) as usize];
476 [VAEntrypointEncSliceLP, VAEntrypointEncSlice]
477 .into_iter()
478 .filter(|e| listed.contains(e))
479 .collect()
480 }
481
482 fn attribute(
485 &self,
486 profile: VAProfile,
487 entrypoint: VAEntrypoint,
488 kind: VAConfigAttribType,
489 ) -> Option<u32> {
490 let mut attrib = VAConfigAttrib {
491 type_: kind,
492 value: 0,
493 };
494 let status = unsafe {
495 (self.api.vaGetConfigAttributes)(self.display, profile, entrypoint, &mut attrib, 1)
496 };
497 (status == VA_STATUS_SUCCESS as VAStatus && attrib.value != VA_ATTRIB_NOT_SUPPORTED)
498 .then_some(attrib.value)
499 }
500
501 fn surface_attributes(&self, config: VAConfigID) -> Vec<VASurfaceAttrib> {
504 let mut count: c_uint = 0;
505 let mut attribs: Vec<VASurfaceAttrib> = Vec::new();
506 for pass in 0..2 {
507 let list = if pass == 0 {
508 ptr::null_mut()
509 } else {
510 attribs.as_mut_ptr()
511 };
512 if unsafe {
513 (self.api.vaQuerySurfaceAttributes)(self.display, config, list, &mut count)
514 } != VA_STATUS_SUCCESS as VAStatus
515 {
516 return Vec::new();
517 }
518 attribs.resize(count as usize, unsafe { std::mem::zeroed() });
519 }
520 attribs
521 }
522
523 fn surface_fourccs(&self, config: VAConfigID) -> Vec<u32> {
525 self.surface_attributes(config)
526 .iter()
527 .filter(|a| a.type_ == VASurfaceAttribPixelFormat)
528 .map(|a| unsafe { a.value.value.i } as u32)
529 .collect()
530 }
531
532 fn surface_alignment(&self, config: VAConfigID) -> (u32, u32) {
535 self.surface_attributes(config)
536 .iter()
537 .find(|a| a.type_ == VASurfaceAttribAlignmentSize)
538 .map(|a| {
539 let v = unsafe { a.value.value.i } as u32;
540 (1 << (v & 0xf), 1 << ((v & 0xf0) >> 4))
541 })
542 .unwrap_or((1, 1))
543 }
544
545 fn create_surfaces(
547 &self,
548 rt_format: u32,
549 fourcc: u32,
550 width: u32,
551 height: u32,
552 count: usize,
553 ) -> Result<Vec<VASurfaceID>, String> {
554 let mut attrib: VASurfaceAttrib = unsafe { std::mem::zeroed() };
555 attrib.type_ = VASurfaceAttribPixelFormat;
556 attrib.flags = VA_SURFACE_ATTRIB_SETTABLE;
557 attrib.value.type_ = VAGenericValueTypeInteger;
558 attrib.value.value.i = fourcc as i32;
559 let mut surfaces = vec![VA_INVALID_SURFACE; count];
560 self.check(
561 unsafe {
562 (self.api.vaCreateSurfaces)(
563 self.display,
564 rt_format,
565 width,
566 height,
567 surfaces.as_mut_ptr(),
568 count as c_uint,
569 &mut attrib,
570 1,
571 )
572 },
573 &format!(
574 "this VA-API driver allocates no {} surfaces",
575 fourcc_name(fourcc)
576 ),
577 )?;
578 Ok(surfaces)
579 }
580
581 fn destroy_surfaces(&self, surfaces: &mut [VASurfaceID]) {
582 if !surfaces.is_empty() {
583 unsafe {
584 (self.api.vaDestroySurfaces)(
585 self.display,
586 surfaces.as_mut_ptr(),
587 surfaces.len() as c_int,
588 )
589 };
590 }
591 }
592
593 fn create_buffer(
594 &self,
595 context: VAContextID,
596 kind: VABufferType,
597 data: &[u8],
598 ) -> Result<VABufferID, String> {
599 self.create_buffer_raw(
600 context,
601 kind,
602 data.len() as c_uint,
603 data.as_ptr() as *mut c_void,
604 )
605 }
606
607 fn create_output_buffer(
610 &self,
611 context: VAContextID,
612 kind: VABufferType,
613 size: u32,
614 ) -> Result<VABufferID, String> {
615 self.create_buffer_raw(context, kind, size as c_uint, ptr::null_mut())
616 }
617
618 fn create_buffer_raw(
619 &self,
620 context: VAContextID,
621 kind: VABufferType,
622 size: c_uint,
623 data: *mut c_void,
624 ) -> Result<VABufferID, String> {
625 let mut id = VA_INVALID_ID;
626 self.check(
627 unsafe {
628 (self.api.vaCreateBuffer)(self.display, context, kind, size, 1, data, &mut id)
629 },
630 &format!("vaCreateBuffer(type {kind})"),
631 )?;
632 Ok(id)
633 }
634}
635
636fn error_text(api: &VaApi, status: VAStatus) -> String {
637 let text = unsafe { (api.vaErrorStr)(status) };
638 if text.is_null() {
639 format!("VA status {status}")
640 } else {
641 unsafe { CStr::from_ptr(text) }
642 .to_string_lossy()
643 .into_owned()
644 }
645}
646
647pub(crate) fn probe_codecs(encode_node_index: i32) -> Result<Vec<Served>, String> {
653 probe_codecs_on(&Device::open(libva()?, encode_node_index)?)
654}
655
656pub(crate) type Served = (Codec, super::Formats);
658
659pub(crate) fn probe_codecs_on(device: &Device) -> Result<Vec<Served>, String> {
663 let profiles = device.profiles()?;
664 let entrypoints = |profile: VAProfile| {
665 if profiles.contains(&profile) {
666 device.encode_entrypoints(profile)
667 } else {
668 Vec::new()
669 }
670 };
671 Ok(Codec::VIDEO
672 .into_iter()
673 .filter(|&codec| {
674 profile_ladder(codec, false, 8)
675 .into_iter()
676 .any(|p| !entrypoints(p).is_empty())
677 })
678 .map(|codec| {
679 let carries = |fullcolor: bool, bit_depth: u32| {
680 profile_ladder(codec, fullcolor, bit_depth)
681 .into_iter()
682 .any(|p| {
683 entrypoints(p).into_iter().any(|e| {
684 device
685 .attribute(p, e, VAConfigAttribRTFormat)
686 .is_none_or(|f| f & rt_format(fullcolor, bit_depth) != 0)
687 })
688 })
689 };
690 (
691 codec,
692 super::Formats {
693 fullcolor: carries(true, 8),
694 ten_bit: [carries(false, 10), carries(true, 10)],
695 },
696 )
697 })
698 .collect())
699}
700
701fn profile_ladder(codec: Codec, fullcolor: bool, bit_depth: u32) -> Vec<VAProfile> {
706 match (codec, fullcolor, bit_depth) {
707 (Codec::H264, false, 8) => vec![
708 VAProfileH264High,
709 VAProfileH264Main,
710 VAProfileH264ConstrainedBaseline,
711 ],
712 (Codec::H265, false, 8) => vec![VAProfileHEVCMain],
713 (Codec::H265, false, 10) => vec![VAProfileHEVCMain10],
714 (Codec::H265, true, 8) => vec![VAProfileHEVCMain444],
715 (Codec::H265, true, 10) => vec![VAProfileHEVCMain444_10],
716 (Codec::Vp8, false, 8) => vec![VAProfileVP8Version0_3],
717 (Codec::Vp9, false, 8) => vec![VAProfileVP9Profile0],
718 (Codec::Vp9, true, 8) => vec![VAProfileVP9Profile1],
719 (Codec::Vp9, false, 10) => vec![VAProfileVP9Profile2],
720 (Codec::Vp9, true, 10) => vec![VAProfileVP9Profile3],
721 (Codec::Av1, false, 8 | 10) => vec![VAProfileAV1Profile0],
722 _ => Vec::new(),
723 }
724}
725
726pub(super) struct Buffers {
728 entries: Vec<(VABufferType, Vec<u8>)>,
729}
730
731impl Buffers {
732 fn new() -> Self {
733 Self {
734 entries: Vec::new(),
735 }
736 }
737
738 pub(super) fn push<T: Copy>(&mut self, kind: VABufferType, value: &T) {
740 let bytes = unsafe {
741 std::slice::from_raw_parts(value as *const T as *const u8, std::mem::size_of::<T>())
742 };
743 self.entries.push((kind, bytes.to_vec()));
744 }
745
746 pub(super) fn push_misc<T: Copy>(&mut self, kind: VAEncMiscParameterType, value: &T) {
748 let mut bytes = kind.to_ne_bytes().to_vec();
749 bytes.extend_from_slice(unsafe {
750 std::slice::from_raw_parts(value as *const T as *const u8, std::mem::size_of::<T>())
751 });
752 self.entries.push((VAEncMiscParameterBufferType, bytes));
753 }
754
755 pub(super) fn push_packed(&mut self, kind: u32, bytes: &[u8], bit_length: u32) {
757 let param = VAEncPackedHeaderParameterBuffer {
758 type_: kind,
759 bit_length,
760 has_emulation_bytes: 1,
761 va_reserved: [0; 4],
762 };
763 self.push(VAEncPackedHeaderParameterBufferType, ¶m);
764 self.entries
765 .push((VAEncPackedHeaderDataBufferType, bytes.to_vec()));
766 }
767
768 #[cfg(test)]
769 pub(crate) fn entries(&self) -> &[(VABufferType, Vec<u8>)] {
770 &self.entries
771 }
772}
773
774#[derive(Clone, Copy)]
776pub(super) struct Negotiated {
777 pub profile: VAProfile,
778 pub entrypoint: VAEntrypoint,
779 pub packed: u32,
781 pub rc_mode: u32,
783 pub width: u32,
784 pub height: u32,
785 pub fps: FrameRate,
786 pub bits_per_second: u32,
787 pub dpb: u32,
789 pub dpb_level: u32,
792 pub fullcolor: bool,
793 pub bit_depth: u32,
794 pub min_qp: u32,
796 pub max_qp: u32,
797}
798
799pub(super) struct Frame<'a> {
802 pub key: bool,
803 pub pts: u64,
805 pub key_pts: u64,
807 pub recon: VASurfaceID,
808 pub coded: VABufferID,
809 pub reference: Option<(u64, VASurfaceID)>,
811 pub held: &'a [(u64, VASurfaceID, bool)],
814 pub anchored: bool,
816 pub anchor: Option<u8>,
818 pub long_term: &'a [(u64, u8)],
820 pub qp: u32,
822 pub slots: SlotPlan,
824 pub slot_surfaces: [VASurfaceID; 3],
826}
827
828enum Arm {
830 H264(Box<h264::Arm>),
831 H265(h265::Arm),
832 Vp8(vp8::Arm),
833 Vp9(vp9::Arm),
834 Av1(av1::Arm),
835}
836
837impl Arm {
838 fn alignment(&self) -> (u32, u32) {
840 match self {
841 Arm::H264(_) | Arm::Vp8(_) => (16, 16),
842 Arm::H265(a) => a.alignment(),
843 Arm::Vp9(_) => (64, 64),
844 Arm::Av1(a) => a.alignment(),
845 }
846 }
847
848 fn wanted_packed(&self) -> u32 {
850 match self {
851 Arm::H264(_) | Arm::H265(_) => {
852 VA_ENC_PACKED_HEADER_SEQUENCE | VA_ENC_PACKED_HEADER_SLICE
853 }
854 Arm::Av1(_) => VA_ENC_PACKED_HEADER_SEQUENCE | VA_ENC_PACKED_HEADER_PICTURE,
855 Arm::Vp8(_) | Arm::Vp9(_) => 0,
856 }
857 }
858
859 fn tracks_references(&self, packed: u32) -> bool {
863 match self {
864 Arm::H264(_) | Arm::H265(_) => packed & VA_ENC_PACKED_HEADER_SLICE != 0,
865 Arm::Av1(_) => packed & VA_ENC_PACKED_HEADER_PICTURE != 0,
866 Arm::Vp8(_) | Arm::Vp9(_) => true,
867 }
868 }
869
870 fn sequence(&mut self, negotiated: &Negotiated, out: &mut Buffers) {
871 match self {
872 Arm::H264(a) => a.sequence(negotiated, out),
873 Arm::H265(a) => a.sequence(negotiated, out),
874 Arm::Vp8(a) => a.sequence(negotiated, out),
875 Arm::Vp9(a) => a.sequence(negotiated, out),
876 Arm::Av1(a) => a.sequence(negotiated, out),
877 }
878 }
879
880 fn picture(
881 &mut self,
882 negotiated: &Negotiated,
883 frame: &Frame,
884 out: &mut Buffers,
885 ) -> Result<(), String> {
886 match self {
887 Arm::H264(a) => a.picture(negotiated, frame, out),
888 Arm::H265(a) => a.picture(negotiated, frame, out),
889 Arm::Vp8(a) => a.picture(negotiated, frame, out),
890 Arm::Vp9(a) => a.picture(negotiated, frame, out),
891 Arm::Av1(a) => a.picture(negotiated, frame, out),
892 }
893 }
894}
895
896enum References {
898 Window(ReferenceWindow),
899 Slots(ReferenceSlots),
900}
901
902struct HostUpload {
905 surface: VASurfaceID,
906 fourcc: u32,
907 derive: bool,
908 image: Option<VAImage>,
909}
910
911pub struct VaapiEncoder {
913 codec: Codec,
914 input: Input,
915 device: Arc<Device>,
916 config: VAConfigID,
917 context: VAContextID,
918 vpp_config: VAConfigID,
919 vpp_context: VAContextID,
920 vpp_standards: (VAProcColorStandardType, VAProcColorStandardType),
922 negotiated: Negotiated,
923 fourcc: u32,
924 rt_format: u32,
925 surface_width: u32,
926 surface_height: u32,
927 low_power: bool,
928 recon: Vec<VASurfaceID>,
930 converted: Vec<VASurfaceID>,
932 host: Option<HostUpload>,
933 coded: VABufferID,
934 arm: Arm,
935 references: Option<References>,
936 frame_count: u64,
939 key_count: u64,
940 surfaces_of: HashMap<u64, VASurfaceID>,
942 last_reference: Reference,
943 rate: RateSettings,
944 qp: u32,
945 held: Option<u32>,
947 frame_cap: bool,
949 last_bytes: Option<usize>,
951 last_quality: Option<u32>,
953 measure: bool,
956 last_psnr: Option<f32>,
957 measures: bool,
958 quality_level: Option<u32>,
961 sequence_start: bool,
964 omit_headers: bool,
965 fresh: bool,
966 reorder: NoReorder,
971}
972
973unsafe impl Send for VaapiEncoder {}
974
975impl Drop for VaapiEncoder {
976 fn drop(&mut self) {
977 let api = self.device.api;
978 let display = self.device.display;
979 unsafe {
980 if self.coded != VA_INVALID_ID {
981 (api.vaDestroyBuffer)(display, self.coded);
982 }
983 if let Some(host) = &self.host
984 && let Some(image) = host.image
985 {
986 (api.vaDestroyImage)(display, image.image_id);
987 }
988 if self.vpp_context != VA_INVALID_ID {
989 (api.vaDestroyContext)(display, self.vpp_context);
990 }
991 if self.context != VA_INVALID_ID {
992 (api.vaDestroyContext)(display, self.context);
993 }
994 let mut surfaces = self.recon.clone();
995 surfaces.extend(&self.converted);
996 surfaces.extend(self.host.iter().map(|h| h.surface));
997 self.device.destroy_surfaces(&mut surfaces);
998 if self.vpp_config != VA_INVALID_ID {
999 (api.vaDestroyConfig)(display, self.vpp_config);
1000 }
1001 if self.config != VA_INVALID_ID {
1002 (api.vaDestroyConfig)(display, self.config);
1003 }
1004 }
1005 }
1006}
1007
1008impl VaapiEncoder {
1009 pub fn new(settings: &RustCaptureSettings, codec: Codec, input: Input) -> Result<Self, String> {
1012 if !codec.is_video() {
1013 return Err("JPEG has no VA-API encoder".into());
1014 }
1015 Self::on_device(
1016 Arc::new(Device::open(libva()?, settings.encode_node_index)?),
1017 settings,
1018 codec,
1019 input,
1020 )
1021 }
1022
1023 pub(crate) fn on_device(
1025 device: Arc<Device>,
1026 settings: &RustCaptureSettings,
1027 codec: Codec,
1028 input: Input,
1029 ) -> Result<Self, String> {
1030 let fullcolor = settings.video_fullcolor && codec.fullcolor();
1031 let depths: &[u32] = if settings.video_bit_depth >= 10 && codec.high_bit_depth() {
1032 &[10, 8]
1033 } else {
1034 &[8]
1035 };
1036 let listed = device.profiles()?;
1037 let mut last = None;
1038 for &bit_depth in depths {
1039 let served = profile_ladder(codec, fullcolor, bit_depth)
1040 .into_iter()
1041 .filter(|p| listed.contains(p))
1042 .map(|p| (p, device.encode_entrypoints(p)))
1043 .find(|(_, entrypoints)| !entrypoints.is_empty());
1044 let Some((profile, mut entrypoints)) = served else {
1045 last.get_or_insert_with(|| {
1046 format!(
1047 "this VA-API driver encodes no {} {} at {bit_depth} bits",
1048 codec.display(),
1049 super::chroma_name(fullcolor)
1050 )
1051 });
1052 continue;
1053 };
1054 if codec == Codec::H264 && device.whole_picture_vdenc {
1055 entrypoints.sort_by_key(|&e| e == VAEntrypointEncSliceLP);
1056 crate::log::debug!(
1057 "[vaapi] This device's low-power H.264 encoder codes one slice a picture: {}",
1058 if entrypoints[0] == VAEntrypointEncSliceLP {
1059 "the driver offers no full entry point, so the session codes one slice."
1060 } else {
1061 "H.264 tries the full entry point first."
1062 }
1063 );
1064 }
1065 for entrypoint in entrypoints {
1066 for &fourcc in surface_fourccs(fullcolor, bit_depth) {
1067 match Self::open(
1068 &device, settings, codec, input, profile, entrypoint, fourcc, fullcolor,
1069 bit_depth,
1070 ) {
1071 Ok(session) => return Ok(session),
1072 Err(e) => last = Some(e),
1073 }
1074 }
1075 }
1076 }
1077 Err(last.unwrap_or_else(|| "no VA-API surface format to try".into()))
1078 }
1079
1080 #[allow(clippy::too_many_arguments)]
1083 fn open(
1084 device: &Arc<Device>,
1085 settings: &RustCaptureSettings,
1086 codec: Codec,
1087 input: Input,
1088 profile: VAProfile,
1089 entrypoint: VAEntrypoint,
1090 fourcc: u32,
1091 fullcolor: bool,
1092 bit_depth: u32,
1093 ) -> Result<Self, String> {
1094 let api = device.api;
1095 let rate = RateSettings::new(settings);
1096 let rc_mode = if rate.cbr { VA_RC_CBR } else { VA_RC_CQP };
1097 let rt_format = rt_format(fullcolor, bit_depth);
1098 let width = settings.width.max(1) as u32;
1099 let height = settings.height.max(1) as u32;
1100 let fps = rate.fps;
1101 let bits_per_second = if rate.cbr {
1102 rate.bps().min(u32::MAX as u64) as u32
1103 } else {
1104 0
1105 };
1106
1107 let mut attribs = Vec::new();
1108 if let Some(formats) = device.attribute(profile, entrypoint, VAConfigAttribRTFormat) {
1109 if formats & rt_format == 0 {
1110 return Err(format!(
1111 "the {} entry point renders no {} {bit_depth}-bit surfaces",
1112 fourcc_name(fourcc),
1113 super::chroma_name(fullcolor)
1114 ));
1115 }
1116 attribs.push(VAConfigAttrib {
1117 type_: VAConfigAttribRTFormat,
1118 value: rt_format,
1119 });
1120 }
1121 match device.attribute(profile, entrypoint, VAConfigAttribRateControl) {
1122 Some(modes) if modes & rc_mode != 0 => attribs.push(VAConfigAttrib {
1123 type_: VAConfigAttribRateControl,
1124 value: rc_mode,
1125 }),
1126 Some(_) => {
1127 return Err(format!(
1128 "this VA-API driver has no {} rate control for {}",
1129 if rate.cbr {
1130 "constant-rate"
1131 } else {
1132 "constant-quantizer"
1133 },
1134 codec.display()
1135 ));
1136 }
1137 None if rate.cbr => {
1138 return Err("this VA-API driver reports no rate control modes".into());
1139 }
1140 None => {}
1141 }
1142 let ref_l0 = device
1143 .attribute(profile, entrypoint, VAConfigAttribEncMaxRefFrames)
1144 .map_or(0, |v| v & 0xffff);
1145 if ref_l0 < 1 {
1146 return Err("this VA-API driver takes no reference frames".into());
1147 }
1148 let level_bitrate = bits_per_second as u64;
1149 let dpb_level = match codec {
1150 Codec::H264 => super::codec::h264_level(width, height, fps.ceil(), level_bitrate),
1151 Codec::H265 => super::codec::h265_level(width, height, fps.ceil(), level_bitrate, true),
1152 _ => 0,
1153 };
1154 let dpb = match codec {
1155 Codec::H264 => super::codec::h264_dpb_frames(dpb_level, width, height)
1156 .min(super::reference_frames(settings)),
1157 Codec::H265 => super::codec::h265_dpb_frames(dpb_level, width, height)
1158 .min(super::reference_frames(settings)),
1159 _ => REFERENCE_FRAMES,
1160 };
1161 let mut arm = match codec {
1162 Codec::H264 => Arm::H264(Box::new(h264::Arm::new(profile))),
1163 Codec::H265 => Arm::H265(h265::Arm::new(
1164 profile,
1165 device.attribute(profile, entrypoint, VAConfigAttribEncHEVCFeatures),
1166 device.attribute(profile, entrypoint, VAConfigAttribEncHEVCBlockSizes),
1167 device.attribute(profile, entrypoint, VAConfigAttribPredictionDirection),
1168 )),
1169 Codec::Vp8 => Arm::Vp8(vp8::Arm::new()),
1170 Codec::Vp9 => Arm::Vp9(vp9::Arm::new()),
1171 Codec::Av1 => Arm::Av1(av1::Arm::new(
1172 device.attribute(profile, entrypoint, VAConfigAttribEncAV1),
1173 device.attribute(profile, entrypoint, VAConfigAttribEncAV1Ext1),
1174 device
1175 .attribute(profile, entrypoint, VAConfigAttribEncAV1Ext2)
1176 .ok_or("this VA-API driver reports no AV1 encoder attributes")?,
1177 )),
1178 Codec::Jpeg => unreachable!(),
1179 };
1180 let packed = device
1181 .attribute(profile, entrypoint, VAConfigAttribEncPackedHeaders)
1182 .map_or(0, |v| v & arm.wanted_packed());
1183 if packed != 0 {
1184 attribs.push(VAConfigAttrib {
1185 type_: VAConfigAttribEncPackedHeaders,
1186 value: packed,
1187 });
1188 }
1189 let dpb = if arm.tracks_references(packed) {
1190 dpb
1191 } else {
1192 1
1193 };
1194 let quality_range = device.attribute(profile, entrypoint, VAConfigAttribEncQualityRange);
1195 let slice_caps = (
1196 device.attribute(profile, entrypoint, VAConfigAttribEncMaxSlices),
1197 device.attribute(profile, entrypoint, VAConfigAttribEncSliceStructure),
1198 );
1199
1200 let mut config = VA_INVALID_ID;
1201 device.check(
1202 unsafe {
1203 (api.vaCreateConfig)(
1204 device.display,
1205 profile,
1206 entrypoint,
1207 attribs.as_mut_ptr(),
1208 attribs.len() as c_int,
1209 &mut config,
1210 )
1211 },
1212 &format!(
1213 "no VA-API encode configuration for {} on {} surfaces",
1214 codec.display(),
1215 fourcc_name(fourcc)
1216 ),
1217 )?;
1218 let mut me = Self {
1219 codec,
1220 input,
1221 device: Arc::clone(device),
1222 config,
1223 context: VA_INVALID_ID,
1224 vpp_config: VA_INVALID_ID,
1225 vpp_context: VA_INVALID_ID,
1226 vpp_standards: (VAProcColorStandardNone, VAProcColorStandardNone),
1227 negotiated: Negotiated {
1228 profile,
1229 entrypoint,
1230 packed,
1231 rc_mode,
1232 width,
1233 height,
1234 fps,
1235 bits_per_second,
1236 dpb,
1237 dpb_level,
1238 fullcolor,
1239 bit_depth,
1240 min_qp: codec.hardware_quantizer_bound(device.hardware(), rate.min_qp),
1241 max_qp: codec.hardware_quantizer_bound(device.hardware(), rate.max_qp),
1242 },
1243 fourcc,
1244 rt_format,
1245 surface_width: 0,
1246 surface_height: 0,
1247 low_power: entrypoint == VAEntrypointEncSliceLP,
1248 recon: Vec::new(),
1249 converted: Vec::new(),
1250 host: None,
1251 coded: VA_INVALID_ID,
1252 arm: Arm::Vp8(vp8::Arm::new()),
1253 references: None,
1254 frame_count: 0,
1255 key_count: 0,
1256 surfaces_of: HashMap::new(),
1257 last_reference: Reference::Untracked,
1258 rate,
1259 qp: codec.hardware_quantizer(device.hardware(), settings.video_crf),
1260 held: None,
1261 frame_cap: false,
1262 last_bytes: None,
1263 last_quality: None,
1264 measure: false,
1265 last_psnr: None,
1266 measures: true,
1267 quality_level: None,
1268 sequence_start: true,
1269 omit_headers: settings.omit_stripe_headers,
1270 fresh: true,
1271 reorder: NoReorder::new("The VA-API driver", true),
1272 };
1273 let (align_w, align_h) = {
1274 let (aw, ah) = arm.alignment();
1275 let (dw, dh) = device.surface_alignment(config);
1276 (aw.max(dw), ah.max(dh))
1277 };
1278 me.surface_width = width.div_ceil(align_w) * align_w;
1279 me.surface_height = height.div_ceil(align_h) * align_h;
1280 let rendered = device.surface_fourccs(config);
1281 if !rendered.is_empty() && !rendered.contains(&fourcc) {
1282 return Err(format!(
1283 "the {} encoder takes no {} surfaces",
1284 codec.display(),
1285 fourcc_name(fourcc)
1286 ));
1287 }
1288 let wanted_slices = if matches!(arm, Arm::H264(_)) && device.one_slice_h264(entrypoint) {
1289 1
1290 } else {
1291 SLICES
1292 };
1293 let slices = match (&arm, slice_caps) {
1294 (Arm::H264(_) | Arm::H265(_), (Some(max), Some(structure))) => Some(slice_layout(
1295 structure,
1296 max,
1297 me.surface_height.div_ceil(arm_block(&arm)),
1298 wanted_slices,
1299 )?),
1300 (Arm::H264(_) | Arm::H265(_), _) => {
1301 Some((1, me.surface_height.div_ceil(arm_block(&arm))))
1302 }
1303 _ => None,
1304 };
1305 match &mut arm {
1306 Arm::H264(a) => a.configure(
1307 &me.negotiated,
1308 me.surface_width,
1309 me.surface_height,
1310 slices.unwrap(),
1311 ),
1312 Arm::H265(a) => a.configure(
1313 &me.negotiated,
1314 me.surface_width,
1315 me.surface_height,
1316 slices.unwrap(),
1317 ),
1318 Arm::Vp8(a) => a.configure(&me.negotiated),
1319 Arm::Vp9(a) => a.configure(&me.negotiated),
1320 Arm::Av1(a) => a.configure(&me.negotiated, me.surface_width, me.surface_height)?,
1321 }
1322 me.arm = arm;
1323 me.references = me.arm.tracks_references(packed).then(|| match me.arm {
1324 Arm::Vp8(_) => References::Slots(ReferenceSlots::new()),
1325 Arm::H264(_) if me.device.keeps_anchors() && dpb >= 3 => {
1326 let mut w = ReferenceWindow::with_anchors(dpb, 1);
1327 if settings.acknowledge_references {
1328 w.set_acknowledged();
1329 }
1330 References::Window(w)
1331 }
1332 Arm::H265(_) if me.device.keeps_anchors() && dpb as usize >= ANCHORS + 2 => {
1333 let mut w = ReferenceWindow::with_anchors(dpb, ANCHORS);
1334 if settings.acknowledge_references {
1335 w.set_acknowledged();
1336 }
1337 References::Window(w)
1338 }
1339 _ => References::Window(ReferenceWindow::new(dpb)),
1340 });
1341 if let Some(References::Window(w)) = &mut me.references
1342 && let Arm::H264(a) = &me.arm
1343 {
1344 w.set_frame_num_range(a.frame_num_range());
1345 }
1346 me.quality_level = quality_range;
1347 me.frame_cap = device
1348 .attribute(profile, entrypoint, VAConfigAttribMaxFrameSize)
1349 .is_some_and(|v| v & 1 != 0);
1350
1351 let recon_count = match me.arm {
1352 Arm::Vp8(_) => 4,
1353 _ => dpb as usize + 1,
1354 };
1355 me.recon = device.create_surfaces(
1356 rt_format,
1357 fourcc,
1358 me.surface_width,
1359 me.surface_height,
1360 recon_count,
1361 )?;
1362 me.converted =
1363 device.create_surfaces(rt_format, fourcc, me.surface_width, me.surface_height, 1)?;
1364 let mut render_targets = me.recon.clone();
1365 render_targets.extend(&me.converted);
1366 device.check(
1367 unsafe {
1368 (api.vaCreateContext)(
1369 device.display,
1370 config,
1371 me.surface_width as c_int,
1372 me.surface_height as c_int,
1373 VA_PROGRESSIVE as c_int,
1374 render_targets.as_mut_ptr(),
1375 render_targets.len() as c_int,
1376 &mut me.context,
1377 )
1378 },
1379 "no VA-API encode context",
1380 )?;
1381 me.coded = device.create_output_buffer(
1382 me.context,
1383 VAEncCodedBufferType,
1384 coded_buffer_size(me.surface_width, me.surface_height),
1385 )?;
1386 me.open_vpp(device, input)?;
1387 Ok(me)
1388 }
1389}
1390
1391fn arm_block(arm: &Arm) -> u32 {
1393 match arm {
1394 Arm::H265(a) => a.ctu_size(),
1395 _ => 16,
1396 }
1397}
1398
1399fn slice_layout(
1407 structure: u32,
1408 max_slices: u32,
1409 rows: u32,
1410 wanted: u32,
1411) -> Result<(u32, u32), String> {
1412 let max_slices = max_slices.max(1);
1413 let wanted = wanted.min(rows).min(max_slices).max(1);
1414 let (count, size) = if structure
1415 & (VA_ENC_SLICE_STRUCTURE_ARBITRARY_ROWS | VA_ENC_SLICE_STRUCTURE_ARBITRARY_MACROBLOCKS)
1416 != 0
1417 {
1418 (wanted, rows / wanted)
1419 } else if structure & VA_ENC_SLICE_STRUCTURE_POWER_OF_TWO_ROWS != 0 {
1420 let mut k = 1;
1421 while (wanted > 1 && 2 * k * (wanted - 1) + 1 < rows) || rows.div_ceil(k) > max_slices {
1422 k *= 2;
1423 }
1424 (rows.div_ceil(k), k)
1425 } else if structure & VA_ENC_SLICE_STRUCTURE_EQUAL_MULTI_ROWS != 0 {
1426 let size = rows.div_ceil(wanted);
1427 (rows.div_ceil(size), size)
1428 } else if structure & VA_ENC_SLICE_STRUCTURE_EQUAL_ROWS != 0 {
1429 (rows, 1)
1430 } else {
1431 return Err(format!(
1432 "this VA-API driver supports no usable slice structure ({structure:#x})"
1433 ));
1434 };
1435 if count > max_slices {
1436 return Err(format!(
1437 "this VA-API driver encodes at most {max_slices} slices, not {count}"
1438 ));
1439 }
1440 Ok((count, size))
1441}
1442
1443impl VaapiEncoder {
1444 fn open_vpp(&mut self, device: &Device, input: Input) -> Result<(), String> {
1449 let api = device.api;
1450 device.check(
1451 unsafe {
1452 (api.vaCreateConfig)(
1453 device.display,
1454 VAProfileNone,
1455 VAEntrypointVideoProc,
1456 ptr::null_mut(),
1457 0,
1458 &mut self.vpp_config,
1459 )
1460 },
1461 "no VA-API video processing configuration",
1462 )?;
1463 let rendered = device.surface_fourccs(self.vpp_config);
1464 if !rendered.is_empty() && !rendered.contains(&self.fourcc) {
1465 return Err(format!(
1466 "this VA-API driver's video processor renders no {} surfaces",
1467 fourcc_name(self.fourcc)
1468 ));
1469 }
1470 device.check(
1471 unsafe {
1472 (api.vaCreateContext)(
1473 device.display,
1474 self.vpp_config,
1475 self.surface_width as c_int,
1476 self.surface_height as c_int,
1477 VA_PROGRESSIVE as c_int,
1478 self.converted.as_mut_ptr(),
1479 self.converted.len() as c_int,
1480 &mut self.vpp_context,
1481 )
1482 },
1483 "no VA-API video processing context",
1484 )?;
1485 let mut caps: VAProcPipelineCaps = unsafe { std::mem::zeroed() };
1486 device.check(
1487 unsafe {
1488 (api.vaQueryVideoProcPipelineCaps)(
1489 device.display,
1490 self.vpp_context,
1491 ptr::null_mut(),
1492 0,
1493 &mut caps,
1494 )
1495 },
1496 "vaQueryVideoProcPipelineCaps",
1497 )?;
1498 let standards =
1499 |list: *mut VAProcColorStandardType, count: u32| -> Vec<VAProcColorStandardType> {
1500 if list.is_null() {
1501 Vec::new()
1502 } else {
1503 unsafe { std::slice::from_raw_parts(list, count as usize) }.to_vec()
1504 }
1505 };
1506 let input_standards = standards(caps.input_color_standards, caps.num_input_color_standards);
1507 let output_standards =
1508 standards(caps.output_color_standards, caps.num_output_color_standards);
1509 self.vpp_standards = (
1510 color_standard(&input_standards, ColorDescription::SRGB_SOURCE),
1511 color_standard(&output_standards, self.declared_color()),
1512 );
1513 if let Input::Host { rgba } = input {
1514 let fourcc = if rgba { VA_FOURCC_RGBA } else { VA_FOURCC_BGRA };
1515 let surface = device.create_surfaces(
1516 VA_RT_FORMAT_RGB32,
1517 fourcc,
1518 self.negotiated.width,
1519 self.negotiated.height,
1520 1,
1521 )?[0];
1522 let mut derived: VAImage = unsafe { std::mem::zeroed() };
1523 let derive = unsafe { (api.vaDeriveImage)(device.display, surface, &mut derived) }
1524 == VA_STATUS_SUCCESS as VAStatus
1525 && {
1526 let same = derived.format.fourcc == fourcc;
1527 unsafe { (api.vaDestroyImage)(device.display, derived.image_id) };
1528 same
1529 };
1530 let host = self.host.insert(HostUpload {
1531 surface,
1532 fourcc,
1533 derive,
1534 image: None,
1535 });
1536 if !derive {
1537 let mut format = image_format(device, fourcc).ok_or_else(|| {
1538 format!(
1539 "this VA-API driver has no {} image format",
1540 fourcc_name(fourcc)
1541 )
1542 })?;
1543 let mut image: VAImage = unsafe { std::mem::zeroed() };
1544 device.check(
1545 unsafe {
1546 (api.vaCreateImage)(
1547 device.display,
1548 &mut format,
1549 self.negotiated.width as c_int,
1550 self.negotiated.height as c_int,
1551 &mut image,
1552 )
1553 },
1554 "vaCreateImage",
1555 )?;
1556 host.image = Some(image);
1557 }
1558 }
1559 Ok(())
1560 }
1561
1562 fn declared_color(&self) -> ColorDescription {
1566 ColorDescription {
1567 primaries: 1,
1568 transfer: 1,
1569 matrix: if self.codec == Codec::Vp8 { 6 } else { 1 },
1570 full_range: false,
1571 rgb: false,
1572 }
1573 }
1574
1575 pub fn codec(&self) -> Codec {
1576 self.codec
1577 }
1578
1579 pub fn is_fullcolor(&self) -> bool {
1581 self.negotiated.fullcolor
1582 }
1583
1584 pub fn bit_depth(&self) -> u32 {
1586 self.negotiated.bit_depth
1587 }
1588
1589 pub fn surface_format_name(&self) -> String {
1591 fourcc_name(self.fourcc)
1592 }
1593
1594 pub fn is_full_range(&self) -> bool {
1596 false
1597 }
1598
1599 pub fn vendor(&self) -> &str {
1601 &self.device.vendor
1602 }
1603
1604 pub fn low_power(&self) -> bool {
1606 self.low_power
1607 }
1608
1609 pub fn last_reference(&self) -> Reference {
1611 self.last_reference
1612 }
1613
1614 pub fn take_report(&mut self, report: ReferenceReport) -> bool {
1617 let (frame_id, held) = match report {
1618 ReferenceReport::Lost(frame_id) => return self.invalidate_reference(frame_id),
1619 ReferenceReport::Held(frame_id) => (frame_id, true),
1620 ReferenceReport::Sent(frame_id) => (frame_id, false),
1621 };
1622 match &mut self.references {
1623 Some(References::Window(w)) => {
1624 w.acknowledge(frame_id, held);
1625 }
1626 Some(References::Slots(s)) => {
1627 s.acknowledge(frame_id, held);
1628 }
1629 None => {}
1630 }
1631 true
1632 }
1633
1634 pub fn invalidate_reference(&mut self, frame_id: u16) -> bool {
1637 match &mut self.references {
1638 Some(References::Window(w)) => {
1639 w.invalidate(frame_id);
1640 true
1641 }
1642 Some(References::Slots(s)) => {
1643 s.invalidate(frame_id);
1644 true
1645 }
1646 None => false,
1647 }
1648 }
1649
1650 pub fn reconfigure_rate(&mut self, settings: &RustCaptureSettings) -> Result<(), String> {
1653 let Some(rate) = self.rate.changed(settings) else {
1654 return Ok(());
1655 };
1656 self.rate = rate;
1657 self.negotiated.fps = rate.fps;
1658 self.negotiated.bits_per_second = if rate.cbr {
1659 rate.bps().min(u32::MAX as u64) as u32
1660 } else {
1661 0
1662 };
1663 self.negotiated.min_qp = self
1664 .codec
1665 .hardware_quantizer_bound(self.device.hardware(), rate.min_qp);
1666 self.negotiated.max_qp = self
1667 .codec
1668 .hardware_quantizer_bound(self.device.hardware(), rate.max_qp);
1669 match &mut self.arm {
1670 Arm::H264(a) => a.configure(
1671 &self.negotiated,
1672 self.surface_width,
1673 self.surface_height,
1674 a.slices(),
1675 ),
1676 Arm::H265(a) => a.configure(
1677 &self.negotiated,
1678 self.surface_width,
1679 self.surface_height,
1680 a.slices(),
1681 ),
1682 Arm::Vp8(a) => a.configure(&self.negotiated),
1683 Arm::Vp9(a) => a.configure(&self.negotiated),
1684 Arm::Av1(a) => {
1685 a.configure(&self.negotiated, self.surface_width, self.surface_height)?
1686 }
1687 }
1688 self.sequence_start = true;
1689 Ok(())
1690 }
1691
1692 pub fn is_cbr(&self) -> bool {
1694 self.rate.cbr
1695 }
1696
1697 pub fn last_size(&self) -> Option<usize> {
1700 self.last_bytes
1701 }
1702
1703 pub fn last_quality(&self) -> Option<u32> {
1709 self.last_quality
1710 }
1711
1712 pub fn measures(&self) -> bool {
1715 self.measures
1716 }
1717
1718 pub fn measure(&mut self) {
1720 self.measure = true;
1721 }
1722
1723 pub fn last_psnr(&self) -> Option<f32> {
1729 self.last_psnr
1730 }
1731
1732 fn luma_rows(&self, surface: VASurfaceID) -> Option<(u32, u16, Vec<u8>)> {
1735 let api = self.device.api;
1736 let display = self.device.display;
1737 let mut image: VAImage = unsafe { std::mem::zeroed() };
1738 if unsafe { (api.vaDeriveImage)(display, surface, &mut image) }
1739 != VA_STATUS_SUCCESS as VAStatus
1740 {
1741 return None;
1742 }
1743 let fourcc = image.format.fourcc;
1744 let (bytes, luma) = match fourcc {
1745 VA_FOURCC_NV12 | VA_FOURCC_444P => (1, 0),
1746 VA_FOURCC_P010 => (2, 1),
1747 VA_FOURCC_XYUV | VA_FOURCC_Y410 => (4, 2),
1748 _ => (0, 0),
1749 };
1750 let mut address: *mut c_void = ptr::null_mut();
1751 let mut rows = None;
1752 if bytes > 0
1753 && image.height as u32 >= self.negotiated.height
1754 && unsafe { (api.vaMapBuffer)(display, image.buf, &mut address) }
1755 == VA_STATUS_SUCCESS as VAStatus
1756 {
1757 let width = (self.negotiated.width as usize).min(image.width as usize);
1758 let height = self.negotiated.height as usize;
1759 let (offset, pitch) = (image.offsets[0] as usize, image.pitches[0] as usize);
1760 let mut out = Vec::with_capacity(width * height.div_ceil(PSNR_ROW_STEP));
1761 for y in (0..height).step_by(PSNR_ROW_STEP) {
1762 let row = unsafe {
1763 std::slice::from_raw_parts(
1764 (address as *const u8).add(offset + y * pitch),
1765 width * bytes,
1766 )
1767 };
1768 if fourcc == VA_FOURCC_Y410 {
1769 out.extend(
1770 row.as_chunks::<4>()
1771 .0
1772 .iter()
1773 .map(|p| ((u32::from_le_bytes(*p) >> 12) & 0xff) as u8),
1774 );
1775 } else {
1776 out.extend(row.iter().skip(luma).step_by(bytes));
1777 }
1778 }
1779 unsafe { (api.vaUnmapBuffer)(display, image.buf) };
1780 rows = Some((fourcc, image.height, out));
1781 }
1782 unsafe { (api.vaDestroyImage)(display, image.image_id) };
1783 rows
1784 }
1785
1786 fn picture_psnr(&self, recon: VASurfaceID) -> Option<f32> {
1791 let (format, rows, source) = self.luma_rows(self.converted[0])?;
1792 let (coded_format, coded_rows, coded) = self.luma_rows(recon)?;
1793 if (format, rows) != (coded_format, coded_rows) || source.is_empty() {
1794 return None;
1795 }
1796 let sse: u64 = source
1797 .iter()
1798 .zip(&coded)
1799 .map(|(&a, &b)| {
1800 let d = a as i64 - b as i64;
1801 (d * d) as u64
1802 })
1803 .sum();
1804 let mse = (sse as f64 / source.len() as f64).max(1e-3);
1805 Some((10.0 * (255.0 * 255.0 / mse).log10()) as f32).filter(|&db| db >= UNREADABLE_DB)
1806 }
1807
1808 fn constant_rate(&self) -> ConstantRate {
1810 if !self.device.starves_in_a_small_buffer() {
1811 return ConstantRate::Buffer;
1812 }
1813 match self.arm {
1814 Arm::H264(_) | Arm::H265(_) => ConstantRate::Unbounded,
1815 Arm::Av1(_) => ConstantRate::Seconds(IHD_AV1_BUFFER_S),
1816 Arm::Vp8(_) | Arm::Vp9(_) => ConstantRate::Buffer,
1817 }
1818 }
1819
1820 pub fn hold_quantizer(&mut self, crf: u32) {
1829 self.held = Some(
1830 self.codec
1831 .hardware_quantizer(self.device.hardware(), crf as i32),
1832 );
1833 }
1834
1835 fn rate_control(&self, out: &mut Buffers) {
1840 if self.rate.cbr {
1841 let bps = self.negotiated.bits_per_second;
1842 let vbv = self.rate.vbv();
1843 let mut rc: VAEncMiscParameterRateControl = unsafe { std::mem::zeroed() };
1844 rc.bits_per_second = bps;
1845 rc.target_percentage = 100;
1846 rc.window_size = (vbv as u64 * 1000 / bps.max(1) as u64) as u32;
1847 rc.initial_qp = 0;
1848 rc.min_qp = if matches!(self.arm, Arm::H264(_)) {
1849 self.negotiated.min_qp.max(h264::MIN_QP)
1850 } else {
1851 self.negotiated.min_qp
1852 };
1853 rc.max_qp = self.negotiated.max_qp;
1854 rc.basic_unit_size = 0;
1855 rc.ICQ_quality_factor = 1;
1856 rc.quality_factor = 0;
1857 let bound = self.constant_rate();
1858 unsafe {
1859 rc.rc_flags.bits.set_mb_rate_control(2);
1860 rc.rc_flags.bits.set_disable_bit_stuffing(1);
1861 }
1862 out.push_misc(VAEncMiscParameterTypeRateControl, &rc);
1863 let buffer = match bound {
1864 ConstantRate::Buffer => Some(vbv),
1865 ConstantRate::Seconds(s) => Some((bps as f64 * s) as u32),
1866 ConstantRate::Unbounded => None,
1867 };
1868 if let Some(bits) = buffer {
1869 let hrd = VAEncMiscParameterHRD {
1870 initial_buffer_fullness: bits,
1871 buffer_size: bits,
1872 va_reserved: [0; 4],
1873 };
1874 out.push_misc(VAEncMiscParameterTypeHRD, &hrd);
1875 }
1876 if self.frame_cap && bound == ConstantRate::Buffer {
1877 let cap = VAEncMiscParameterBufferMaxFrameSize {
1878 type_: VAEncMiscParameterTypeMaxFrameSize,
1879 max_frame_size: vbv,
1880 va_reserved: [0; 4],
1881 };
1882 out.push_misc(VAEncMiscParameterTypeMaxFrameSize, &cap);
1883 }
1884 }
1885 let fps = self.negotiated.fps.within(0xffff);
1886 let mut frame_rate: VAEncMiscParameterFrameRate = unsafe { std::mem::zeroed() };
1887 frame_rate.framerate = (fps.den << 16) | fps.num;
1888 out.push_misc(VAEncMiscParameterTypeFrameRate, &frame_rate);
1889 if let Some(level) = self.quality_level {
1890 let quality = VAEncMiscParameterBufferQualityLevel {
1891 quality_level: level,
1892 va_reserved: [0; 4],
1893 };
1894 out.push_misc(VAEncMiscParameterTypeQualityLevel, &quality);
1895 }
1896 }
1897
1898 pub fn encode_host(
1902 &mut self,
1903 pixels: &[u8],
1904 stride: usize,
1905 rgba: bool,
1906 frame_number: u64,
1907 crf: u32,
1908 force_idr: bool,
1909 ) -> Result<Vec<u8>, String> {
1910 let Input::Host { rgba: built } = self.input else {
1911 return Err("this session takes dmabufs".into());
1912 };
1913 if built != rgba {
1914 return Err("this session was built for the other byte order".into());
1915 }
1916 let (width, height) = (
1917 self.negotiated.width as usize,
1918 self.negotiated.height as usize,
1919 );
1920 check_host_frame(pixels, stride, width, height)?;
1921 let host = self.host.as_ref().ok_or("no host upload surface")?;
1922 let api = self.device.api;
1923 let display = self.device.display;
1924 unsafe {
1925 let mut image: VAImage = std::mem::zeroed();
1926 if host.derive {
1927 self.device.check(
1928 (api.vaDeriveImage)(display, host.surface, &mut image),
1929 "vaDeriveImage",
1930 )?;
1931 } else {
1932 image = host.image.unwrap();
1933 }
1934 let mut address: *mut c_void = ptr::null_mut();
1935 let mapped = self.device.check(
1936 (api.vaMapBuffer)(display, image.buf, &mut address),
1937 "vaMapBuffer",
1938 );
1939 if let Err(e) = mapped {
1940 if host.derive {
1941 (api.vaDestroyImage)(display, image.image_id);
1942 }
1943 return Err(e);
1944 }
1945 let pitch = image.pitches[0] as usize;
1946 let dst = (address as *mut u8).add(image.offsets[0] as usize);
1947 for row in 0..height {
1948 ptr::copy_nonoverlapping(
1949 pixels.as_ptr().add(row * stride),
1950 dst.add(row * pitch),
1951 width * 4,
1952 );
1953 }
1954 (api.vaUnmapBuffer)(display, image.buf);
1955 if host.derive {
1956 (api.vaDestroyImage)(display, image.image_id);
1957 } else {
1958 self.device.check(
1959 (api.vaPutImage)(
1960 display,
1961 host.surface,
1962 image.image_id,
1963 0,
1964 0,
1965 width as c_uint,
1966 height as c_uint,
1967 0,
1968 0,
1969 width as c_uint,
1970 height as c_uint,
1971 ),
1972 "vaPutImage",
1973 )?;
1974 }
1975 }
1976 let surface = host.surface;
1977 self.encode_surface(surface, frame_number, crf, force_idr)
1978 }
1979
1980 pub fn encode_dmabuf(
1983 &mut self,
1984 dmabuf: &Dmabuf,
1985 frame_number: u64,
1986 crf: u32,
1987 force_idr: bool,
1988 ) -> Result<Vec<u8>, String> {
1989 if self.input != Input::Dmabuf {
1990 return Err("this session takes host frames".into());
1991 }
1992 let surface = self.import_dmabuf(dmabuf)?;
1993 let result = self.encode_surface(surface, frame_number, crf, force_idr);
1994 self.device.destroy_surfaces(&mut [surface]);
1995 result
1996 }
1997
1998 fn import_dmabuf(&mut self, dmabuf: &Dmabuf) -> Result<VASurfaceID, String> {
2001 let api = self.device.api;
2002 let display = self.device.display;
2003 let (width, height) = (self.negotiated.width, self.negotiated.height);
2004 let handles: Vec<i32> = dmabuf.handles().map(|h| h.as_raw_fd()).collect();
2005 if handles.len() != 1 {
2006 return Err("VA-API maps only a dmabuf made of a single object".into());
2007 }
2008 let size = unsafe { libc::lseek(handles[0], 0, libc::SEEK_END) };
2009 if size <= 0 {
2010 return Err("the dmabuf object reports no size".into());
2011 }
2012 let drm_format = dmabuf.format().code as u32;
2013 let fourcc = match drm_format {
2014 0x34325241 => VA_FOURCC_BGRA,
2015 0x34325258 => VA_FOURCC_BGRX,
2016 0x34324241 => VA_FOURCC_RGBA,
2017 0x34324258 => VA_FOURCC_RGBX,
2018 other => return Err(format!("DRM format {other:#x} is not one VA-API maps")),
2019 };
2020 let planes: Vec<(u32, u32)> = dmabuf.strides().zip(dmabuf.offsets()).collect();
2021 let modifier = u64::from(dmabuf.format().modifier);
2022 if modifier == 0
2023 && !self.negotiated.fullcolor
2024 && self.device.rounds_linear_pitch()
2025 && !planes[0].0.is_multiple_of(64)
2026 {
2027 return Err(format!(
2028 "this VA-API driver reads a linear surface at a 64-byte pitch, not the {} of this dmabuf",
2029 planes[0].0
2030 ));
2031 }
2032 let mut surface = VA_INVALID_SURFACE;
2033 let mut attribs: [VASurfaceAttrib; 2] = unsafe { std::mem::zeroed() };
2034 attribs[0].type_ = VASurfaceAttribMemoryType;
2035 attribs[0].flags = VA_SURFACE_ATTRIB_SETTABLE;
2036 attribs[0].value.type_ = VAGenericValueTypeInteger;
2037 attribs[1].type_ = VASurfaceAttribExternalBufferDescriptor;
2038 attribs[1].flags = VA_SURFACE_ATTRIB_SETTABLE;
2039 attribs[1].value.type_ = VAGenericValueTypePointer;
2040 if modifier != 0x00ff_ffff_ffff_ffff {
2041 let mut desc: VADRMPRIMESurfaceDescriptor = unsafe { std::mem::zeroed() };
2042 desc.fourcc = fourcc;
2043 desc.width = width;
2044 desc.height = height;
2045 desc.num_objects = 1;
2046 desc.objects[0].fd = handles[0];
2047 desc.objects[0].size = size as u32;
2048 desc.objects[0].drm_format_modifier = modifier;
2049 desc.num_layers = 1;
2050 desc.layers[0].drm_format = drm_format;
2051 desc.layers[0].num_planes = planes.len() as u32;
2052 for (i, &(pitch, offset)) in planes.iter().enumerate().take(4) {
2053 desc.layers[0].object_index[i] = 0;
2054 desc.layers[0].offset[i] = offset;
2055 desc.layers[0].pitch[i] = pitch;
2056 }
2057 attribs[0].value.value.i = VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2 as i32;
2058 attribs[1].value.value.p = &mut desc as *mut VADRMPRIMESurfaceDescriptor as *mut c_void;
2059 let status = unsafe {
2060 (api.vaCreateSurfaces)(
2061 display,
2062 VA_RT_FORMAT_RGB32,
2063 width,
2064 height,
2065 &mut surface,
2066 1,
2067 attribs.as_mut_ptr(),
2068 2,
2069 )
2070 };
2071 if status == VA_STATUS_SUCCESS as VAStatus {
2072 return Ok(surface);
2073 }
2074 }
2075 let mut handle = handles[0] as usize;
2076 let mut desc: VASurfaceAttribExternalBuffers = unsafe { std::mem::zeroed() };
2077 desc.pixel_format = fourcc;
2078 desc.width = width;
2079 desc.height = height;
2080 desc.data_size = size as u32;
2081 desc.buffers = &mut handle;
2082 desc.num_buffers = 1;
2083 desc.num_planes = planes.len() as u32;
2084 for (i, &(pitch, offset)) in planes.iter().enumerate().take(4) {
2085 desc.pitches[i] = pitch;
2086 desc.offsets[i] = offset;
2087 }
2088 attribs[0].value.value.i = VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME as i32;
2089 attribs[1].value.value.p = &mut desc as *mut VASurfaceAttribExternalBuffers as *mut c_void;
2090 self.device.check(
2091 unsafe {
2092 (api.vaCreateSurfaces)(
2093 display,
2094 VA_RT_FORMAT_RGB32,
2095 width,
2096 height,
2097 &mut surface,
2098 1,
2099 attribs.as_mut_ptr(),
2100 2,
2101 )
2102 },
2103 "no VA surface over the dmabuf",
2104 )?;
2105 Ok(surface)
2106 }
2107
2108 fn convert(&mut self, source: VASurfaceID) -> Result<(), String> {
2113 let api = self.device.api;
2114 let display = self.device.display;
2115 let region = VARectangle {
2116 x: 0,
2117 y: 0,
2118 width: self.negotiated.width as u16,
2119 height: self.negotiated.height as u16,
2120 };
2121 let declared = self.declared_color();
2122 let mut params: VAProcPipelineParameterBuffer = unsafe { std::mem::zeroed() };
2123 params.surface = source;
2124 params.surface_region = ®ion;
2125 params.output_region = ®ion;
2126 params.output_background_color = 0xff00_0000;
2127 params.pipeline_flags = 0;
2128 params.filter_flags = VA_FRAME_PICTURE;
2129 params.rotation_state = VA_ROTATION_NONE;
2130 params.mirror_state = VA_MIRROR_NONE;
2131 params.surface_color_standard = self.vpp_standards.0;
2132 params.output_color_standard = self.vpp_standards.1;
2133 params.input_color_properties = ColorDescription::SRGB_SOURCE.properties();
2134 params.output_color_properties = declared.properties();
2135 let buffer = self.device.create_buffer(
2136 self.vpp_context,
2137 VAProcPipelineParameterBufferType,
2138 unsafe {
2139 std::slice::from_raw_parts(
2140 ¶ms as *const VAProcPipelineParameterBuffer as *const u8,
2141 std::mem::size_of::<VAProcPipelineParameterBuffer>(),
2142 )
2143 },
2144 )?;
2145 let mut buffers = [buffer];
2146 let result = (|| {
2147 self.device.check(
2148 unsafe { (api.vaBeginPicture)(display, self.vpp_context, self.converted[0]) },
2149 "vaBeginPicture (video processing)",
2150 )?;
2151 let rendered = self.device.check(
2152 unsafe {
2153 (api.vaRenderPicture)(display, self.vpp_context, buffers.as_mut_ptr(), 1)
2154 },
2155 "vaRenderPicture (video processing)",
2156 );
2157 let ended = self.device.check(
2158 unsafe { (api.vaEndPicture)(display, self.vpp_context) },
2159 "vaEndPicture (video processing)",
2160 );
2161 rendered.and(ended)
2162 })();
2163 unsafe { (api.vaDestroyBuffer)(display, buffer) };
2164 result
2165 }
2166
2167 fn encode_surface(
2170 &mut self,
2171 source: VASurfaceID,
2172 frame_number: u64,
2173 crf: u32,
2174 force_idr: bool,
2175 ) -> Result<Vec<u8>, String> {
2176 if !self.rate.cbr {
2177 self.qp = self
2178 .codec
2179 .hardware_quantizer(self.device.hardware(), crf as i32);
2180 }
2181 let held_qp = self.held.take();
2182 self.convert(source)?;
2183 let frame_id = frame_number as u16;
2184 let has_reference = match &self.references {
2185 Some(References::Window(w)) => w.has_reference(),
2186 Some(References::Slots(s)) => s.has_reference(),
2187 None => !self.fresh,
2188 };
2189 let mut key = force_idr || self.sequence_start || !has_reference;
2190 let (mut anchor, mut shared) = (None, false);
2191 if let Some(References::Window(w)) = &mut self.references
2192 && w.anchored()
2193 {
2194 if !key && w.plan_anchor(false).is_some() && w.settle() == Some(Invalidation::KeyFrame)
2195 {
2196 key = true;
2197 }
2198 if !key && w.forget_stale() == Some(Invalidation::KeyFrame) {
2199 key = true;
2200 }
2201 anchor = w.plan_anchor(key);
2202 shared = w.acknowledging() && w.predicts_from_shared();
2203 }
2204 let pts = match &self.references {
2205 Some(References::Window(w)) => w.next_pts(),
2206 Some(References::Slots(s)) => s.next_pts(),
2207 None => self.frame_count,
2208 };
2209 let held: Vec<(u64, VASurfaceID, bool)> = match &self.references {
2210 Some(References::Window(w)) => w
2211 .held()
2212 .filter_map(|(_, p, lost)| self.surfaces_of.get(&p).map(|&s| (p, s, lost)))
2213 .collect(),
2214 _ => Vec::new(),
2215 };
2216 let (anchored, long_term): (bool, Vec<(u64, u8)>) = match &self.references {
2217 Some(References::Window(w)) if w.anchored() => (
2218 true,
2219 held.iter()
2220 .filter_map(|h| w.anchor_slot(h.0).map(|slot| (h.0, slot)))
2221 .collect(),
2222 ),
2223 _ => (false, Vec::new()),
2224 };
2225 let (reference, key_pts, slots, slot_surfaces) = match &self.references {
2226 Some(References::Window(w)) => (
2227 if key {
2228 None
2229 } else {
2230 w.newest_valid()
2231 .and_then(|(_, p)| self.surfaces_of.get(&p).map(|&s| (p, s)))
2232 },
2233 if key { pts } else { w.key_pts() },
2234 SlotPlan::KEY,
2235 [VA_INVALID_SURFACE; 3],
2236 ),
2237 Some(References::Slots(s)) => {
2238 let plan = s.plan(key);
2239 let surfaces = [1u8, 2, 4].map(|slot| {
2240 s.slot(slot)
2241 .and_then(|(_, p, _)| self.surfaces_of.get(&p).copied())
2242 .unwrap_or(VA_INVALID_SURFACE)
2243 });
2244 let reference = (plan.predict_from != 0)
2245 .then(|| s.slot(plan.predict_from))
2246 .flatten()
2247 .and_then(|(_, p, _)| self.surfaces_of.get(&p).map(|&surf| (p, surf)));
2248 (reference, 0, plan, surfaces)
2249 }
2250 None => (
2251 if key {
2252 None
2253 } else {
2254 self.surfaces_of.get(&(pts - 1)).map(|&s| (pts - 1, s))
2255 },
2256 if key { pts } else { self.key_count },
2257 SlotPlan::KEY,
2258 [VA_INVALID_SURFACE; 3],
2259 ),
2260 };
2261 let recon = match &self.references {
2262 Some(References::Slots(_)) => *self
2263 .recon
2264 .iter()
2265 .find(|s| !slot_surfaces.contains(s))
2266 .ok_or("no VP8 reconstruction surface is free")?,
2267 Some(References::Window(_)) => *self
2268 .recon
2269 .iter()
2270 .find(|s| !held.iter().any(|h| h.1 == **s))
2271 .ok_or("no reconstruction surface is free")?,
2272 None => self.recon[(pts % self.recon.len() as u64) as usize],
2273 };
2274 let qp = if self.rate.cbr {
2275 self.qp
2276 } else {
2277 held_qp.unwrap_or(self.qp)
2278 };
2279 let frame = Frame {
2280 key,
2281 pts,
2282 key_pts,
2283 recon,
2284 coded: self.coded,
2285 reference,
2286 held: &held,
2287 anchored,
2288 anchor,
2289 long_term: &long_term,
2290 qp,
2291 slots,
2292 slot_surfaces,
2293 };
2294 let mut out = Buffers::new();
2295 if key {
2296 self.arm.sequence(&self.negotiated, &mut out);
2297 self.rate_control(&mut out);
2298 }
2299 self.arm.picture(&self.negotiated, &frame, &mut out)?;
2300 let header_len = if self.omit_headers {
2301 0
2302 } else {
2303 VIDEO_HEADER_LEN
2304 };
2305 let mut output = vec![0; header_len];
2306 self.issue(&out, &mut output)?;
2307 if std::mem::take(&mut self.measure) {
2308 self.last_psnr = self.picture_psnr(recon);
2309 self.measures = self.last_psnr.is_some();
2310 }
2311 if key
2312 && self.codec == Codec::H264
2313 && let Some(bounded) = self.reorder.apply(&output[header_len..])
2314 {
2315 output.truncate(header_len);
2316 output.extend_from_slice(&bounded);
2317 }
2318 self.fresh = false;
2319 self.sequence_start = false;
2320 self.surfaces_of.insert(pts, recon);
2321 let coded = &output[header_len..];
2322 let frame_type = match self.codec {
2323 Codec::H264 => h264_frame_type(coded),
2324 Codec::H265 => h265_frame_type(coded),
2325 Codec::Vp8 => frame_type_from_key(vp8_is_key(coded)),
2326 Codec::Vp9 => frame_type_from_key(vp9_is_key(coded)),
2327 _ => frame_type_from_key(av1_is_key(coded)),
2328 };
2329 let is_key = frame_type != super::codec::FRAME_DELTA;
2330 if is_key
2331 && self.codec == Codec::H264
2332 && let Some(References::Window(w)) = &mut self.references
2333 && let Some(range) = h264_frame_num_range(coded)
2334 {
2335 w.set_frame_num_range(range);
2336 }
2337 self.last_reference = match &mut self.references {
2338 Some(References::Window(w)) => w.record_marked(frame_id, is_key, anchor),
2339 Some(References::Slots(s)) => {
2340 let plan = if is_key { s.plan(true) } else { slots };
2341 s.record(frame_id, plan)
2342 }
2343 None => {
2344 self.frame_count = pts + 1;
2345 if is_key {
2346 self.key_count = pts;
2347 }
2348 Reference::Untracked
2349 }
2350 };
2351 match &self.references {
2352 Some(References::Slots(s)) => {
2353 let held: Vec<u64> = [1u8, 2, 4]
2354 .iter()
2355 .filter_map(|&slot| s.slot(slot).map(|(_, p, _)| p))
2356 .collect();
2357 self.surfaces_of.retain(|p, _| held.contains(p));
2358 }
2359 Some(References::Window(w)) => {
2360 let held: Vec<u64> = w.held().map(|(_, p, _)| p).collect();
2361 self.surfaces_of.retain(|p, _| held.contains(p));
2362 }
2363 None => self
2364 .surfaces_of
2365 .retain(|&p, _| p + self.recon.len() as u64 > pts),
2366 }
2367 if !self.omit_headers {
2368 let mut header = Vec::with_capacity(VIDEO_HEADER_LEN);
2369 let anchor_flag = if anchor.is_some() && !is_key && shared {
2370 super::codec::FRAME_ANCHOR
2371 } else {
2372 0
2373 };
2374 push_video_header(
2375 &mut header,
2376 self.codec,
2377 frame_type | anchor_flag,
2378 frame_id,
2379 0,
2380 self.negotiated.width as u16,
2381 self.negotiated.height as u16,
2382 self.last_reference,
2383 );
2384 output[..VIDEO_HEADER_LEN].copy_from_slice(&header);
2385 }
2386 Ok(output)
2387 }
2388
2389 fn issue(&mut self, buffers: &Buffers, out: &mut Vec<u8>) -> Result<(), String> {
2392 let api = self.device.api;
2393 let display = self.device.display;
2394 let mut ids = Vec::with_capacity(buffers.entries.len());
2395 let mut created = Ok(());
2396 for (kind, bytes) in &buffers.entries {
2397 match self.device.create_buffer(self.context, *kind, bytes) {
2398 Ok(id) => ids.push(id),
2399 Err(e) => {
2400 created = Err(e);
2401 break;
2402 }
2403 }
2404 }
2405 let result = created.and_then(|()| {
2406 self.device.check(
2407 unsafe { (api.vaBeginPicture)(display, self.context, self.converted[0]) },
2408 "vaBeginPicture",
2409 )?;
2410 let rendered = self.device.check(
2411 unsafe {
2412 (api.vaRenderPicture)(
2413 display,
2414 self.context,
2415 ids.as_mut_ptr(),
2416 ids.len() as c_int,
2417 )
2418 },
2419 "vaRenderPicture",
2420 );
2421 let ended = self.device.check(
2422 unsafe { (api.vaEndPicture)(display, self.context) },
2423 "vaEndPicture",
2424 );
2425 rendered.and(ended)
2426 });
2427 for id in ids {
2428 unsafe { (api.vaDestroyBuffer)(display, id) };
2429 }
2430 result?;
2431 let synced = match api.vaSyncBuffer {
2432 Some(sync) => {
2433 let status = unsafe { sync(display, self.coded, u64::MAX) };
2434 if status == VA_STATUS_ERROR_UNIMPLEMENTED as VAStatus {
2435 None
2436 } else {
2437 Some(self.device.check(status, "vaSyncBuffer"))
2438 }
2439 }
2440 None => None,
2441 };
2442 match synced {
2443 Some(result) => result?,
2444 None => self.device.check(
2445 unsafe { (api.vaSyncSurface)(display, self.converted[0]) },
2446 "vaSyncSurface",
2447 )?,
2448 }
2449 let mut list: *mut c_void = ptr::null_mut();
2450 self.device.check(
2451 unsafe { (api.vaMapBuffer)(display, self.coded, &mut list) },
2452 "vaMapBuffer (coded)",
2453 )?;
2454 let segments = || {
2455 std::iter::successors(
2456 unsafe { (list as *const VACodedBufferSegment).as_ref() },
2457 |s| unsafe { (s.next as *const VACodedBufferSegment).as_ref() },
2458 )
2459 .filter(|s| !s.buf.is_null())
2460 };
2461 let from = out.len();
2462 let average_qp = segments()
2463 .next()
2464 .map_or(0, |s| s.status & VA_CODED_BUF_STATUS_PICTURE_AVE_QP_MASK);
2465 out.reserve(segments().map(|s| s.size as usize).sum());
2466 for s in segments() {
2467 out.extend_from_slice(unsafe {
2468 std::slice::from_raw_parts(s.buf as *const u8, s.size as usize)
2469 });
2470 }
2471 unsafe { (api.vaUnmapBuffer)(display, self.coded) };
2472 if self.rate.cbr && matches!(self.arm, Arm::H264(_) | Arm::H265(_)) {
2473 strip_zero_padding(out, from);
2474 }
2475 self.last_bytes = Some(out.len() - from);
2476 let quantizer = match &self.arm {
2477 Arm::H264(_) | Arm::H265(_) => Some(average_qp).filter(|&q| q > 0),
2478 Arm::Av1(a) => a.coded_qindex(&out[from..]),
2479 Arm::Vp9(_) => super::codec::vp9_base_q_idx(&out[from..]),
2480 Arm::Vp8(_) => None,
2481 };
2482 self.last_quality = quantizer
2483 .filter(|_| self.rate.cbr)
2484 .map(|q| self.codec.hardware_quality_index(self.device.hardware(), q));
2485 Ok(())
2486 }
2487}
2488
2489#[derive(Clone, Copy)]
2492struct ColorDescription {
2493 primaries: u8,
2494 transfer: u8,
2495 matrix: u8,
2496 full_range: bool,
2497 rgb: bool,
2498}
2499
2500impl ColorDescription {
2501 const SRGB_SOURCE: Self = Self {
2504 primaries: 1,
2505 transfer: 1,
2506 matrix: 0,
2507 full_range: true,
2508 rgb: true,
2509 };
2510
2511 fn properties(self) -> VAProcColorProperties {
2512 VAProcColorProperties {
2513 chroma_sample_location: (VA_CHROMA_SITING_VERTICAL_CENTER
2514 | VA_CHROMA_SITING_HORIZONTAL_CENTER) as u8,
2515 color_range: if self.full_range {
2516 VA_SOURCE_RANGE_FULL
2517 } else {
2518 VA_SOURCE_RANGE_REDUCED
2519 } as u8,
2520 colour_primaries: self.primaries,
2521 transfer_characteristics: self.transfer,
2522 matrix_coefficients: self.matrix,
2523 reserved: [0; 3],
2524 }
2525 }
2526}
2527
2528fn color_standard(
2533 offered: &[VAProcColorStandardType],
2534 wanted: ColorDescription,
2535) -> VAProcColorStandardType {
2536 if offered.contains(&VAProcColorStandardExplicit) {
2537 return VAProcColorStandardExplicit;
2538 }
2539 const TABLE: [(VAProcColorStandardType, u8, u8, u8); 12] = [
2540 (VAProcColorStandardBT601, 5, 6, 5),
2541 (VAProcColorStandardBT601, 6, 6, 6),
2542 (VAProcColorStandardBT709, 1, 1, 1),
2543 (VAProcColorStandardBT470M, 4, 4, 4),
2544 (VAProcColorStandardBT470BG, 5, 5, 5),
2545 (VAProcColorStandardSMPTE170M, 6, 6, 6),
2546 (VAProcColorStandardSMPTE240M, 7, 7, 7),
2547 (VAProcColorStandardGenericFilm, 8, 1, 1),
2548 (VAProcColorStandardSRGB, 1, 13, 0),
2549 (VAProcColorStandardXVYCC601, 1, 11, 5),
2550 (VAProcColorStandardXVYCC709, 1, 11, 1),
2551 (VAProcColorStandardBT2020, 9, 14, 9),
2552 ];
2553 let matrix_counts = !wanted.rgb;
2554 let worst = 4 * matrix_counts as u32 + 2 + 1;
2555 let mut best = (worst, VAProcColorStandardNone);
2556 for &standard in offered {
2557 for &(_, primaries, transfer, matrix) in TABLE.iter().filter(|t| t.0 == standard) {
2558 let score = 4 * (matrix_counts && wanted.matrix != matrix) as u32
2559 + 2 * (wanted.transfer != transfer) as u32
2560 + (wanted.primaries != primaries) as u32;
2561 if score < best.0 {
2562 best = (score, standard);
2563 }
2564 }
2565 }
2566 best.1
2567}
2568
2569fn image_format(device: &Device, fourcc: u32) -> Option<VAImageFormat> {
2571 let api = device.api;
2572 let mut formats: Vec<VAImageFormat> = vec![
2573 unsafe { std::mem::zeroed() };
2574 unsafe { (api.vaMaxNumImageFormats)(device.display) }.max(0)
2575 as usize
2576 ];
2577 let mut count: c_int = 0;
2578 if unsafe { (api.vaQueryImageFormats)(device.display, formats.as_mut_ptr(), &mut count) }
2579 != VA_STATUS_SUCCESS as VAStatus
2580 {
2581 return None;
2582 }
2583 formats[..count.max(0) as usize]
2584 .iter()
2585 .find(|f| f.fourcc == fourcc)
2586 .copied()
2587}
2588
2589#[cfg(test)]
2590mod tests {
2591 use super::*;
2592
2593 #[test]
2597 fn surface_and_profile_ladders() {
2598 assert_eq!(FULLCOLOR_FOURCCS, [VA_FOURCC_444P, VA_FOURCC_XYUV]);
2599 assert_eq!(fourcc_name(VA_FOURCC_XYUV), "vuyx");
2600 assert_eq!(fourcc_name(VA_FOURCC_444P), "yuv444p");
2601 assert!(
2602 profile_ladder(Codec::H264, true, 8).is_empty(),
2603 "no 4:4:4 H.264 profile is served"
2604 );
2605 assert_eq!(profile_ladder(Codec::H265, true, 8), [VAProfileHEVCMain444]);
2606 assert_eq!(profile_ladder(Codec::Vp9, true, 8), [VAProfileVP9Profile1]);
2607 assert!(profile_ladder(Codec::Av1, true, 8).is_empty());
2608 assert_eq!(
2609 profile_ladder(Codec::H265, false, 10),
2610 [VAProfileHEVCMain10]
2611 );
2612 assert_eq!(
2613 profile_ladder(Codec::H265, true, 10),
2614 [VAProfileHEVCMain444_10]
2615 );
2616 assert_eq!(
2617 profile_ladder(Codec::Vp9, false, 10),
2618 [VAProfileVP9Profile2]
2619 );
2620 assert_eq!(profile_ladder(Codec::Vp9, true, 10), [VAProfileVP9Profile3]);
2621 assert_eq!(
2622 profile_ladder(Codec::Av1, false, 10),
2623 [VAProfileAV1Profile0]
2624 );
2625 assert!(profile_ladder(Codec::H264, false, 10).is_empty());
2626 assert!(profile_ladder(Codec::Vp8, false, 10).is_empty());
2627 for codec in Codec::VIDEO {
2628 assert!(!profile_ladder(codec, false, 8).is_empty(), "{codec:?}");
2629 }
2630 }
2631
2632 #[test]
2637 fn the_color_standard_is_the_nearest_the_driver_offers() {
2638 let explicit = [VAProcColorStandardBT601, VAProcColorStandardExplicit];
2639 assert_eq!(
2640 color_standard(&explicit, ColorDescription::SRGB_SOURCE),
2641 VAProcColorStandardExplicit
2642 );
2643 let classic = [
2644 VAProcColorStandardBT601,
2645 VAProcColorStandardBT709,
2646 VAProcColorStandardSMPTE170M,
2647 VAProcColorStandardSRGB,
2648 ];
2649 assert_eq!(
2650 color_standard(&classic, ColorDescription::SRGB_SOURCE),
2651 VAProcColorStandardBT709
2652 );
2653 let bt709 = ColorDescription {
2654 primaries: 1,
2655 transfer: 1,
2656 matrix: 1,
2657 full_range: false,
2658 rgb: false,
2659 };
2660 assert_eq!(color_standard(&classic, bt709), VAProcColorStandardBT709);
2661 let bt601 = ColorDescription { matrix: 6, ..bt709 };
2662 assert_eq!(
2663 color_standard(&classic, bt601),
2664 VAProcColorStandardBT601,
2665 "the driver's first of two equal matches"
2666 );
2667 assert_eq!(
2668 color_standard(&classic[2..], bt601),
2669 VAProcColorStandardSMPTE170M
2670 );
2671 assert_eq!(
2672 color_standard(&[VAProcColorStandardBT2020], bt709),
2673 VAProcColorStandardNone,
2674 "a total mismatch names no standard"
2675 );
2676 }
2677
2678 #[test]
2682 fn slice_layout_follows_the_driver() {
2683 assert_eq!(
2684 slice_layout(VA_ENC_SLICE_STRUCTURE_ARBITRARY_ROWS, 32, 68, 4),
2685 Ok((4, 17))
2686 );
2687 assert_eq!(
2688 slice_layout(VA_ENC_SLICE_STRUCTURE_POWER_OF_TWO_ROWS, 32, 68, 4),
2689 Ok((5, 16))
2690 );
2691 assert_eq!(
2692 slice_layout(VA_ENC_SLICE_STRUCTURE_EQUAL_ROWS, 128, 68, 4),
2693 Ok((68, 1))
2694 );
2695 assert!(
2696 slice_layout(VA_ENC_SLICE_STRUCTURE_EQUAL_ROWS, 32, 68, 4).is_err(),
2697 "more slices than the driver takes"
2698 );
2699 assert_eq!(
2700 slice_layout(
2701 VA_ENC_SLICE_STRUCTURE_EQUAL_ROWS | VA_ENC_SLICE_STRUCTURE_EQUAL_MULTI_ROWS,
2702 256,
2703 68,
2704 4
2705 ),
2706 Ok((4, 17)),
2707 "equal rows of more than one where the driver offers both"
2708 );
2709 assert_eq!(
2710 slice_layout(VA_ENC_SLICE_STRUCTURE_EQUAL_MULTI_ROWS, 32, 68, 4),
2711 Ok((4, 17))
2712 );
2713 assert_eq!(
2714 slice_layout(VA_ENC_SLICE_STRUCTURE_EQUAL_MULTI_ROWS, 32, 67, 4),
2715 Ok((4, 17)),
2716 "the last slice is the shorter one"
2717 );
2718 assert!(slice_layout(0, 32, 68, 4).is_err());
2719 assert_eq!(
2720 slice_layout(VA_ENC_SLICE_STRUCTURE_ARBITRARY_ROWS, 32, 2, 4),
2721 Ok((2, 1)),
2722 "no more slices than rows"
2723 );
2724 assert_eq!(
2725 slice_layout(VA_ENC_SLICE_STRUCTURE_ARBITRARY_ROWS, 2, 68, 4),
2726 Ok((2, 34)),
2727 "no more slices than the driver takes"
2728 );
2729 assert_eq!(
2730 slice_layout(VA_ENC_SLICE_STRUCTURE_ARBITRARY_ROWS, 1, 68, 4),
2731 Ok((1, 68))
2732 );
2733 assert_eq!(
2734 slice_layout(VA_ENC_SLICE_STRUCTURE_POWER_OF_TWO_ROWS, 4, 68, 4),
2735 Ok((3, 32))
2736 );
2737 assert_eq!(
2738 slice_layout(VA_ENC_SLICE_STRUCTURE_POWER_OF_TWO_ROWS, 1, 68, 4),
2739 Ok((1, 128))
2740 );
2741 }
2742
2743 #[test]
2747 fn construction_answers_or_refuses() {
2748 let mut settings = RustCaptureSettings {
2749 width: 128,
2750 height: 128,
2751 codec: Codec::H264,
2752 video_fullcolor: true,
2753 ..Default::default()
2754 };
2755 for codec in Codec::VIDEO {
2756 settings.codec = codec;
2757 match VaapiEncoder::new(&settings, codec, Input::Host { rgba: false }) {
2758 Ok(enc) => assert_eq!(
2759 enc.is_fullcolor(),
2760 matches!(codec, Codec::H265 | Codec::Vp9),
2761 "{codec:?}"
2762 ),
2763 Err(e) => assert!(!e.is_empty(), "refusal must carry a reason"),
2764 }
2765 }
2766 settings.video_fullcolor = false;
2767 match VaapiEncoder::new(&settings, Codec::H264, Input::Host { rgba: false }) {
2768 Ok(enc) => assert!(!enc.is_fullcolor(), "4:2:0 session reports 4:4:4"),
2769 Err(e) => assert!(!e.is_empty(), "refusal must carry a reason"),
2770 }
2771 assert!(VaapiEncoder::new(&settings, Codec::Jpeg, Input::Host { rgba: false }).is_err());
2772 }
2773
2774 #[test]
2778 #[ignore]
2779 fn vaapi_vce_h264_is_one_slice() {
2780 let (w, h) = (1920usize, 1080usize);
2781 let settings = RustCaptureSettings {
2782 width: w as i32,
2783 height: h as i32,
2784 codec: Codec::H264,
2785 omit_stripe_headers: true,
2786 ..Default::default()
2787 };
2788 let mut enc = VaapiEncoder::new(&settings, Codec::H264, Input::Host { rgba: false })
2789 .expect("a VA-API H.264 session");
2790 let frame: Vec<u8> = (0..w * h)
2791 .flat_map(|i| [(i % w) as u8, (i / w) as u8, 0x80, 0xff])
2792 .collect();
2793 let key = enc
2794 .encode_host(&frame, w * 4, false, 0, 25, true)
2795 .expect("encode");
2796 let slices = super::super::codec::annexb_nals(&key)
2797 .filter(|n| matches!(n[0] & 0x1f, 1 | 5))
2798 .count();
2799 println!(
2800 "{}: VCE {}, {slices} slice(s) in the key frame",
2801 enc.vendor(),
2802 enc.device.vce
2803 );
2804 let wanted = if enc.device.one_slice_h264(enc.negotiated.entrypoint) {
2805 1
2806 } else {
2807 SLICES as usize
2808 };
2809 assert_eq!(slices, wanted);
2810 }
2811
2812 fn moving_picture(t: usize) -> Vec<u8> {
2815 let (w, h) = (1280usize, 720usize);
2816 let noise = |n: usize| {
2817 let mut x = (n as u64 ^ 0x9e37_79b9_7f4a_7c15).wrapping_mul(0xbf58_476d_1ce4_e5b9);
2818 x ^= x >> 31;
2819 x as u8
2820 };
2821 let bar = (t * 12) % (w - 160);
2822 let mut f = vec![0u8; w * h * 4];
2823 for (i, px) in f.as_chunks_mut::<4>().0.iter_mut().enumerate() {
2824 let (x, y) = (i % w, i / w);
2825 let bgr: [u8; 3] = if y >= 480 {
2826 let n = (t % 4) * w * 240 + (y - 480) * w + x;
2827 [noise(3 * n), noise(3 * n + 1), noise(3 * n + 2)]
2828 } else if (96..160).contains(&y) {
2829 [if ((x + t * 4) / 16 + (y - 96) / 16).is_multiple_of(2) {
2830 255
2831 } else {
2832 0
2833 }; 3]
2834 } else if y >= 160 && (bar..bar + 160).contains(&x) {
2835 [0x28, 0x3c, 0xdc]
2836 } else {
2837 [0x78, 0x28, 0x1e]
2838 };
2839 px[..3].copy_from_slice(&bgr);
2840 px[3] = 0xff;
2841 }
2842 f
2843 }
2844
2845 fn predict_past_losses(
2851 codec: Codec,
2852 from: usize,
2853 every: usize,
2854 depth: usize,
2855 ) -> Option<(usize, usize, usize, f64, f64)> {
2856 use crate::webcam::decode::{Decoder as _, VideoDecoder};
2857 let (w, h) = (1280usize, 720usize);
2858 let settings = RustCaptureSettings {
2859 width: w as i32,
2860 height: h as i32,
2861 codec,
2862 target_fps: 60.0,
2863 video_cbr_mode: true,
2864 video_bitrate_kbps: 8000,
2865 omit_stripe_headers: true,
2866 ..Default::default()
2867 };
2868 let mut enc = match VaapiEncoder::new(&settings, codec, Input::Host { rgba: false }) {
2869 Ok(enc) => enc,
2870 Err(e) => {
2871 println!("{codec:?}: {e}");
2872 return None;
2873 }
2874 };
2875 println!("{codec:?} on {}", enc.vendor());
2876 let (mut whole, mut lossy) = (
2877 VideoDecoder::new(codec).expect("decoder"),
2878 VideoDecoder::new(codec).expect("decoder"),
2879 );
2880 let reported = |r: usize| r >= from && (r - from).is_multiple_of(every);
2881 let (mut past, mut anchored, mut keys, mut apart, mut worst) = (0, 0, 0, 0f64, 0f64);
2882 for t in 0..120usize {
2883 if reported(t) {
2884 assert!(enc.invalidate_reference((t - depth) as u16), "{codec:?}");
2885 }
2886 let from_anchor = matches!(&enc.references,
2887 Some(References::Window(win)) if win.predicting_anchor().is_some());
2888 let src = moving_picture(t);
2889 let out = enc
2890 .encode_host(&src, w * 4, false, t as u64, 25, t == 0)
2891 .expect("encode");
2892 match enc.last_reference() {
2893 Reference::None if t > 0 => keys += 1,
2894 Reference::Frame(r) if r as usize + 1 != t => {
2895 past += 1;
2896 anchored += usize::from(from_anchor);
2897 }
2898 _ => {}
2899 }
2900 assert!(whole.decode(&out).expect("decode"), "{codec:?}: frame {t}");
2901 if (t + 1..=t + depth).any(reported) {
2902 continue;
2903 }
2904 assert!(
2905 lossy.decode(&out).expect("decode"),
2906 "{codec:?}: lossy frame {t}"
2907 );
2908 let (a, b) = (whole.frame().unwrap(), lossy.frame().unwrap());
2909 let mut sum = 0f64;
2910 for y in (0..480).step_by(2) {
2911 for x in (0..w).step_by(2) {
2912 let i = (y * w + x) * 4;
2913 let (bb, g, r) = (src[i] as f64, src[i + 1] as f64, src[i + 2] as f64);
2914 let luma = 16.0 + (0.2126 * r + 0.7152 * g + 0.0722 * bb) * 219.0 / 255.0;
2915 let (pa, pb) = (
2916 a.y[y * a.y_stride + x] as f64,
2917 b.y[y * b.y_stride + x] as f64,
2918 );
2919 sum += (pb - luma).abs();
2920 apart = apart.max((pa - pb).abs());
2921 }
2922 }
2923 worst = worst.max(sum / (240.0 * (w / 2) as f64));
2924 }
2925 println!(
2926 "{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"
2927 );
2928 Some((past, anchored, keys, apart, worst))
2929 }
2930
2931 #[test]
2935 #[ignore]
2936 fn vaapi_predicts_past_a_lost_frame() {
2937 for codec in [Codec::H264, Codec::H265] {
2938 if let Some((past, _, keys, apart, worst)) = predict_past_losses(codec, 13, 13, 4) {
2939 assert!(past >= 5, "{codec:?}: {past} predicted past a loss");
2940 assert_eq!(keys, 0, "{codec:?}");
2941 assert!(
2942 apart < 2.0,
2943 "{codec:?}: the decoder without the lost frames is {apart} off"
2944 );
2945 assert!(worst < 20.0, "{codec:?}: {worst} off the source");
2946 }
2947 }
2948 }
2949
2950 #[test]
2958 #[ignore]
2959 fn vaapi_predicts_from_an_anchor() {
2960 for (codec, from, every, depth, want) in [
2961 (Codec::H264, 70, 40, 12, 2),
2962 (Codec::H264, 52, 1000, 4, 0),
2963 (Codec::H265, 70, 40, 12, 2),
2964 ] {
2965 if let Some((past, anchored, keys, apart, worst)) =
2966 predict_past_losses(codec, from, every, depth)
2967 {
2968 assert!(
2969 past >= 1 && anchored == want.min(past),
2970 "{codec:?}: {anchored} of {past} from an anchor"
2971 );
2972 assert_eq!(keys, 0);
2973 assert!(
2974 apart < 2.0,
2975 "the decoder without the lost frames is {apart} off"
2976 );
2977 assert!(worst < 20.0, "{worst} off the source");
2978 }
2979 }
2980 }
2981
2982 #[test]
2984 fn a_node_that_is_not_amdgpu_is_not_vce() {
2985 let node = std::fs::File::open("/dev/null").unwrap();
2986 assert!(!amd_vce(node.as_raw_fd()));
2987 }
2988
2989 #[test]
2992 #[ignore]
2993 fn vaapi_h264_bounds_reordering_at_zero() {
2994 use crate::encoders::sps::fixtures::assert_no_reorder;
2995 use crate::webcam::decode::{Decoder as _, VideoDecoder};
2996 let (w, h) = (1280usize, 720usize);
2997 let settings = RustCaptureSettings {
2998 width: w as i32,
2999 height: h as i32,
3000 codec: Codec::H264,
3001 video_cbr_mode: true,
3002 video_bitrate_kbps: 4000,
3003 omit_stripe_headers: true,
3004 ..Default::default()
3005 };
3006 let mut enc = VaapiEncoder::new(&settings, Codec::H264, Input::Host { rgba: false })
3007 .expect("a VA-API H.264 session");
3008 let frame: Vec<u8> = (0..w * h)
3009 .flat_map(|i| [(i % w) as u8, (i / w) as u8, 0x80, 0xff])
3010 .collect();
3011 let key = enc
3012 .encode_host(&frame, w * 4, false, 0, 25, true)
3013 .expect("encode");
3014 assert_no_reorder(&key, enc.vendor());
3015 let mut dec = VideoDecoder::new(Codec::H264).expect("decoder");
3016 assert!(
3017 dec.decode(&key).expect("the bounded key frame decodes"),
3018 "no picture"
3019 );
3020 }
3021}