// Copyright 2023 The ChromiumOS Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #[cfg(any(test, fuzzing))] mod dummy; #[cfg(feature = "v4l2")] mod v4l2; #[cfg(feature = "vaapi")] mod vaapi; use std::collections::btree_map::Entry; use std::io::Cursor; use std::os::fd::AsFd; use std::os::fd::BorrowedFd; use std::rc::Rc; use anyhow::anyhow; use anyhow::Context; use log::debug; use thiserror::Error; use crate::codec::h264::dpb::Dpb; use crate::codec::h264::dpb::DpbEntry; use crate::codec::h264::dpb::DpbPicRefList; use crate::codec::h264::dpb::MmcoError; use crate::codec::h264::dpb::ReferencePicLists; use crate::codec::h264::parser::MaxLongTermFrameIdx; use crate::codec::h264::parser::Nalu; use crate::codec::h264::parser::NaluType; use crate::codec::h264::parser::Parser; use crate::codec::h264::parser::Pps; use crate::codec::h264::parser::RefPicListModification; use crate::codec::h264::parser::Slice; use crate::codec::h264::parser::SliceHeader; use crate::codec::h264::parser::SliceType; use crate::codec::h264::parser::Sps; use crate::codec::h264::picture::Field; use crate::codec::h264::picture::FieldRank; use crate::codec::h264::picture::IsIdr; use crate::codec::h264::picture::PictureData; use crate::codec::h264::picture::RcPictureData; use crate::codec::h264::picture::Reference; use crate::decoder::stateless::DecodeError; use crate::decoder::stateless::DecodingState; use crate::decoder::stateless::NewPictureResult; use crate::decoder::stateless::StatelessBackendResult; use crate::decoder::stateless::StatelessCodec; use crate::decoder::stateless::StatelessDecoder; use crate::decoder::stateless::StatelessDecoderBackend; use crate::decoder::stateless::StatelessDecoderBackendPicture; use crate::decoder::stateless::StatelessVideoDecoder; use crate::decoder::BlockingMode; use crate::decoder::DecodedHandle; use crate::decoder::DecoderEvent; use crate::decoder::StreamInfo; use crate::Resolution; pub fn get_raster_from_zigzag_8x8(src: [u8; 64], dst: &mut [u8; 64]) { const ZIGZAG_8X8: [usize; 64] = [ 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, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63, ]; for i in 0..64 { dst[ZIGZAG_8X8[i]] = src[i]; } } pub fn get_raster_from_zigzag_4x4(src: [u8; 16], dst: &mut [u8; 16]) { const ZIGZAG_4X4: [usize; 16] = [0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15]; for i in 0..16 { dst[ZIGZAG_4X4[i]] = src[i]; } } /// Stateless backend methods specific to H.264. pub trait StatelessH264DecoderBackend: StatelessDecoderBackend + StatelessDecoderBackendPicture { /// Called when a new SPS is parsed. fn new_sequence(&mut self, sps: &Rc) -> StatelessBackendResult<()>; /// Called when the decoder determines that a frame or field was found. fn new_picture( &mut self, timestamp: u64, alloc_cb: &mut dyn FnMut() -> Option< <::Handle as DecodedHandle>::Frame, >, ) -> NewPictureResult; /// Called when the decoder determines that a second field was found. /// Indicates that the underlying BackendHandle is to be shared between the /// two pictures. This is so both fields decode to the same underlying /// resource and can thus be presented together as a single frame. fn new_field_picture( &mut self, timestamp: u64, first_field: &Self::Handle, ) -> NewPictureResult; /// Called by the decoder when starting a new frame or field. fn start_picture( &mut self, picture: &mut Self::Picture, picture_data: &PictureData, sps: &Sps, pps: &Pps, dpb: &Dpb, hdr: &SliceHeader, ) -> StatelessBackendResult<()>; /// Called to dispatch a decode operation to the backend. #[allow(clippy::too_many_arguments)] fn decode_slice( &mut self, picture: &mut Self::Picture, slice: &Slice, sps: &Sps, pps: &Pps, ref_pic_list0: &[&DpbEntry], ref_pic_list1: &[&DpbEntry], ) -> StatelessBackendResult<()>; /// Called when the decoder wants the backend to finish the decoding /// operations for `picture`. At this point, `decode_slice` has been called /// for all slices. /// /// This call will assign the ownership of the BackendHandle to the Picture /// and then assign the ownership of the Picture to the Handle. fn submit_picture(&mut self, picture: Self::Picture) -> StatelessBackendResult; } /// Keeps track of the last values seen for negotiation purposes. #[derive(Clone, Debug, Default, PartialEq, Eq)] struct NegotiationInfo { /// The current coded resolution coded_resolution: Resolution, /// Same meaning as the specification. profile_idc: u8, /// Same meaning as the specification. bit_depth_luma_minus8: u8, /// Same meaning as the specification. bit_depth_chroma_minus8: u8, /// Same meaning as the specification. chroma_format_idc: u8, /// The maximum size of the dpb in frames. max_dpb_frames: usize, /// Whether this is an interlaced stream interlaced: bool, } impl From<&Sps> for NegotiationInfo { fn from(sps: &Sps) -> Self { NegotiationInfo { coded_resolution: Resolution::from((sps.width(), sps.height())), profile_idc: sps.profile_idc, bit_depth_luma_minus8: sps.bit_depth_luma_minus8, bit_depth_chroma_minus8: sps.bit_depth_chroma_minus8, chroma_format_idc: sps.chroma_format_idc, max_dpb_frames: sps.max_dpb_frames(), interlaced: !sps.frame_mbs_only_flag, } } } #[derive(Copy, Clone, Debug)] enum RefPicList { RefPicList0, RefPicList1, } pub struct PrevReferencePicInfo { frame_num: u32, has_mmco_5: bool, top_field_order_cnt: i32, pic_order_cnt_msb: i32, pic_order_cnt_lsb: i32, field: Field, } impl Default for PrevReferencePicInfo { fn default() -> Self { Self { frame_num: Default::default(), has_mmco_5: Default::default(), top_field_order_cnt: Default::default(), pic_order_cnt_msb: Default::default(), pic_order_cnt_lsb: Default::default(), field: Field::Frame, } } } impl PrevReferencePicInfo { /// Store some variables related to the previous reference picture. These /// will be used in the decoding of future pictures. fn fill(&mut self, pic: &PictureData) { self.has_mmco_5 = pic.has_mmco_5; self.top_field_order_cnt = pic.top_field_order_cnt; self.pic_order_cnt_msb = pic.pic_order_cnt_msb; self.pic_order_cnt_lsb = pic.pic_order_cnt_lsb; self.field = pic.field; self.frame_num = pic.frame_num; } } #[derive(Default)] pub struct PrevPicInfo { frame_num: u32, frame_num_offset: u32, has_mmco_5: bool, } impl PrevPicInfo { /// Store some variables related to the previous picture. These will be used /// in the decoding of future pictures. fn fill(&mut self, pic: &PictureData) { self.frame_num = pic.frame_num; self.has_mmco_5 = pic.has_mmco_5; self.frame_num_offset = pic.frame_num_offset; } } /// Corresponds to RefPicList0 and RefPicList1 in the specification. Computed for every slice, /// points to the pictures in the DPB. struct RefPicLists<'a, T> { ref_pic_list0: DpbPicRefList<'a, T>, ref_pic_list1: DpbPicRefList<'a, T>, } /// Used to track that first_mb_in_slice increases monotonically. enum CurrentMacroblockTracking { SeparateColorPlane(std::collections::BTreeMap), NonSeparateColorPlane(u32), } /// State of the picture being currently decoded. /// /// Stored between calls to [`StatelessDecoder::handle_slice`] that belong to the same picture. struct CurrentPicState

{ /// Data for the current picture as extracted from the stream. pic: PictureData, /// PPS at the time of the current picture. pps: Rc, /// Backend-specific data for that picture. backend_pic: P, /// List of reference pictures, used once per slice. ref_pic_lists: ReferencePicLists, /// The current macroblock we are processing current_macroblock: CurrentMacroblockTracking, } /// State of the H.264 decoder. /// /// `B` is the backend used for this decoder. pub struct H264DecoderState { /// H.264 bitstream parser. parser: Parser, /// Keeps track of the last stream parameters seen for negotiation purposes. negotiation_info: NegotiationInfo, /// The decoded picture buffer. dpb: Dpb, /// Cached variables from the previous reference picture. prev_ref_pic_info: PrevReferencePicInfo, /// Cached variables from the previous picture. prev_pic_info: PrevPicInfo, /// Maximum index of the long-term frame. max_long_term_frame_idx: MaxLongTermFrameIdx, /// The picture currently being decoded. We need to preserve it between calls to `decode` /// because multiple slices will be processed in different calls to `decode`. current_pic: Option>, } impl Default for H264DecoderState where H: DecodedHandle, { fn default() -> Self { H264DecoderState { parser: Default::default(), negotiation_info: Default::default(), dpb: Default::default(), prev_ref_pic_info: Default::default(), prev_pic_info: Default::default(), max_long_term_frame_idx: Default::default(), current_pic: None, } } } /// [`StatelessCodec`] structure to use in order to create a H.264 stateless decoder. /// /// # Accepted input /// /// A decoder using this codec processes exactly one NAL unit of input per call to /// [`StatelessDecoder::decode`], and returns the number of bytes until the end of this NAL unit. /// This makes it possible to call [`Decode`](StatelessDecoder::decode) repeatedly on some unsplit /// Annex B stream and shrinking it by the number of bytes processed after each call, until the /// stream ends up being empty. pub struct H264; impl StatelessCodec for H264 { type FormatInfo = Rc; type DecoderState = H264DecoderState; } #[derive(Debug, Error)] enum FindFirstFieldError { #[error("expected complementary field {0:?}, got {1:?}")] ExpectedComplementaryField(Field, Field), #[error("the previous field's frame_num value {0} differs from the current one's {1}")] FrameNumDiffers(u32, u32), } impl H264DecoderState where H: DecodedHandle + Clone, { fn compute_pic_order_count(&mut self, pic: &mut PictureData, sps: &Sps) -> anyhow::Result<()> { match pic.pic_order_cnt_type { // Spec 8.2.1.1 0 => { let prev_pic_order_cnt_msb; let prev_pic_order_cnt_lsb; if matches!(pic.is_idr, IsIdr::Yes { .. }) { prev_pic_order_cnt_lsb = 0; prev_pic_order_cnt_msb = 0; } else if self.prev_ref_pic_info.has_mmco_5 { if !matches!(self.prev_ref_pic_info.field, Field::Bottom) { prev_pic_order_cnt_msb = 0; prev_pic_order_cnt_lsb = self.prev_ref_pic_info.top_field_order_cnt; } else { prev_pic_order_cnt_msb = 0; prev_pic_order_cnt_lsb = 0; } } else { prev_pic_order_cnt_msb = self.prev_ref_pic_info.pic_order_cnt_msb; prev_pic_order_cnt_lsb = self.prev_ref_pic_info.pic_order_cnt_lsb; } let max_pic_order_cnt_lsb = 1 << (sps.log2_max_pic_order_cnt_lsb_minus4 + 4); pic.pic_order_cnt_msb = if (pic.pic_order_cnt_lsb < self.prev_ref_pic_info.pic_order_cnt_lsb) && (prev_pic_order_cnt_lsb - pic.pic_order_cnt_lsb >= max_pic_order_cnt_lsb / 2) { prev_pic_order_cnt_msb + max_pic_order_cnt_lsb } else if (pic.pic_order_cnt_lsb > prev_pic_order_cnt_lsb) && (pic.pic_order_cnt_lsb - prev_pic_order_cnt_lsb > max_pic_order_cnt_lsb / 2) { prev_pic_order_cnt_msb - max_pic_order_cnt_lsb } else { prev_pic_order_cnt_msb }; if !matches!(pic.field, Field::Bottom) { pic.top_field_order_cnt = pic.pic_order_cnt_msb + pic.pic_order_cnt_lsb; } if !matches!(pic.field, Field::Top) { if matches!(pic.field, Field::Frame) { pic.bottom_field_order_cnt = pic.top_field_order_cnt + pic.delta_pic_order_cnt_bottom; } else { pic.bottom_field_order_cnt = pic.pic_order_cnt_msb + pic.pic_order_cnt_lsb; } } } 1 => { if self.prev_pic_info.has_mmco_5 { self.prev_pic_info.frame_num_offset = 0; } if matches!(pic.is_idr, IsIdr::Yes { .. }) { pic.frame_num_offset = 0; } else if self.prev_pic_info.frame_num > pic.frame_num { pic.frame_num_offset = self.prev_pic_info.frame_num_offset + sps.max_frame_num(); } else { pic.frame_num_offset = self.prev_pic_info.frame_num_offset; } let mut abs_frame_num = if sps.num_ref_frames_in_pic_order_cnt_cycle != 0 { pic.frame_num_offset + pic.frame_num } else { 0 }; if pic.nal_ref_idc == 0 && abs_frame_num > 0 { abs_frame_num -= 1; } let mut expected_pic_order_cnt = 0; if abs_frame_num > 0 { if sps.num_ref_frames_in_pic_order_cnt_cycle == 0 { return Err(anyhow!("Invalid num_ref_frames_in_pic_order_cnt_cycle")); } let pic_order_cnt_cycle_cnt = (abs_frame_num - 1) / sps.num_ref_frames_in_pic_order_cnt_cycle as u32; let frame_num_in_pic_order_cnt_cycle = (abs_frame_num - 1) % sps.num_ref_frames_in_pic_order_cnt_cycle as u32; expected_pic_order_cnt = pic_order_cnt_cycle_cnt as i32 * sps.expected_delta_per_pic_order_cnt_cycle; assert!(frame_num_in_pic_order_cnt_cycle < 255); for i in 0..sps.num_ref_frames_in_pic_order_cnt_cycle { expected_pic_order_cnt += sps.offset_for_ref_frame[i as usize]; } } if pic.nal_ref_idc == 0 { expected_pic_order_cnt += sps.offset_for_non_ref_pic; } if matches!(pic.field, Field::Frame) { pic.top_field_order_cnt = expected_pic_order_cnt + pic.delta_pic_order_cnt0; pic.bottom_field_order_cnt = pic.top_field_order_cnt + sps.offset_for_top_to_bottom_field + pic.delta_pic_order_cnt1; } else if !matches!(pic.field, Field::Bottom) { pic.top_field_order_cnt = expected_pic_order_cnt + pic.delta_pic_order_cnt0; } else { pic.bottom_field_order_cnt = expected_pic_order_cnt + sps.offset_for_top_to_bottom_field + pic.delta_pic_order_cnt0; } } 2 => { // Spec 8.2.1.3 if self.prev_pic_info.has_mmco_5 { self.prev_pic_info.frame_num_offset = 0; } if matches!(pic.is_idr, IsIdr::Yes { .. }) { pic.frame_num_offset = 0; } else if self.prev_pic_info.frame_num > pic.frame_num { pic.frame_num_offset = self.prev_pic_info.frame_num_offset + sps.max_frame_num(); } else { pic.frame_num_offset = self.prev_pic_info.frame_num_offset; } let pic_order_cnt = if matches!(pic.is_idr, IsIdr::Yes { .. }) { 0 } else if pic.nal_ref_idc == 0 { 2 * (pic.frame_num_offset + pic.frame_num) as i32 - 1 } else { 2 * (pic.frame_num_offset + pic.frame_num) as i32 }; if matches!(pic.field, Field::Frame | Field::Top) { pic.top_field_order_cnt = pic_order_cnt; } if matches!(pic.field, Field::Frame | Field::Bottom) { pic.bottom_field_order_cnt = pic_order_cnt; } } _ => return Err(anyhow!("Invalid pic_order_cnt_type: {}", sps.pic_order_cnt_type)), } match pic.field { Field::Frame => { pic.pic_order_cnt = std::cmp::min(pic.top_field_order_cnt, pic.bottom_field_order_cnt); } Field::Top => { pic.pic_order_cnt = pic.top_field_order_cnt; } Field::Bottom => { pic.pic_order_cnt = pic.bottom_field_order_cnt; } } Ok(()) } /// Returns an iterator of the handles of the frames that need to be bumped into the ready /// queue. fn bump_as_needed(&mut self, current_pic: &PictureData) -> impl Iterator { self.dpb.bump_as_needed(current_pic).into_iter().flatten() } /// Returns an iterator of the handles of all the frames still present in the DPB. fn drain(&mut self) -> impl Iterator { let pics = self.dpb.drain(); pics.into_iter().flatten() } /// Find the first field for the picture started by `slice`, if any. fn find_first_field( &self, hdr: &SliceHeader, ) -> Result, FindFirstFieldError> { let mut prev_field = None; if self.dpb.interlaced() { if let Some(last_dpb_entry) = self.dpb.entries().last() { // Use the last entry in the DPB let last_pic = last_dpb_entry.pic.borrow(); // If the picture is interlaced but doesn't have its other field set yet, // then it must be the first field. if !matches!(last_pic.field, Field::Frame) && matches!(last_pic.field_rank(), FieldRank::Single) { if let Some(handle) = &last_dpb_entry.reference { // Still waiting for the second field prev_field = Some((last_dpb_entry.pic.clone(), handle.clone())); } } } } let prev_field = match prev_field { None => return Ok(None), Some(prev_field) => prev_field, }; let prev_field_pic = prev_field.0.borrow(); if prev_field_pic.frame_num != u32::from(hdr.frame_num) { return Err(FindFirstFieldError::FrameNumDiffers( prev_field_pic.frame_num, hdr.frame_num as u32, )); } let cur_field = if hdr.bottom_field_flag { Field::Bottom } else { Field::Top }; if !hdr.field_pic_flag || cur_field == prev_field_pic.field { let field = prev_field_pic.field; return Err(FindFirstFieldError::ExpectedComplementaryField(field.opposite(), field)); } drop(prev_field_pic); Ok(Some(prev_field)) } // 8.2.4.3.1 Modification process of reference picture lists for short-term // reference pictures #[allow(clippy::too_many_arguments)] fn short_term_pic_list_modification<'a>( cur_pic: &PictureData, dpb: &'a Dpb, ref_pic_list_x: &mut DpbPicRefList<'a, H>, num_ref_idx_lx_active_minus1: u8, max_pic_num: i32, rplm: &RefPicListModification, pic_num_lx_pred: &mut i32, ref_idx_lx: &mut usize, ) -> anyhow::Result<()> { let pic_num_lx_no_wrap; let abs_diff_pic_num = rplm.abs_diff_pic_num_minus1 as i32 + 1; let modification_of_pic_nums_idc = rplm.modification_of_pic_nums_idc; if modification_of_pic_nums_idc == 0 { if *pic_num_lx_pred - abs_diff_pic_num < 0 { pic_num_lx_no_wrap = *pic_num_lx_pred - abs_diff_pic_num + max_pic_num; } else { pic_num_lx_no_wrap = *pic_num_lx_pred - abs_diff_pic_num; } } else if modification_of_pic_nums_idc == 1 { if *pic_num_lx_pred + abs_diff_pic_num >= max_pic_num { pic_num_lx_no_wrap = *pic_num_lx_pred + abs_diff_pic_num - max_pic_num; } else { pic_num_lx_no_wrap = *pic_num_lx_pred + abs_diff_pic_num; } } else { anyhow::bail!( "unexpected value for modification_of_pic_nums_idc {:?}", rplm.modification_of_pic_nums_idc ); } *pic_num_lx_pred = pic_num_lx_no_wrap; let pic_num_lx = if pic_num_lx_no_wrap > cur_pic.pic_num { pic_num_lx_no_wrap - max_pic_num } else { pic_num_lx_no_wrap }; let handle = dpb .find_short_term_with_pic_num(pic_num_lx) .with_context(|| format!("No ShortTerm reference found with pic_num {}", pic_num_lx))?; if *ref_idx_lx >= ref_pic_list_x.len() { anyhow::bail!("invalid ref_idx_lx index"); } ref_pic_list_x.insert(*ref_idx_lx, handle); *ref_idx_lx += 1; let mut nidx = *ref_idx_lx; for cidx in *ref_idx_lx..=usize::from(num_ref_idx_lx_active_minus1) + 1 { if cidx == ref_pic_list_x.len() { break; } let target = &ref_pic_list_x[cidx].pic; if target.borrow().pic_num_f(max_pic_num) != pic_num_lx { ref_pic_list_x[nidx] = ref_pic_list_x[cidx]; nidx += 1; } } while ref_pic_list_x.len() > (usize::from(num_ref_idx_lx_active_minus1) + 1) { ref_pic_list_x.pop(); } Ok(()) } fn long_term_pic_list_modification<'a>( dpb: &'a Dpb, ref_pic_list_x: &mut DpbPicRefList<'a, H>, num_ref_idx_lx_active_minus1: u8, max_long_term_frame_idx: MaxLongTermFrameIdx, rplm: &RefPicListModification, ref_idx_lx: &mut usize, ) -> anyhow::Result<()> { let long_term_pic_num = rplm.long_term_pic_num; let handle = dpb.find_long_term_with_long_term_pic_num(long_term_pic_num).with_context(|| { format!("No LongTerm reference found with long_term_pic_num {}", long_term_pic_num) })?; if *ref_idx_lx >= ref_pic_list_x.len() { anyhow::bail!("invalid ref_idx_lx index"); } ref_pic_list_x.insert(*ref_idx_lx, handle); *ref_idx_lx += 1; let mut nidx = *ref_idx_lx; for cidx in *ref_idx_lx..=usize::from(num_ref_idx_lx_active_minus1) + 1 { if cidx == ref_pic_list_x.len() { break; } let target = &ref_pic_list_x[cidx].pic; if target.borrow().long_term_pic_num_f(max_long_term_frame_idx) != long_term_pic_num { ref_pic_list_x[nidx] = ref_pic_list_x[cidx]; nidx += 1; } } while ref_pic_list_x.len() > (usize::from(num_ref_idx_lx_active_minus1) + 1) { ref_pic_list_x.pop(); } Ok(()) } fn modify_ref_pic_list( &self, cur_pic: &PictureData, hdr: &SliceHeader, ref_pic_list_type: RefPicList, ref_pic_list_indices: &[usize], ) -> anyhow::Result> { let (ref_pic_list_modification_flag_lx, num_ref_idx_lx_active_minus1, rplm) = match ref_pic_list_type { RefPicList::RefPicList0 => ( hdr.ref_pic_list_modification_flag_l0, hdr.num_ref_idx_l0_active_minus1, &hdr.ref_pic_list_modification_l0, ), RefPicList::RefPicList1 => ( hdr.ref_pic_list_modification_flag_l1, hdr.num_ref_idx_l1_active_minus1, &hdr.ref_pic_list_modification_l1, ), }; let mut ref_pic_list: Vec<_> = ref_pic_list_indices .iter() .map(|&i| &self.dpb.entries()[i]) .take(usize::from(num_ref_idx_lx_active_minus1) + 1) .collect(); if !ref_pic_list_modification_flag_lx { return Ok(ref_pic_list); } let mut pic_num_lx_pred = cur_pic.pic_num; let mut ref_idx_lx = 0; for modification in rplm { let idc = modification.modification_of_pic_nums_idc; match idc { 0 | 1 => { Self::short_term_pic_list_modification( cur_pic, &self.dpb, &mut ref_pic_list, num_ref_idx_lx_active_minus1, hdr.max_pic_num as i32, modification, &mut pic_num_lx_pred, &mut ref_idx_lx, )?; } 2 => Self::long_term_pic_list_modification( &self.dpb, &mut ref_pic_list, num_ref_idx_lx_active_minus1, self.max_long_term_frame_idx, modification, &mut ref_idx_lx, )?, 3 => break, _ => anyhow::bail!("unexpected modification_of_pic_nums_idc {:?}", idc), } } Ok(ref_pic_list) } /// Generate RefPicList0 and RefPicList1 in the specification. Computed for every slice, points /// to the pictures in the DPB. fn create_ref_pic_lists( &mut self, cur_pic: &PictureData, hdr: &SliceHeader, ref_pic_lists: &ReferencePicLists, ) -> anyhow::Result> { let ref_pic_list0 = match hdr.slice_type { SliceType::P | SliceType::Sp => self.modify_ref_pic_list( cur_pic, hdr, RefPicList::RefPicList0, &ref_pic_lists.ref_pic_list_p0, )?, SliceType::B => self.modify_ref_pic_list( cur_pic, hdr, RefPicList::RefPicList0, &ref_pic_lists.ref_pic_list_b0, )?, _ => Vec::new(), }; let ref_pic_list1 = match hdr.slice_type { SliceType::B => self.modify_ref_pic_list( cur_pic, hdr, RefPicList::RefPicList1, &ref_pic_lists.ref_pic_list_b1, )?, _ => Vec::new(), }; Ok(RefPicLists { ref_pic_list0, ref_pic_list1 }) } fn handle_memory_management_ops(&mut self, pic: &mut PictureData) -> Result<(), MmcoError> { let markings = pic.ref_pic_marking.clone(); for marking in &markings.inner { match marking.memory_management_control_operation { 0 => break, 1 => self.dpb.mmco_op_1(pic, marking)?, 2 => self.dpb.mmco_op_2(pic, marking)?, 3 => self.dpb.mmco_op_3(pic, marking)?, 4 => self.max_long_term_frame_idx = self.dpb.mmco_op_4(marking), 5 => self.max_long_term_frame_idx = self.dpb.mmco_op_5(pic), 6 => self.dpb.mmco_op_6(pic, marking), other => return Err(MmcoError::UnknownMmco(other)), } } Ok(()) } fn reference_pic_marking(&mut self, pic: &mut PictureData, sps: &Sps) -> anyhow::Result<()> { /* 8.2.5.1 */ if matches!(pic.is_idr, IsIdr::Yes { .. }) { self.dpb.mark_all_as_unused_for_ref(); if pic.ref_pic_marking.long_term_reference_flag { pic.set_reference(Reference::LongTerm, false); pic.long_term_frame_idx = 0; self.max_long_term_frame_idx = MaxLongTermFrameIdx::Idx(0); } else { pic.set_reference(Reference::ShortTerm, false); self.max_long_term_frame_idx = MaxLongTermFrameIdx::NoLongTermFrameIndices; } return Ok(()); } if pic.ref_pic_marking.adaptive_ref_pic_marking_mode_flag { self.handle_memory_management_ops(pic)?; } else { self.dpb.sliding_window_marking(pic, sps); } Ok(()) } // Apply the parameters of `sps` to the decoding state. fn apply_sps(&mut self, sps: &Sps) { self.negotiation_info = NegotiationInfo::from(sps); let max_dpb_frames = sps.max_dpb_frames(); let interlaced = !sps.frame_mbs_only_flag; let max_num_order_frames = sps.max_num_order_frames() as usize; let max_num_reorder_frames = if max_num_order_frames > max_dpb_frames { 0 } else { max_num_order_frames }; self.dpb.set_limits(max_dpb_frames, max_num_reorder_frames); self.dpb.set_interlaced(interlaced); } } impl StatelessDecoder where B: StatelessH264DecoderBackend, B::Handle: Clone, { fn negotiation_possible(sps: &Sps, old_negotiation_info: &NegotiationInfo) -> bool { let negotiation_info = NegotiationInfo::from(sps); *old_negotiation_info != negotiation_info } fn renegotiate_if_needed(&mut self, sps: &Rc) -> anyhow::Result<()> { if Self::negotiation_possible(sps, &self.codec.negotiation_info) { // Make sure all the frames we decoded so far are in the ready queue. self.drain()?; self.backend.new_sequence(sps)?; self.await_format_change(sps.clone()); } Ok(()) } // Apply the parameters of `sps` to the decoder. fn apply_sps(&mut self, sps: &Sps) { self.codec.apply_sps(sps); self.coded_resolution = Resolution::from((sps.width(), sps.height())); } fn drain(&mut self) -> anyhow::Result<()> { // Finish the current picture if there is one pending. if let Some(cur_pic) = self.codec.current_pic.take() { self.finish_picture(cur_pic)?; } self.ready_queue.extend(self.codec.drain()); Ok(()) } /// Adds picture to the ready queue if it could not be added to the DPB. fn add_to_ready_queue(&mut self, pic: PictureData, handle: B::Handle) { if matches!(pic.field, Field::Frame) { self.ready_queue.push(handle); } else if let FieldRank::Second(..) = pic.field_rank() { self.ready_queue.push(handle) } } fn finish_picture(&mut self, pic: CurrentPicState) -> anyhow::Result<()> { debug!("Finishing picture POC {:?}", pic.pic.pic_order_cnt); // Submit the picture to the backend. let handle = self.submit_picture(pic.backend_pic)?; let pps = pic.pps; let mut pic = pic.pic; if matches!(pic.reference(), Reference::ShortTerm | Reference::LongTerm) { self.codec.reference_pic_marking(&mut pic, &pps.sps)?; self.codec.prev_ref_pic_info.fill(&pic); } self.codec.prev_pic_info.fill(&pic); if pic.has_mmco_5 { // C.4.5.3 "Bumping process" // The bumping process is invoked in the following cases: // Clause 3: // The current picture has memory_management_control_operation equal // to 5, as specified in clause C.4.4. self.drain()?; } // Bump the DPB as per C.4.5.3 to cover clauses 1, 4, 5 and 6. self.ready_queue.extend(self.codec.bump_as_needed(&pic)); // C.4.5.1, C.4.5.2 // If the current decoded picture is the second field of a complementary // reference field pair, add to DPB. // C.4.5.1 // For a reference decoded picture, the "bumping" process is invoked // repeatedly until there is an empty frame buffer, by which point it is // added to the DPB. Notice that Dpb::needs_bumping already accounts for // this. // C.4.5.2 // For a non-reference decoded picture, if there is empty frame buffer // after bumping the smaller POC, add to DPB. Otherwise, add it to the // ready queue. if pic.is_second_field_of_complementary_ref_pair() || pic.is_ref() || self.codec.dpb.has_empty_frame_buffer() { if self.codec.dpb.interlaced() && matches!(pic.field, Field::Frame) { // Split the Frame into two complementary fields so reference // marking is easier. This is inspired by the GStreamer implementation. let (first_field, second_field) = PictureData::split_frame(pic); self.codec.dpb.store_picture(first_field, Some(handle.clone()))?; self.codec.dpb.store_picture(second_field, Some(handle))?; } else { self.codec.dpb.store_picture(pic.into_rc(), Some(handle))?; } } else { self.add_to_ready_queue(pic, handle); } Ok(()) } fn handle_frame_num_gap( &mut self, sps: &Sps, frame_num: u32, timestamp: u64, ) -> anyhow::Result<()> { if self.codec.dpb.is_empty() { return Ok(()); } debug!("frame_num gap detected."); if !sps.gaps_in_frame_num_value_allowed_flag { return Err(anyhow!( "Invalid frame_num: {}. Assuming unintentional loss of pictures", frame_num )); } let mut unused_short_term_frame_num = (self.codec.prev_ref_pic_info.frame_num + 1) % sps.max_frame_num(); while unused_short_term_frame_num != frame_num { let max_frame_num = sps.max_frame_num(); let mut pic = PictureData::new_non_existing(unused_short_term_frame_num, timestamp); self.codec.compute_pic_order_count(&mut pic, sps)?; self.codec.dpb.update_pic_nums(unused_short_term_frame_num, max_frame_num, &pic); self.codec.dpb.sliding_window_marking(&mut pic, sps); self.ready_queue.extend(self.codec.bump_as_needed(&pic)); if self.codec.dpb.interlaced() { let (first_field, second_field) = PictureData::split_frame(pic); self.codec.dpb.store_picture(first_field, None)?; self.codec.dpb.store_picture(second_field, None)?; } else { self.codec.dpb.store_picture(pic.into_rc(), None)?; } unused_short_term_frame_num += 1; unused_short_term_frame_num %= max_frame_num; } Ok(()) } /// Init the current picture being decoded. fn init_current_pic( &mut self, slice: &Slice, sps: &Sps, first_field: Option<&RcPictureData>, timestamp: u64, ) -> anyhow::Result { let mut pic = PictureData::new_from_slice(slice, sps, timestamp, first_field); self.codec.compute_pic_order_count(&mut pic, sps)?; if matches!(pic.is_idr, IsIdr::Yes { .. }) { // C.4.5.3 "Bumping process" // The bumping process is invoked in the following cases: // Clause 2: // The current picture is an IDR picture and // no_output_of_prior_pics_flag is not equal to 1 and is not // inferred to be equal to 1, as specified in clause C.4.4. if !pic.ref_pic_marking.no_output_of_prior_pics_flag { self.drain()?; } else { // C.4.4 When no_output_of_prior_pics_flag is equal to 1 or is // inferred to be equal to 1, all frame buffers in the DPB are // emptied without output of the pictures they contain, and DPB // fullness is set to 0. self.codec.dpb.clear(); } } self.codec.dpb.update_pic_nums( u32::from(slice.header.frame_num), sps.max_frame_num(), &pic, ); Ok(pic) } /// Called once per picture to start it. fn begin_picture( &mut self, timestamp: u64, slice: &Slice, alloc_cb: &mut dyn FnMut() -> Option< <::Handle as DecodedHandle>::Frame, >, ) -> Result, DecodeError> { let hdr = &slice.header; let pps = Rc::clone( self.codec .parser .get_pps(hdr.pic_parameter_set_id) .context("Invalid PPS in handle_picture")?, ); // A picture's SPS may require negociation. self.renegotiate_if_needed(&pps.sps)?; if let DecodingState::AwaitingFormat(_) = &self.decoding_state { return Err(DecodeError::CheckEvents); } // Start by securing the backend picture before modifying our state. let first_field = self.codec.find_first_field(&slice.header).context("while looking for first field")?; let mut backend_pic = if let Some(first_field) = &first_field { self.backend.new_field_picture(timestamp, &first_field.1) } else { self.backend.new_picture(timestamp, alloc_cb) }?; let nalu_hdr = &slice.nalu.header; if nalu_hdr.idr_pic_flag { self.codec.prev_ref_pic_info.frame_num = 0; } let frame_num = u32::from(hdr.frame_num); let current_macroblock = match pps.sps.separate_colour_plane_flag { true => CurrentMacroblockTracking::SeparateColorPlane(Default::default()), false => CurrentMacroblockTracking::NonSeparateColorPlane(0), }; if frame_num != self.codec.prev_ref_pic_info.frame_num && frame_num != (self.codec.prev_ref_pic_info.frame_num + 1) % pps.sps.max_frame_num() { self.handle_frame_num_gap(&pps.sps, frame_num, timestamp)?; } let pic = self.init_current_pic(slice, &pps.sps, first_field.as_ref().map(|f| &f.0), timestamp)?; let ref_pic_lists = self.codec.dpb.build_ref_pic_lists(&pic); debug!("Decode picture POC {:?}", pic.pic_order_cnt); self.backend.start_picture( &mut backend_pic, &pic, pps.sps.as_ref(), pps.as_ref(), &self.codec.dpb, &slice.header, )?; Ok(CurrentPicState { pic, pps, backend_pic, ref_pic_lists, current_macroblock }) } // Check whether first_mb_in_slice increases monotonically for the current // picture as required by the specification. fn check_first_mb_in_slice( &mut self, current_macroblock: &mut CurrentMacroblockTracking, slice: &Slice, ) { match current_macroblock { CurrentMacroblockTracking::SeparateColorPlane(current_macroblock) => { match current_macroblock.entry(slice.header.colour_plane_id) { Entry::Vacant(current_macroblock) => { current_macroblock.insert(slice.header.first_mb_in_slice); } Entry::Occupied(mut current_macroblock) => { let current_macroblock = current_macroblock.get_mut(); if slice.header.first_mb_in_slice >= *current_macroblock { log::trace!("first_mb_in_slice does not increase monotically, expect corrupted output"); } *current_macroblock = slice.header.first_mb_in_slice; } } } CurrentMacroblockTracking::NonSeparateColorPlane(current_macroblock) => { if slice.header.first_mb_in_slice >= *current_macroblock { log::trace!( "first_mb_in_slice does not increase monotically, expect corrupted output" ); } *current_macroblock = slice.header.first_mb_in_slice; } } } /// Handle a slice. Called once per slice NALU. fn handle_slice( &mut self, cur_pic: &mut CurrentPicState, slice: &Slice, ) -> anyhow::Result<()> { self.check_first_mb_in_slice(&mut cur_pic.current_macroblock, slice); // A slice can technically refer to another PPS. let pps = self.codec.parser.get_pps(slice.header.pic_parameter_set_id).context("Invalid PPS")?; cur_pic.pps = Rc::clone(pps); // Make sure that no negotiation is possible mid-picture. How could it? // We'd lose the context with the previous slices on it. if Self::negotiation_possible(&cur_pic.pps.sps, &self.codec.negotiation_info) { anyhow::bail!("invalid stream: inter-frame renegotiation requested"); } let RefPicLists { ref_pic_list0, ref_pic_list1 } = self.codec.create_ref_pic_lists(&cur_pic.pic, &slice.header, &cur_pic.ref_pic_lists)?; self.backend.decode_slice( &mut cur_pic.backend_pic, slice, cur_pic.pps.sps.as_ref(), cur_pic.pps.as_ref(), &ref_pic_list0, &ref_pic_list1, )?; Ok(()) } /// Submits the picture to the accelerator. fn submit_picture(&mut self, backend_pic: B::Picture) -> Result { let handle = self.backend.submit_picture(backend_pic)?; if self.blocking_mode == BlockingMode::Blocking { handle.sync()?; } Ok(handle) } fn process_nalu( &mut self, timestamp: u64, nalu: Nalu, alloc_cb: &mut dyn FnMut() -> Option< <::Handle as DecodedHandle>::Frame, >, ) -> Result<(), DecodeError> { match nalu.header.type_ { NaluType::Sps => { self.codec .parser .parse_sps(&nalu) .map_err(|err| DecodeError::ParseFrameError(err))?; } NaluType::Pps => { self.codec .parser .parse_pps(&nalu) .map_err(|err| DecodeError::ParseFrameError(err))?; } NaluType::Slice | NaluType::SliceDpa | NaluType::SliceDpb | NaluType::SliceDpc | NaluType::SliceIdr | NaluType::SliceExt => { let slice = self .codec .parser .parse_slice_header(nalu) .map_err(|err| DecodeError::ParseFrameError(err))?; let mut cur_pic = match self.codec.current_pic.take() { // No current picture, start a new one. None => self.begin_picture(timestamp, &slice, alloc_cb)?, // We have a current picture but are starting a new field, or first_mb_in_slice // indicates that a new picture is starting: finish the current picture and // start a new one. Some(cur_pic) if (self.codec.dpb.interlaced() && matches!(cur_pic.pic.field, Field::Frame) && !cur_pic.pic.is_second_field() && cur_pic.pic.field != slice.header.field()) || (slice.header.first_mb_in_slice == 0) => { self.finish_picture(cur_pic)?; self.begin_picture(timestamp, &slice, alloc_cb)? } // This slice is part of the current picture. Some(cur_pic) => cur_pic, }; self.handle_slice(&mut cur_pic, &slice)?; self.codec.current_pic = Some(cur_pic); } other => { debug!("Unsupported NAL unit type {:?}", other,); } } Ok(()) } } impl StatelessVideoDecoder for StatelessDecoder where B: StatelessH264DecoderBackend, B::Handle: Clone + 'static, { type Handle = B::Handle; fn decode( &mut self, timestamp: u64, bitstream: &[u8], alloc_cb: &mut dyn FnMut() -> Option< <::Handle as DecodedHandle>::Frame, >, ) -> Result { self.wait_for_drc_flush()?; let mut cursor = Cursor::new(bitstream); let nalu = Nalu::next(&mut cursor).map_err(|err| DecodeError::ParseFrameError(err))?; if nalu.header.type_ == NaluType::Sps { let sps = self .codec .parser .parse_sps(&nalu) .map_err(|err| DecodeError::ParseFrameError(err))? .clone(); if Self::negotiation_possible(&sps, &self.codec.negotiation_info) && matches!(self.decoding_state, DecodingState::Decoding) { // DRC occurs when an SPS packet is received that indicates an IDR, // the format is different, and the decoder is already decoding frames. self.flush()?; self.decoding_state = DecodingState::FlushingForDRC; // Start signaling the awaiting format event to process a format change. self.awaiting_format_event.write(1).unwrap(); return Err(DecodeError::CheckEvents); } if matches!(self.decoding_state, DecodingState::AwaitingStreamInfo) { // If more SPS come along we will renegotiate in begin_picture(). self.renegotiate_if_needed(&sps)?; } else if matches!(self.decoding_state, DecodingState::Reset) { // We can resume decoding since the decoding parameters have not changed. self.decoding_state = DecodingState::Decoding; } } else if matches!(self.decoding_state, DecodingState::Reset) { let mut cursor = Cursor::new(bitstream); while let Ok(nalu) = Nalu::next(&mut cursor) { // In the Reset state we can resume decoding from any key frame. if matches!(nalu.header.type_, NaluType::SliceIdr) { self.decoding_state = DecodingState::Decoding; break; } } } let nalu_len = nalu.offset + nalu.size; match &mut self.decoding_state { // Process parameter sets, but skip input until we get information // from the stream. DecodingState::AwaitingStreamInfo | DecodingState::Reset => { if matches!(nalu.header.type_, NaluType::Pps) { self.process_nalu(timestamp, nalu, alloc_cb)?; } } // Ask the client to confirm the format before we can process this. DecodingState::FlushingForDRC | DecodingState::AwaitingFormat(_) => { return Err(DecodeError::CheckEvents) } DecodingState::Decoding => { self.process_nalu(timestamp, nalu, alloc_cb)?; } } Ok(nalu_len) } fn flush(&mut self) -> Result<(), DecodeError> { self.drain()?; self.decoding_state = DecodingState::Reset; Ok(()) } fn next_event(&mut self) -> Option> { self.query_next_event(|decoder, sps| { // Apply the SPS settings to the decoder so we don't enter the AwaitingFormat state // on the next decode() call. decoder.apply_sps(sps); }) } fn stream_info(&self) -> Option<&StreamInfo> { self.backend.stream_info() } fn poll_fd(&self) -> BorrowedFd { self.epoll_fd.0.as_fd() } } #[cfg(test)] pub mod tests { use crate::bitstream_utils::NalIterator; use crate::codec::h264::parser::Nalu; use crate::decoder::stateless::h264::H264; use crate::decoder::stateless::tests::test_decode_stream; use crate::decoder::stateless::tests::TestStream; use crate::decoder::stateless::StatelessDecoder; use crate::decoder::BlockingMode; use crate::utils::simple_playback_loop; use crate::utils::simple_playback_loop_owned_frames; use crate::DecodedFormat; /// Run `test` using the dummy decoder, in both blocking and non-blocking modes. fn test_decoder_dummy(test: &TestStream, blocking_mode: BlockingMode) { let decoder = StatelessDecoder::::new_dummy(blocking_mode).unwrap(); test_decode_stream( |d, s, f| { simple_playback_loop( d, NalIterator::::new(s), f, &mut simple_playback_loop_owned_frames, DecodedFormat::NV12, blocking_mode, ) }, decoder, test, false, false, ); } /// A 64x64 progressive byte-stream encoded I-frame to make it easier to /// spot errors on the libva trace. /// Encoded with the following GStreamer pipeline: /// /// gst-launch-1.0 videotestsrc num-buffers=1 ! video/x-raw,format=I420,width=64,height=64 ! x264enc ! video/x-h264,profile=constrained-baseline,stream-format=byte-stream ! filesink location="64x64-I.h264" pub const DECODE_64X64_PROGRESSIVE_I: TestStream = TestStream { stream: include_bytes!("../../codec/h264/test_data/64x64-I.h264"), crcs: include_str!("../../codec/h264/test_data/64x64-I.h264.crc"), }; #[test] fn test_64x64_progressive_i_block() { test_decoder_dummy(&DECODE_64X64_PROGRESSIVE_I, BlockingMode::Blocking); } #[test] fn test_64x64_progressive_i_nonblock() { test_decoder_dummy(&DECODE_64X64_PROGRESSIVE_I, BlockingMode::NonBlocking); } /// A 64x64 progressive byte-stream encoded I-frame and P-frame to make /// it easier to spot errors on the libva trace. /// Encoded with the following GStreamer pipeline: /// gst-launch-1.0 videotestsrc num-buffers=2 ! video/x-raw,format=I420,width=64,height=64 ! x264enc b-adapt=false ! video/x-h264,profile=constrained-baseline,stream-format=byte-stream ! filesink location="64x64-I-P.h264" pub const DECODE_64X64_PROGRESSIVE_I_P: TestStream = TestStream { stream: include_bytes!("../../codec/h264/test_data/64x64-I-P.h264"), crcs: include_str!("../../codec/h264/test_data/64x64-I-P.h264.crc"), }; #[test] fn test_64x64_progressive_i_p_block() { test_decoder_dummy(&DECODE_64X64_PROGRESSIVE_I_P, BlockingMode::Blocking); } #[test] fn test_64x64_progressive_i_p_nonblock() { test_decoder_dummy(&DECODE_64X64_PROGRESSIVE_I_P, BlockingMode::NonBlocking); } /// A 64x64 progressive byte-stream encoded I-P-B-P sequence to make it /// easier to it easier to spot errors on the libva trace. /// Encoded with the following GStreamer pipeline: /// gst-launch-1.0 videotestsrc num-buffers=3 ! video/x-raw,format=I420,width=64,height=64 ! x264enc b-adapt=false bframes=1 ! video/x-h264,profile=constrained-baseline,stream-format=byte-stream ! filesink location="64x64-I-P-B-P.h264" pub const DECODE_64X64_PROGRESSIVE_I_P_B_P: TestStream = TestStream { stream: include_bytes!("../../codec/h264/test_data/64x64-I-P-B-P.h264"), crcs: include_str!("../../codec/h264/test_data/64x64-I-P-B-P.h264.crc"), }; #[test] fn test_64x64_progressive_i_p_b_p_block() { test_decoder_dummy(&DECODE_64X64_PROGRESSIVE_I_P_B_P, BlockingMode::Blocking); } #[test] fn test_64x64_progressive_i_p_b_p_nonblock() { test_decoder_dummy(&DECODE_64X64_PROGRESSIVE_I_P_B_P, BlockingMode::NonBlocking); } /// A 64x64 progressive byte-stream encoded I-P-B-P sequence to make it /// easier to it easier to spot errors on the libva trace. /// Also tests whether the decoder supports the high profile. /// /// Encoded with the following GStreamer pipeline: /// gst-launch-1.0 videotestsrc num-buffers=3 ! video/x-raw,format=I420,width=64,height=64 ! x264enc b-adapt=false bframes=1 ! video/x-h264,profile=high,stream-format=byte-stream ! filesink location="64x64-I-P-B-P-high.h264" pub const DECODE_64X64_PROGRESSIVE_I_P_B_P_HIGH: TestStream = TestStream { stream: include_bytes!("../../codec/h264/test_data/64x64-I-P-B-P-high.h264"), crcs: include_str!("../../codec/h264/test_data/64x64-I-P-B-P-high.h264.crc"), }; #[test] fn test_64x64_progressive_i_p_b_p_high_block() { test_decoder_dummy(&DECODE_64X64_PROGRESSIVE_I_P_B_P_HIGH, BlockingMode::Blocking); } #[test] fn test_64x64_progressive_i_p_b_p_high_nonblock() { test_decoder_dummy(&DECODE_64X64_PROGRESSIVE_I_P_B_P_HIGH, BlockingMode::NonBlocking); } /// Same as Chromium's test-25fps.h264 pub const DECODE_TEST_25FPS: TestStream = TestStream { stream: include_bytes!("../../codec/h264/test_data/test-25fps.h264"), crcs: include_str!("../../codec/h264/test_data/test-25fps.h264.crc"), }; #[test] fn test_25fps_block() { test_decoder_dummy(&DECODE_TEST_25FPS, BlockingMode::Blocking); } #[test] fn test_25fps_nonblock() { test_decoder_dummy(&DECODE_TEST_25FPS, BlockingMode::NonBlocking); } // Adapted from Chromium's test-25fps.h264. Same file, but encoded as // interlaced instead using the following ffmpeg command: // ffmpeg -i // src/third_party/blink/web_tests/media/content/test-25fps.mp4 // -flags +ilme+ildct -vbsf h264_mp4toannexb -an test-25fps.h264 // // This test makes sure that the interlaced logic in the decoder // actually works, specially that "frame splitting" works, as the fields // here were encoded as frames. pub const DECODE_TEST_25FPS_INTERLACED: TestStream = TestStream { stream: include_bytes!("../../codec/h264/test_data/test-25fps-interlaced.h264"), crcs: include_str!("../../codec/h264/test_data/test-25fps-interlaced.h264.crc"), }; #[test] fn test_25fps_interlaced_block() { test_decoder_dummy(&DECODE_TEST_25FPS_INTERLACED, BlockingMode::Blocking); } #[test] fn test_25fps_interlaced_nonblock() { test_decoder_dummy(&DECODE_TEST_25FPS_INTERLACED, BlockingMode::NonBlocking); } }