Skip to content

Commit

Permalink
Initial AVCC support
Browse files Browse the repository at this point in the history
  • Loading branch information
dholroyd committed Mar 15, 2020
1 parent b624903 commit b4a080a
Show file tree
Hide file tree
Showing 3 changed files with 210 additions and 1 deletion.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ The following list shows the current state of support per H264 syntax element:

* Bytestream formats
* [x] _Annex B_ format (e.g. in MPEG-TS)
* [ ] _AVCC_ format (e.g. in MP4)
* [x] _AVCC_ format (e.g. in MP4)
* Network Abstraction Layer Units (NAL Units)
* [ ] `slice_layer_without_partitioning_rbsp()`
* [ ] `slice_data_partition_a_layer_rbsp()`
Expand Down
208 changes: 208 additions & 0 deletions src/avcc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
//! Support for handling _Advanced Video Coding Configuration_ data, used in the _ISO Base Media
//! File Format_ (AKA MP4), as the specified in _ISO/IEC 14496-15_.
//!

use nal::{sps, UnitType, NalHeader, NalHeaderError, pps};
use std::convert::TryFrom;
use nal::sps::{ProfileIdc, Level, ConstraintFlags, SeqParameterSet};
use Context;
use nal::pps::PicParameterSet;

#[derive(Debug)]
pub enum AvccError {
NotEnoughData { expected: usize, actual: usize },
/// The AvcDecoderConfigurationRecord used a version number other than `1`.
UnsupportedConfigurationVersion(u8),
ParamSet(ParamSetError),
Sps(sps::SpsError),
Pps(pps::PpsError),
}

pub struct AvcDecoderConfigurationRecord<'buf> {
data: &'buf[u8],
}
impl<'buf> TryFrom<&'buf[u8]> for AvcDecoderConfigurationRecord<'buf> {
type Error = AvccError;

fn try_from(data: &'buf[u8]) -> Result<Self, Self::Error> {
let avcc = AvcDecoderConfigurationRecord { data };
// we must confirm we have enough bytes for all fixed fields before we do anything else,
avcc.ck(Self::MIN_CONF_SIZE)?;
if avcc.configuration_version() != 1 {
// The spec requires that decoders ignore streams where the version number is not 1,
// indicating there was an incompatible change in the configuration format,
return Err(AvccError::UnsupportedConfigurationVersion(avcc.configuration_version()));
}
// Do a whole load of work to ensure that the buffer is large enough for all the optional
// fields actually indicated to be present, so that we don't have to put these checks into
// the accessor functions of individual fields,
let mut len = avcc.seq_param_sets_end()?;

avcc.ck(len + 1)?;
let mut num_pps = data[len];
len += 1;
while num_pps > 0 {
avcc.ck(len + 2)?;
let pps_len = (u16::from(data[len]) << 8 | u16::from(data[len +1 ])) as usize;
len += 2;
avcc.ck(len + pps_len)?;
len += pps_len;
num_pps -= 1;

}

Ok(avcc)
}
}
impl<'buf> AvcDecoderConfigurationRecord<'buf> {
const MIN_CONF_SIZE: usize = 6;

fn seq_param_sets_end(&self) -> Result<usize, AvccError> {
let mut num_sps = self.num_of_sequence_parameter_sets();
let mut len = Self::MIN_CONF_SIZE;
while num_sps > 0 {
self.ck(len + 2)?;
let sps_len = (u16::from(self.data[len]) << 8 | u16::from(self.data[len + 1])) as usize;
len += 2;
self.ck(len + sps_len)?;
len += sps_len;
num_sps -= 1;
}
Ok(len)
}
fn ck(&self, len: usize) -> Result<(), AvccError> {
if self.data.len() < len {
Err(AvccError::NotEnoughData { expected: len, actual: self.data.len() })
} else {
Ok(())
}
}
pub fn configuration_version(&self) -> u8 {
self.data[0]
}
pub fn num_of_sequence_parameter_sets(&self) -> usize {
(self.data[5] & 0b0001_1111) as usize
}
pub fn avc_profile_indication(&self) -> ProfileIdc {
self.data[1].into()
}
pub fn profile_compatibility(&self) -> ConstraintFlags {
self.data[2].into()
}
pub fn avc_level_indication(&self) -> Level {
Level::from_constraint_flags_and_level_idc(self.profile_compatibility(), self.data[3])
}
/// Number of bytes used to specify the length of each NAL unit
/// 0 => 1 byte, 1 => 2 bytes, 2 => 3 bytes, 3 => 4 bytes
pub fn length_size_minus_one(&self) -> u8 {
self.data[4] & 0b0000_0011
}
pub fn sequence_parameter_sets(&self) -> impl Iterator<Item = Result<&'buf[u8], ParamSetError>> {
let num = self.num_of_sequence_parameter_sets();
let mut data = &self.data[Self::MIN_CONF_SIZE..];
let profile = self.avc_profile_indication();
let level = self.avc_level_indication();
ParamSetIter::new(data, UnitType::SeqParameterSet)
.take(num)
}
pub fn picture_parameter_sets(&self) -> impl Iterator<Item = Result<&'buf[u8], ParamSetError>> + 'buf {
let offset = self.seq_param_sets_end().unwrap();
let num = self.data[offset];
let data = &self.data[offset+1..];
ParamSetIter::new(data, UnitType::PicParameterSet)
.take(num as usize)
}

/// Creates an H264 parser context from the given user context, using the settings encoded into
/// this `AvcDecoderConfigurationRecord`.
///
/// In particular, the _sequence parameter set_ and _picture parameter set_ values of this
/// configuration record will be inserted into the resulting context.
pub fn create_context<C>(&self, ctx: C) -> Result<Context<C>, AvccError> {
let mut ctx = Context::new(ctx);
for sps in self.sequence_parameter_sets() {
let sps = SeqParameterSet::from_bytes(sps.map_err(AvccError::ParamSet)?)
.map_err(AvccError::Sps)?;
ctx.put_seq_param_set(sps);
}
for pps in self.picture_parameter_sets() {
let pps = PicParameterSet::from_bytes(&ctx, pps.map_err(AvccError::ParamSet)?)
.map_err(AvccError::Pps)?;
ctx.put_pic_param_set(pps);
}
Ok(ctx)
}
}

#[derive(Debug)]
pub enum ParamSetError {
NalHeader(NalHeaderError),
IncorrectNalType { expected: UnitType, actual: UnitType },
/// A _sequence parameter set_ found within the AVC decoder config was not consistent with the
/// settings of the decoder config itself
IncompatibleSps(SeqParameterSet),
}

struct ParamSetIter<'buf>(&'buf[u8], UnitType);

impl<'buf> ParamSetIter<'buf> {
pub fn new(buf: &'buf[u8], unit_type: UnitType) -> ParamSetIter<'buf> {
ParamSetIter(buf, unit_type)
}
}
impl<'buf> Iterator for ParamSetIter<'buf>
{
type Item = Result<&'buf[u8], ParamSetError>;

fn next(&mut self) -> Option<Self::Item> {
if self.0.is_empty() {
None
} else {
let len = u16::from(self.0[0]) << 8 | u16::from(self.0[1]);
let data = &self.0[2..];
let res = match NalHeader::new(data[0]) {
Ok(nal_header) => {
if dbg!(nal_header).nal_unit_type() == self.1 {
let (data, remainder) = data.split_at(len as usize);
self.0 = remainder;
Ok(&data[1..]) // trim off the nal_header byte
} else {
Err(ParamSetError::IncorrectNalType { expected: self.1, actual: nal_header.nal_unit_type() })
}
},
Err(err) => Err(ParamSetError::NalHeader(err)),
};
Some(res)
}
}
}

#[cfg(test)]
mod test {
use super::*;
use nal::pps::ParamSetId;

#[test]
fn it_works() {
let avcc_data = hex!("0142c01e ffe10020 6742c01e b91061ff 78088000 00030080 00001971 3006d600 daf7bdc0 7c2211a8 01000468 de3c80");
let avcc = AvcDecoderConfigurationRecord::try_from(&avcc_data[..]).unwrap();
assert_eq!(1, avcc.configuration_version());
assert_eq!(1, avcc.num_of_sequence_parameter_sets());
assert_eq!(ProfileIdc::from(66), avcc.avc_profile_indication());
let flags = avcc.profile_compatibility();
assert!(flags.flag0());
assert!(flags.flag1());
assert!(!flags.flag2());
assert!(!flags.flag3());
assert!(!flags.flag4());
assert!(!flags.flag5());
let ctx = avcc.create_context(()).unwrap();
let sps = ctx.sps_by_id(ParamSetId::from_u32(0).unwrap())
.expect("missing sps");
assert_eq!(avcc.avc_level_indication(), sps.level());
assert_eq!(avcc.avc_profile_indication(), sps.profile_idc);
assert_eq!(ParamSetId::from_u32(0).unwrap(), sps.seq_parameter_set_id);
let pps = ctx.pps_by_id(ParamSetId::from_u32(0).unwrap())
.expect("missing pps");
}
}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ extern crate memchr;
pub mod rbsp;
pub mod annexb;
pub mod nal;
pub mod avcc;

/// Contextual data that needs to be tracked between evaluations of different portions of H264
/// syntax.
Expand Down

0 comments on commit b4a080a

Please sign in to comment.