1use std::cell::Cell;
36use std::io::Cursor;
37use std::path::PathBuf;
38use std::rc::Rc;
39use std::sync::{Arc, Mutex, Weak};
40
41use anyhow::{anyhow, bail, Context as _};
42
43use crate::codec::h264::dpb::{Dpb, DpbEntry, DpbPicRefList, MmcoError, ReferencePicLists};
44use crate::codec::h264::parser::{
45 Level, MaxLongTermFrameIdx, Nalu, NaluType, Parser, Pps, Profile, RefPicListModification, Slice, SliceHeader,
46 SliceType, Sps,
47};
48use crate::codec::h264::picture::{Field, IsIdr, PictureData, Reference};
49use crate::{
50 bindings, Buffer, BufferType, Config as VaConfig, Context, Display, DrmPrimeSurfaceDescriptor, H264PicFields,
51 H264SeqFields, IQMatrix, IQMatrixBufferH264, Image, Picture, PictureH264, PictureParameter,
52 PictureParameterBufferH264, SliceParameter, SliceParameterBufferH264, Surface, UsageHint, VAConfigAttrib,
53 VAConfigAttribType, VAEntrypoint, VAProfile, VA_FOURCC_NV12, VA_INVALID_ID, VA_PICTURE_H264_INVALID,
54 VA_PICTURE_H264_LONG_TERM_REFERENCE, VA_PICTURE_H264_SHORT_TERM_REFERENCE, VA_RT_FORMAT_YUV420,
55 VA_SLICE_DATA_FLAG_ALL,
56};
57
58#[derive(Clone, Debug)]
60pub struct Config {
61 pub device: Option<PathBuf>,
65}
66
67impl Config {
68 pub fn new() -> Self {
70 Self { device: None }
71 }
72}
73
74impl Default for Config {
75 fn default() -> Self {
76 Self::new()
77 }
78}
79
80#[derive(Clone)]
82pub struct Frame {
83 pub timestamp: u64,
86 pub width: u32,
88 pub height: u32,
90 pub data: Vec<u8>,
93}
94
95pub struct ExportedFrame {
120 pub timestamp: u64,
123 pub width: u32,
125 pub height: u32,
127 pub descriptor: DrmPrimeSurfaceDescriptor,
130 surface: Arc<PooledSurface>,
134}
135
136impl ExportedFrame {
137 pub fn from_surface(surface: Surface<()>, timestamp: u64) -> anyhow::Result<Self> {
149 surface.sync().map_err(|e| anyhow!("surface sync: {e:?}"))?;
150 let (width, height) = surface.size();
151 let descriptor = surface
152 .export_prime()
153 .map_err(|e| anyhow!("export the surface: {e:?}"))?;
154 if descriptor.fourcc != VA_FOURCC_NV12 {
155 anyhow::bail!("exported fourcc {:#010x}, expected NV12", descriptor.fourcc);
156 }
157 Ok(Self {
158 timestamp,
159 width,
160 height,
161 descriptor,
162 surface: Arc::new(PooledSurface::unpooled(surface)),
163 })
164 }
165
166 pub fn download(&self) -> anyhow::Result<Frame> {
175 read_back(self.surface.get(), self.timestamp, self.width, self.height)
176 }
177}
178
179impl std::fmt::Debug for ExportedFrame {
180 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
181 f.debug_struct("ExportedFrame")
182 .field("timestamp", &self.timestamp)
183 .field("width", &self.width)
184 .field("height", &self.height)
185 .field("fourcc", &self.descriptor.fourcc)
186 .finish()
187 }
188}
189
190impl std::fmt::Debug for Frame {
191 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
192 f.debug_struct("Frame")
193 .field("timestamp", &self.timestamp)
194 .field("width", &self.width)
195 .field("height", &self.height)
196 .field("bytes", &self.data.len())
197 .finish()
198 }
199}
200
201pub struct Decoder {
223 parser: Parser,
224 dpb: Dpb<Handle>,
225 prev_ref_pic_info: PrevReferencePicInfo,
227 prev_pic_info: PrevPicInfo,
229 max_long_term_frame_idx: MaxLongTermFrameIdx,
230 sequence: Option<Sequence>,
233 ready: Vec<Handle>,
235 display: Arc<Display>,
237}
238
239impl Decoder {
240 pub fn new(config: Config) -> anyhow::Result<Self> {
249 let (device, display) = match config.device {
250 Some(device) => {
251 let display =
252 Display::open_drm_display(&device).map_err(|e| anyhow!("open DRM display {device:?}: {e:?}"))?;
253 probe(&display)?;
254 (device, display)
255 }
256 None => {
257 crate::display::open_first(probe).map_err(|e| e.context("find a render node that decodes H.264"))?
258 }
259 };
260
261 log::info!("opened VA-API H.264 decoder on {device:?}");
262 Ok(Self {
263 parser: Parser::default(),
264 dpb: Dpb::default(),
265 prev_ref_pic_info: PrevReferencePicInfo::default(),
266 prev_pic_info: PrevPicInfo::default(),
267 max_long_term_frame_idx: MaxLongTermFrameIdx::default(),
268 sequence: None,
269 ready: Vec::new(),
270 display,
271 })
272 }
273
274 pub fn decode(&mut self, access_unit: &[u8], timestamp: u64) -> anyhow::Result<Vec<Frame>> {
282 self.submit(access_unit, timestamp)?;
283 self.take_frames()
284 }
285
286 pub fn decode_exported(&mut self, access_unit: &[u8], timestamp: u64) -> anyhow::Result<Vec<ExportedFrame>> {
305 self.submit(access_unit, timestamp)?;
306 self.take_exported()
307 }
308
309 fn submit(&mut self, access_unit: &[u8], timestamp: u64) -> anyhow::Result<()> {
313 let mut cursor = Cursor::new(access_unit);
314 let mut current: Option<CurrentPicture> = None;
315 let mut nalus = 0usize;
316
317 while let Ok(nalu) = Nalu::next(&mut cursor) {
320 nalus += 1;
321 match nalu.header.type_ {
322 NaluType::Sps => {
323 self.parser.parse_sps(&nalu).map_err(|e| anyhow!("parse SPS: {e}"))?;
324 }
325 NaluType::Pps => {
326 self.parser.parse_pps(&nalu).map_err(|e| anyhow!("parse PPS: {e}"))?;
327 }
328 NaluType::Slice
329 | NaluType::SliceDpa
330 | NaluType::SliceDpb
331 | NaluType::SliceDpc
332 | NaluType::SliceIdr
333 | NaluType::SliceExt => {
334 let slice = self
335 .parser
336 .parse_slice_header(nalu)
337 .map_err(|e| anyhow!("parse slice header: {e}"))?;
338
339 let mut picture = match current.take() {
340 Some(picture) if slice.header.first_mb_in_slice == 0 => {
344 self.finish_picture(picture)?;
345 self.begin_picture(&slice, timestamp)?
346 }
347 Some(picture) => picture,
348 None => self.begin_picture(&slice, timestamp)?,
349 };
350
351 self.decode_slice(&mut picture, &slice)?;
352 current = Some(picture);
353 }
354 other => log::trace!("ignoring NAL unit of type {other:?}"),
355 }
356 }
357
358 if nalus == 0 && !access_unit.is_empty() {
362 log::warn!(
363 "access unit of {} bytes holds no Annex-B NAL unit; length-prefixed input has to be converted first",
364 access_unit.len()
365 );
366 }
367
368 if let Some(picture) = current.take() {
371 self.finish_picture(picture)?;
372 }
373
374 Ok(())
375 }
376
377 pub fn flush(&mut self) -> anyhow::Result<Vec<Frame>> {
387 self.reset();
388 self.take_frames()
389 }
390
391 pub fn flush_exported(&mut self) -> anyhow::Result<Vec<ExportedFrame>> {
393 self.reset();
394 self.take_exported()
395 }
396
397 fn reset(&mut self) {
400 self.drain();
401 self.prev_ref_pic_info = PrevReferencePicInfo::default();
402 self.prev_pic_info = PrevPicInfo::default();
403 self.max_long_term_frame_idx = MaxLongTermFrameIdx::default();
404 }
405
406 fn take_frames(&mut self) -> anyhow::Result<Vec<Frame>> {
408 std::mem::take(&mut self.ready).iter().map(download).collect()
409 }
410
411 fn take_exported(&mut self) -> anyhow::Result<Vec<ExportedFrame>> {
414 std::mem::take(&mut self.ready).iter().map(export).collect()
415 }
416
417 fn drain(&mut self) {
419 self.ready.extend(self.dpb.drain().into_iter().flatten());
420 }
421
422 fn apply_sps(&mut self, sps: &Sps) -> anyhow::Result<()> {
425 let info = SequenceInfo::new(sps)?;
426 let max_dpb_frames = sps.max_dpb_frames();
429 let max_num_order_frames = sps.max_num_order_frames() as usize;
430 let max_num_reorder_frames = if max_num_order_frames > max_dpb_frames {
431 0
432 } else {
433 max_num_order_frames
434 };
435 self.dpb.set_limits(max_dpb_frames, max_num_reorder_frames);
436
437 if self.sequence.as_ref().map(|sequence| &sequence.info) == Some(&info) {
438 return Ok(());
439 }
440
441 self.drain();
445
446 self.sequence = Some(Sequence::new(&self.display, sps, info)?);
447 Ok(())
448 }
449
450 fn begin_picture(&mut self, slice: &Slice, timestamp: u64) -> anyhow::Result<CurrentPicture> {
453 let pps = Rc::clone(
454 self.parser
455 .get_pps(slice.header.pic_parameter_set_id)
456 .context("slice refers to an unknown PPS")?,
457 );
458 let sps = Rc::clone(&pps.sps);
459 self.apply_sps(&sps)?;
460
461 if slice.nalu.header.idr_pic_flag {
462 self.prev_ref_pic_info.frame_num = 0;
463 }
464
465 let frame_num = u32::from(slice.header.frame_num);
466 if frame_num != self.prev_ref_pic_info.frame_num
467 && frame_num != (self.prev_ref_pic_info.frame_num + 1) % sps.max_frame_num()
468 {
469 self.handle_frame_num_gap(&sps, frame_num, timestamp)?;
470 }
471
472 let pic = self.init_current_pic(slice, &sps, timestamp)?;
473 let ref_pic_lists = self.dpb.build_ref_pic_lists(&pic);
474
475 let sequence = self.sequence.as_ref().expect("sequence built by apply_sps");
477 let context = Rc::clone(&sequence.context);
478 let handle = Handle {
479 surface: Arc::new(sequence.pool.alloc()?),
480 timestamp,
481 width: sequence.width,
482 height: sequence.height,
483 };
484
485 let mut picture = Picture::new(timestamp, Rc::clone(&context), handle.clone());
486 let pic_param = build_pic_param(&slice.header, &pic, handle.surface.id(), &self.dpb, &sps, &pps);
487 picture.add_buffer(create_buffer(&context, pic_param)?);
488 picture.add_buffer(create_buffer(&context, build_iq_matrix(&pps))?);
489
490 log::trace!("starting picture POC {}", pic.pic_order_cnt);
491 Ok(CurrentPicture {
492 pic,
493 pps,
494 handle,
495 picture,
496 ref_pic_lists,
497 })
498 }
499
500 fn decode_slice(&mut self, current: &mut CurrentPicture, slice: &Slice) -> anyhow::Result<()> {
503 if slice.header.first_mb_in_slice > u32::from(u16::MAX) {
507 bail!(
508 "slice starts at macroblock {}, past what a VA slice parameter addresses",
509 slice.header.first_mb_in_slice
510 );
511 }
512 if slice.header.header_bit_size > usize::from(u16::MAX) {
513 bail!(
514 "slice header is {} bits long, past what a VA slice parameter addresses",
515 slice.header.header_bit_size
516 );
517 }
518
519 let pps = Rc::clone(
522 self.parser
523 .get_pps(slice.header.pic_parameter_set_id)
524 .context("slice refers to an unknown PPS")?,
525 );
526 if SequenceInfo::new(&pps.sps)? != SequenceInfo::new(¤t.pps.sps)? {
527 bail!("invalid stream: the sequence changed between slices of one picture");
528 }
529 current.pps = Rc::clone(&pps);
530
531 let (list0, list1) = self.create_ref_pic_lists(¤t.pic, &slice.header, ¤t.ref_pic_lists)?;
532 let context = &self.sequence.as_ref().expect("sequence built by apply_sps").context;
533
534 let slice_param = build_slice_param(&slice.header, slice.nalu.size, &list0, &list1, &pps.sps, &pps);
535 current.picture.add_buffer(create_buffer(context, slice_param)?);
536 current.picture.add_buffer(create_buffer(
537 context,
538 BufferType::SliceData(slice.nalu.as_ref().to_vec()),
539 )?);
540
541 Ok(())
542 }
543
544 fn finish_picture(&mut self, current: CurrentPicture) -> anyhow::Result<()> {
547 let CurrentPicture {
548 mut pic,
549 pps,
550 handle,
551 picture,
552 ..
553 } = current;
554
555 let picture = picture.begin::<()>().map_err(|e| anyhow!("picture begin: {e:?}"))?;
556 let picture = picture.render().map_err(|e| anyhow!("picture render: {e:?}"))?;
557 let picture = picture.end().map_err(|e| anyhow!("picture end: {e:?}"))?;
558 drop(picture.sync::<()>().map_err(|(e, _)| anyhow!("picture sync: {e:?}"))?);
562 log::trace!("decoded picture POC {}", pic.pic_order_cnt);
563
564 if pic.is_ref() {
565 self.reference_pic_marking(&mut pic, &pps.sps)?;
566 self.prev_ref_pic_info.fill(&pic);
567 }
568 self.prev_pic_info.fill(&pic);
569
570 if pic.has_mmco_5 {
573 self.drain();
574 }
575
576 self.ready.extend(self.dpb.bump_as_needed(&pic).into_iter().flatten());
578
579 if pic.is_ref() || self.dpb.has_empty_frame_buffer() {
583 self.dpb
584 .store_picture(pic.into_rc(), Some(handle))
585 .map_err(|e| anyhow!("store picture in DPB: {e}"))?;
586 } else {
587 self.ready.push(handle);
588 }
589
590 Ok(())
591 }
592
593 fn init_current_pic(&mut self, slice: &Slice, sps: &Sps, timestamp: u64) -> anyhow::Result<PictureData> {
595 let mut pic = PictureData::new_from_slice(slice, sps, timestamp, None);
597 self.compute_pic_order_count(&mut pic, sps)?;
598
599 if matches!(pic.is_idr, IsIdr::Yes { .. }) {
600 if pic.ref_pic_marking.no_output_of_prior_pics_flag {
603 self.dpb.clear();
604 } else {
605 self.drain();
606 }
607 }
608
609 self.dpb
610 .update_pic_nums(u32::from(slice.header.frame_num), sps.max_frame_num(), &pic);
611
612 Ok(pic)
613 }
614
615 fn handle_frame_num_gap(&mut self, sps: &Sps, frame_num: u32, timestamp: u64) -> anyhow::Result<()> {
618 if self.dpb.is_empty() {
619 return Ok(());
620 }
621
622 if !sps.gaps_in_frame_num_value_allowed_flag {
623 bail!("invalid frame_num {frame_num}: assuming unintentional loss of pictures");
624 }
625 log::debug!("frame_num gap up to {frame_num}");
626
627 let max_frame_num = sps.max_frame_num();
628 let mut unused = (self.prev_ref_pic_info.frame_num + 1) % max_frame_num;
629 while unused != frame_num {
630 let mut pic = PictureData::new_non_existing(unused, timestamp);
631 self.compute_pic_order_count(&mut pic, sps)?;
632
633 self.dpb.update_pic_nums(unused, max_frame_num, &pic);
634 self.dpb.sliding_window_marking(&mut pic, sps);
635 self.ready.extend(self.dpb.bump_as_needed(&pic).into_iter().flatten());
636 self.dpb
637 .store_picture(pic.into_rc(), None)
638 .map_err(|e| anyhow!("store non-existing picture in DPB: {e}"))?;
639
640 unused = (unused + 1) % max_frame_num;
641 }
642
643 Ok(())
644 }
645
646 fn compute_pic_order_count(&mut self, pic: &mut PictureData, sps: &Sps) -> anyhow::Result<()> {
648 match pic.pic_order_cnt_type {
649 0 => {
651 let (prev_pic_order_cnt_msb, prev_pic_order_cnt_lsb) = if matches!(pic.is_idr, IsIdr::Yes { .. }) {
652 (0, 0)
653 } else if self.prev_ref_pic_info.has_mmco_5 {
654 if matches!(self.prev_ref_pic_info.field, Field::Bottom) {
655 (0, 0)
656 } else {
657 (0, self.prev_ref_pic_info.top_field_order_cnt)
658 }
659 } else {
660 (
661 self.prev_ref_pic_info.pic_order_cnt_msb,
662 self.prev_ref_pic_info.pic_order_cnt_lsb,
663 )
664 };
665
666 let max_pic_order_cnt_lsb = 1 << (sps.log2_max_pic_order_cnt_lsb_minus4 + 4);
667
668 pic.pic_order_cnt_msb = if (pic.pic_order_cnt_lsb < prev_pic_order_cnt_lsb)
669 && (prev_pic_order_cnt_lsb - pic.pic_order_cnt_lsb >= max_pic_order_cnt_lsb / 2)
670 {
671 prev_pic_order_cnt_msb + max_pic_order_cnt_lsb
672 } else if (pic.pic_order_cnt_lsb > prev_pic_order_cnt_lsb)
673 && (pic.pic_order_cnt_lsb - prev_pic_order_cnt_lsb > max_pic_order_cnt_lsb / 2)
674 {
675 prev_pic_order_cnt_msb - max_pic_order_cnt_lsb
676 } else {
677 prev_pic_order_cnt_msb
678 };
679
680 if !matches!(pic.field, Field::Bottom) {
681 pic.top_field_order_cnt = pic.pic_order_cnt_msb + pic.pic_order_cnt_lsb;
682 }
683
684 if !matches!(pic.field, Field::Top) {
685 pic.bottom_field_order_cnt = if matches!(pic.field, Field::Frame) {
686 pic.top_field_order_cnt + pic.delta_pic_order_cnt_bottom
687 } else {
688 pic.pic_order_cnt_msb + pic.pic_order_cnt_lsb
689 };
690 }
691 }
692
693 1 => {
695 if self.prev_pic_info.has_mmco_5 {
696 self.prev_pic_info.frame_num_offset = 0;
697 }
698
699 pic.frame_num_offset = if matches!(pic.is_idr, IsIdr::Yes { .. }) {
700 0
701 } else if self.prev_pic_info.frame_num > pic.frame_num {
702 self.prev_pic_info.frame_num_offset + sps.max_frame_num()
703 } else {
704 self.prev_pic_info.frame_num_offset
705 };
706
707 let mut abs_frame_num = if sps.num_ref_frames_in_pic_order_cnt_cycle != 0 {
708 pic.frame_num_offset + pic.frame_num
709 } else {
710 0
711 };
712
713 if pic.nal_ref_idc == 0 && abs_frame_num > 0 {
714 abs_frame_num -= 1;
715 }
716
717 let mut expected_pic_order_cnt = 0;
718
719 if abs_frame_num > 0 {
720 if sps.num_ref_frames_in_pic_order_cnt_cycle == 0 {
721 bail!("invalid num_ref_frames_in_pic_order_cnt_cycle");
722 }
723
724 let cycle_len = u32::from(sps.num_ref_frames_in_pic_order_cnt_cycle);
725 let cycles = (abs_frame_num - 1) / cycle_len;
726 let position_in_cycle = (abs_frame_num - 1) % cycle_len;
729 expected_pic_order_cnt = cycles as i32 * sps.expected_delta_per_pic_order_cnt_cycle;
730
731 for i in 0..=position_in_cycle {
732 expected_pic_order_cnt += sps.offset_for_ref_frame[i as usize];
733 }
734 }
735
736 if pic.nal_ref_idc == 0 {
737 expected_pic_order_cnt += sps.offset_for_non_ref_pic;
738 }
739
740 match pic.field {
741 Field::Frame => {
742 pic.top_field_order_cnt = expected_pic_order_cnt + pic.delta_pic_order_cnt0;
743 pic.bottom_field_order_cnt =
744 pic.top_field_order_cnt + sps.offset_for_top_to_bottom_field + pic.delta_pic_order_cnt1;
745 }
746 Field::Top => {
747 pic.top_field_order_cnt = expected_pic_order_cnt + pic.delta_pic_order_cnt0;
748 }
749 Field::Bottom => {
750 pic.bottom_field_order_cnt =
751 expected_pic_order_cnt + sps.offset_for_top_to_bottom_field + pic.delta_pic_order_cnt0;
752 }
753 }
754 }
755
756 2 => {
758 if self.prev_pic_info.has_mmco_5 {
759 self.prev_pic_info.frame_num_offset = 0;
760 }
761
762 pic.frame_num_offset = if matches!(pic.is_idr, IsIdr::Yes { .. }) {
763 0
764 } else if self.prev_pic_info.frame_num > pic.frame_num {
765 self.prev_pic_info.frame_num_offset + sps.max_frame_num()
766 } else {
767 self.prev_pic_info.frame_num_offset
768 };
769
770 let pic_order_cnt = if matches!(pic.is_idr, IsIdr::Yes { .. }) {
771 0
772 } else if pic.nal_ref_idc == 0 {
773 2 * (pic.frame_num_offset + pic.frame_num) as i32 - 1
774 } else {
775 2 * (pic.frame_num_offset + pic.frame_num) as i32
776 };
777
778 if matches!(pic.field, Field::Frame | Field::Top) {
779 pic.top_field_order_cnt = pic_order_cnt;
780 }
781 if matches!(pic.field, Field::Frame | Field::Bottom) {
782 pic.bottom_field_order_cnt = pic_order_cnt;
783 }
784 }
785
786 other => bail!("invalid pic_order_cnt_type {other}"),
787 }
788
789 pic.pic_order_cnt = match pic.field {
790 Field::Frame => std::cmp::min(pic.top_field_order_cnt, pic.bottom_field_order_cnt),
791 Field::Top => pic.top_field_order_cnt,
792 Field::Bottom => pic.bottom_field_order_cnt,
793 };
794
795 Ok(())
796 }
797
798 fn reference_pic_marking(&mut self, pic: &mut PictureData, sps: &Sps) -> anyhow::Result<()> {
800 if matches!(pic.is_idr, IsIdr::Yes { .. }) {
801 self.dpb.mark_all_as_unused_for_ref();
802
803 if pic.ref_pic_marking.long_term_reference_flag {
804 pic.set_reference(Reference::LongTerm, false);
805 pic.long_term_frame_idx = 0;
806 self.max_long_term_frame_idx = MaxLongTermFrameIdx::Idx(0);
807 } else {
808 pic.set_reference(Reference::ShortTerm, false);
809 self.max_long_term_frame_idx = MaxLongTermFrameIdx::NoLongTermFrameIndices;
810 }
811
812 return Ok(());
813 }
814
815 if pic.ref_pic_marking.adaptive_ref_pic_marking_mode_flag {
816 self.handle_memory_management_ops(pic)?;
817 } else {
818 self.dpb.sliding_window_marking(pic, sps);
819 }
820
821 Ok(())
822 }
823
824 fn handle_memory_management_ops(&mut self, pic: &mut PictureData) -> Result<(), MmcoError> {
826 let markings = pic.ref_pic_marking.clone();
827
828 for marking in &markings.inner {
829 match marking.memory_management_control_operation {
830 0 => break,
831 1 => self.dpb.mmco_op_1(pic, marking)?,
832 2 => self.dpb.mmco_op_2(pic, marking)?,
833 3 => self.dpb.mmco_op_3(pic, marking)?,
834 4 => self.max_long_term_frame_idx = self.dpb.mmco_op_4(marking),
835 5 => self.max_long_term_frame_idx = self.dpb.mmco_op_5(pic),
836 6 => self.dpb.mmco_op_6(pic, marking),
837 other => return Err(MmcoError::UnknownMmco(other)),
838 }
839 }
840
841 Ok(())
842 }
843
844 fn create_ref_pic_lists<'a>(
847 &'a self,
848 pic: &PictureData,
849 hdr: &SliceHeader,
850 lists: &ReferencePicLists,
851 ) -> anyhow::Result<(DpbPicRefList<'a, Handle>, DpbPicRefList<'a, Handle>)> {
852 let list0 = match hdr.slice_type {
853 SliceType::P | SliceType::Sp => self.modify_ref_pic_list(pic, hdr, false, &lists.ref_pic_list_p0)?,
854 SliceType::B => self.modify_ref_pic_list(pic, hdr, false, &lists.ref_pic_list_b0)?,
855 _ => Vec::new(),
856 };
857
858 let list1 = match hdr.slice_type {
859 SliceType::B => self.modify_ref_pic_list(pic, hdr, true, &lists.ref_pic_list_b1)?,
860 _ => Vec::new(),
861 };
862
863 Ok((list0, list1))
864 }
865
866 fn modify_ref_pic_list<'a>(
868 &'a self,
869 pic: &PictureData,
870 hdr: &SliceHeader,
871 list1: bool,
872 indices: &[usize],
873 ) -> anyhow::Result<DpbPicRefList<'a, Handle>> {
874 let (modify, num_ref_idx_active_minus1, modifications) = if list1 {
875 (
876 hdr.ref_pic_list_modification_flag_l1,
877 hdr.num_ref_idx_l1_active_minus1,
878 &hdr.ref_pic_list_modification_l1,
879 )
880 } else {
881 (
882 hdr.ref_pic_list_modification_flag_l0,
883 hdr.num_ref_idx_l0_active_minus1,
884 &hdr.ref_pic_list_modification_l0,
885 )
886 };
887
888 let mut list: DpbPicRefList<'a, Handle> = indices
889 .iter()
890 .map(|&i| &self.dpb.entries()[i])
891 .take(usize::from(num_ref_idx_active_minus1) + 1)
892 .collect();
893
894 if !modify {
895 return Ok(list);
896 }
897
898 let mut pic_num_pred = pic.pic_num;
899 let mut ref_idx = 0;
900
901 for modification in modifications {
902 match modification.modification_of_pic_nums_idc {
903 idc @ (0 | 1) => self.short_term_modification(
904 pic,
905 &mut list,
906 num_ref_idx_active_minus1,
907 hdr.max_pic_num as i32,
908 idc,
909 modification,
910 &mut pic_num_pred,
911 &mut ref_idx,
912 )?,
913 2 => self.long_term_modification(&mut list, num_ref_idx_active_minus1, modification, &mut ref_idx)?,
914 3 => break,
915 other => bail!("unexpected modification_of_pic_nums_idc {other}"),
916 }
917 }
918
919 Ok(list)
920 }
921
922 #[allow(clippy::too_many_arguments)]
924 fn short_term_modification<'a>(
925 &'a self,
926 pic: &PictureData,
927 list: &mut DpbPicRefList<'a, Handle>,
928 num_ref_idx_active_minus1: u8,
929 max_pic_num: i32,
930 idc: u8,
931 modification: &RefPicListModification,
932 pic_num_pred: &mut i32,
933 ref_idx: &mut usize,
934 ) -> anyhow::Result<()> {
935 let abs_diff_pic_num = modification.abs_diff_pic_num_minus1 as i32 + 1;
936
937 let pic_num_no_wrap = if idc == 0 {
938 if *pic_num_pred - abs_diff_pic_num < 0 {
939 *pic_num_pred - abs_diff_pic_num + max_pic_num
940 } else {
941 *pic_num_pred - abs_diff_pic_num
942 }
943 } else if *pic_num_pred + abs_diff_pic_num >= max_pic_num {
944 *pic_num_pred + abs_diff_pic_num - max_pic_num
945 } else {
946 *pic_num_pred + abs_diff_pic_num
947 };
948
949 *pic_num_pred = pic_num_no_wrap;
950
951 let pic_num = if pic_num_no_wrap > pic.pic_num {
952 pic_num_no_wrap - max_pic_num
953 } else {
954 pic_num_no_wrap
955 };
956
957 let entry = self
958 .dpb
959 .find_short_term_with_pic_num(pic_num)
960 .with_context(|| format!("no short-term reference with pic_num {pic_num}"))?;
961
962 Self::insert_modification(list, entry, num_ref_idx_active_minus1, ref_idx, |target| {
963 target.pic_num_f(max_pic_num) != pic_num
964 });
965
966 Ok(())
967 }
968
969 fn long_term_modification<'a>(
971 &'a self,
972 list: &mut DpbPicRefList<'a, Handle>,
973 num_ref_idx_active_minus1: u8,
974 modification: &RefPicListModification,
975 ref_idx: &mut usize,
976 ) -> anyhow::Result<()> {
977 let long_term_pic_num = modification.long_term_pic_num;
978 let max_long_term_frame_idx = self.max_long_term_frame_idx;
979
980 let entry = self
981 .dpb
982 .find_long_term_with_long_term_pic_num(long_term_pic_num)
983 .with_context(|| format!("no long-term reference with long_term_pic_num {long_term_pic_num}"))?;
984
985 Self::insert_modification(list, entry, num_ref_idx_active_minus1, ref_idx, |target| {
986 target.long_term_pic_num_f(max_long_term_frame_idx) != long_term_pic_num
987 });
988
989 Ok(())
990 }
991
992 fn insert_modification<'a>(
995 list: &mut DpbPicRefList<'a, Handle>,
996 entry: &'a DpbEntry<Handle>,
997 num_ref_idx_active_minus1: u8,
998 ref_idx: &mut usize,
999 keep: impl Fn(&PictureData) -> bool,
1000 ) {
1001 let at = (*ref_idx).min(list.len());
1004 list.insert(at, entry);
1005 *ref_idx = at + 1;
1006
1007 let mut next = *ref_idx;
1008 for current in *ref_idx..=usize::from(num_ref_idx_active_minus1) + 1 {
1009 if current >= list.len() {
1010 break;
1011 }
1012
1013 if keep(&list[current].pic.borrow()) {
1014 list[next] = list[current];
1015 next += 1;
1016 }
1017 }
1018
1019 list.truncate(usize::from(num_ref_idx_active_minus1) + 1);
1020 }
1021}
1022
1023struct CurrentPicture {
1025 pic: PictureData,
1026 pps: Rc<Pps>,
1028 handle: Handle,
1030 picture: Picture<crate::PictureNew, Handle>,
1031 ref_pic_lists: ReferencePicLists,
1033}
1034
1035#[derive(Clone)]
1039struct Handle {
1040 surface: Arc<PooledSurface>,
1041 timestamp: u64,
1042 width: u32,
1043 height: u32,
1044}
1045
1046impl std::borrow::Borrow<Surface<()>> for Handle {
1047 fn borrow(&self) -> &Surface<()> {
1048 self.surface.get()
1049 }
1050}
1051
1052#[derive(Clone, Debug, PartialEq, Eq)]
1055struct SequenceInfo {
1056 visible: (u32, u32),
1057 coded: (u32, u32),
1058 profile_idc: u8,
1059 bit_depth_luma_minus8: u8,
1060 bit_depth_chroma_minus8: u8,
1061 chroma_format_idc: u8,
1062 frame_mbs_only_flag: bool,
1063 max_dpb_frames: usize,
1064}
1065
1066impl SequenceInfo {
1067 fn new(sps: &Sps) -> anyhow::Result<Self> {
1068 if !sps.frame_mbs_only_flag {
1069 bail!("interlaced H.264 is not supported");
1070 }
1071 if sps.chroma_format_idc != 1 {
1072 bail!(
1073 "only 4:2:0 chroma is supported, got chroma_format_idc {}",
1074 sps.chroma_format_idc
1075 );
1076 }
1077 if sps.bit_depth_luma_minus8 != 0 || sps.bit_depth_chroma_minus8 != 0 {
1078 bail!(
1079 "only 8-bit samples are supported, got {}-bit luma and {}-bit chroma",
1080 sps.bit_depth_luma_minus8 + 8,
1081 sps.bit_depth_chroma_minus8 + 8
1082 );
1083 }
1084
1085 if sps.frame_cropping_flag && (sps.frame_crop_left_offset != 0 || sps.frame_crop_top_offset != 0) {
1086 bail!("left and top H.264 cropping is not supported");
1087 }
1088 let (crop_right, crop_bottom) = if sps.frame_cropping_flag {
1089 (sps.frame_crop_right_offset, sps.frame_crop_bottom_offset)
1090 } else {
1091 (0, 0)
1092 };
1093 let width = crop_right
1094 .checked_mul(2)
1095 .and_then(|crop| sps.width().checked_sub(crop))
1096 .context("invalid horizontal H.264 crop")?;
1097 let height = crop_bottom
1098 .checked_mul(2)
1099 .and_then(|crop| sps.height().checked_sub(crop))
1100 .context("invalid vertical H.264 crop")?;
1101 if width == 0 || height == 0 || width % 2 != 0 || height % 2 != 0 {
1102 bail!("visible size {width}x{height} is not a non-zero even 4:2:0 size");
1103 }
1104
1105 Ok(Self {
1106 visible: (width, height),
1107 coded: (sps.width(), sps.height()),
1108 profile_idc: sps.profile_idc,
1109 bit_depth_luma_minus8: sps.bit_depth_luma_minus8,
1110 bit_depth_chroma_minus8: sps.bit_depth_chroma_minus8,
1111 chroma_format_idc: sps.chroma_format_idc,
1112 frame_mbs_only_flag: sps.frame_mbs_only_flag,
1113 max_dpb_frames: sps.max_dpb_frames(),
1114 })
1115 }
1116}
1117
1118struct Sequence {
1120 info: SequenceInfo,
1121 width: u32,
1123 height: u32,
1125 pool: SurfacePool,
1126 context: Rc<Context>,
1128 _config: VaConfig,
1129}
1130
1131impl Sequence {
1132 fn new(display: &Arc<Display>, sps: &Sps, info: SequenceInfo) -> anyhow::Result<Self> {
1133 let (width, height) = info.visible;
1134
1135 let attrs = vec![VAConfigAttrib {
1136 type_: VAConfigAttribType::VAConfigAttribRTFormat,
1137 value: VA_RT_FORMAT_YUV420,
1138 }];
1139 let config = display
1140 .create_config(attrs, va_profile(sps)?, VAEntrypoint::VAEntrypointVLD)
1141 .map_err(|e| anyhow!("create VA config: {e:?}"))?;
1142
1143 let (coded_width, coded_height) = info.coded;
1144 let context = display
1145 .create_context::<()>(&config, coded_width, coded_height, None, true)
1146 .map_err(|e| anyhow!("create VA context: {e:?}"))?;
1147
1148 log::info!(
1149 "VA-API H.264 sequence: {width}x{height} visible, {coded_width}x{coded_height} coded, \
1150 profile_idc {}, DPB {} frames",
1151 sps.profile_idc,
1152 info.max_dpb_frames
1153 );
1154
1155 let capacity = info.max_dpb_frames + 1;
1160 Ok(Self {
1161 info,
1162 width,
1163 height,
1164 pool: SurfacePool {
1165 display: Arc::clone(display),
1166 width: coded_width,
1167 height: coded_height,
1168 free: Arc::new(FreeList {
1169 surfaces: Mutex::new(Vec::new()),
1170 capacity,
1171 }),
1172 allocated: Cell::new(0),
1173 },
1174 context,
1175 _config: config,
1176 })
1177 }
1178}
1179
1180struct SurfacePool {
1192 display: Arc<Display>,
1193 width: u32,
1194 height: u32,
1195 free: Arc<FreeList>,
1198 allocated: Cell<usize>,
1200}
1201
1202struct FreeList {
1204 surfaces: Mutex<Vec<Surface<()>>>,
1205 capacity: usize,
1207}
1208
1209impl SurfacePool {
1210 fn alloc(&self) -> anyhow::Result<PooledSurface> {
1211 let recycled = self.free.surfaces.lock().expect("poisoned").pop();
1212 let surface = match recycled {
1213 Some(surface) => surface,
1214 None => {
1215 let surface = self
1216 .display
1217 .create_surfaces::<()>(
1218 VA_RT_FORMAT_YUV420,
1219 Some(VA_FOURCC_NV12),
1220 self.width,
1221 self.height,
1222 Some(UsageHint::USAGE_HINT_DECODER),
1223 vec![()],
1224 )
1225 .map_err(|e| anyhow!("create decode surface: {e:?}"))?
1226 .pop()
1227 .context("VA-API returned an empty surface list")?;
1228 self.allocated.set(self.allocated.get() + 1);
1229 log::debug!(
1230 "allocated decode surface {} at {}x{}",
1231 self.allocated.get(),
1232 self.width,
1233 self.height
1234 );
1235 surface
1236 }
1237 };
1238
1239 Ok(PooledSurface {
1240 surface: Some(surface),
1241 free: Arc::downgrade(&self.free),
1242 })
1243 }
1244}
1245
1246struct PooledSurface {
1253 surface: Option<Surface<()>>,
1256 free: Weak<FreeList>,
1259}
1260
1261impl PooledSurface {
1262 fn unpooled(surface: Surface<()>) -> Self {
1264 Self {
1265 surface: Some(surface),
1266 free: Weak::new(),
1267 }
1268 }
1269
1270 fn get(&self) -> &Surface<()> {
1271 self.surface.as_ref().expect("surface is taken only on drop")
1272 }
1273
1274 fn id(&self) -> bindings::VASurfaceID {
1275 self.get().id()
1276 }
1277}
1278
1279impl Drop for PooledSurface {
1280 fn drop(&mut self) {
1281 let (Some(surface), Some(free)) = (self.surface.take(), self.free.upgrade()) else {
1282 return;
1283 };
1284 let mut surfaces = free.surfaces.lock().expect("poisoned");
1285 if surfaces.len() < free.capacity {
1286 surfaces.push(surface);
1287 }
1288 }
1289}
1290
1291fn export(handle: &Handle) -> anyhow::Result<ExportedFrame> {
1297 let surface = handle.surface.get();
1298 surface.sync().map_err(|e| anyhow!("surface sync: {e:?}"))?;
1299
1300 let descriptor = surface
1301 .export_prime()
1302 .map_err(|e| anyhow!("export the decode surface: {e:?}"))?;
1303
1304 Ok(ExportedFrame {
1305 timestamp: handle.timestamp,
1306 width: handle.width,
1307 height: handle.height,
1308 descriptor,
1309 surface: Arc::clone(&handle.surface),
1310 })
1311}
1312
1313fn download(handle: &Handle) -> anyhow::Result<Frame> {
1315 read_back(handle.surface.get(), handle.timestamp, handle.width, handle.height)
1316}
1317
1318fn read_back(surface: &Surface<()>, timestamp: u64, width: u32, height: u32) -> anyhow::Result<Frame> {
1324 surface.sync().map_err(|e| anyhow!("surface sync: {e:?}"))?;
1325
1326 let visible = (width, height);
1327
1328 match Image::derive_from(surface, visible) {
1333 Ok(image) if image.image().format.fourcc == VA_FOURCC_NV12 => {
1334 return pack_nv12(&image, timestamp, width, height);
1335 }
1336 Ok(_) => log::debug!("derived image is not NV12, falling back to vaGetImage"),
1337 Err(e) => log::debug!("vaDeriveImage failed ({e:?}), falling back to vaGetImage"),
1338 }
1339
1340 let format = surface
1341 .display()
1342 .query_image_formats()
1343 .map_err(|e| anyhow!("query image formats: {e:?}"))?
1344 .into_iter()
1345 .find(|format| format.fourcc == VA_FOURCC_NV12)
1346 .context("driver has no NV12 image format")?;
1347 let image = Image::create_from(surface, format, surface.size(), visible)
1348 .map_err(|e| anyhow!("vaGetImage into an NV12 image: {e:?}"))?;
1349
1350 pack_nv12(&image, timestamp, width, height)
1351}
1352
1353fn pack_nv12(image: &Image<'_>, timestamp: u64, frame_width: u32, frame_height: u32) -> anyhow::Result<Frame> {
1356 let va_image = image.image();
1357 let src: &[u8] = image.as_ref();
1358 let (width, height) = (frame_width as usize, frame_height as usize);
1359 let chroma_rows = height / 2;
1360
1361 let mut data = vec![0u8; width * (height + chroma_rows)];
1362 let (luma, chroma) = data.split_at_mut(width * height);
1363 copy_plane(
1364 luma,
1365 src,
1366 va_image.offsets[0] as usize,
1367 va_image.pitches[0] as usize,
1368 width,
1369 height,
1370 )?;
1371 copy_plane(
1372 chroma,
1373 src,
1374 va_image.offsets[1] as usize,
1375 va_image.pitches[1] as usize,
1376 width,
1377 chroma_rows,
1378 )?;
1379
1380 Ok(Frame {
1381 timestamp,
1382 width: frame_width,
1383 height: frame_height,
1384 data,
1385 })
1386}
1387
1388fn copy_plane(
1390 dst: &mut [u8],
1391 src: &[u8],
1392 offset: usize,
1393 pitch: usize,
1394 width: usize,
1395 rows: usize,
1396) -> anyhow::Result<()> {
1397 if rows == 0 {
1398 return Ok(());
1399 }
1400 let end = offset + pitch * (rows - 1) + width;
1401 if pitch < width || src.len() < end {
1402 bail!("VA image plane (offset {offset}, pitch {pitch}) does not hold {rows} rows of {width} bytes");
1403 }
1404
1405 for row in 0..rows {
1406 let from = offset + row * pitch;
1407 dst[row * width..][..width].copy_from_slice(&src[from..from + width]);
1408 }
1409
1410 Ok(())
1411}
1412
1413pub fn probe(display: &Display) -> anyhow::Result<()> {
1420 let profiles = display
1421 .query_config_profiles()
1422 .map_err(|e| anyhow!("query VA profiles: {e:?}"))?;
1423
1424 let supported = profiles
1425 .into_iter()
1426 .filter(|profile| {
1427 matches!(
1428 *profile,
1429 VAProfile::VAProfileH264ConstrainedBaseline
1430 | VAProfile::VAProfileH264Main
1431 | VAProfile::VAProfileH264High
1432 )
1433 })
1434 .any(|profile| {
1435 display
1436 .query_config_entrypoints(profile)
1437 .is_ok_and(|entrypoints| entrypoints.contains(&VAEntrypoint::VAEntrypointVLD))
1438 });
1439
1440 if !supported {
1441 bail!("driver exposes no H.264 decode entrypoint");
1442 }
1443
1444 Ok(())
1445}
1446
1447fn va_profile(sps: &Sps) -> anyhow::Result<VAProfile::Type> {
1449 let profile = Profile::try_from(sps.profile_idc).map_err(|e| anyhow!("{e}"))?;
1450
1451 Ok(match profile {
1452 Profile::Baseline => VAProfile::VAProfileH264ConstrainedBaseline,
1458 Profile::Main | Profile::Extended => VAProfile::VAProfileH264Main,
1459 Profile::High | Profile::High10 | Profile::High422P => VAProfile::VAProfileH264High,
1462 })
1463}
1464
1465fn create_buffer(context: &Rc<Context>, buffer: BufferType) -> anyhow::Result<Buffer> {
1466 context
1467 .create_buffer(buffer)
1468 .map_err(|e| anyhow!("create VA buffer: {e:?}"))
1469}
1470
1471#[derive(Debug)]
1473struct PrevReferencePicInfo {
1474 frame_num: u32,
1475 has_mmco_5: bool,
1476 top_field_order_cnt: i32,
1477 pic_order_cnt_msb: i32,
1478 pic_order_cnt_lsb: i32,
1479 field: Field,
1480}
1481
1482impl Default for PrevReferencePicInfo {
1483 fn default() -> Self {
1484 Self {
1485 frame_num: 0,
1486 has_mmco_5: false,
1487 top_field_order_cnt: 0,
1488 pic_order_cnt_msb: 0,
1489 pic_order_cnt_lsb: 0,
1490 field: Field::Frame,
1491 }
1492 }
1493}
1494
1495impl PrevReferencePicInfo {
1496 fn fill(&mut self, pic: &PictureData) {
1497 self.has_mmco_5 = pic.has_mmco_5;
1498 self.top_field_order_cnt = pic.top_field_order_cnt;
1499 self.pic_order_cnt_msb = pic.pic_order_cnt_msb;
1500 self.pic_order_cnt_lsb = pic.pic_order_cnt_lsb;
1501 self.field = pic.field;
1502 self.frame_num = pic.frame_num;
1503 }
1504}
1505
1506#[derive(Debug, Default)]
1508struct PrevPicInfo {
1509 frame_num: u32,
1510 frame_num_offset: u32,
1511 has_mmco_5: bool,
1512}
1513
1514impl PrevPicInfo {
1515 fn fill(&mut self, pic: &PictureData) {
1516 self.frame_num = pic.frame_num;
1517 self.has_mmco_5 = pic.has_mmco_5;
1518 self.frame_num_offset = pic.frame_num_offset;
1519 }
1520}
1521
1522fn va_surface_id(handle: &Option<Handle>) -> bindings::VASurfaceID {
1525 match handle {
1526 Some(handle) => handle.surface.id(),
1527 None => VA_INVALID_ID,
1528 }
1529}
1530
1531fn fill_va_h264_pic(pic: &PictureData, surface_id: bindings::VASurfaceID) -> PictureH264 {
1534 let mut flags = 0;
1535 let frame_idx = match pic.reference() {
1536 Reference::LongTerm => {
1537 flags |= VA_PICTURE_H264_LONG_TERM_REFERENCE;
1538 pic.long_term_frame_idx
1539 }
1540 Reference::ShortTerm => {
1541 flags |= VA_PICTURE_H264_SHORT_TERM_REFERENCE;
1542 pic.frame_num
1543 }
1544 Reference::None => pic.frame_num,
1545 };
1546
1547 PictureH264::new(
1548 surface_id,
1549 frame_idx,
1550 flags,
1551 pic.top_field_order_cnt,
1552 pic.bottom_field_order_cnt,
1553 )
1554}
1555
1556fn build_invalid_va_h264_pic() -> PictureH264 {
1558 PictureH264::new(VA_INVALID_ID, 0, VA_PICTURE_H264_INVALID, 0, 0)
1559}
1560
1561fn build_iq_matrix(pps: &Pps) -> BufferType {
1564 const ZIGZAG_4X4: [usize; 16] = [0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15];
1565 #[rustfmt::skip]
1566 const ZIGZAG_8X8: [usize; 64] = [
1567 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27,
1568 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51,
1569 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63,
1570 ];
1571
1572 let mut scaling_list4x4 = [[0u8; 16]; 6];
1573 for (list, src) in scaling_list4x4.iter_mut().zip(pps.scaling_lists_4x4.iter()) {
1574 for (i, &value) in src.iter().enumerate() {
1575 list[ZIGZAG_4X4[i]] = value;
1576 }
1577 }
1578
1579 let mut scaling_list8x8 = [[0u8; 64]; 2];
1580 for (list, src) in scaling_list8x8.iter_mut().zip(pps.scaling_lists_8x8.iter()) {
1581 for (i, &value) in src.iter().enumerate() {
1582 list[ZIGZAG_8X8[i]] = value;
1583 }
1584 }
1585
1586 BufferType::IQMatrix(IQMatrix::H264(IQMatrixBufferH264::new(
1587 scaling_list4x4,
1588 scaling_list8x8,
1589 )))
1590}
1591
1592fn build_pic_param(
1594 hdr: &SliceHeader,
1595 pic: &PictureData,
1596 surface_id: bindings::VASurfaceID,
1597 dpb: &Dpb<Handle>,
1598 sps: &Sps,
1599 pps: &Pps,
1600) -> BufferType {
1601 let curr_pic = fill_va_h264_pic(pic, surface_id);
1602
1603 let short_term = dpb
1604 .short_term_refs_iter()
1605 .filter(|entry| !entry.pic.borrow().nonexisting);
1606 let mut refs: Vec<PictureH264> = short_term
1607 .chain(dpb.long_term_refs_iter())
1608 .take(16)
1609 .map(|entry| fill_va_h264_pic(&entry.pic.borrow(), va_surface_id(&entry.reference)))
1610 .collect();
1611 refs.resize_with(16, build_invalid_va_h264_pic);
1612 let refs: [PictureH264; 16] = refs.try_into().unwrap_or_else(|_| unreachable!("resized to 16"));
1613
1614 let seq_fields = H264SeqFields::new(
1615 sps.chroma_format_idc as u32,
1616 sps.separate_colour_plane_flag as u32,
1617 sps.gaps_in_frame_num_value_allowed_flag as u32,
1618 sps.frame_mbs_only_flag as u32,
1619 sps.mb_adaptive_frame_field_flag as u32,
1620 sps.direct_8x8_inference_flag as u32,
1621 (sps.level_idc >= Level::L3_1) as u32,
1623 sps.log2_max_frame_num_minus4 as u32,
1624 sps.pic_order_cnt_type as u32,
1625 sps.log2_max_pic_order_cnt_lsb_minus4 as u32,
1626 sps.delta_pic_order_always_zero_flag as u32,
1627 );
1628
1629 let pic_fields = H264PicFields::new(
1630 pps.entropy_coding_mode_flag as u32,
1631 pps.weighted_pred_flag as u32,
1632 pps.weighted_bipred_idc as u32,
1633 pps.transform_8x8_mode_flag as u32,
1634 hdr.field_pic_flag as u32,
1635 pps.constrained_intra_pred_flag as u32,
1636 pps.bottom_field_pic_order_in_frame_present_flag as u32,
1637 pps.deblocking_filter_control_present_flag as u32,
1638 pps.redundant_pic_cnt_present_flag as u32,
1639 (pic.nal_ref_idc != 0) as u32,
1640 );
1641
1642 let picture_height_in_mbs_minus1 = sps.pic_height_in_map_units_minus1;
1644
1645 BufferType::PictureParameter(PictureParameter::H264(PictureParameterBufferH264::new(
1646 curr_pic,
1647 refs,
1648 sps.pic_width_in_mbs_minus1,
1649 picture_height_in_mbs_minus1,
1650 sps.bit_depth_luma_minus8,
1651 sps.bit_depth_chroma_minus8,
1652 sps.max_num_ref_frames,
1653 &seq_fields,
1654 0,
1656 0,
1657 0,
1658 pps.pic_init_qp_minus26,
1659 pps.pic_init_qs_minus26,
1660 pps.chroma_qp_index_offset,
1661 pps.second_chroma_qp_index_offset,
1662 &pic_fields,
1663 hdr.frame_num,
1664 )))
1665}
1666
1667fn fill_ref_pic_list(list: &[&DpbEntry<Handle>]) -> [PictureH264; 32] {
1669 let mut pics: Vec<PictureH264> = list
1670 .iter()
1671 .take(32)
1672 .map(|entry| fill_va_h264_pic(&entry.pic.borrow(), va_surface_id(&entry.reference)))
1673 .collect();
1674 pics.resize_with(32, build_invalid_va_h264_pic);
1675
1676 pics.try_into().unwrap_or_else(|_| unreachable!("resized to 32"))
1677}
1678
1679fn build_slice_param(
1681 hdr: &SliceHeader,
1682 slice_size: usize,
1683 list0: &[&DpbEntry<Handle>],
1684 list1: &[&DpbEntry<Handle>],
1685 sps: &Sps,
1686 pps: &Pps,
1687) -> BufferType {
1688 let pwt = &hdr.pred_weight_table;
1689
1690 let mut luma_weight_l0 = [0i16; 32];
1691 let mut luma_offset_l0 = [0i16; 32];
1692 let mut chroma_weight_l0 = [[0i16; 2]; 32];
1693 let mut chroma_offset_l0 = [[0i16; 2]; 32];
1694
1695 let mut luma_weight_l1 = [0i16; 32];
1696 let mut luma_offset_l1 = [0i16; 32];
1697 let mut chroma_weight_l1 = [[0i16; 2]; 32];
1698 let mut chroma_offset_l1 = [[0i16; 2]; 32];
1699
1700 let fill_l0 = (pps.weighted_pred_flag && (hdr.slice_type.is_p() || hdr.slice_type.is_sp()))
1703 || (pps.weighted_bipred_idc == 1 && hdr.slice_type.is_b());
1704 let fill_l1 = pps.weighted_bipred_idc == 1 && hdr.slice_type.is_b();
1705 let chroma = sps.chroma_array_type() != 0;
1706
1707 if fill_l0 {
1708 for i in 0..=usize::from(hdr.num_ref_idx_l0_active_minus1) {
1709 luma_weight_l0[i] = pwt.luma_weight_l0[i];
1710 luma_offset_l0[i] = i16::from(pwt.luma_offset_l0[i]);
1711 if chroma {
1712 for j in 0..2 {
1713 chroma_weight_l0[i][j] = pwt.chroma_weight_l0[i][j];
1714 chroma_offset_l0[i][j] = i16::from(pwt.chroma_offset_l0[i][j]);
1715 }
1716 }
1717 }
1718 }
1719
1720 if fill_l1 {
1721 for i in 0..=usize::from(hdr.num_ref_idx_l1_active_minus1) {
1722 luma_weight_l1[i] = pwt.luma_weight_l1[i];
1723 luma_offset_l1[i] = i16::from(pwt.luma_offset_l1[i]);
1724 if chroma {
1725 for j in 0..2 {
1726 chroma_weight_l1[i][j] = pwt.chroma_weight_l1[i][j];
1727 chroma_offset_l1[i][j] = i16::from(pwt.chroma_offset_l1[i][j]);
1728 }
1729 }
1730 }
1731 }
1732
1733 BufferType::SliceParameter(SliceParameter::H264(SliceParameterBufferH264::new(
1734 slice_size as u32,
1735 0,
1736 VA_SLICE_DATA_FLAG_ALL,
1737 hdr.header_bit_size as u16,
1738 hdr.first_mb_in_slice as u16,
1739 hdr.slice_type as u8,
1740 hdr.direct_spatial_mv_pred_flag as u8,
1741 hdr.num_ref_idx_l0_active_minus1,
1742 hdr.num_ref_idx_l1_active_minus1,
1743 hdr.cabac_init_idc,
1744 hdr.slice_qp_delta,
1745 hdr.disable_deblocking_filter_idc,
1746 hdr.slice_alpha_c0_offset_div2,
1747 hdr.slice_beta_offset_div2,
1748 fill_ref_pic_list(list0),
1749 fill_ref_pic_list(list1),
1750 pwt.luma_log2_weight_denom,
1751 pwt.chroma_log2_weight_denom,
1752 fill_l0 as u8,
1753 luma_weight_l0,
1754 luma_offset_l0,
1755 (fill_l0 && chroma) as u8,
1756 chroma_weight_l0,
1757 chroma_offset_l0,
1758 fill_l1 as u8,
1759 luma_weight_l1,
1760 luma_offset_l1,
1761 (fill_l1 && chroma) as u8,
1762 chroma_weight_l1,
1763 chroma_offset_l1,
1764 )))
1765}
1766
1767#[cfg(test)]
1768mod tests {
1769 use std::os::fd::OwnedFd;
1770 use std::process::Command;
1771
1772 use super::*;
1773
1774 fn progressive_sps() -> Sps {
1775 Sps {
1776 frame_mbs_only_flag: true,
1777 chroma_format_idc: 1,
1778 pic_width_in_mbs_minus1: 19,
1779 pic_height_in_map_units_minus1: 14,
1780 ..Default::default()
1781 }
1782 }
1783
1784 #[test]
1785 fn rejects_nonzero_crop_origins() {
1786 for (left, top) in [(1, 0), (0, 1)] {
1787 let sps = Sps {
1788 frame_cropping_flag: true,
1789 frame_crop_left_offset: left,
1790 frame_crop_top_offset: top,
1791 ..progressive_sps()
1792 };
1793 assert!(SequenceInfo::new(&sps)
1794 .unwrap_err()
1795 .to_string()
1796 .contains("left and top"));
1797 }
1798 }
1799
1800 #[test]
1801 fn crop_only_updates_change_sequence_identity() {
1802 let original = SequenceInfo::new(&progressive_sps()).unwrap();
1803 for (right, bottom, expected) in [(1, 0, (318, 240)), (0, 1, (320, 238))] {
1804 let cropped = SequenceInfo::new(&Sps {
1805 frame_cropping_flag: true,
1806 frame_crop_right_offset: right,
1807 frame_crop_bottom_offset: bottom,
1808 ..progressive_sps()
1809 })
1810 .unwrap();
1811 assert_eq!(original.coded, cropped.coded);
1812 assert_eq!(cropped.visible, expected);
1813 assert_ne!(original, cropped);
1814 }
1815 }
1816
1817 #[test]
1818 fn rejects_invalid_crop_dimensions() {
1819 for crop in [160, 161, u32::MAX] {
1820 assert!(SequenceInfo::new(&Sps {
1821 frame_cropping_flag: true,
1822 frame_crop_right_offset: crop,
1823 ..progressive_sps()
1824 })
1825 .is_err());
1826 }
1827 }
1828
1829 #[test]
1830 fn maximum_progressive_height_does_not_overflow_va_parameters() {
1831 let sps = Rc::new(Sps {
1832 pic_height_in_map_units_minus1: u16::MAX,
1833 ..progressive_sps()
1834 });
1835 let params = build_pic_param(
1836 &SliceHeader::default(),
1837 &PictureData::new_non_existing(0, 0),
1838 VA_INVALID_ID,
1839 &Dpb::default(),
1840 &sps,
1841 &crate::codec::h264::parser::PpsBuilder::new(Rc::clone(&sps)).build(),
1842 );
1843 let BufferType::PictureParameter(PictureParameter::H264(params)) = params else {
1844 panic!("expected H.264 picture parameters");
1845 };
1846 assert_eq!(params.inner().picture_height_in_mbs_minus1, u16::MAX);
1847 assert!(SequenceInfo::new(&Sps {
1848 frame_mbs_only_flag: false,
1849 ..progressive_sps()
1850 })
1851 .is_err());
1852 }
1853
1854 fn encoder_config(width: u32, height: u32) -> crate::encode::Config {
1857 crate::encode::Config::new(width, height, 30, 2_000_000, 30)
1858 }
1859
1860 fn nv12_frame(width: u32, height: u32, step: u32) -> Vec<u8> {
1863 let (w, h) = (width as usize, height as usize);
1864 let mut data = vec![128u8; w * h * 3 / 2];
1865 let x0 = (step * 16) as usize % (w / 2);
1866 for y in h / 4..h / 2 {
1867 for x in x0..x0 + w / 4 {
1868 data[y * w + x] = 235;
1869 }
1870 }
1871 data
1872 }
1873
1874 #[test]
1883 fn exported_pictures_do_not_share_a_surface() {
1884 let (width, height) = (320u32, 240u32);
1885 let Ok(mut encoder) = crate::encode::Encoder::new(encoder_config(width, height)) else {
1886 eprintln!("skipping: no VA-API H.264 encoder");
1887 return;
1888 };
1889 let Ok(mut decoder) = Decoder::new(Config::new()) else {
1890 eprintln!("skipping: no VA-API H.264 decoder");
1891 return;
1892 };
1893
1894 let mut exported = Vec::new();
1895 for step in 0..8 {
1896 let unit = encoder
1897 .encode_nv12(&nv12_frame(width, height, step), step == 0)
1898 .expect("encode a picture");
1899 exported.extend(decoder.decode_exported(&unit, step as u64).expect("decode a picture"));
1900 }
1901 exported.extend(decoder.flush_exported().expect("flush the decoder"));
1902 assert!(!exported.is_empty(), "the decoder produced no pictures");
1903
1904 let first = &exported[0];
1905 eprintln!(
1906 "exported {} pictures: {}x{} fourcc {:#x} modifier {:#x}",
1907 exported.len(),
1908 first.width,
1909 first.height,
1910 first.descriptor.fourcc,
1911 first.descriptor.objects[0].drm_format_modifier
1912 );
1913
1914 let mut seen = Vec::new();
1915 for (index, frame) in exported.iter().enumerate() {
1916 assert_eq!((frame.width, frame.height), (width, height));
1917 assert_eq!(frame.descriptor.fourcc, VA_FOURCC_NV12);
1918 assert_eq!(frame.descriptor.objects.len(), 1, "a composed export is one object");
1919 assert_eq!(frame.descriptor.layers[0].num_planes, 2, "NV12 is two planes");
1920
1921 let inode = inode(&frame.descriptor.objects[0].fd);
1925 assert!(
1926 !seen.contains(&inode),
1927 "picture {index} was decoded into an allocation an earlier one still holds"
1928 );
1929 seen.push(inode);
1930 }
1931 }
1932
1933 #[test]
1936 fn the_render_node_is_found_or_named() {
1937 if let Err(err) = Decoder::new(Config::new()) {
1938 eprintln!("skipping: no VA-API H.264 decoder: {err:#}");
1939 return;
1940 }
1941 let named = Config {
1942 device: Some(PathBuf::from("/dev/null")),
1943 };
1944 assert!(
1945 Decoder::new(named).is_err(),
1946 "a named node that is not a render node was swapped for one that is"
1947 );
1948 }
1949
1950 #[test]
1958 fn flush_returns_the_pictures_the_dpb_still_holds() {
1959 const PICTURES: u64 = 5;
1960 let (width, height) = (320u32, 240u32);
1961
1962 let Ok(mut encoder) = crate::encode::Encoder::new(encoder_config(width, height)) else {
1963 eprintln!("skipping: no VA-API H.264 encoder");
1964 return;
1965 };
1966 let Ok(mut decoder) = Decoder::new(Config::new()) else {
1967 eprintln!("skipping: no VA-API H.264 decoder");
1968 return;
1969 };
1970
1971 let mut streamed = Vec::new();
1972 for step in 0..PICTURES {
1973 let unit = encoder
1974 .encode_nv12(&nv12_frame(width, height, step as u32), step == 0)
1975 .expect("encode a picture");
1976 streamed.extend(decoder.decode(&unit, step).expect("decode a picture"));
1977 }
1978 assert!(
1979 (streamed.len() as u64) < PICTURES,
1980 "the DPB held nothing back, so this test proves nothing"
1981 );
1982
1983 let flushed = decoder.flush().expect("flush the decoder");
1984 let timestamps: Vec<u64> = streamed
1985 .iter()
1986 .chain(&flushed)
1987 .map(|picture| picture.timestamp)
1988 .collect();
1989 assert_eq!(
1990 timestamps,
1991 (0..PICTURES).collect::<Vec<_>>(),
1992 "the stream lost pictures at its end"
1993 );
1994
1995 assert!(decoder.flush().expect("flush an empty decoder").is_empty());
1998 }
1999
2000 #[test]
2008 fn a_dropped_export_is_decoded_into_again() {
2009 const PICTURES: u32 = 60;
2010 let (width, height) = (320u32, 240u32);
2011 let Ok(mut encoder) = crate::encode::Encoder::new(encoder_config(width, height)) else {
2012 eprintln!("skipping: no VA-API H.264 encoder");
2013 return;
2014 };
2015 let Ok(mut exporting) = Decoder::new(Config::new()) else {
2016 eprintln!("skipping: no VA-API H.264 decoder");
2017 return;
2018 };
2019 let mut downloading = Decoder::new(Config::new()).expect("a second decoder");
2020
2021 let mut compared = 0;
2022 for step in 0..PICTURES {
2023 let unit = encoder
2024 .encode_nv12(&nv12_frame(width, height, step), step == 0)
2025 .expect("encode a picture");
2026 let exported = exporting
2027 .decode_exported(&unit, step as u64)
2028 .expect("decode to a descriptor");
2029 let downloaded = downloading.decode(&unit, step as u64).expect("decode to client memory");
2030 assert_eq!(exported.len(), downloaded.len(), "the two decoders disagreed");
2031 for (gpu, cpu) in exported.iter().zip(&downloaded) {
2032 let read_back = gpu.download().expect("read the exported surface back");
2033 assert!(read_back.data == cpu.data, "picture {} came back wrong", gpu.timestamp);
2034 compared += 1;
2035 }
2036 }
2037 assert!(compared > 0, "the decoder produced no pictures");
2038
2039 let pool = &exporting.sequence.as_ref().expect("a sequence").pool;
2040 let allocated = pool.allocated.get();
2041 eprintln!("{allocated} surfaces allocated for {PICTURES} exported pictures");
2042 assert!(
2043 allocated <= pool.free.capacity,
2044 "{allocated} surfaces for {PICTURES} pictures; a full DPB and the picture in flight need {}",
2045 pool.free.capacity
2046 );
2047 }
2048
2049 #[test]
2052 fn the_pool_keeps_a_bounded_number_of_free_surfaces() {
2053 const HELD: u32 = 40;
2054 let (width, height) = (320u32, 240u32);
2055 let Ok(mut encoder) = crate::encode::Encoder::new(encoder_config(width, height)) else {
2056 eprintln!("skipping: no VA-API H.264 encoder");
2057 return;
2058 };
2059 let Ok(mut decoder) = Decoder::new(Config::new()) else {
2060 eprintln!("skipping: no VA-API H.264 decoder");
2061 return;
2062 };
2063
2064 let mut step = 0;
2065 let mut decode = |decoder: &mut Decoder| {
2066 let unit = encoder
2067 .encode_nv12(&nv12_frame(width, height, step), step == 0)
2068 .expect("encode a picture");
2069 step += 1;
2070 decoder.decode_exported(&unit, step as u64).expect("decode a picture")
2071 };
2072
2073 let mut held = Vec::new();
2074 for _ in 0..HELD {
2075 held.extend(decode(&mut decoder));
2076 }
2077 let pool = &decoder.sequence.as_ref().expect("a sequence").pool;
2078 let allocated = pool.allocated.get();
2079 assert!(
2080 allocated >= held.len(),
2081 "{} pictures held on {allocated} surfaces",
2082 held.len()
2083 );
2084
2085 drop(held);
2086 let free = pool.free.surfaces.lock().unwrap().len();
2087 assert!(
2088 free <= pool.free.capacity,
2089 "{free} surfaces left free, more than the capacity of {}",
2090 pool.free.capacity
2091 );
2092
2093 for _ in 0..10 {
2094 drop(decode(&mut decoder));
2095 }
2096 let pool = &decoder.sequence.as_ref().expect("a sequence").pool;
2097 assert_eq!(
2098 pool.allocated.get(),
2099 allocated,
2100 "pictures after the release did not reuse the free surfaces"
2101 );
2102 }
2103
2104 #[test]
2109 fn an_exported_frame_is_send_and_sync() {
2110 const fn assert_send_sync<T: Send + Sync>() {}
2111 assert_send_sync::<ExportedFrame>();
2112 }
2113
2114 #[test]
2123 fn an_exported_picture_downloads_to_the_same_pixels() {
2124 let (width, height) = (320u32, 240u32);
2125 let Ok(mut encoder) = crate::encode::Encoder::new(encoder_config(width, height)) else {
2126 eprintln!("skipping: no VA-API H.264 encoder");
2127 return;
2128 };
2129 let Ok(mut exporting) = Decoder::new(Config::new()) else {
2130 eprintln!("skipping: no VA-API H.264 decoder");
2131 return;
2132 };
2133 let mut downloading = Decoder::new(Config::new()).expect("a second decoder");
2134
2135 let mut exported = Vec::new();
2136 let mut downloaded = Vec::new();
2137 for step in 0..8 {
2138 let unit = encoder
2139 .encode_nv12(&nv12_frame(width, height, step), step == 0)
2140 .expect("encode a picture");
2141 exported.extend(
2142 exporting
2143 .decode_exported(&unit, step as u64)
2144 .expect("decode to a descriptor"),
2145 );
2146 downloaded.extend(downloading.decode(&unit, step as u64).expect("decode to client memory"));
2147 }
2148 exported.extend(exporting.flush_exported().expect("flush the exporting decoder"));
2149 downloaded.extend(downloading.flush().expect("flush the downloading decoder"));
2150
2151 assert!(!exported.is_empty(), "the decoder produced no pictures");
2152 assert_eq!(exported.len(), downloaded.len(), "the two decoders disagreed");
2153
2154 for (index, (gpu, cpu)) in exported.iter().zip(&downloaded).enumerate() {
2155 let read_back = gpu.download().expect("read the exported surface back");
2156 assert_eq!(read_back.timestamp, cpu.timestamp, "picture {index} lost its timestamp");
2157 assert_eq!((read_back.width, read_back.height), (cpu.width, cpu.height));
2158 assert!(
2159 read_back.data == cpu.data,
2160 "picture {index} read back differently than it downloaded"
2161 );
2162 }
2163 }
2164
2165 #[test]
2173 fn decoded_pictures_match_a_software_decoder() {
2174 const WIDTH: u32 = 320;
2175 const HEIGHT: u32 = 240;
2176 const PICTURES: usize = 30;
2177
2178 let Some(stream) = ffmpeg(&[
2179 "-f",
2180 "lavfi",
2181 "-i",
2182 &format!("testsrc2=size={WIDTH}x{HEIGHT}:rate=30"),
2183 "-frames:v",
2184 &PICTURES.to_string(),
2185 "-c:v",
2186 "libx264",
2187 "-profile:v",
2188 "main",
2189 "-bf",
2190 "2",
2191 "-refs",
2192 "3",
2193 "-g",
2194 "15",
2195 "-pix_fmt",
2196 "yuv420p",
2197 "-f",
2198 "h264",
2199 "-",
2200 ]) else {
2201 eprintln!("skipping: no ffmpeg with libx264");
2202 return;
2203 };
2204
2205 let path = std::env::temp_dir().join(format!("moq-vaapi-decode-{}.264", std::process::id()));
2208 std::fs::write(&path, &stream).expect("write the elementary stream");
2209 let reference = ffmpeg(&[
2210 "-i",
2211 path.to_str().expect("a UTF-8 temporary path"),
2212 "-pix_fmt",
2213 "nv12",
2214 "-f",
2215 "rawvideo",
2216 "-",
2217 ]);
2218 let _ = std::fs::remove_file(&path);
2219 let reference = reference.expect("ffmpeg decodes what it just encoded");
2220
2221 let picture_len = (WIDTH * HEIGHT * 3 / 2) as usize;
2222 assert_eq!(
2223 reference.len(),
2224 PICTURES * picture_len,
2225 "ffmpeg decoded a different count"
2226 );
2227
2228 let Ok(mut decoder) = Decoder::new(Config::new()) else {
2229 eprintln!("skipping: no VA-API H.264 decoder");
2230 return;
2231 };
2232
2233 let units = split_access_units(&stream);
2234 let mut decoded = Vec::new();
2235 for (index, unit) in units.iter().enumerate() {
2236 decoded.extend(decoder.decode(unit, index as u64).expect("decode an access unit"));
2237 }
2238 decoded.extend(decoder.flush().expect("flush the decoder"));
2239 assert_eq!(decoded.len(), PICTURES, "the decoder lost or invented pictures");
2240
2241 let mut timestamps: Vec<u64> = decoded.iter().map(|picture| picture.timestamp).collect();
2242 assert!(
2243 timestamps.windows(2).any(|pair| pair[0] > pair[1]),
2244 "the stream came out in decode order, so nothing here tests reordering"
2245 );
2246 timestamps.sort_unstable();
2247 assert_eq!(
2248 timestamps,
2249 (0..PICTURES as u64).collect::<Vec<_>>(),
2250 "a picture's timestamp did not survive reordering"
2251 );
2252
2253 for (index, picture) in decoded.iter().enumerate() {
2254 assert_eq!((picture.width, picture.height), (WIDTH, HEIGHT));
2255 assert!(
2256 picture.data == reference[index * picture_len..][..picture_len],
2257 "picture {index} of {PICTURES} differs from the software decoder"
2258 );
2259 }
2260 }
2261
2262 #[test]
2269 fn a_new_sequence_decodes_at_its_own_size() {
2270 const PER_SEQUENCE: u32 = 6;
2271 let sizes = [(320u32, 240u32), (192u32, 144u32)];
2272
2273 let Ok(mut decoder) = Decoder::new(Config::new()) else {
2274 eprintln!("skipping: no VA-API H.264 decoder");
2275 return;
2276 };
2277
2278 let mut decoded = Vec::new();
2279 let mut timestamp = 0u64;
2280 for &(width, height) in &sizes {
2281 let Ok(mut encoder) = crate::encode::Encoder::new(encoder_config(width, height)) else {
2282 eprintln!("skipping: no VA-API H.264 encoder");
2283 return;
2284 };
2285 for step in 0..PER_SEQUENCE {
2286 let unit = encoder
2287 .encode_nv12(&nv12_frame(width, height, step), step == 0)
2288 .expect("encode a picture");
2289 decoded.extend(decoder.decode(&unit, timestamp).expect("decode a picture"));
2290 timestamp += 1;
2291 }
2292 }
2293 decoded.extend(decoder.flush().expect("flush the decoder"));
2294
2295 let expected: Vec<(u32, u32)> = sizes
2296 .iter()
2297 .flat_map(|&size| std::iter::repeat_n(size, PER_SEQUENCE as usize))
2298 .collect();
2299 let actual: Vec<(u32, u32)> = decoded.iter().map(|picture| (picture.width, picture.height)).collect();
2300 assert_eq!(actual, expected, "a picture came out at the wrong size or out of order");
2301
2302 let timestamps: Vec<u64> = decoded.iter().map(|picture| picture.timestamp).collect();
2303 assert_eq!(timestamps, (0..timestamp).collect::<Vec<_>>());
2304
2305 for picture in &decoded {
2306 let expected_len = (picture.width * picture.height * 3 / 2) as usize;
2307 assert_eq!(
2308 picture.data.len(),
2309 expected_len,
2310 "a picture was read back at the wrong size"
2311 );
2312 }
2313 }
2314
2315 fn ffmpeg(args: &[&str]) -> Option<Vec<u8>> {
2318 let output = Command::new("ffmpeg").arg("-v").arg("error").args(args).output().ok()?;
2319 if !output.status.success() {
2320 eprintln!("ffmpeg {args:?} failed: {}", String::from_utf8_lossy(&output.stderr));
2321 return None;
2322 }
2323 Some(output.stdout)
2324 }
2325
2326 fn split_access_units(stream: &[u8]) -> Vec<Vec<u8>> {
2331 let starts: Vec<usize> = (0..stream.len().saturating_sub(3))
2332 .filter(|&i| stream[i..i + 3] == [0, 0, 1])
2333 .collect();
2334
2335 let mut units = Vec::new();
2336 let mut current: Vec<u8> = Vec::new();
2337 let mut current_has_picture = false;
2338
2339 for (index, &start) in starts.iter().enumerate() {
2340 let end = starts.get(index + 1).copied().unwrap_or(stream.len());
2341 let nal_type = stream[start + 3] & 0x1f;
2342 let is_slice = (1..=5).contains(&nal_type);
2343
2344 if current_has_picture && (is_slice || matches!(nal_type, 6..=9)) {
2345 units.push(std::mem::take(&mut current));
2346 current_has_picture = false;
2347 }
2348
2349 current.extend_from_slice(&stream[start..end]);
2350 current_has_picture |= is_slice;
2351 }
2352
2353 if !current.is_empty() {
2354 units.push(current);
2355 }
2356 units
2357 }
2358
2359 fn inode(fd: &OwnedFd) -> u64 {
2362 use std::os::unix::fs::MetadataExt as _;
2363
2364 let file = std::fs::File::from(fd.try_clone().expect("duplicate the descriptor"));
2367 file.metadata().expect("stat the descriptor").ino()
2368 }
2369}