Skip to content

Commit

Permalink
impl Debug for Context; decode_avcc example
Browse files Browse the repository at this point in the history
The new example is useful e.g. here:
scottlamb/moonfire-nvr#302 (comment)

Use a private helper to represent param set maps that impls `Debug`.

Along the way, switch the representation to let the alloc grow as
necessary, rather than pre-allocating 32 slots. In the common case when
only SPS 0 and PPS 0 are filled, the `Vec` capacity seems to be size 4,
which means the allocations are 4*232=928 bytes and 4*72=288 bytes
rather than 32*232=7,424 bytes and 32*72=2,304 bytes, which seems like a
worthwhile savings for no real effort.

I played with a `Box<[Option[T]]>` to have `Context` be 32 bytes rather
than 48, but `resize_with` overallocates capacity, and then
`into_boxed_slice` does an extra copy to undo that, and writing more
code to avoid that felt like premature optimization.

I also considered an `[Option<Box<T>>; 32]` but having `Context` take
2*32*8=512 bytes on the stack struck me as potentially problematic.
  • Loading branch information
scottlamb authored and dholroyd committed Jan 16, 2024
1 parent 0803db8 commit 5670413
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 37 deletions.
22 changes: 22 additions & 0 deletions examples/decode_avcc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//! Creates a context from an encoded
//! [`h264_reader::avc::AVCDecoderConfigurationRecord`] and prints it.
use std::convert::TryFrom;

use h264_reader::avcc::AvcDecoderConfigurationRecord;

fn main() {
let path = {
let mut args = std::env::args_os();
if args.len() != 2 {
eprintln!("Usage: decode_avcc path/to/avcc");
std::process::exit(1);
}
args.nth(1).unwrap()
};

let raw = std::fs::read(path).unwrap();
let record = AvcDecoderConfigurationRecord::try_from(&raw[..]).unwrap();
let ctx = record.create_context().unwrap();
println!("{:#?}", &ctx);
}
107 changes: 70 additions & 37 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
#![forbid(unsafe_code)]
#![deny(rust_2018_idioms)]

use std::fmt::Debug;

pub mod annexb;
pub mod avcc;
pub mod nal;
Expand All @@ -11,58 +13,89 @@ pub mod rbsp;

/// Contextual data that needs to be tracked between evaluations of different portions of H264
/// syntax.
#[derive(Default, Debug)]
pub struct Context {
seq_param_sets: Vec<Option<nal::sps::SeqParameterSet>>,
pic_param_sets: Vec<Option<nal::pps::PicParameterSet>>,
}
impl Default for Context {
fn default() -> Self {
Self::new()
}
seq_param_sets: ParamSetMap<nal::sps::SeqParameterSet>,
pic_param_sets: ParamSetMap<nal::pps::PicParameterSet>,
}
impl Context {
#[inline]
pub fn new() -> Self {
let mut seq_param_sets = vec![];
for _ in 0..32 {
seq_param_sets.push(None);
}
let mut pic_param_sets = vec![];
for _ in 0..32 {
pic_param_sets.push(None);
}
Context {
seq_param_sets,
pic_param_sets,
}
Default::default()
}
}
impl Context {
#[inline]
pub fn sps_by_id(&self, id: nal::pps::ParamSetId) -> Option<&nal::sps::SeqParameterSet> {
if id.id() > 31 {
None
} else {
self.seq_param_sets[id.id() as usize].as_ref()
}
self.seq_param_sets.get(usize::from(id.id()))
}
#[inline]
pub fn sps(&self) -> impl Iterator<Item = &nal::sps::SeqParameterSet> {
self.seq_param_sets.iter().filter_map(Option::as_ref)
self.seq_param_sets.iter()
}
#[inline]
pub fn put_seq_param_set(&mut self, sps: nal::sps::SeqParameterSet) {
let i = sps.seq_parameter_set_id.id() as usize;
self.seq_param_sets[i] = Some(sps);
let i = usize::from(sps.seq_parameter_set_id.id());
self.seq_param_sets.put(i, sps);
}
#[inline]
pub fn pps_by_id(&self, id: nal::pps::ParamSetId) -> Option<&nal::pps::PicParameterSet> {
if id.id() > 31 {
None
} else {
self.pic_param_sets[id.id() as usize].as_ref()
}
self.pic_param_sets.get(usize::from(id.id()))
}
#[inline]
pub fn pps(&self) -> impl Iterator<Item = &nal::pps::PicParameterSet> {
self.pic_param_sets.iter().filter_map(Option::as_ref)
self.pic_param_sets.iter()
}
#[inline]
pub fn put_pic_param_set(&mut self, pps: nal::pps::PicParameterSet) {
let i = pps.pic_parameter_set_id.id() as usize;
self.pic_param_sets[i] = Some(pps);
let i = usize::from(pps.pic_parameter_set_id.id());
self.pic_param_sets.put(i, pps);
}
}

/// A map for very small indexes; SPS/PPS IDs must be in `[0, 32)`, and typically only 0 is used.
struct ParamSetMap<T>(Vec<Option<T>>);
impl<T> Default for ParamSetMap<T> {
fn default() -> Self {
Self(Default::default())
}
}
impl<T> ParamSetMap<T> {
fn get(&self, index: usize) -> Option<&T> {
self.0.get(index).map(Option::as_ref).flatten()
}
fn put(&mut self, index: usize, t: T) {
if self.0.len() <= index {
self.0.resize_with(index + 1, || None);
}
self.0[index] = Some(t);
}
fn iter(&self) -> impl Iterator<Item = &T> {
self.0.iter().filter_map(Option::as_ref)
}
}
impl<T: Debug> Debug for ParamSetMap<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_map()
.entries(
self.0
.iter()
.enumerate()
.filter_map(|(i, p)| p.as_ref().map(|p| (i, p))),
)
.finish()
}
}

#[cfg(test)]
mod tests {
#[test]
fn map() {
let mut s = super::ParamSetMap::default();
assert_eq!(s.iter().copied().collect::<Vec<_>>(), &[]);
s.put(0, 0);
assert_eq!(s.iter().copied().collect::<Vec<_>>(), &[0]);
s.put(2, 2);
assert_eq!(s.iter().copied().collect::<Vec<_>>(), &[0, 2]);
s.put(1, 1);
assert_eq!(s.iter().copied().collect::<Vec<_>>(), &[0, 1, 2]);
}
}

1 comment on commit 5670413

@Radu1409
Copy link

@Radu1409 Radu1409 commented on 5670413 Oct 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@scottlamb @dholroyd Hello, can you explain to me please how this program works? I tried using cargo run --example decode_avcc -- <path_to_video_mp4_or_h264>, but i receive errors. One of them is for configuration AVC version which is not 1. UnsupportedConfigurationVersion(0). I tried with different videos in mp4 and h264 format.

Thank you.

Please sign in to comment.