use std::collections::VecDeque;
use oxideav_core::Decoder;
use oxideav_core::{
CodecId, CodecParameters, Error, Frame, Packet, Result, TimeBase, VideoFrame, VideoPlane,
};
use crate::decoder::{Decoder as H264Driver, Event};
use crate::dpb_output::{DpbOutput, OutputEntry};
use crate::mb_grid::MbGrid;
use crate::picture::Picture;
use crate::poc::{derive_poc, PocResult, PocSlice, PocSps, PocState};
use crate::ref_list::{self, DpbEntry, MmcoOp as RefMmcoOp, PicStructure, RefMarking, RplmOp};
use crate::ref_store::{RefPicProvider, RefPicStore};
use crate::slice_header::{
MmcoOp as SliceMmcoOp, RefPicListModificationOp as SliceRplmOp, SliceHeader, SliceType,
};
use crate::sps::{FrameCropMargins, Sps};
use crate::{reconstruct, slice_data};
struct PictureInProgress {
pic: Picture,
grid: MbGrid,
first_nal_unit_type: u8,
first_nal_ref_idc: u8,
first_header: SliceHeader,
is_reference: bool,
is_idr: bool,
poc: PocResult,
structure: PicStructure,
pts: Option<i64>,
time_base: TimeBase,
deblock_enabled: bool,
deblock_alpha_off: i32,
deblock_beta_off: i32,
mb_field_flags: Vec<bool>,
sps: Sps,
pps: crate::pps::Pps,
any_slice_succeeded: bool,
}
struct PendingField {
pic: Picture,
is_bottom: bool,
frame_num: u32,
field_poc: i32,
pts: Option<i64>,
crop: FrameCropMargins,
}
struct ScpState {
subs: [Box<H264CodecDecoder>; 3],
queues: [VecDeque<VideoFrame>; 3],
}
impl ScpState {
fn new(codec_id: &CodecId) -> Self {
let mk = || {
let mut d = H264CodecDecoder::new(codec_id.clone());
d.scp_plane_mode = true;
Box::new(d)
};
Self {
subs: [mk(), mk(), mk()],
queues: [VecDeque::new(), VecDeque::new(), VecDeque::new()],
}
}
}
pub fn make_decoder(params: &CodecParameters) -> Result<Box<dyn Decoder>> {
let mut dec = H264CodecDecoder::new(params.codec_id.clone());
if !params.extradata.is_empty() {
dec.consume_extradata(¶ms.extradata)?;
}
Ok(Box::new(dec))
}
struct PendingDpSlice {
nal_ref_idc: u8,
header: SliceHeader,
rbsp_a: Vec<u8>,
cursor_a: (usize, u8),
slice_id: u32,
sps: Sps,
pps: crate::pps::Pps,
part_b: Option<(Vec<u8>, (usize, u8))>,
part_c: Option<(Vec<u8>, (usize, u8))>,
}
pub struct H264CodecDecoder {
codec_id: CodecId,
length_size: Option<u8>,
driver: H264Driver,
#[doc(hidden)] pub last_slice: Option<SliceHeader>,
decode_errors: u64,
eof: bool,
output_dpb: DpbOutput<VideoFrame>,
ready: VecDeque<VideoFrame>,
pending_pts: Option<i64>,
pending_dp: Option<PendingDpSlice>,
pending_time_base: TimeBase,
ref_store: RefPicStore,
dpb_entries: Vec<DpbEntry>,
poc_state: PocState,
next_dpb_key: u32,
prev_had_mmco5: bool,
prev_reference_top_foc: i32,
prev_ref_frame_num: Option<u32>,
in_progress: Option<PictureInProgress>,
pending_field: Option<PendingField>,
scp: Option<Box<ScpState>>,
scp_plane_mode: bool,
avcc_profile_idc: Option<u8>,
avcc_level_idc: Option<u8>,
avcc_chroma_format: Option<u8>,
avcc_bit_depth_luma: Option<u8>,
avcc_bit_depth_chroma: Option<u8>,
}
impl H264CodecDecoder {
pub fn new(codec_id: CodecId) -> Self {
Self {
codec_id,
length_size: None,
driver: H264Driver::new(),
last_slice: None,
decode_errors: 0,
eof: false,
output_dpb: DpbOutput::<VideoFrame>::new(16, 16),
ready: VecDeque::new(),
pending_pts: None,
pending_dp: None,
pending_time_base: TimeBase::new(1, 1),
ref_store: RefPicStore::new(),
dpb_entries: Vec::new(),
poc_state: PocState::default(),
next_dpb_key: 0,
prev_had_mmco5: false,
prev_reference_top_foc: 0,
prev_ref_frame_num: None,
in_progress: None,
pending_field: None,
scp: None,
scp_plane_mode: false,
avcc_profile_idc: None,
avcc_level_idc: None,
avcc_chroma_format: None,
avcc_bit_depth_luma: None,
avcc_bit_depth_chroma: None,
}
}
pub fn consume_extradata(&mut self, extra: &[u8]) -> Result<()> {
if extra.len() < 7 {
return Err(Error::invalid("h264: extradata shorter than avcC header"));
}
if extra[0] != 1 {
return Err(Error::invalid("h264: avcC configurationVersion must be 1"));
}
let profile_idc = extra[1];
self.avcc_profile_idc = Some(profile_idc);
self.avcc_level_idc = Some(extra[3]);
let length_size_minus_one = extra[4] & 0x03;
if length_size_minus_one == 2 {
return Err(Error::invalid(
"h264: avcC lengthSizeMinusOne == 2 (3-byte prefix) forbidden by ISO/IEC 14496-15 §5.2.4.1.1",
));
}
self.length_size = Some(length_size_minus_one + 1);
let num_sps = (extra[5] & 0x1f) as usize;
let mut pos = 6;
for _ in 0..num_sps {
if pos + 2 > extra.len() {
return Err(Error::invalid("h264: avcC truncated at SPS length"));
}
let len = u16::from_be_bytes([extra[pos], extra[pos + 1]]) as usize;
pos += 2;
if pos + len > extra.len() {
return Err(Error::invalid("h264: avcC truncated at SPS body"));
}
let _ = self
.driver
.process_nal(&extra[pos..pos + len])
.map_err(|e| Error::invalid(format!("h264 avcC SPS: {e}")))?;
pos += len;
}
if pos >= extra.len() {
return Err(Error::invalid("h264: avcC truncated at PPS count"));
}
let num_pps = extra[pos] as usize;
pos += 1;
for _ in 0..num_pps {
if pos + 2 > extra.len() {
return Err(Error::invalid("h264: avcC truncated at PPS length"));
}
let len = u16::from_be_bytes([extra[pos], extra[pos + 1]]) as usize;
pos += 2;
if pos + len > extra.len() {
return Err(Error::invalid("h264: avcC truncated at PPS body"));
}
let _ = self
.driver
.process_nal(&extra[pos..pos + len])
.map_err(|e| Error::invalid(format!("h264 avcC PPS: {e}")))?;
pos += len;
}
if crate::avcc::AvcDecoderConfigurationRecord::has_extension_trailer(profile_idc) {
if pos >= extra.len() {
return Ok(());
}
if pos + 4 > extra.len() {
return Err(Error::invalid(
"h264: avcC truncated at High-profile extension header",
));
}
let chroma_format = extra[pos] & 0x03;
let bit_depth_luma_minus8 = extra[pos + 1] & 0x07;
let bit_depth_chroma_minus8 = extra[pos + 2] & 0x07;
let num_sps_ext = extra[pos + 3] as usize;
if bit_depth_luma_minus8 > 6 {
return Err(Error::invalid(format!(
"h264: avcC bit_depth_luma_minus8 = {bit_depth_luma_minus8} exceeds §7.4.2.1.1 cap"
)));
}
if bit_depth_chroma_minus8 > 6 {
return Err(Error::invalid(format!(
"h264: avcC bit_depth_chroma_minus8 = {bit_depth_chroma_minus8} exceeds §7.4.2.1.1 cap"
)));
}
self.avcc_chroma_format = Some(chroma_format);
self.avcc_bit_depth_luma = Some(8 + bit_depth_luma_minus8);
self.avcc_bit_depth_chroma = Some(8 + bit_depth_chroma_minus8);
pos += 4;
for _ in 0..num_sps_ext {
if pos + 2 > extra.len() {
return Err(Error::invalid("h264: avcC truncated at SPS-Ext length"));
}
let len = u16::from_be_bytes([extra[pos], extra[pos + 1]]) as usize;
pos += 2;
if pos + len > extra.len() {
return Err(Error::invalid("h264: avcC truncated at SPS-Ext body"));
}
let _ = self
.driver
.process_nal(&extra[pos..pos + len])
.map_err(|e| Error::invalid(format!("h264 avcC SPS-Ext: {e}")))?;
pos += len;
}
}
Ok(())
}
pub fn avcc_profile_idc(&self) -> Option<u8> {
self.avcc_profile_idc
}
pub fn avcc_level_idc(&self) -> Option<u8> {
self.avcc_level_idc
}
pub fn avcc_chroma_format(&self) -> Option<u8> {
self.avcc_chroma_format
}
pub fn avcc_bit_depth_luma(&self) -> Option<u8> {
self.avcc_bit_depth_luma
}
pub fn avcc_bit_depth_chroma(&self) -> Option<u8> {
self.avcc_bit_depth_chroma
}
#[doc(hidden)] pub fn decode_error_count(&self) -> u64 {
self.decode_errors
}
#[doc(hidden)] pub fn active_sps(&self) -> Option<&Sps> {
self.driver.active_sps()
}
#[doc(hidden)] pub fn stored_sps(&self, id: u32) -> Option<&Sps> {
self.driver.sps(id)
}
#[doc(hidden)] pub fn ref_picture_count(&self) -> usize {
self.ref_store.stored_count()
}
fn handle_event(&mut self, ev: Event) -> Result<()> {
if !matches!(ev, Event::SliceDataPartitionBc { .. }) {
self.flush_pending_dp_slice()?;
}
match ev {
Event::Slice {
nal_unit_type,
nal_ref_idc,
header,
rbsp,
slice_data_cursor,
pps,
sps,
} => {
self.last_slice = Some(header.clone());
if sps.separate_colour_plane_flag && !self.scp_plane_mode {
return self.route_scp_slice(
nal_unit_type,
nal_ref_idc,
header,
rbsp,
slice_data_cursor,
sps,
pps,
);
}
self.handle_slice(
nal_unit_type,
nal_ref_idc,
header,
rbsp,
slice_data_cursor,
sps,
pps,
)
}
Event::AccessUnitDelimiter(_) => {
self.forward_event_to_scp_subs(&ev)?;
self.finalize_in_progress_picture()?;
Ok(())
}
Event::EndOfSequence | Event::EndOfStream => {
self.forward_event_to_scp_subs(&ev)?;
self.finalize_in_progress_picture()?;
Ok(())
}
Event::SliceDataPartitionA {
nal_ref_idc,
header,
slice_id,
rbsp,
slice_data_cursor,
pps,
sps,
} => {
if sps.separate_colour_plane_flag {
return Err(Error::invalid(
"h264: separate_colour_plane_flag with slice data partitioning is not supported",
));
}
self.last_slice = Some(header.clone());
self.pending_dp = Some(PendingDpSlice {
nal_ref_idc,
header,
rbsp_a: rbsp,
cursor_a: slice_data_cursor,
slice_id,
sps,
pps,
part_b: None,
part_c: None,
});
Ok(())
}
Event::SliceDataPartitionBc {
is_c,
slice_id,
redundant_pic_cnt,
rbsp,
slice_data_cursor,
} => {
if redundant_pic_cnt > 0 {
return Ok(());
}
let Some(pending) = self.pending_dp.as_mut() else {
return Err(Error::invalid(format!(
"h264: slice data partition {} (slice_id {slice_id}) without a preceding partition A",
if is_c { 'C' } else { 'B' },
)));
};
if pending.slice_id != slice_id {
return Err(Error::invalid(format!(
"h264: slice data partition {} slice_id {slice_id} does not match partition A slice_id {}",
if is_c { 'C' } else { 'B' },
pending.slice_id,
)));
}
let slot = if is_c {
&mut pending.part_c
} else {
&mut pending.part_b
};
if slot.is_some() {
return Err(Error::invalid(format!(
"h264: duplicate slice data partition {} for slice_id {slice_id}",
if is_c { 'C' } else { 'B' },
)));
}
*slot = Some((rbsp, slice_data_cursor));
Ok(())
}
_ => Ok(()),
}
}
fn flush_pending_dp_slice(&mut self) -> Result<()> {
let Some(pending) = self.pending_dp.take() else {
return Ok(());
};
self.handle_slice_with_dp(
2,
pending.nal_ref_idc,
pending.header,
pending.rbsp_a,
pending.cursor_a,
pending.sps,
pending.pps,
Some((pending.part_b, pending.part_c)),
)
}
#[allow(clippy::too_many_arguments)] fn route_scp_slice(
&mut self,
nal_unit_type: u8,
nal_ref_idc: u8,
header: SliceHeader,
rbsp: Vec<u8>,
slice_data_cursor: (usize, u8),
sps: Sps,
pps: crate::pps::Pps,
) -> Result<()> {
let plane = header.colour_plane_id as usize;
if plane > 2 {
return Err(Error::invalid(format!(
"h264 slice_header: colour_plane_id {} out of range (§7.4.3 requires 0..=2)",
header.colour_plane_id
)));
}
let scp = self
.scp
.get_or_insert_with(|| Box::new(ScpState::new(&self.codec_id)));
if plane == 0 {
if let Some(pts) = self.pending_pts.take() {
scp.subs[0].pending_pts = Some(pts);
}
scp.subs[0].pending_time_base = self.pending_time_base;
}
scp.subs[plane].handle_event(Event::Slice {
nal_unit_type,
nal_ref_idc,
header,
rbsp,
slice_data_cursor,
pps,
sps,
})?;
self.drain_and_merge_scp();
Ok(())
}
fn forward_event_to_scp_subs(&mut self, ev: &Event) -> Result<()> {
if let Some(scp) = self.scp.as_mut() {
for sub in scp.subs.iter_mut() {
sub.handle_event(ev.clone())?;
}
self.drain_and_merge_scp();
}
Ok(())
}
fn drain_and_merge_scp(&mut self) {
let Some(scp) = self.scp.as_mut() else {
return;
};
for (sub, queue) in scp.subs.iter_mut().zip(scp.queues.iter_mut()) {
while let Ok(Frame::Video(vf)) = sub.receive_frame() {
queue.push_back(vf);
}
}
while scp.queues.iter().all(|q| !q.is_empty()) {
let y = scp.queues[0].pop_front().expect("checked non-empty");
let cb = scp.queues[1].pop_front().expect("checked non-empty");
let cr = scp.queues[2].pop_front().expect("checked non-empty");
let mut planes = y.planes;
planes.extend(cb.planes);
planes.extend(cr.planes);
self.ready.push_back(VideoFrame { pts: y.pts, planes });
}
const SCP_QUEUE_CAP: usize = 64;
let mut dropped = 0u64;
for q in scp.queues.iter_mut() {
while q.len() > SCP_QUEUE_CAP {
q.pop_front();
dropped += 1;
}
}
self.decode_errors += dropped;
}
fn is_first_vcl_of_new_picture(
&self,
nal_unit_type: u8,
nal_ref_idc: u8,
header: &SliceHeader,
) -> bool {
let Some(in_progress) = self.in_progress.as_ref() else {
return true;
};
let prev = &in_progress.first_header;
let prev_idr = in_progress.first_nal_unit_type == 5;
let curr_idr = nal_unit_type == 5;
let prev_is_ref = in_progress.first_nal_ref_idc != 0;
let curr_is_ref = nal_ref_idc != 0;
if prev.frame_num != header.frame_num {
return true;
}
if prev.pic_parameter_set_id != header.pic_parameter_set_id {
return true;
}
if prev.field_pic_flag != header.field_pic_flag {
return true;
}
if header.field_pic_flag && prev.bottom_field_flag != header.bottom_field_flag {
return true;
}
if prev_is_ref != curr_is_ref {
return true;
}
if prev.pic_order_cnt_lsb != header.pic_order_cnt_lsb {
return true;
}
if prev.delta_pic_order_cnt_bottom != header.delta_pic_order_cnt_bottom {
return true;
}
if prev.delta_pic_order_cnt[0] != header.delta_pic_order_cnt[0] {
return true;
}
if prev.delta_pic_order_cnt[1] != header.delta_pic_order_cnt[1] {
return true;
}
if prev_idr != curr_idr {
return true;
}
if curr_idr && prev.idr_pic_id != header.idr_pic_id {
return true;
}
false
}
#[allow(clippy::too_many_arguments)] fn handle_slice(
&mut self,
nal_unit_type: u8,
nal_ref_idc: u8,
header: SliceHeader,
rbsp: Vec<u8>,
cursor: (usize, u8),
sps: Sps,
pps: crate::pps::Pps,
) -> Result<()> {
self.handle_slice_with_dp(
nal_unit_type,
nal_ref_idc,
header,
rbsp,
cursor,
sps,
pps,
None,
)
}
#[allow(clippy::too_many_arguments, clippy::type_complexity)]
fn handle_slice_with_dp(
&mut self,
nal_unit_type: u8,
nal_ref_idc: u8,
header: SliceHeader,
rbsp: Vec<u8>,
cursor: (usize, u8),
sps: Sps,
pps: crate::pps::Pps,
dp: Option<(
Option<(Vec<u8>, (usize, u8))>,
Option<(Vec<u8>, (usize, u8))>,
)>,
) -> Result<()> {
if header.redundant_pic_cnt > 0 {
return Ok(());
}
let is_idr = nal_unit_type == 5;
let is_reference = nal_ref_idc != 0;
let starts_new_picture =
self.is_first_vcl_of_new_picture(nal_unit_type, nal_ref_idc, &header);
if starts_new_picture {
self.finalize_in_progress_picture()?;
if header.first_mb_in_slice != 0 && !matches!(sps.profile_idc, 66 | 88) {
return Err(Error::invalid(format!(
"h264 slice_header: first_mb_in_slice = {} for first slice of a coded picture (§7.4.3 / Annex A require 0 when ASO is not allowed — profile_idc {})",
header.first_mb_in_slice, sps.profile_idc
)));
}
if !is_idr && !sps.gaps_in_frame_num_value_allowed_flag {
if let Some(prev) = self.prev_ref_frame_num {
let max_frame_num = 1u32 << (sps.log2_max_frame_num_minus4 + 4);
let expected = (prev + 1) % max_frame_num;
if header.frame_num != prev && header.frame_num != expected {
return Err(Error::invalid(format!(
"h264 slice_header: frame_num {} after PrevRefFrameNum {} (§7.4.3 requires {} when gaps_in_frame_num_value_allowed_flag is 0)",
header.frame_num, prev, expected
)));
}
}
}
if !is_idr && sps.gaps_in_frame_num_value_allowed_flag {
self.fill_frame_num_gap(&sps, header.frame_num)?;
}
let poc_sps = make_poc_sps(&sps);
let poc_slice = PocSlice {
is_reference,
is_idr,
frame_num: header.frame_num,
field_pic_flag: header.field_pic_flag,
bottom_field_flag: header.bottom_field_flag,
pic_order_cnt_lsb: header.pic_order_cnt_lsb,
delta_pic_order_cnt_bottom: header.delta_pic_order_cnt_bottom,
delta_pic_order_cnt: header.delta_pic_order_cnt,
prev_had_mmco5: self.prev_had_mmco5,
prev_reference_top_foc_for_mmco5: self.prev_reference_top_foc,
};
let poc = derive_poc(&poc_sps, &poc_slice, &mut self.poc_state)
.map_err(|e| Error::invalid(format!("h264 POC: {e:?}")))?;
let pic_height_in_mbs = sps.pic_height_in_mbs(header.field_pic_flag);
let width_samples = sps.pic_width_in_mbs() * 16;
let height_samples = pic_height_in_mbs * 16;
let chroma_array_type = sps.chroma_array_type();
let pic = Picture::new(
width_samples,
height_samples,
chroma_array_type,
sps.bit_depth_luma_minus8 + 8,
sps.bit_depth_chroma_minus8 + 8,
);
let grid = MbGrid::new(sps.pic_width_in_mbs(), pic_height_in_mbs);
let structure =
pic_structure_from_flags(header.field_pic_flag, header.bottom_field_flag);
let pts = self.pending_pts.take();
let time_base = self.pending_time_base;
let deblock_enabled = header.disable_deblocking_filter_idc != 1;
let deblock_alpha_off = header.slice_alpha_c0_offset_div2 * 2;
let deblock_beta_off = header.slice_beta_offset_div2 * 2;
let mb_count = (sps.pic_width_in_mbs() * pic_height_in_mbs) as usize;
let mb_field_flags = vec![false; mb_count];
self.in_progress = Some(PictureInProgress {
pic,
grid,
first_nal_unit_type: nal_unit_type,
first_nal_ref_idc: nal_ref_idc,
first_header: header.clone(),
is_reference,
is_idr,
poc,
structure,
pts,
time_base,
deblock_enabled,
deblock_alpha_off,
deblock_beta_off,
mb_field_flags,
sps: sps.clone(),
pps: pps.clone(),
any_slice_succeeded: false,
});
}
self.reconstruct_slice_into_in_progress(
nal_ref_idc,
&header,
&rbsp,
cursor,
&sps,
&pps,
dp,
)?;
Ok(())
}
#[allow(clippy::too_many_arguments, clippy::type_complexity)]
fn reconstruct_slice_into_in_progress(
&mut self,
nal_ref_idc: u8,
header: &SliceHeader,
rbsp: &[u8],
cursor: (usize, u8),
sps: &Sps,
pps: &crate::pps::Pps,
dp: Option<(
Option<(Vec<u8>, (usize, u8))>,
Option<(Vec<u8>, (usize, u8))>,
)>,
) -> Result<()> {
let sd = match &dp {
None => slice_data::parse_slice_data(rbsp, cursor.0, cursor.1, header, sps, pps),
Some((b, c)) => slice_data::parse_slice_data_partitioned(
rbsp,
cursor.0,
cursor.1,
b.as_ref().map(|(buf, cur)| (&buf[..], cur.0, cur.1)),
c.as_ref().map(|(buf, cur)| (&buf[..], cur.0, cur.1)),
header,
sps,
pps,
),
}
.map_err(|e| Error::invalid(format!("h264 slice_data: {e}")))?;
let in_progress = self
.in_progress
.as_mut()
.expect("in_progress must have been seeded by handle_slice");
if nal_ref_idc != 0 {
in_progress.is_reference = true;
}
let current_structure = in_progress.structure;
let current_bottom = matches!(current_structure, PicStructure::BottomField);
let current_is_field = header.field_pic_flag;
let pic_order_cnt = in_progress.poc.pic_order_cnt;
let is_idr = in_progress.is_idr;
let mut l0_overrides: Vec<Option<Picture>> = Vec::new();
let mut l1_overrides: Vec<Option<Picture>> = Vec::new();
let mut l0_parities: Vec<Option<u8>> = Vec::new();
let mut l1_parities: Vec<Option<u8>> = Vec::new();
let mut l0_unit_keys: Vec<u32> = Vec::new();
let mut l1_unit_keys: Vec<u32> = Vec::new();
let mut l0_focs: Vec<(i32, i32)> = Vec::new();
let mut l1_focs: Vec<(i32, i32)> = Vec::new();
let mut l0_pair_keys: Vec<Option<(u32, u32)>> = Vec::new();
let mut l1_pair_keys: Vec<Option<(u32, u32)>> = Vec::new();
let mut field_pocs_lt: Option<FieldListPocsLt> = None;
let (list0, list1) = if is_idr {
(Vec::new(), Vec::new())
} else if current_is_field {
let max_frame_num = 1u32 << (sps.log2_max_frame_num_minus4 + 4);
let (mut fl0, mut fl1) = match header.slice_type {
SliceType::P | SliceType::SP => (
ref_list::init_ref_pic_list_p_field(
&self.dpb_entries,
header.frame_num,
max_frame_num,
current_bottom,
),
Vec::new(),
),
SliceType::B => ref_list::init_ref_pic_lists_b_field(
&self.dpb_entries,
pic_order_cnt,
current_bottom,
),
SliceType::I | SliceType::SI => (Vec::new(), Vec::new()),
};
if header.slice_type.has_list_0() && fl0.is_empty() {
return Err(Error::invalid(format!(
"h264 slice_header: {:?} field slice but the DPB holds no reference field (§8.2.4 RefPicList0 would be all 'no reference picture')",
header.slice_type
)));
}
if header.slice_type.has_list_0() {
let ops_l0: Vec<RplmOp> = header
.ref_pic_list_modification
.modifications_l0
.iter()
.map(slice_rplm_to_ref_rplm)
.collect();
ref_list::modify_ref_pic_list_field(
&mut fl0,
&ops_l0,
&self.dpb_entries,
header.num_ref_idx_l0_active_minus1 + 1,
header.frame_num,
max_frame_num,
current_bottom,
);
}
if header.slice_type.has_list_1() {
let ops_l1: Vec<RplmOp> = header
.ref_pic_list_modification
.modifications_l1
.iter()
.map(slice_rplm_to_ref_rplm)
.collect();
ref_list::modify_ref_pic_list_field(
&mut fl1,
&ops_l1,
&self.dpb_entries,
header.num_ref_idx_l1_active_minus1 + 1,
header.frame_num,
max_frame_num,
current_bottom,
);
}
let r0 = Self::resolve_field_list(&self.dpb_entries, &self.ref_store, &fl0);
let r1 = Self::resolve_field_list(&self.dpb_entries, &self.ref_store, &fl1);
l0_overrides = r0.overrides;
l1_overrides = r1.overrides;
l0_parities = r0.parities;
l1_parities = r1.parities;
l0_unit_keys = r0.unit_keys;
l1_unit_keys = r1.unit_keys;
l0_focs = r0.pocs.iter().map(|&p| (p, p)).collect();
l1_focs = r1.pocs.iter().map(|&p| (p, p)).collect();
l0_pair_keys = vec![None; r0.keys.len()];
l1_pair_keys = vec![None; r1.keys.len()];
field_pocs_lt = Some((r0.pocs, r0.longterm, r1.pocs, r1.longterm));
(r0.keys, r1.keys)
} else {
let max_frame_num = 1u32 << (sps.log2_max_frame_num_minus4 + 4);
let (frame_units, pairings) = ref_list::collapse_field_pairs(&self.dpb_entries);
let (mut l0, mut l1) = match header.slice_type {
SliceType::P | SliceType::SP => (
ref_list::init_ref_pic_list_p(
&frame_units,
header.frame_num,
max_frame_num,
current_structure,
current_bottom,
),
Vec::new(),
),
SliceType::B => ref_list::init_ref_pic_lists_b(
&frame_units,
pic_order_cnt,
current_structure,
current_bottom,
),
SliceType::I | SliceType::SI => (Vec::new(), Vec::new()),
};
if header.slice_type.has_list_0() && l0.is_empty() {
return Err(Error::invalid(format!(
"h264 slice_header: {:?} slice but the DPB holds no reference picture (§8.2.4 RefPicList0 would be all 'no reference picture')",
header.slice_type
)));
}
if header.slice_type.has_list_0() {
let ops_l0: Vec<RplmOp> = header
.ref_pic_list_modification
.modifications_l0
.iter()
.map(slice_rplm_to_ref_rplm)
.collect();
ref_list::modify_ref_pic_list(
&mut l0,
&ops_l0,
&frame_units,
header.num_ref_idx_l0_active_minus1 + 1,
header.frame_num,
max_frame_num,
current_is_field,
current_bottom,
);
}
if header.slice_type.has_list_1() {
let ops_l1: Vec<RplmOp> = header
.ref_pic_list_modification
.modifications_l1
.iter()
.map(slice_rplm_to_ref_rplm)
.collect();
ref_list::modify_ref_pic_list(
&mut l1,
&ops_l1,
&frame_units,
header.num_ref_idx_l1_active_minus1 + 1,
header.frame_num,
max_frame_num,
current_is_field,
current_bottom,
);
}
type FrameListResolution = (
Vec<Option<Picture>>,
Vec<i32>,
Vec<bool>,
Vec<(i32, i32)>,
Vec<Option<(u32, u32)>>,
);
let resolve_frame_list = |keys: &[u32]| -> FrameListResolution {
let mut overrides = Vec::with_capacity(keys.len());
let mut pocs = Vec::with_capacity(keys.len());
let mut lts = Vec::with_capacity(keys.len());
let mut focs = Vec::with_capacity(keys.len());
let mut pair_keys = Vec::with_capacity(keys.len());
for &key in keys {
let unit = frame_units.iter().find(|u| u.dpb_key == key);
let pairing = pairings.iter().find(|p| p.unit_key == key);
let ov = pairing.and_then(|p| {
let top = self.ref_store.get_by_key(p.top_key)?;
let bottom = self.ref_store.get_by_key(p.bottom_key)?;
let mut merged = interleave_fields(top, bottom);
if let Some(u) = unit {
merged.pic_order_cnt = u.pic_order_cnt;
merged.frame_num = u.frame_num;
}
Some(merged)
});
overrides.push(ov);
pocs.push(unit.map(|u| u.pic_order_cnt).unwrap_or_else(|| {
self.ref_store
.get_by_key(key)
.map(|p| p.pic_order_cnt)
.unwrap_or(0)
}));
lts.push(unit.map(|u| u.is_long_term()).unwrap_or(false));
focs.push(
unit.map(|u| (u.top_field_order_cnt, u.bottom_field_order_cnt))
.unwrap_or((0, 0)),
);
pair_keys.push(pairing.map(|p| (p.top_key, p.bottom_key)));
}
(overrides, pocs, lts, focs, pair_keys)
};
let (ov0, pocs0, lt0, focs0, pk0) = resolve_frame_list(&l0);
let (ov1, pocs1, lt1, focs1, pk1) = resolve_frame_list(&l1);
l0_overrides = ov0;
l1_overrides = ov1;
l0_unit_keys = l0.clone();
l1_unit_keys = l1.clone();
l0_parities = vec![None; l0.len()];
l1_parities = vec![None; l1.len()];
l0_focs = focs0;
l1_focs = focs1;
l0_pair_keys = pk0;
l1_pair_keys = pk1;
field_pocs_lt = Some((pocs0, lt0, pocs1, lt1));
(l0, l1)
};
in_progress.pic.pic_order_cnt = in_progress.poc.pic_order_cnt;
in_progress.pic.frame_num = header.frame_num;
in_progress.pic.coding_struct = if header.field_pic_flag {
crate::picture::PicCodingStruct::Fld
} else if sps.mb_adaptive_frame_field_flag {
crate::picture::PicCodingStruct::Afrm
} else {
crate::picture::PicCodingStruct::Frm
};
in_progress.pic.is_bottom_field = header.bottom_field_flag;
in_progress.pic.top_field_order_cnt = in_progress.poc.top_field_order_cnt;
in_progress.pic.bottom_field_order_cnt = in_progress.poc.bottom_field_order_cnt;
let (list_0_pocs, list_0_longterm, list_1_pocs, list_1_longterm) =
if let Some(t) = field_pocs_lt {
t
} else {
let list_0_pocs: Vec<i32> = list0
.iter()
.map(|&key| {
self.ref_store
.get_by_key(key)
.map(|p| p.pic_order_cnt)
.unwrap_or(0)
})
.collect();
let list_0_longterm: Vec<bool> = list0
.iter()
.map(|&key| {
self.dpb_entries
.iter()
.find(|e| e.dpb_key == key)
.map(|e| e.is_long_term())
.unwrap_or(false)
})
.collect();
let list_1_pocs: Vec<i32> = list1
.iter()
.map(|&key| {
self.ref_store
.get_by_key(key)
.map(|p| p.pic_order_cnt)
.unwrap_or(0)
})
.collect();
let list_1_longterm: Vec<bool> = list1
.iter()
.map(|&key| {
self.dpb_entries
.iter()
.find(|e| e.dpb_key == key)
.map(|e| e.is_long_term())
.unwrap_or(false)
})
.collect();
(list_0_pocs, list_0_longterm, list_1_pocs, list_1_longterm)
};
if in_progress.pic.ref_list_0_pocs.is_empty() {
in_progress.pic.ref_list_0_pocs = list_0_pocs.clone();
in_progress.pic.ref_list_0_longterm = list_0_longterm.clone();
in_progress.pic.ref_list_1_pocs = list_1_pocs.clone();
in_progress.pic.ref_list_1_longterm = list_1_longterm.clone();
in_progress.pic.ref_list_0_keys = list0.clone();
in_progress.pic.ref_list_1_keys = list1.clone();
in_progress.pic.ref_list_0_parities = l0_parities.clone();
in_progress.pic.ref_list_1_parities = l1_parities.clone();
in_progress.pic.ref_list_0_unit_keys = l0_unit_keys.clone();
in_progress.pic.ref_list_1_unit_keys = l1_unit_keys.clone();
}
let _ = list_1_pocs;
let provider = BorrowedRefProvider {
store: &self.ref_store,
list_0: &list0,
list_1: &list1,
list_0_pocs,
list_0_longterm,
list_1_longterm,
list_0_overrides: l0_overrides,
list_1_overrides: l1_overrides,
list_0_parities: l0_parities,
list_1_parities: l1_parities,
list_0_unit_keys: l0_unit_keys,
list_1_unit_keys: l1_unit_keys,
list_0_focs: l0_focs,
list_1_focs: l1_focs,
list_0_pair_keys: l0_pair_keys,
list_1_pair_keys: l1_pair_keys,
};
reconstruct::reconstruct_slice_no_deblock(
&sd,
header,
sps,
pps,
&provider,
&mut in_progress.pic,
&mut in_progress.grid,
)
.map_err(|e| Error::invalid(format!("h264 reconstruct: {e}")))?;
let mbaff_frame_flag = sps.mb_adaptive_frame_field_flag && !header.field_pic_flag;
let mut curr_addr = header.first_mb_in_slice * (1 + u32::from(mbaff_frame_flag));
let mb_map = crate::mb_address::slice_mb_to_slice_group_map(sps, pps, header);
for flag in sd.mb_field_decoding_flags.iter().copied() {
if let Some(slot) = in_progress.mb_field_flags.get_mut(curr_addr as usize) {
*slot = flag;
}
curr_addr = crate::mb_address::advance_mb_addr(curr_addr, mb_map.as_deref());
}
in_progress.any_slice_succeeded = true;
Ok(())
}
fn resolve_field_list(
dpb_entries: &[DpbEntry],
ref_store: &RefPicStore,
entries: &[ref_list::RefFieldEntry],
) -> ResolvedFieldList {
let mut keys = Vec::with_capacity(entries.len());
let mut overrides = Vec::with_capacity(entries.len());
let mut pocs = Vec::with_capacity(entries.len());
let mut lts = Vec::with_capacity(entries.len());
let mut parities = Vec::with_capacity(entries.len());
let mut unit_keys = Vec::with_capacity(entries.len());
let unit_key_of = |dpb: &DpbEntry| -> u32 {
if dpb.structure.is_field() {
let partner = dpb_entries.iter().find(|d| {
d.structure.is_field()
&& d.frame_num == dpb.frame_num
&& d.structure.is_bottom() != dpb.structure.is_bottom()
});
match partner {
Some(p) => {
if dpb.structure.is_bottom() {
p.dpb_key
} else {
dpb.dpb_key
}
}
None => dpb.dpb_key,
}
} else {
dpb.dpb_key
}
};
for e in entries {
let Some(dpb) = dpb_entries.iter().find(|d| d.dpb_key == e.dpb_key) else {
keys.push(u32::MAX);
overrides.push(None);
pocs.push(0);
lts.push(false);
parities.push(None);
unit_keys.push(u32::MAX);
continue;
};
unit_keys.push(unit_key_of(dpb));
let bottom = e.parity == ref_list::FieldParity::Bottom;
let field_poc = if bottom {
dpb.bottom_field_order_cnt
} else {
dpb.top_field_order_cnt
};
match dpb.structure {
PicStructure::TopField | PicStructure::BottomField => {
keys.push(e.dpb_key);
overrides.push(None);
}
PicStructure::Frame | PicStructure::FieldPair => {
let ov = ref_store.get_by_key(e.dpb_key).map(|p| {
let mut v = p.field_view(bottom);
v.pic_order_cnt = field_poc;
v
});
keys.push(e.dpb_key);
overrides.push(ov);
}
}
pocs.push(field_poc);
lts.push(dpb.is_long_term());
parities.push(Some(u8::from(bottom)));
}
ResolvedFieldList {
keys,
overrides,
pocs,
longterm: lts,
parities,
unit_keys,
}
}
fn finalize_in_progress_picture(&mut self) -> Result<()> {
let Some(in_progress) = self.in_progress.take() else {
return Ok(());
};
if !in_progress.any_slice_succeeded {
return Ok(());
}
if in_progress.grid.info.iter().any(|m| !m.available) {
return Ok(());
}
let PictureInProgress {
mut pic,
grid,
first_nal_unit_type: _,
first_nal_ref_idc: _,
first_header,
is_reference,
is_idr,
poc,
structure,
pts,
time_base,
deblock_enabled,
deblock_alpha_off,
deblock_beta_off,
mb_field_flags,
sps,
pps,
any_slice_succeeded: _,
} = in_progress;
snapshot_grid_into_picture(&mut pic, &grid);
let bit_depth_y = 8 + sps.bit_depth_luma_minus8;
let bit_depth_c = 8 + sps.bit_depth_chroma_minus8;
let mbaff_frame_flag = sps.mb_adaptive_frame_field_flag && !first_header.field_pic_flag;
if deblock_enabled {
reconstruct::deblock_picture_full(
&mut pic,
&grid,
deblock_alpha_off,
deblock_beta_off,
bit_depth_y,
bit_depth_c,
&pps,
mbaff_frame_flag,
first_header.field_pic_flag,
&mb_field_flags,
);
}
let mut current_entry = DpbEntry {
frame_num: first_header.frame_num,
top_field_order_cnt: poc.top_field_order_cnt,
bottom_field_order_cnt: poc.bottom_field_order_cnt,
pic_order_cnt: poc.pic_order_cnt,
structure,
marking: if is_reference {
RefMarking::ShortTerm
} else {
RefMarking::Unused
},
long_term_frame_idx: 0,
dpb_key: self.mint_dpb_key(),
field_markings: [RefMarking::Unused; 2],
};
current_entry.sync_field_markings();
let mut mmco5_triggered = false;
if is_reference {
let marking = first_header.dec_ref_pic_marking.as_ref();
let long_term_ref_flag = marking.is_some_and(|m| m.long_term_reference_flag);
let no_output = marking.is_some_and(|m| m.no_output_of_prior_pics_flag);
let adaptive_ops_vec: Option<Vec<RefMmcoOp>> = marking
.and_then(|m| m.adaptive_marking.as_ref())
.map(|ops| ops.iter().map(slice_mmco_to_ref_mmco).collect());
mmco5_triggered = ref_list::perform_marking(
&mut self.dpb_entries,
&mut current_entry,
sps.max_num_ref_frames,
is_idr,
long_term_ref_flag,
no_output,
adaptive_ops_vec.as_deref(),
first_header.frame_num,
1u32 << (sps.log2_max_frame_num_minus4 + 4),
);
self.dpb_entries.retain(|e| e.is_any_field_ref());
if mmco5_triggered {
let temp = current_entry.pic_order_cnt;
current_entry.frame_num = 0;
current_entry.top_field_order_cnt -= temp;
current_entry.bottom_field_order_cnt -= temp;
current_entry.pic_order_cnt = current_entry
.top_field_order_cnt
.min(current_entry.bottom_field_order_cnt);
pic.pic_order_cnt = current_entry.pic_order_cnt;
pic.frame_num = 0;
}
self.ref_store.insert(current_entry.dpb_key, pic.clone());
self.dpb_entries.push(current_entry);
self.prune_ref_store();
}
if is_reference {
self.prev_had_mmco5 = mmco5_triggered;
self.prev_reference_top_foc = if mmco5_triggered {
poc.top_field_order_cnt - poc.pic_order_cnt
} else {
0
};
self.prev_ref_frame_num = if mmco5_triggered {
Some(0)
} else {
Some(first_header.frame_num)
};
}
let _ = time_base;
if is_idr || mmco5_triggered {
self.flush_pending_field();
for drained in self.output_dpb.flush() {
self.ready.push_back(drained.picture);
}
self.output_dpb.reset();
}
self.ensure_output_dpb_sized(&sps);
let output_poc = if mmco5_triggered {
let temp = poc.pic_order_cnt;
let top_after = poc.top_field_order_cnt - temp;
let bot_after = poc.bottom_field_order_cnt - temp;
if first_header.field_pic_flag && first_header.bottom_field_flag {
bot_after
} else if first_header.field_pic_flag {
top_after
} else {
top_after.min(bot_after)
}
} else {
poc.pic_order_cnt
};
if first_header.field_pic_flag {
self.handle_field_output(
pic,
first_header.bottom_field_flag,
first_header.frame_num,
output_poc,
pts,
sps.frame_crop_margins(),
);
return Ok(());
}
let vf = picture_to_video_frame(&pic, pts, sps.frame_crop_margins());
let entry = OutputEntry {
picture: vf,
pic_order_cnt: output_poc,
frame_num: first_header.frame_num,
needed_for_output: true,
};
if let Some(bumped) = self.output_dpb.push(entry) {
self.ready.push_back(bumped.picture);
}
Ok(())
}
fn handle_field_output(
&mut self,
pic: Picture,
is_bottom: bool,
frame_num: u32,
field_poc: i32,
pts: Option<i64>,
crop: FrameCropMargins,
) {
if let Some(prev) = self.pending_field.take() {
if prev.is_bottom != is_bottom && prev.frame_num == frame_num {
let (top, bottom) = if prev.is_bottom {
(&pic, &prev.pic)
} else {
(&prev.pic, &pic)
};
let frame = interleave_fields(top, bottom);
let frame_poc = prev.field_poc.min(field_poc);
let frame_pts = prev.pts.or(pts);
let vf = picture_to_video_frame(&frame, frame_pts, crop);
let entry = OutputEntry {
picture: vf,
pic_order_cnt: frame_poc,
frame_num,
needed_for_output: true,
};
if let Some(bumped) = self.output_dpb.push(entry) {
self.ready.push_back(bumped.picture);
}
return;
}
self.emit_unpaired_field(prev);
}
self.pending_field = Some(PendingField {
pic,
is_bottom,
frame_num,
field_poc,
pts,
crop,
});
}
fn emit_unpaired_field(&mut self, field: PendingField) {
let vf = picture_to_video_frame(&field.pic, field.pts, field.crop.for_field());
let entry = OutputEntry {
picture: vf,
pic_order_cnt: field.field_poc,
frame_num: field.frame_num,
needed_for_output: true,
};
if let Some(bumped) = self.output_dpb.push(entry) {
self.ready.push_back(bumped.picture);
}
}
fn flush_pending_field(&mut self) {
if let Some(field) = self.pending_field.take() {
self.emit_unpaired_field(field);
}
}
fn ensure_output_dpb_sized(&mut self, sps: &Sps) {
let (reorder, buffering) = output_dpb_sizing(sps);
if self.output_dpb.max_num_reorder_frames != reorder
|| self.output_dpb.max_dec_frame_buffering != buffering
{
let pending = self.output_dpb.flush();
let mut new_dpb = DpbOutput::<VideoFrame>::new(reorder, buffering);
for e in pending {
if let Some(bumped) = new_dpb.push(e) {
self.ready.push_back(bumped.picture);
}
}
self.output_dpb = new_dpb;
}
}
fn mint_dpb_key(&mut self) -> u32 {
let k = self.next_dpb_key;
self.next_dpb_key = self.next_dpb_key.wrapping_add(1);
k
}
fn prune_ref_store(&mut self) {
let live: Vec<u32> = self.dpb_entries.iter().map(|e| e.dpb_key).collect();
self.ref_store.retain_keys(&live);
}
fn fill_frame_num_gap(&mut self, sps: &Sps, current_frame_num: u32) -> Result<()> {
let max_frame_num: u32 = 1u32 << (sps.log2_max_frame_num_minus4 + 4);
let Some(prev) = self.prev_ref_frame_num else {
return Ok(());
};
let mut expected = (prev + 1) % max_frame_num;
if expected == current_frame_num {
return Ok(());
}
let first_gap_key = self.next_dpb_key;
let mut iterations: u32 = 0;
while expected != current_frame_num && iterations < max_frame_num {
if matches!(sps.pic_order_cnt_type, 1 | 2) {
let prev_fnum_offset = self.poc_state.prev_frame_num_offset;
let new_offset = if self.poc_state.prev_frame_num > expected {
prev_fnum_offset + max_frame_num as i64
} else {
prev_fnum_offset
};
self.poc_state.prev_frame_num_offset = new_offset;
}
self.poc_state.prev_frame_num = expected;
let (top_foc, bot_foc, poc_value) = non_existing_poc(sps, &self.poc_state, expected);
ref_list::sliding_window_marking(
&mut self.dpb_entries,
sps.max_num_ref_frames,
expected,
max_frame_num,
None,
);
self.dpb_entries.retain(|e| e.is_any_field_ref());
let key = self.mint_dpb_key();
let mut entry = DpbEntry {
frame_num: expected,
top_field_order_cnt: top_foc,
bottom_field_order_cnt: bot_foc,
pic_order_cnt: poc_value,
structure: PicStructure::Frame,
marking: RefMarking::ShortTerm,
long_term_frame_idx: 0,
dpb_key: key,
field_markings: [RefMarking::Unused; 2],
};
entry.sync_field_markings();
self.dpb_entries.push(entry);
self.prev_ref_frame_num = Some(expected);
expected = (expected + 1) % max_frame_num;
iterations += 1;
}
if iterations > 0 {
let gray = gray_picture(
sps.pic_width_in_mbs() * 16,
sps.frame_height_in_mbs() * 16,
sps.chroma_array_type(),
sps.bit_depth_luma_minus8 + 8,
sps.bit_depth_chroma_minus8 + 8,
);
let gap_keys = first_gap_key..self.next_dpb_key;
for entry in &self.dpb_entries {
if gap_keys.contains(&entry.dpb_key) {
self.ref_store.insert(entry.dpb_key, gray.clone());
}
}
self.prune_ref_store();
}
Ok(())
}
}
fn non_existing_poc(sps: &Sps, state: &PocState, frame_num: u32) -> (i32, i32, i32) {
match sps.pic_order_cnt_type {
2 => {
let poc = 2 * (state.prev_frame_num_offset + frame_num as i64);
let poc = poc.clamp(i32::MIN as i64, i32::MAX as i64) as i32;
(poc, poc, poc)
}
1 => {
(0, 0, 0)
}
_ => (0, 0, 0),
}
}
fn gray_picture(
width_samples: u32,
height_samples: u32,
chroma_array_type: u32,
bit_depth_y: u32,
bit_depth_c: u32,
) -> Picture {
let mut p = Picture::new(
width_samples,
height_samples,
chroma_array_type,
bit_depth_y,
bit_depth_c,
);
p.non_existing = true;
let grey_y: i32 = 1 << (bit_depth_y.saturating_sub(1));
let grey_c: i32 = 1 << (bit_depth_c.saturating_sub(1));
for v in p.luma.iter_mut() {
*v = grey_y;
}
for v in p.cb.iter_mut() {
*v = grey_c;
}
for v in p.cr.iter_mut() {
*v = grey_c;
}
p
}
type FieldListPocsLt = (Vec<i32>, Vec<bool>, Vec<i32>, Vec<bool>);
struct ResolvedFieldList {
keys: Vec<u32>,
overrides: Vec<Option<Picture>>,
pocs: Vec<i32>,
longterm: Vec<bool>,
parities: Vec<Option<u8>>,
unit_keys: Vec<u32>,
}
struct BorrowedRefProvider<'a> {
store: &'a RefPicStore,
list_0: &'a [u32],
list_1: &'a [u32],
list_0_pocs: Vec<i32>,
list_0_longterm: Vec<bool>,
list_1_longterm: Vec<bool>,
list_0_overrides: Vec<Option<Picture>>,
list_1_overrides: Vec<Option<Picture>>,
list_0_parities: Vec<Option<u8>>,
list_1_parities: Vec<Option<u8>>,
list_0_unit_keys: Vec<u32>,
list_1_unit_keys: Vec<u32>,
list_0_focs: Vec<(i32, i32)>,
list_1_focs: Vec<(i32, i32)>,
list_0_pair_keys: Vec<Option<(u32, u32)>>,
list_1_pair_keys: Vec<Option<(u32, u32)>>,
}
impl RefPicProvider for BorrowedRefProvider<'_> {
fn ref_pic(&self, list: u8, idx: u32) -> Option<&Picture> {
let (keys, overrides) = match list {
0 => (self.list_0, &self.list_0_overrides),
1 => (self.list_1, &self.list_1_overrides),
_ => return None,
};
if let Some(Some(p)) = overrides.get(idx as usize) {
return Some(p);
}
let key = *keys.get(idx as usize)?;
self.store.get_by_key(key)
}
fn ref_field_parity(&self, list: u8, idx: u32) -> Option<u8> {
let parities = match list {
0 => &self.list_0_parities,
1 => &self.list_1_parities,
_ => return None,
};
parities.get(idx as usize).copied().flatten()
}
fn ref_list_0_pocs(&self) -> &[i32] {
&self.list_0_pocs
}
fn ref_list_0_longterm(&self) -> &[bool] {
&self.list_0_longterm
}
fn ref_list_1_longterm(&self) -> &[bool] {
&self.list_1_longterm
}
fn ref_list_0_keys(&self) -> &[u32] {
self.list_0
}
fn ref_list_0_unit_keys(&self) -> &[u32] {
&self.list_0_unit_keys
}
fn ref_list_0_parities(&self) -> &[Option<u8>] {
&self.list_0_parities
}
fn ref_entry_identity(&self, list: u8, idx: u32) -> Option<(u32, Option<u8>, u32)> {
let (keys, parities, unit_keys) = match list {
0 => (self.list_0, &self.list_0_parities, &self.list_0_unit_keys),
1 => (self.list_1, &self.list_1_parities, &self.list_1_unit_keys),
_ => return None,
};
let key = *keys.get(idx as usize)?;
let parity = parities.get(idx as usize).copied().flatten();
let unit = unit_keys.get(idx as usize).copied().unwrap_or(key);
Some((key, parity, unit))
}
fn ref_entry_unit_focs(&self, list: u8, idx: u32) -> Option<(i32, i32)> {
let focs = match list {
0 => &self.list_0_focs,
1 => &self.list_1_focs,
_ => return None,
};
focs.get(idx as usize).copied()
}
fn ref_pair_field(&self, list: u8, idx: u32, bottom: bool) -> Option<&Picture> {
let pair_keys = match list {
0 => &self.list_0_pair_keys,
1 => &self.list_1_pair_keys,
_ => return None,
};
let (top_key, bottom_key) = (*pair_keys.get(idx as usize)?)?;
self.store
.get_by_key(if bottom { bottom_key } else { top_key })
}
}
fn output_dpb_sizing(sps: &Sps) -> (u32, u32) {
let constraint_set3_flag = (sps.constraint_set_flags & 0b0000_1000) != 0;
let max_dpb_mbs = max_dpb_mbs_for_level(sps.level_idc, constraint_set3_flag);
let pic_size_mbs = sps
.pic_width_in_mbs()
.saturating_mul(sps.frame_height_in_mbs())
.max(1);
let level_cap = (max_dpb_mbs / pic_size_mbs).clamp(1, 16);
if let Some(br) = sps
.vui
.as_ref()
.and_then(|v| v.bitstream_restriction.as_ref())
{
let reorder = br.max_num_reorder_frames.max(level_cap);
let buffering = br
.max_dec_frame_buffering
.max(reorder)
.max(level_cap)
.max(1);
return (reorder, buffering);
}
(level_cap, level_cap)
}
fn max_dpb_mbs_for_level(level_idc: u8, constraint_set3_flag: bool) -> u32 {
match level_idc {
9 => 396, 10 => 396, 11 => {
if constraint_set3_flag {
396 } else {
900 }
}
12 => 2_376, 13 => 2_376, 20 => 2_376, 21 => 4_752, 22 => 8_100, 30 => 8_100, 31 => 18_000,
32 => 20_480,
40 => 32_768,
41 => 32_768,
42 => 34_816,
50 => 110_400,
51 => 184_320,
52 => 184_320,
60 => 696_320,
61 => 696_320,
62 => 696_320,
_ => 396, }
}
fn pic_structure_from_flags(field_pic_flag: bool, bottom_field_flag: bool) -> PicStructure {
match (field_pic_flag, bottom_field_flag) {
(false, _) => PicStructure::Frame,
(true, false) => PicStructure::TopField,
(true, true) => PicStructure::BottomField,
}
}
fn make_poc_sps(sps: &Sps) -> PocSps {
PocSps {
pic_order_cnt_type: sps.pic_order_cnt_type,
log2_max_frame_num_minus4: sps.log2_max_frame_num_minus4,
log2_max_pic_order_cnt_lsb_minus4: sps.log2_max_pic_order_cnt_lsb_minus4,
delta_pic_order_always_zero_flag: sps.delta_pic_order_always_zero_flag,
offset_for_non_ref_pic: sps.offset_for_non_ref_pic,
offset_for_top_to_bottom_field: sps.offset_for_top_to_bottom_field,
num_ref_frames_in_pic_order_cnt_cycle: sps.num_ref_frames_in_pic_order_cnt_cycle,
offset_for_ref_frame: sps.offset_for_ref_frame.clone(),
frame_mbs_only_flag: sps.frame_mbs_only_flag,
}
}
fn slice_rplm_to_ref_rplm(op: &SliceRplmOp) -> RplmOp {
match *op {
SliceRplmOp::Subtract(v) => RplmOp::Subtract(v),
SliceRplmOp::Add(v) => RplmOp::Add(v),
SliceRplmOp::LongTerm(v) => RplmOp::LongTerm(v),
}
}
fn slice_mmco_to_ref_mmco(op: &SliceMmcoOp) -> RefMmcoOp {
match *op {
SliceMmcoOp::MarkShortTermUnused(v) => RefMmcoOp::MarkShortTermUnused(v),
SliceMmcoOp::MarkLongTermUnused(v) => RefMmcoOp::MarkLongTermUnused(v),
SliceMmcoOp::AssignLongTerm(d, ltfi) => RefMmcoOp::AssignLongTerm(d, ltfi),
SliceMmcoOp::SetMaxLongTermIdx(v) => RefMmcoOp::SetMaxLongTermIdx(v),
SliceMmcoOp::MarkAllUnused => RefMmcoOp::MarkAllUnused,
SliceMmcoOp::AssignCurrentLongTerm(v) => RefMmcoOp::AssignCurrentLongTerm(v),
}
}
impl Decoder for H264CodecDecoder {
fn codec_id(&self) -> &CodecId {
&self.codec_id
}
fn send_packet(&mut self, packet: &Packet) -> Result<()> {
self.pending_pts = packet.pts;
self.pending_time_base = packet.time_base;
let data = packet.data.clone();
match self.length_size {
Some(n) => {
let mut i = 0usize;
let n = n as usize;
while i < data.len() {
if i + n > data.len() {
return Err(Error::invalid("h264: AVCC length prefix truncated"));
}
let mut len = 0usize;
for k in 0..n {
len = (len << 8) | data[i + k] as usize;
}
i += n;
if i + len > data.len() {
return Err(Error::invalid("h264: AVCC NAL payload truncated"));
}
let ev = self
.driver
.process_nal(&data[i..i + len])
.map_err(|e| Error::invalid(format!("h264 NAL parse: {e}")))?;
if let Err(e) = self.handle_event(ev) {
self.decode_errors += 1;
eprintln!("h264 slice skipped: {e}");
}
i += len;
}
Ok(())
}
None => {
let events: Vec<_> = self.driver.process_annex_b(&data).collect();
for ev in events {
match ev {
Ok(ev) => {
if let Err(e) = self.handle_event(ev) {
self.decode_errors += 1;
eprintln!("h264 slice skipped: {e}");
}
}
Err(e) => {
return Err(Error::invalid(format!("h264 NAL parse: {e}")));
}
}
}
Ok(())
}
}
}
fn receive_frame(&mut self) -> Result<Frame> {
if let Some(vf) = self.ready.pop_front() {
return Ok(Frame::Video(vf));
}
if let Some(bumped) = self.output_dpb.pop_ready() {
return Ok(Frame::Video(bumped.picture));
}
if self.eof {
let drained = self.output_dpb.flush();
if drained.is_empty() {
return Err(Error::Eof);
}
for e in drained {
self.ready.push_back(e.picture);
}
if let Some(vf) = self.ready.pop_front() {
return Ok(Frame::Video(vf));
}
return Err(Error::Eof);
}
Err(Error::NeedMore)
}
fn flush(&mut self) -> Result<()> {
self.flush_pending_dp_slice()?;
if let Err(e) = self.finalize_in_progress_picture() {
self.decode_errors += 1;
eprintln!("h264 flush: final picture skipped: {e}");
}
self.flush_pending_field();
if let Some(scp) = self.scp.as_mut() {
for sub in scp.subs.iter_mut() {
sub.flush()?;
}
self.drain_and_merge_scp();
}
self.eof = true;
Ok(())
}
fn reset(&mut self) -> Result<()> {
self.driver = H264Driver::new();
self.last_slice = None;
self.decode_errors = 0;
self.eof = false;
self.output_dpb.reset();
self.ready.clear();
self.pending_pts = None;
self.ref_store = RefPicStore::new();
self.dpb_entries.clear();
self.poc_state = PocState::default();
self.next_dpb_key = 0;
self.prev_had_mmco5 = false;
self.prev_reference_top_foc = 0;
self.prev_ref_frame_num = None;
self.in_progress = None;
self.pending_field = None;
self.scp = None;
Ok(())
}
}
fn snapshot_grid_into_picture(pic: &mut Picture, grid: &MbGrid) {
let w = grid.width_in_mbs as usize;
let h = grid.height_in_mbs as usize;
let nmb = w * h;
pic.mb_width_in_picture = grid.width_in_mbs;
pic.mv_l0_grid = vec![(0i16, 0i16); nmb * 16];
pic.mv_l1_grid = vec![(0i16, 0i16); nmb * 16];
pic.ref_idx_l0_grid = vec![-1i8; nmb * 4];
pic.ref_idx_l1_grid = vec![-1i8; nmb * 4];
pic.is_intra_grid = vec![false; nmb];
pic.mb_field_flags = vec![false; nmb];
for (addr, info) in grid.info.iter().enumerate() {
let base_mv = addr * 16;
let base_r = addr * 4;
for blk4 in 0..16 {
pic.mv_l0_grid[base_mv + blk4] = info.mv_l0[blk4];
pic.mv_l1_grid[base_mv + blk4] = info.mv_l1[blk4];
}
for blk8 in 0..4 {
pic.ref_idx_l0_grid[base_r + blk8] = info.ref_idx_l0[blk8];
pic.ref_idx_l1_grid[base_r + blk8] = info.ref_idx_l1[blk8];
}
pic.is_intra_grid[addr] = info.is_intra;
pic.mb_field_flags[addr] = info.mb_field_decoding_flag;
}
}
fn interleave_fields(top: &Picture, bottom: &Picture) -> Picture {
let w = top.width_in_samples;
let field_h = top.height_in_samples;
let frame_h = field_h * 2;
let mut frame = Picture::new(
w,
frame_h,
top.chroma_array_type,
top.bit_depth_luma,
top.bit_depth_chroma,
);
let wl = w as usize;
for r in 0..field_h as usize {
let src = &top.luma[r * wl..r * wl + wl];
let dst_row = 2 * r;
frame.luma[dst_row * wl..dst_row * wl + wl].copy_from_slice(src);
let src_b = &bottom.luma[r * wl..r * wl + wl];
let dst_row_b = 2 * r + 1;
frame.luma[dst_row_b * wl..dst_row_b * wl + wl].copy_from_slice(src_b);
}
if top.chroma_array_type != 0 {
let cw = top.chroma_width() as usize;
let cfh = top.chroma_height() as usize;
for r in 0..cfh {
let dst_row = 2 * r;
let dst_row_b = 2 * r + 1;
frame.cb[dst_row * cw..dst_row * cw + cw].copy_from_slice(&top.cb[r * cw..r * cw + cw]);
frame.cb[dst_row_b * cw..dst_row_b * cw + cw]
.copy_from_slice(&bottom.cb[r * cw..r * cw + cw]);
frame.cr[dst_row * cw..dst_row * cw + cw].copy_from_slice(&top.cr[r * cw..r * cw + cw]);
frame.cr[dst_row_b * cw..dst_row_b * cw + cw]
.copy_from_slice(&bottom.cr[r * cw..r * cw + cw]);
}
}
frame.pic_order_cnt = top.pic_order_cnt.min(bottom.pic_order_cnt);
frame.frame_num = top.frame_num;
frame
}
fn picture_to_video_frame(pic: &Picture, pts: Option<i64>, crop: FrameCropMargins) -> VideoFrame {
let w = pic.width_in_samples as usize;
let h = pic.height_in_samples as usize;
let cw = pic.chroma_width() as usize;
let left = (crop.left as usize).min(w);
let top = (crop.top as usize).min(h);
let out_w = w - left - (crop.right as usize).min(w - left);
let out_h = h - top - (crop.bottom as usize).min(h - top);
let (sub_w, sub_h) = match pic.chroma_array_type {
1 => (2, 2),
2 => (2, 1),
_ => (1, 1),
};
let luma_wide = pic.bit_depth_luma > 8;
let chroma_wide = pic.bit_depth_chroma > 8;
let luma_max: i32 = (1i32 << pic.bit_depth_luma) - 1;
let chroma_max: i32 = (1i32 << pic.bit_depth_chroma) - 1;
#[allow(clippy::too_many_arguments)]
fn pack(
src: &[i32],
src_stride: usize,
x0: usize,
y0: usize,
rect_w: usize,
rect_h: usize,
wide: bool,
max: i32,
) -> Vec<u8> {
let mut out = Vec::with_capacity(rect_w * rect_h * if wide { 2 } else { 1 });
for row in src.chunks(src_stride.max(1)).skip(y0).take(rect_h) {
let row = &row[x0.min(row.len())..(x0 + rect_w).min(row.len())];
if wide {
for &s in row {
out.extend_from_slice(&(s.clamp(0, max) as u16).to_le_bytes());
}
} else {
out.extend(row.iter().map(|&s| s.clamp(0, 255) as u8));
}
}
out
}
let luma_data = pack(&pic.luma, w, left, top, out_w, out_h, luma_wide, luma_max);
let luma_stride = if luma_wide { out_w * 2 } else { out_w };
let mut planes = vec![VideoPlane {
stride: luma_stride,
data: luma_data,
}];
if pic.chroma_array_type != 0 {
let (cx, cy) = (left / sub_w, top / sub_h);
let (ow, oh) = (out_w / sub_w, out_h / sub_h);
let chroma_stride = if chroma_wide { ow * 2 } else { ow };
for src in [&pic.cb, &pic.cr] {
planes.push(VideoPlane {
stride: chroma_stride,
data: pack(src, cw, cx, cy, ow, oh, chroma_wide, chroma_max),
});
}
}
VideoFrame { pts, planes }
}
#[cfg(test)]
mod tests {
use super::*;
use crate::dpb_output::OutputEntry;
fn vf(tag: u8) -> VideoFrame {
VideoFrame {
pts: None,
planes: vec![VideoPlane {
stride: 1,
data: vec![tag],
}],
}
}
fn vf_tag(f: &Frame) -> u8 {
match f {
Frame::Video(v) => v.planes[0].data[0],
_ => panic!("non-video frame"),
}
}
fn push_entry(dec: &mut H264CodecDecoder, tag: u8, poc: i32, frame_num: u32) {
let entry = OutputEntry {
picture: vf(tag),
pic_order_cnt: poc,
frame_num,
needed_for_output: true,
};
if let Some(bumped) = dec.output_dpb.push(entry) {
dec.ready.push_back(bumped.picture);
}
}
#[test]
fn reorder_with_small_dpb_matches_conservative_bumping() {
let mut dec = H264CodecDecoder::new(CodecId::new("h264"));
dec.output_dpb = DpbOutput::<VideoFrame>::new(2, 3);
push_entry(&mut dec, 10, 0, 0); push_entry(&mut dec, 11, 4, 1); push_entry(&mut dec, 12, 2, 2); push_entry(&mut dec, 13, 1, 3); push_entry(&mut dec, 14, 3, 4);
dec.flush().expect("flush");
let mut tags = Vec::new();
while let Ok(f) = dec.receive_frame() {
tags.push(vf_tag(&f));
}
assert_eq!(tags, vec![10, 12, 13, 14, 11]);
}
#[test]
fn reorder_with_sufficient_dpb_yields_strictly_ascending_poc() {
let mut dec = H264CodecDecoder::new(CodecId::new("h264"));
dec.output_dpb = DpbOutput::<VideoFrame>::new(5, 5);
push_entry(&mut dec, 10, 0, 0); push_entry(&mut dec, 11, 4, 1); push_entry(&mut dec, 12, 2, 2); push_entry(&mut dec, 13, 1, 3); push_entry(&mut dec, 14, 3, 4);
dec.flush().expect("flush");
let mut tags = Vec::new();
let mut pocs = Vec::new();
while let Ok(f) = dec.receive_frame() {
tags.push(vf_tag(&f));
let poc = match vf_tag(&f) {
10 => 0,
11 => 4,
12 => 2,
13 => 1,
14 => 3,
_ => unreachable!(),
};
pocs.push(poc);
}
assert_eq!(tags, vec![10, 13, 12, 14, 11]);
assert_eq!(pocs, vec![0, 1, 2, 3, 4]);
}
#[test]
fn flush_at_eof_drains_in_poc_order() {
let mut dec = H264CodecDecoder::new(CodecId::new("h264"));
dec.output_dpb = DpbOutput::<VideoFrame>::new(8, 8);
push_entry(&mut dec, 0xA0, 3, 0);
push_entry(&mut dec, 0xA1, 1, 1);
push_entry(&mut dec, 0xA2, 2, 2);
assert!(matches!(dec.receive_frame(), Err(Error::NeedMore)));
dec.flush().expect("flush");
let tags: Vec<u8> =
std::iter::from_fn(|| dec.receive_frame().ok().map(|f| vf_tag(&f))).collect();
assert_eq!(tags, vec![0xA1, 0xA2, 0xA0]);
}
#[test]
fn eof_on_empty_queue_after_flush() {
let mut dec = H264CodecDecoder::new(CodecId::new("h264"));
dec.flush().expect("flush");
assert!(matches!(dec.receive_frame(), Err(Error::Eof)));
}
#[test]
fn need_more_before_any_push() {
let mut dec = H264CodecDecoder::new(CodecId::new("h264"));
assert!(matches!(dec.receive_frame(), Err(Error::NeedMore)));
}
#[test]
fn idr_drains_pending_pictures_in_poc_order() {
let mut dec = H264CodecDecoder::new(CodecId::new("h264"));
dec.output_dpb = DpbOutput::<VideoFrame>::new(4, 4);
push_entry(&mut dec, 1, 0, 0);
push_entry(&mut dec, 2, 4, 1);
push_entry(&mut dec, 3, 2, 2);
push_entry(&mut dec, 4, 1, 3);
for drained in dec.output_dpb.flush() {
dec.ready.push_back(drained.picture);
}
dec.output_dpb.reset();
push_entry(&mut dec, 100, 0, 0);
dec.flush().expect("flush");
let tags: Vec<u8> =
std::iter::from_fn(|| dec.receive_frame().ok().map(|f| vf_tag(&f))).collect();
assert_eq!(tags, vec![1, 4, 3, 2, 100]);
}
#[test]
fn reset_clears_output_dpb_and_ready() {
let mut dec = H264CodecDecoder::new(CodecId::new("h264"));
dec.output_dpb = DpbOutput::<VideoFrame>::new(2, 2);
push_entry(&mut dec, 1, 0, 0);
push_entry(&mut dec, 2, 1, 1);
push_entry(&mut dec, 3, 2, 2); assert_eq!(dec.ready.len(), 1);
assert_eq!(dec.output_dpb.len(), 2);
dec.reset().expect("reset");
assert_eq!(dec.output_dpb.len(), 0);
assert!(dec.ready.is_empty());
assert!(matches!(dec.receive_frame(), Err(Error::NeedMore)));
}
#[test]
fn dpb_sizing_honours_vui_when_at_or_above_level_cap() {
use crate::sps::Sps;
use crate::vui::{BitstreamRestriction, VuiParameters};
let sps = Sps {
profile_idc: 66,
constraint_set_flags: 0,
level_idc: 30,
seq_parameter_set_id: 0,
chroma_format_idc: 1,
separate_colour_plane_flag: false,
bit_depth_luma_minus8: 0,
bit_depth_chroma_minus8: 0,
qpprime_y_zero_transform_bypass_flag: false,
seq_scaling_matrix_present_flag: false,
seq_scaling_lists: None,
log2_max_frame_num_minus4: 0,
pic_order_cnt_type: 0,
log2_max_pic_order_cnt_lsb_minus4: 0,
delta_pic_order_always_zero_flag: false,
offset_for_non_ref_pic: 0,
offset_for_top_to_bottom_field: 0,
num_ref_frames_in_pic_order_cnt_cycle: 0,
offset_for_ref_frame: Vec::new(),
max_num_ref_frames: 4,
gaps_in_frame_num_value_allowed_flag: false,
pic_width_in_mbs_minus1: 10,
pic_height_in_map_units_minus1: 8,
frame_mbs_only_flag: true,
mb_adaptive_frame_field_flag: false,
direct_8x8_inference_flag: true,
frame_cropping: None,
vui_parameters_present_flag: true,
vui: Some(VuiParameters {
bitstream_restriction: Some(BitstreamRestriction {
motion_vectors_over_pic_boundaries_flag: true,
max_bytes_per_pic_denom: 0,
max_bits_per_mb_denom: 0,
log2_max_mv_length_horizontal: 0,
log2_max_mv_length_vertical: 0,
max_num_reorder_frames: 16,
max_dec_frame_buffering: 16,
}),
..Default::default()
}),
};
assert_eq!(output_dpb_sizing(&sps), (16, 16));
}
#[test]
fn dpb_sizing_raises_undersized_vui_to_level_cap() {
use crate::sps::Sps;
use crate::vui::{BitstreamRestriction, VuiParameters};
let sps = Sps {
profile_idc: 100, constraint_set_flags: 0,
level_idc: 31, seq_parameter_set_id: 0,
chroma_format_idc: 1,
separate_colour_plane_flag: false,
bit_depth_luma_minus8: 0,
bit_depth_chroma_minus8: 0,
qpprime_y_zero_transform_bypass_flag: false,
seq_scaling_matrix_present_flag: false,
seq_scaling_lists: None,
log2_max_frame_num_minus4: 0,
pic_order_cnt_type: 0,
log2_max_pic_order_cnt_lsb_minus4: 0,
delta_pic_order_always_zero_flag: false,
offset_for_non_ref_pic: 0,
offset_for_top_to_bottom_field: 0,
num_ref_frames_in_pic_order_cnt_cycle: 0,
offset_for_ref_frame: Vec::new(),
max_num_ref_frames: 5,
gaps_in_frame_num_value_allowed_flag: false,
pic_width_in_mbs_minus1: 79,
pic_height_in_map_units_minus1: 44,
frame_mbs_only_flag: true,
mb_adaptive_frame_field_flag: false,
direct_8x8_inference_flag: true,
frame_cropping: None,
vui_parameters_present_flag: true,
vui: Some(VuiParameters {
bitstream_restriction: Some(BitstreamRestriction {
motion_vectors_over_pic_boundaries_flag: true,
max_bytes_per_pic_denom: 0,
max_bits_per_mb_denom: 0,
log2_max_mv_length_horizontal: 0,
log2_max_mv_length_vertical: 0,
max_num_reorder_frames: 2, max_dec_frame_buffering: 5,
}),
..Default::default()
}),
};
assert_eq!(output_dpb_sizing(&sps), (5, 5));
}
#[test]
fn b_pyramid_emits_in_poc_order_at_level_31_720p() {
let mut dec = H264CodecDecoder::new(CodecId::new("h264"));
dec.output_dpb = DpbOutput::<VideoFrame>::new(5, 5);
push_entry(&mut dec, 1, 0, 0); push_entry(&mut dec, 5, 8, 1); push_entry(&mut dec, 3, 4, 2); push_entry(&mut dec, 2, 2, 3); push_entry(&mut dec, 4, 6, 3); push_entry(&mut dec, 9, 16, 4); push_entry(&mut dec, 7, 12, 5); push_entry(&mut dec, 6, 10, 5); push_entry(&mut dec, 8, 14, 5);
dec.flush().expect("flush");
let mut tags = Vec::new();
while let Ok(f) = dec.receive_frame() {
tags.push(vf_tag(&f));
}
assert_eq!(tags, vec![1, 2, 3, 4, 5, 6, 7, 8, 9]);
}
#[test]
fn dpb_sizing_falls_back_to_level_default() {
use crate::sps::Sps;
let sps = Sps {
profile_idc: 66,
constraint_set_flags: 0,
level_idc: 30, seq_parameter_set_id: 0,
chroma_format_idc: 1,
separate_colour_plane_flag: false,
bit_depth_luma_minus8: 0,
bit_depth_chroma_minus8: 0,
qpprime_y_zero_transform_bypass_flag: false,
seq_scaling_matrix_present_flag: false,
seq_scaling_lists: None,
log2_max_frame_num_minus4: 0,
pic_order_cnt_type: 0,
log2_max_pic_order_cnt_lsb_minus4: 0,
delta_pic_order_always_zero_flag: false,
offset_for_non_ref_pic: 0,
offset_for_top_to_bottom_field: 0,
num_ref_frames_in_pic_order_cnt_cycle: 0,
offset_for_ref_frame: Vec::new(),
max_num_ref_frames: 4,
gaps_in_frame_num_value_allowed_flag: false,
pic_width_in_mbs_minus1: 10, pic_height_in_map_units_minus1: 8, frame_mbs_only_flag: true,
mb_adaptive_frame_field_flag: false,
direct_8x8_inference_flag: true,
frame_cropping: None,
vui_parameters_present_flag: false,
vui: None,
};
assert_eq!(output_dpb_sizing(&sps), (16, 16));
}
#[test]
fn max_dpb_mbs_per_level_table() {
assert_eq!(max_dpb_mbs_for_level(10, false), 396);
assert_eq!(max_dpb_mbs_for_level(12, false), 2_376);
assert_eq!(max_dpb_mbs_for_level(21, false), 4_752);
assert_eq!(max_dpb_mbs_for_level(30, false), 8_100);
assert_eq!(max_dpb_mbs_for_level(31, false), 18_000);
assert_eq!(max_dpb_mbs_for_level(40, false), 32_768);
assert_eq!(max_dpb_mbs_for_level(42, false), 34_816);
assert_eq!(max_dpb_mbs_for_level(50, false), 110_400);
assert_eq!(max_dpb_mbs_for_level(51, false), 184_320);
assert_eq!(max_dpb_mbs_for_level(60, false), 696_320);
assert_eq!(max_dpb_mbs_for_level(200, false), 396);
}
#[test]
fn max_dpb_mbs_level_1b_versus_level_1_1() {
assert_eq!(max_dpb_mbs_for_level(11, false), 900);
assert_eq!(max_dpb_mbs_for_level(11, true), 396);
assert_eq!(max_dpb_mbs_for_level(9, false), 396);
assert_eq!(max_dpb_mbs_for_level(9, true), 396);
}
#[test]
fn level_1b_qcif_stream_sized_at_level_1b_cap_not_level_1_1() {
let mut sps = Sps {
profile_idc: 66,
constraint_set_flags: 0b0000_1000, level_idc: 11,
seq_parameter_set_id: 0,
chroma_format_idc: 1,
separate_colour_plane_flag: false,
bit_depth_luma_minus8: 0,
bit_depth_chroma_minus8: 0,
qpprime_y_zero_transform_bypass_flag: false,
seq_scaling_matrix_present_flag: false,
seq_scaling_lists: None,
log2_max_frame_num_minus4: 0,
pic_order_cnt_type: 0,
log2_max_pic_order_cnt_lsb_minus4: 0,
delta_pic_order_always_zero_flag: false,
offset_for_non_ref_pic: 0,
offset_for_top_to_bottom_field: 0,
num_ref_frames_in_pic_order_cnt_cycle: 0,
offset_for_ref_frame: Vec::new(),
max_num_ref_frames: 1,
gaps_in_frame_num_value_allowed_flag: false,
pic_width_in_mbs_minus1: 10, pic_height_in_map_units_minus1: 8, frame_mbs_only_flag: true,
mb_adaptive_frame_field_flag: false,
direct_8x8_inference_flag: true,
frame_cropping: None,
vui_parameters_present_flag: false,
vui: None,
};
let (reorder, buffering) = output_dpb_sizing(&sps);
assert_eq!(reorder, 4);
assert_eq!(buffering, 4);
sps.constraint_set_flags = 0;
let (reorder, buffering) = output_dpb_sizing(&sps);
assert_eq!(reorder, 9);
assert_eq!(buffering, 9);
}
use crate::poc::PocResult;
use crate::ref_list::PicStructure;
use crate::slice_header::{RefPicListModification, SliceHeader as Hdr, SliceType as ST};
fn test_sps() -> crate::sps::Sps {
crate::sps::Sps {
profile_idc: 66,
constraint_set_flags: 0,
level_idc: 10,
seq_parameter_set_id: 0,
chroma_format_idc: 1,
separate_colour_plane_flag: false,
bit_depth_luma_minus8: 0,
bit_depth_chroma_minus8: 0,
qpprime_y_zero_transform_bypass_flag: false,
seq_scaling_matrix_present_flag: false,
seq_scaling_lists: None,
log2_max_frame_num_minus4: 0,
pic_order_cnt_type: 0,
log2_max_pic_order_cnt_lsb_minus4: 0,
delta_pic_order_always_zero_flag: false,
offset_for_non_ref_pic: 0,
offset_for_top_to_bottom_field: 0,
num_ref_frames_in_pic_order_cnt_cycle: 0,
offset_for_ref_frame: Vec::new(),
max_num_ref_frames: 1,
gaps_in_frame_num_value_allowed_flag: false,
pic_width_in_mbs_minus1: 0,
pic_height_in_map_units_minus1: 0,
frame_mbs_only_flag: true,
mb_adaptive_frame_field_flag: false,
direct_8x8_inference_flag: false,
frame_cropping: None,
vui_parameters_present_flag: false,
vui: None,
}
}
fn test_pps() -> crate::pps::Pps {
crate::pps::Pps {
pic_parameter_set_id: 0,
seq_parameter_set_id: 0,
entropy_coding_mode_flag: false,
bottom_field_pic_order_in_frame_present_flag: false,
num_slice_groups_minus1: 0,
slice_group_map: None,
num_ref_idx_l0_default_active_minus1: 0,
num_ref_idx_l1_default_active_minus1: 0,
weighted_pred_flag: false,
weighted_bipred_idc: 0,
pic_init_qp_minus26: 0,
pic_init_qs_minus26: 0,
chroma_qp_index_offset: 0,
deblocking_filter_control_present_flag: false,
constrained_intra_pred_flag: false,
redundant_pic_cnt_present_flag: false,
extension: None,
}
}
fn hdr_base() -> Hdr {
Hdr {
first_mb_in_slice: 0,
slice_type_raw: 0,
slice_type: ST::P,
all_slices_same_type: false,
pic_parameter_set_id: 0,
colour_plane_id: 0,
frame_num: 0,
field_pic_flag: false,
bottom_field_flag: false,
idr_pic_id: 0,
pic_order_cnt_lsb: 0,
delta_pic_order_cnt_bottom: 0,
delta_pic_order_cnt: [0, 0],
redundant_pic_cnt: 0,
direct_spatial_mv_pred_flag: false,
num_ref_idx_active_override_flag: false,
num_ref_idx_l0_active_minus1: 0,
num_ref_idx_l1_active_minus1: 0,
ref_pic_list_modification: RefPicListModification::default(),
pred_weight_table: None,
dec_ref_pic_marking: None,
cabac_init_idc: 0,
slice_qp_delta: 0,
sp_for_switch_flag: false,
slice_qs_delta: 0,
disable_deblocking_filter_idc: 0,
slice_alpha_c0_offset_div2: 0,
slice_beta_offset_div2: 0,
slice_group_change_cycle: 0,
}
}
fn seed_in_progress(dec: &mut H264CodecDecoder, nut: u8, nri: u8, header: Hdr) {
let pic = Picture::new(16, 16, 1, 8, 8);
let grid = MbGrid::new(1, 1);
dec.in_progress = Some(PictureInProgress {
pic,
grid,
first_nal_unit_type: nut,
first_nal_ref_idc: nri,
first_header: header,
is_reference: nri != 0,
is_idr: nut == 5,
poc: PocResult {
top_field_order_cnt: 0,
bottom_field_order_cnt: 0,
pic_order_cnt: 0,
},
structure: PicStructure::Frame,
pts: None,
time_base: TimeBase::new(1, 1),
deblock_enabled: false,
deblock_alpha_off: 0,
deblock_beta_off: 0,
mb_field_flags: Vec::new(),
sps: test_sps(),
pps: test_pps(),
any_slice_succeeded: true,
});
}
#[test]
fn first_vcl_when_no_picture_in_progress() {
let dec = H264CodecDecoder::new(CodecId::new("h264"));
assert!(dec.is_first_vcl_of_new_picture(1, 2, &hdr_base()));
}
#[test]
fn same_picture_when_all_conditions_match() {
let mut dec = H264CodecDecoder::new(CodecId::new("h264"));
seed_in_progress(&mut dec, 1, 2, hdr_base());
let mut h = hdr_base();
h.first_mb_in_slice = 384;
assert!(!dec.is_first_vcl_of_new_picture(1, 2, &h));
}
#[test]
fn different_frame_num_is_new_picture() {
let mut dec = H264CodecDecoder::new(CodecId::new("h264"));
seed_in_progress(&mut dec, 1, 2, hdr_base());
let mut h = hdr_base();
h.frame_num = 1;
assert!(dec.is_first_vcl_of_new_picture(1, 2, &h));
}
#[test]
fn different_pps_id_is_new_picture() {
let mut dec = H264CodecDecoder::new(CodecId::new("h264"));
seed_in_progress(&mut dec, 1, 2, hdr_base());
let mut h = hdr_base();
h.pic_parameter_set_id = 3;
assert!(dec.is_first_vcl_of_new_picture(1, 2, &h));
}
#[test]
fn different_field_pic_flag_is_new_picture() {
let mut dec = H264CodecDecoder::new(CodecId::new("h264"));
seed_in_progress(&mut dec, 1, 2, hdr_base());
let mut h = hdr_base();
h.field_pic_flag = true;
assert!(dec.is_first_vcl_of_new_picture(1, 2, &h));
}
#[test]
fn different_bottom_field_flag_is_new_picture() {
let mut dec = H264CodecDecoder::new(CodecId::new("h264"));
let mut top = hdr_base();
top.field_pic_flag = true;
top.bottom_field_flag = false;
seed_in_progress(&mut dec, 1, 2, top);
let mut bottom = hdr_base();
bottom.field_pic_flag = true;
bottom.bottom_field_flag = true;
assert!(dec.is_first_vcl_of_new_picture(1, 2, &bottom));
}
#[test]
fn bottom_field_flag_ignored_for_frame_pictures() {
let mut dec = H264CodecDecoder::new(CodecId::new("h264"));
let mut a = hdr_base();
a.field_pic_flag = false;
a.bottom_field_flag = false;
seed_in_progress(&mut dec, 1, 2, a);
let mut b = hdr_base();
b.field_pic_flag = false;
b.bottom_field_flag = true; assert!(!dec.is_first_vcl_of_new_picture(1, 2, &b));
}
#[test]
fn different_nal_ref_idc_zero_ness_is_new_picture() {
let mut dec = H264CodecDecoder::new(CodecId::new("h264"));
seed_in_progress(&mut dec, 1, 2, hdr_base());
assert!(dec.is_first_vcl_of_new_picture(1, 0, &hdr_base()));
}
#[test]
fn same_nal_ref_idc_nonzero_is_same_picture() {
let mut dec = H264CodecDecoder::new(CodecId::new("h264"));
seed_in_progress(&mut dec, 1, 2, hdr_base());
assert!(!dec.is_first_vcl_of_new_picture(1, 1, &hdr_base()));
}
#[test]
fn different_poc_lsb_is_new_picture() {
let mut dec = H264CodecDecoder::new(CodecId::new("h264"));
seed_in_progress(&mut dec, 1, 2, hdr_base());
let mut h = hdr_base();
h.pic_order_cnt_lsb = 4;
assert!(dec.is_first_vcl_of_new_picture(1, 2, &h));
}
#[test]
fn different_delta_poc_bottom_is_new_picture() {
let mut dec = H264CodecDecoder::new(CodecId::new("h264"));
seed_in_progress(&mut dec, 1, 2, hdr_base());
let mut h = hdr_base();
h.delta_pic_order_cnt_bottom = 1;
assert!(dec.is_first_vcl_of_new_picture(1, 2, &h));
}
#[test]
fn different_delta_poc_0_is_new_picture() {
let mut dec = H264CodecDecoder::new(CodecId::new("h264"));
seed_in_progress(&mut dec, 1, 2, hdr_base());
let mut h = hdr_base();
h.delta_pic_order_cnt[0] = 2;
assert!(dec.is_first_vcl_of_new_picture(1, 2, &h));
}
#[test]
fn different_delta_poc_1_is_new_picture() {
let mut dec = H264CodecDecoder::new(CodecId::new("h264"));
seed_in_progress(&mut dec, 1, 2, hdr_base());
let mut h = hdr_base();
h.delta_pic_order_cnt[1] = 3;
assert!(dec.is_first_vcl_of_new_picture(1, 2, &h));
}
#[test]
fn different_idr_flag_is_new_picture() {
let mut dec = H264CodecDecoder::new(CodecId::new("h264"));
seed_in_progress(&mut dec, 1, 2, hdr_base());
let mut h = hdr_base();
h.slice_type_raw = 2;
h.slice_type = ST::I;
assert!(dec.is_first_vcl_of_new_picture(5, 2, &h));
}
#[test]
fn different_idr_pic_id_is_new_picture() {
let mut dec = H264CodecDecoder::new(CodecId::new("h264"));
let mut prev = hdr_base();
prev.idr_pic_id = 0;
prev.slice_type_raw = 2;
prev.slice_type = ST::I;
seed_in_progress(&mut dec, 5, 3, prev);
let mut h = hdr_base();
h.idr_pic_id = 1;
h.slice_type_raw = 2;
h.slice_type = ST::I;
assert!(dec.is_first_vcl_of_new_picture(5, 3, &h));
}
#[test]
fn same_idr_pic_id_is_same_picture() {
let mut dec = H264CodecDecoder::new(CodecId::new("h264"));
let mut prev = hdr_base();
prev.idr_pic_id = 7;
prev.slice_type_raw = 2;
prev.slice_type = ST::I;
seed_in_progress(&mut dec, 5, 3, prev.clone());
let mut h = prev;
h.first_mb_in_slice = 384;
assert!(!dec.is_first_vcl_of_new_picture(5, 3, &h));
}
fn baseline_avcc_zero_sps_zero_pps(length_size_minus_one: u8) -> Vec<u8> {
vec![
0x01, 66, 0x00, 30, 0xfc | (length_size_minus_one & 3), 0xe0, 0x00, ]
}
#[test]
fn avcc_minimal_baseline_record_accepted() {
let mut dec = H264CodecDecoder::new(CodecId::new("h264"));
let extra = baseline_avcc_zero_sps_zero_pps(3);
dec.consume_extradata(&extra).expect("minimal avcC ok");
assert_eq!(dec.length_size, Some(4));
assert_eq!(dec.avcc_profile_idc(), Some(66));
assert_eq!(dec.avcc_level_idc(), Some(30));
assert_eq!(dec.avcc_chroma_format(), None);
assert_eq!(dec.avcc_bit_depth_luma(), None);
assert_eq!(dec.avcc_bit_depth_chroma(), None);
}
#[test]
fn avcc_length_size_minus_one_0_means_1_byte_prefix() {
let mut dec = H264CodecDecoder::new(CodecId::new("h264"));
dec.consume_extradata(&baseline_avcc_zero_sps_zero_pps(0))
.expect("lengthSize = 1 ok");
assert_eq!(dec.length_size, Some(1));
}
#[test]
fn avcc_length_size_minus_one_1_means_2_byte_prefix() {
let mut dec = H264CodecDecoder::new(CodecId::new("h264"));
dec.consume_extradata(&baseline_avcc_zero_sps_zero_pps(1))
.expect("lengthSize = 2 ok");
assert_eq!(dec.length_size, Some(2));
}
#[test]
fn avcc_length_size_minus_one_2_is_rejected() {
let mut dec = H264CodecDecoder::new(CodecId::new("h264"));
let err = dec
.consume_extradata(&baseline_avcc_zero_sps_zero_pps(2))
.expect_err("lengthSize == 3 must be rejected");
let msg = format!("{err}");
assert!(
msg.contains("lengthSizeMinusOne"),
"error message should name the forbidden field: {msg}"
);
assert_eq!(dec.length_size, None);
}
#[test]
fn avcc_wrong_version_is_rejected() {
let mut dec = H264CodecDecoder::new(CodecId::new("h264"));
let mut extra = baseline_avcc_zero_sps_zero_pps(3);
extra[0] = 2;
let err = dec
.consume_extradata(&extra)
.expect_err("version 2 must be rejected");
assert!(format!("{err}").contains("configurationVersion"));
}
#[test]
fn avcc_short_header_is_rejected() {
let mut dec = H264CodecDecoder::new(CodecId::new("h264"));
let err = dec
.consume_extradata(&[0x01, 0x42, 0x00, 0x1e, 0xff, 0xe0])
.expect_err("6-byte avcC must be rejected");
assert!(format!("{err}").contains("shorter than avcC header"));
}
#[test]
fn avcc_high_profile_extension_parsed() {
let mut dec = H264CodecDecoder::new(CodecId::new("h264"));
let extra = vec![
0x01, 100, 0x00, 30, 0xff, 0xe0, 0x00, 0xfc | 0x01, 0xf8 | 0x02, 0xf8 | 0x02, 0x00, ];
dec.consume_extradata(&extra).expect("High avcC ext ok");
assert_eq!(dec.avcc_profile_idc(), Some(100));
assert_eq!(dec.avcc_chroma_format(), Some(1));
assert_eq!(dec.avcc_bit_depth_luma(), Some(10));
assert_eq!(dec.avcc_bit_depth_chroma(), Some(10));
}
#[test]
fn avcc_high_profile_missing_extension_tolerated() {
let mut dec = H264CodecDecoder::new(CodecId::new("h264"));
let mut extra = baseline_avcc_zero_sps_zero_pps(3);
extra[1] = 100; dec.consume_extradata(&extra)
.expect("missing High ext tolerated");
assert_eq!(dec.avcc_profile_idc(), Some(100));
assert_eq!(dec.length_size, Some(4));
assert_eq!(dec.avcc_chroma_format(), None);
assert_eq!(dec.avcc_bit_depth_luma(), None);
assert_eq!(dec.avcc_bit_depth_chroma(), None);
}
#[test]
fn avcc_high_444_profile_extension_parsed() {
let mut dec = H264CodecDecoder::new(CodecId::new("h264"));
let extra = vec![
0x01,
244,
0x00,
30, 0xff, 0xe0, 0x00, 0xfc | 0x03, 0xf8 | 0x04, 0xf8 | 0x04, 0x00, ];
dec.consume_extradata(&extra).expect("4:4:4 avcC ok");
assert_eq!(dec.avcc_chroma_format(), Some(3));
assert_eq!(dec.avcc_bit_depth_luma(), Some(12));
assert_eq!(dec.avcc_bit_depth_chroma(), Some(12));
}
#[test]
fn avcc_bit_depth_minus8_overflow_is_rejected() {
let mut dec = H264CodecDecoder::new(CodecId::new("h264"));
let extra = vec![
0x01,
100,
0x00,
30,
0xff,
0xe0,
0x00,
0xfc | 0x01, 0xf8 | 0x07, 0xf8, 0x00, ];
let err = dec
.consume_extradata(&extra)
.expect_err("bit_depth = 7 must be rejected");
assert!(format!("{err}").contains("bit_depth_luma_minus8"));
}
#[test]
fn avcc_truncated_sps_body_is_rejected() {
let mut dec = H264CodecDecoder::new(CodecId::new("h264"));
let extra = vec![
0x01, 66, 0x00, 30, 0xff, 0xe1, 0x00, 0x10, 0xde, ];
let err = dec.consume_extradata(&extra).expect_err("truncated SPS");
assert!(format!("{err}").contains("avcC truncated at SPS body"));
}
#[test]
fn avcc_truncated_at_pps_count_is_rejected() {
let mut dec = H264CodecDecoder::new(CodecId::new("h264"));
let extra = vec![0x01, 66, 0x00, 30, 0xff, 0xe0];
let err = dec
.consume_extradata(&extra)
.expect_err("missing PPS count");
assert!(format!("{err}").contains("avcC"));
}
fn field_pic(w: u32, field_h: u32, fill: i32, cfill: i32, poc: i32, frame_num: u32) -> Picture {
let mut p = Picture::new(w, field_h, 1, 8, 8);
for s in p.luma.iter_mut() {
*s = fill;
}
for s in p.cb.iter_mut() {
*s = cfill;
}
for s in p.cr.iter_mut() {
*s = cfill;
}
p.pic_order_cnt = poc;
p.frame_num = frame_num;
p
}
#[test]
fn interleave_fields_places_top_on_even_bottom_on_odd_rows() {
let w = 16u32;
let field_h = 4u32; let top = field_pic(w, field_h, 10, 110, 4, 7);
let bottom = field_pic(w, field_h, 20, 120, 6, 7);
let frame = interleave_fields(&top, &bottom);
assert_eq!(frame.width_in_samples, w);
assert_eq!(frame.height_in_samples, field_h * 2);
let wl = w as usize;
for r in 0..(field_h * 2) as usize {
let expect = if r % 2 == 0 { 10 } else { 20 };
for c in 0..wl {
assert_eq!(frame.luma[r * wl + c], expect, "luma row {r}");
}
}
let cw = frame.chroma_width() as usize;
let cfh = top.chroma_height() as usize;
for r in 0..(cfh * 2) {
let expect = if r % 2 == 0 { 110 } else { 120 };
for c in 0..cw {
assert_eq!(frame.cb[r * cw + c], expect, "cb row {r}");
assert_eq!(frame.cr[r * cw + c], expect, "cr row {r}");
}
}
assert_eq!(frame.pic_order_cnt, 4);
assert_eq!(frame.frame_num, 7);
}
#[test]
fn complementary_field_pair_outputs_single_full_height_frame() {
let mut dec = H264CodecDecoder::new(CodecId::new("h264"));
let top = field_pic(16, 4, 30, 128, 8, 3);
dec.handle_field_output(top, false, 3, 8, Some(99), FrameCropMargins::default());
assert!(dec.ready.is_empty());
assert!(dec.pending_field.is_some());
let bottom = field_pic(16, 4, 40, 128, 10, 3);
dec.handle_field_output(bottom, true, 3, 10, None, FrameCropMargins::default());
assert!(dec.pending_field.is_none());
dec.eof = true;
let f = dec.receive_frame().expect("paired frame must drain");
let vf = match f {
Frame::Video(v) => v,
other => panic!("expected video, got {other:?}"),
};
assert_eq!(vf.pts, Some(99));
assert_eq!(vf.planes[0].stride, 16);
assert_eq!(vf.planes[0].data.len(), 16 * 8);
for r in 0..8 {
let expect = if r % 2 == 0 { 30u8 } else { 40u8 };
for c in 0..16 {
assert_eq!(vf.planes[0].data[r * 16 + c], expect);
}
}
}
#[test]
fn non_complementary_second_field_flushes_orphan() {
let mut dec = H264CodecDecoder::new(CodecId::new("h264"));
let top1 = field_pic(16, 4, 30, 128, 8, 3);
dec.handle_field_output(top1, false, 3, 8, None, FrameCropMargins::default());
let top2 = field_pic(16, 4, 50, 128, 12, 4);
dec.handle_field_output(top2, false, 4, 12, None, FrameCropMargins::default());
assert!(dec.pending_field.is_some());
dec.eof = true;
let f = dec.receive_frame().expect("orphan field drains");
let vf = match f {
Frame::Video(v) => v,
other => panic!("expected video, got {other:?}"),
};
assert_eq!(vf.planes[0].data.len(), 16 * 4);
assert_eq!(vf.planes[0].data[0], 30);
}
#[test]
fn output_frame_is_the_cropping_rectangle() {
let mut pic = Picture::new(32, 32, 1, 8, 8);
for (i, s) in pic.luma.iter_mut().enumerate() {
*s = ((i % 32) + 4 * (i / 32)) as i32;
}
for (i, s) in pic.cb.iter_mut().enumerate() {
*s = ((i % 16) + 16 * (i / 16)) as i32;
}
for (i, s) in pic.cr.iter_mut().enumerate() {
*s = 255 - ((i % 16) + 16 * (i / 16)) as i32;
}
let crop = FrameCropMargins {
left: 2,
right: 4,
top: 2,
bottom: 6,
};
let vf = picture_to_video_frame(&pic, Some(1), crop);
assert_eq!(vf.planes.len(), 3);
let (w, h) = (26usize, 24usize);
assert_eq!(vf.planes[0].stride, w);
assert_eq!(vf.planes[0].data.len(), w * h);
for y in 0..h {
for x in 0..w {
let want = ((x + 2) + 4 * (y + 2)) as u8;
assert_eq!(vf.planes[0].data[y * w + x], want, "luma ({x},{y})");
}
}
let (cw, ch) = (13usize, 12usize);
for (plane, flip) in [(1usize, false), (2, true)] {
assert_eq!(vf.planes[plane].stride, cw);
assert_eq!(vf.planes[plane].data.len(), cw * ch);
for y in 0..ch {
for x in 0..cw {
let v = (x + 1) + 16 * (y + 1);
let want = if flip { 255 - v } else { v } as u8;
assert_eq!(vf.planes[plane].data[y * cw + x], want, "plane {plane}");
}
}
}
}
#[test]
fn cropping_keeps_wide_samples_little_endian() {
let mut pic = Picture::new(16, 16, 0, 10, 10);
for (i, s) in pic.luma.iter_mut().enumerate() {
*s = (i as i32) * 4;
}
let crop = FrameCropMargins {
left: 0,
right: 6,
top: 0,
bottom: 2,
};
let vf = picture_to_video_frame(&pic, None, crop);
assert_eq!(vf.planes.len(), 1);
assert_eq!(vf.planes[0].stride, 20);
assert_eq!(vf.planes[0].data.len(), 20 * 14);
let row1 = &vf.planes[0].data[20..22];
assert_eq!(u16::from_le_bytes([row1[0], row1[1]]), 16 * 4);
}
#[test]
fn field_output_applies_frame_and_field_cropping() {
let crop = FrameCropMargins {
left: 0,
right: 2,
top: 0,
bottom: 4,
};
let mut dec = H264CodecDecoder::new(CodecId::new("h264"));
dec.handle_field_output(field_pic(16, 4, 30, 128, 8, 3), false, 3, 8, None, crop);
dec.handle_field_output(field_pic(16, 4, 40, 128, 10, 3), true, 3, 10, None, crop);
dec.handle_field_output(field_pic(16, 4, 50, 128, 12, 4), false, 4, 12, None, crop);
dec.flush_pending_field();
dec.eof = true;
let mut sizes = Vec::new();
while let Ok(Frame::Video(vf)) = dec.receive_frame() {
sizes.push((
vf.planes[0].stride,
vf.planes[0].data.len() / vf.planes[0].stride,
));
assert_eq!(vf.planes[1].stride, 7);
}
assert_eq!(sizes, vec![(14, 4), (14, 2)]);
}
#[test]
fn frame_num_gap_fill_is_memory_bounded() {
use crate::sps::Sps;
let sps = Sps {
profile_idc: 66,
constraint_set_flags: 0,
level_idc: 30,
seq_parameter_set_id: 0,
chroma_format_idc: 1,
separate_colour_plane_flag: false,
bit_depth_luma_minus8: 0,
bit_depth_chroma_minus8: 0,
qpprime_y_zero_transform_bypass_flag: false,
seq_scaling_matrix_present_flag: false,
seq_scaling_lists: None,
log2_max_frame_num_minus4: 12, pic_order_cnt_type: 2,
log2_max_pic_order_cnt_lsb_minus4: 0,
delta_pic_order_always_zero_flag: false,
offset_for_non_ref_pic: 0,
offset_for_top_to_bottom_field: 0,
num_ref_frames_in_pic_order_cnt_cycle: 0,
offset_for_ref_frame: Vec::new(),
max_num_ref_frames: 3,
gaps_in_frame_num_value_allowed_flag: true,
pic_width_in_mbs_minus1: 3, pic_height_in_map_units_minus1: 3,
frame_mbs_only_flag: true,
mb_adaptive_frame_field_flag: false,
direct_8x8_inference_flag: true,
frame_cropping: None,
vui_parameters_present_flag: false,
vui: None,
};
let mut dec = H264CodecDecoder::new(CodecId::new("h264"));
dec.prev_ref_frame_num = Some(0);
dec.fill_frame_num_gap(&sps, 40_000)
.expect("gap fill must succeed");
assert_eq!(dec.dpb_entries.len(), 3);
let frame_nums: Vec<u32> = dec.dpb_entries.iter().map(|e| e.frame_num).collect();
assert_eq!(frame_nums, vec![39_997, 39_998, 39_999]);
assert_eq!(dec.ref_picture_count(), 3);
for e in &dec.dpb_entries {
let pic = dec.ref_store.get_by_key(e.dpb_key).expect("stored");
assert!(pic.non_existing, "gap placeholders are non-existing");
}
assert_eq!(dec.poc_state.prev_frame_num, 39_999);
assert_eq!(dec.prev_ref_frame_num, Some(39_999));
dec.fill_frame_num_gap(&sps, 40_010).expect("second gap");
assert_eq!(dec.dpb_entries.len(), 3);
assert_eq!(dec.ref_picture_count(), 3);
}
}