Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/any.rs
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ any! {
Minf,
Stbl,
Stsd,
Avc1,
Avc1, Avc3,
Avcc,
Btrt,
Ccst,
Expand Down
12 changes: 8 additions & 4 deletions src/moov/trak/mdia/minf/stbl/stsd/h264/avc1.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use crate::*;

const AVC1_CODE: u32 = u32::from_be_bytes([b'a', b'v', b'c', b'1']);

#[derive(Debug, Clone, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Avc1 {
pub struct AvcSampleEntry<const KIND_CODE: u32> {
pub visual: Visual,
pub avcc: Avcc,
pub btrt: Option<Btrt>,
Expand All @@ -11,8 +13,10 @@ pub struct Avc1 {
pub taic: Option<Taic>,
}

impl Atom for Avc1 {
const KIND: FourCC = FourCC::new(b"avc1");
pub type Avc1 = AvcSampleEntry<{ AVC1_CODE }>;

impl<const KIND_CODE: u32> Atom for AvcSampleEntry<KIND_CODE> {
const KIND: FourCC = FourCC::from_u32(KIND_CODE);

fn decode_body<B: Buf>(buf: &mut B) -> Result<Self> {
let visual = Visual::decode(buf)?;
Expand All @@ -33,7 +37,7 @@ impl Atom for Avc1 {
}
}

Ok(Avc1 {
Ok(Self {
visual,
avcc: avcc.ok_or(Error::MissingBox(Avcc::KIND))?,
btrt,
Expand Down
146 changes: 146 additions & 0 deletions src/moov/trak/mdia/minf/stbl/stsd/h264/avc3.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
use super::avc1::AvcSampleEntry;

const AVC3_CODE: u32 = u32::from_be_bytes(*b"avc3");

pub type Avc3 = AvcSampleEntry<{ AVC3_CODE }>;

#[cfg(test)]
mod tests {
use super::*;
use crate::*;

fn base(compressor: &str) -> Avc3 {
Avc3 {
visual: Visual {
data_reference_index: 1,
width: 320,
height: 240,
horizresolution: 0x48.into(),
vertresolution: 0x48.into(),
frame_count: 1,
compressor: compressor.into(),
depth: 24,
},
avcc: Avcc {
configuration_version: 1,
avc_profile_indication: 100,
profile_compatibility: 0,
avc_level_indication: 13,
length_size: 4,
sequence_parameter_sets: vec![vec![
0x67, 0x64, 0x00, 0x0D, 0xAC, 0xD9, 0x41, 0x41, 0xFA, 0x10, 0x00, 0x00, 0x03,
0x00, 0x10, 0x00, 0x00, 0x03, 0x03, 0x20, 0xF1, 0x42, 0x99, 0x60,
]],
picture_parameter_sets: vec![vec![0x68, 0xEB, 0xE3, 0xCB, 0x22, 0xC0]],
..Default::default()
},
..Default::default()
}
}

// Extracted from the initialization segment (`IS.mp4`) of the BBC Testcard
// HLS stream: https://vs-dash-ww-rd-live.akamaized.net/pl/testcard2020/192x108p25/media.m3u8
const BBC_AVC3_SAMPLE: &[u8; 136] = include_bytes!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/data/bbc_avc3.bin"
));

fn bbc_expected() -> Avc3 {
Avc3 {
visual: Visual {
data_reference_index: 1,
width: 192,
height: 108,
horizresolution: 0x48.into(),
vertresolution: 0x48.into(),
frame_count: 1,
compressor: "\x04h264".into(),
depth: 24,
},
avcc: Avcc {
configuration_version: 1,
avc_profile_indication: 0x42,
profile_compatibility: 0xC0,
avc_level_indication: 0x15,
length_size: 4,
sequence_parameter_sets: Vec::new(),
picture_parameter_sets: Vec::new(),
ext: None,
},
btrt: None,
colr: Some(Colr::Nclx {
colour_primaries: 1,
transfer_characteristics: 1,
matrix_coefficients: 1,
full_range_flag: false,
}),
pasp: Some(Pasp {
h_spacing: 1,
v_spacing: 1,
}),
taic: None,
}
}

fn roundtrip(expected: Avc3) {
let mut buf = Vec::new();
expected.encode(&mut buf).unwrap();

let mut buf = buf.as_ref();
let decoded = Avc3::decode(&mut buf).unwrap();
assert_eq!(decoded, expected);
}

#[test]
fn test_avc3() {
roundtrip(base("ya boy"));
}

#[test]
fn test_avc3_with_extras() {
let mut avc3 = base("they");
avc3.btrt = Some(Btrt {
buffer_size_db: 14075,
max_bitrate: 374288,
avg_bitrate: 240976,
});
avc3.colr = Some(Colr::default());
avc3.pasp = Some(Pasp {
h_spacing: 4,
v_spacing: 3,
});
avc3.taic = Some(Taic {
time_uncertainty: u64::MAX,
clock_resolution: 1000,
clock_drift_rate: i32::MAX,
clock_type: ClockType::CanSync,
});
roundtrip(avc3);
}

#[test]
fn test_avc3_decodes_real_bbc_stream() {
assert_eq!(BBC_AVC3_SAMPLE.len(), 136);
// Sanity-check the extracted box still contains the expected children.
let mut inspect = BBC_AVC3_SAMPLE.as_slice();
let header = Header::decode(&mut inspect).unwrap();
assert_eq!(header.kind, Avc3::KIND);
let mut body = inspect;
Visual::decode(&mut body).unwrap();
assert_eq!(body.remaining(), 50);
let mut child_kinds = Vec::new();
while let Some(atom) = Any::decode_maybe(&mut body).unwrap() {
child_kinds.push(atom.kind());
}
assert_eq!(
child_kinds,
vec![Avcc::KIND, Pasp::KIND, Colr::KIND],
"unexpected children: {:?}",
child_kinds
);

let mut buf = BBC_AVC3_SAMPLE.as_slice();
let decoded = Avc3::decode(&mut buf).unwrap();
assert_eq!(decoded, bbc_expected());
}
}
2 changes: 2 additions & 0 deletions src/moov/trak/mdia/minf/stbl/stsd/h264/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod avc1;
mod avc3;
mod avcc;

// Incomplete H264 decoder, saved for possible future use
Expand All @@ -7,4 +8,5 @@ mod avcc;
//mod sps;

pub use avc1::*;
pub use avc3::*;
pub use avcc::*;
5 changes: 5 additions & 0 deletions src/moov/trak/mdia/minf/stbl/stsd/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ pub enum Codec {
// H264
Avc1(Avc1),

// H264: SPS/PPS/VPS is inline
Avc3(Avc3),

// HEVC: SPS/PPS/VPS is inline
Hev1(Hev1),

Expand Down Expand Up @@ -97,6 +100,7 @@ impl Decode for Codec {
let atom = Any::decode(buf)?;
Ok(match atom {
Any::Avc1(atom) => atom.into(),
Any::Avc3(atom) => atom.into(),
Any::Hev1(atom) => atom.into(),
Any::Hvc1(atom) => atom.into(),
Any::Vp08(atom) => atom.into(),
Expand All @@ -120,6 +124,7 @@ impl Encode for Codec {
match self {
Self::Unknown(kind) => kind.encode(buf),
Self::Avc1(atom) => atom.encode(buf),
Self::Avc3(atom) => atom.encode(buf),
Self::Hev1(atom) => atom.encode(buf),
Self::Hvc1(atom) => atom.encode(buf),
Self::Vp08(atom) => atom.encode(buf),
Expand Down
4 changes: 4 additions & 0 deletions src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ impl FourCC {
pub const fn new(value: &[u8; 4]) -> Self {
FourCC(*value)
}

pub const fn from_u32(value: u32) -> Self {
FourCC(value.to_be_bytes())
}
}

impl From<u32> for FourCC {
Expand Down
Binary file added tests/data/bbc_avc3.bin
Binary file not shown.