use crate::boxes::{CSGP, SBGP, SGPD};
#[derive(Clone, Debug, Default)]
pub struct SampleToGroup {
pub grouping_type: [u8; 4],
pub grouping_type_parameter: Option<u32>,
pub entries: Vec<(u32, u32)>,
}
#[derive(Clone, Debug, Default)]
pub struct SampleGroupDescription {
pub grouping_type: [u8; 4],
pub default_sample_description_index: Option<u32>,
pub entries: Vec<Vec<u8>>,
}
pub fn build_sbgp(s: &SampleToGroup) -> Vec<u8> {
let version: u8 = if s.grouping_type_parameter.is_some() {
1
} else {
0
};
let mut body =
Vec::with_capacity(4 + 4 + if version == 1 { 4 } else { 0 } + 4 + s.entries.len() * 8);
body.push(version);
body.extend_from_slice(&[0, 0, 0]); body.extend_from_slice(&s.grouping_type);
if let Some(p) = s.grouping_type_parameter {
body.extend_from_slice(&p.to_be_bytes());
}
body.extend_from_slice(&(s.entries.len() as u32).to_be_bytes());
for (count, idx) in &s.entries {
body.extend_from_slice(&count.to_be_bytes());
body.extend_from_slice(&idx.to_be_bytes());
}
wrap(&SBGP, &body)
}
pub fn build_sgpd(s: &SampleGroupDescription) -> Vec<u8> {
let common_len = entries_common_length(&s.entries);
let want_v2 = s.default_sample_description_index.is_some() && common_len.is_some();
let (version, default_length): (u8, u32) = if want_v2 {
(2, 0)
} else if let Some(cl) = common_len {
(1, cl as u32)
} else {
(1, 0)
};
let entries_payload_len = match (version, default_length) {
(1, 0) => s.entries.iter().map(|e| 4 + e.len()).sum::<usize>(),
(1, dl) => s.entries.len() * dl as usize,
(2, _) => s.entries.iter().map(|e| e.len()).sum::<usize>(),
_ => unreachable!(),
};
let extra = match version {
1 => 4,
2 => 4,
_ => unreachable!(),
};
let mut body = Vec::with_capacity(4 + 4 + extra + 4 + entries_payload_len);
body.push(version);
body.extend_from_slice(&[0, 0, 0]); body.extend_from_slice(&s.grouping_type);
match version {
1 => body.extend_from_slice(&default_length.to_be_bytes()),
2 => body.extend_from_slice(
&s.default_sample_description_index
.unwrap_or(0)
.to_be_bytes(),
),
_ => unreachable!(),
}
body.extend_from_slice(&(s.entries.len() as u32).to_be_bytes());
match (version, default_length) {
(1, 0) => {
for e in &s.entries {
body.extend_from_slice(&(e.len() as u32).to_be_bytes());
body.extend_from_slice(e);
}
}
(1, _) => {
for e in &s.entries {
body.extend_from_slice(e);
}
}
(2, _) => {
for e in &s.entries {
body.extend_from_slice(e);
}
}
_ => unreachable!(),
}
wrap(&SGPD, &body)
}
fn entries_common_length(entries: &[Vec<u8>]) -> Option<usize> {
let first = entries.first()?;
if first.is_empty() {
return None;
}
let len = first.len();
if entries.iter().all(|e| e.len() == len) {
Some(len)
} else {
None
}
}
#[derive(Clone, Debug, Default)]
pub struct CompactSampleToGroupPattern {
pub sample_count: u32,
pub indices: Vec<u32>,
}
#[derive(Clone, Debug, Default)]
pub struct CompactSampleToGroup {
pub grouping_type: [u8; 4],
pub grouping_type_parameter: Option<u32>,
pub index_msb_indicates_fragment_local_description: bool,
pub patterns: Vec<CompactSampleToGroupPattern>,
}
fn size_code_for(max: u32) -> u8 {
if max <= 0xF {
0
} else if max <= 0xFF {
1
} else if max <= 0xFFFF {
2
} else {
3
}
}
struct BitWriter {
out: Vec<u8>,
bits_filled: u8,
}
impl BitWriter {
fn new() -> Self {
BitWriter {
out: Vec::new(),
bits_filled: 0,
}
}
fn write(&mut self, value: u32, n: u32) {
debug_assert!(n <= 32);
for i in (0..n).rev() {
let bit = ((value >> i) & 1) as u8;
if self.bits_filled == 0 {
self.out.push(0);
}
let last = self.out.len() - 1;
self.out[last] |= bit << (7 - self.bits_filled);
self.bits_filled = (self.bits_filled + 1) & 7;
}
}
fn finish(self) -> Vec<u8> {
self.out
}
}
pub fn build_csgp(c: &CompactSampleToGroup) -> Vec<u8> {
let max_pattern_length = c
.patterns
.iter()
.map(|p| p.indices.len() as u32)
.max()
.unwrap_or(0);
let max_sample_count = c.patterns.iter().map(|p| p.sample_count).max().unwrap_or(0);
let max_index = c
.patterns
.iter()
.flat_map(|p| p.indices.iter().copied())
.max()
.unwrap_or(0);
let mut pattern_size_code = size_code_for(max_pattern_length);
let mut count_size_code = size_code_for(max_sample_count);
let index_size_code = size_code_for(max_index);
if (pattern_size_code == 0) != (count_size_code == 0) {
pattern_size_code = pattern_size_code.max(1);
count_size_code = count_size_code.max(1);
}
let pattern_w = 4u32 << pattern_size_code;
let count_w = 4u32 << count_size_code;
let index_w = 4u32 << index_size_code;
let gtpp = c.grouping_type_parameter.is_some();
let flags: u32 = (index_size_code as u32)
| ((count_size_code as u32) << 2)
| ((pattern_size_code as u32) << 4)
| (if gtpp { 1 } else { 0 } << 6)
| (if c.index_msb_indicates_fragment_local_description {
1
} else {
0
} << 7);
let mut body = Vec::new();
body.push(0); body.extend_from_slice(&flags.to_be_bytes()[1..]); body.extend_from_slice(&c.grouping_type);
if let Some(p) = c.grouping_type_parameter {
body.extend_from_slice(&p.to_be_bytes());
}
body.extend_from_slice(&(c.patterns.len() as u32).to_be_bytes());
let mut bits = BitWriter::new();
for p in &c.patterns {
bits.write(p.indices.len() as u32, pattern_w);
bits.write(p.sample_count, count_w);
}
for p in &c.patterns {
for &idx in &p.indices {
bits.write(idx, index_w);
}
}
body.extend_from_slice(&bits.finish());
wrap(&CSGP, &body)
}
fn wrap(kind: &[u8; 4], body: &[u8]) -> Vec<u8> {
let total = (8 + body.len()) as u32;
let mut out = Vec::with_capacity(total as usize);
out.extend_from_slice(&total.to_be_bytes());
out.extend_from_slice(kind);
out.extend_from_slice(body);
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sbgp_v0_two_runs_byte_exact() {
let s = SampleToGroup {
grouping_type: *b"roll",
grouping_type_parameter: None,
entries: vec![(10, 1), (5, 0)],
};
let b = build_sbgp(&s);
assert_eq!(b.len(), 36);
assert_eq!(&b[0..4], &36u32.to_be_bytes());
assert_eq!(&b[4..8], b"sbgp");
assert_eq!(&b[8..12], &[0, 0, 0, 0]); assert_eq!(&b[12..16], b"roll");
assert_eq!(&b[16..20], &2u32.to_be_bytes()); assert_eq!(&b[20..24], &10u32.to_be_bytes());
assert_eq!(&b[24..28], &1u32.to_be_bytes());
assert_eq!(&b[28..32], &5u32.to_be_bytes());
assert_eq!(&b[32..36], &0u32.to_be_bytes());
}
#[test]
fn sbgp_v1_with_parameter_byte_exact() {
let s = SampleToGroup {
grouping_type: *b"rap ",
grouping_type_parameter: Some(7),
entries: vec![(3, 2)],
};
let b = build_sbgp(&s);
assert_eq!(b.len(), 32);
assert_eq!(&b[8..12], &[1, 0, 0, 0]); assert_eq!(&b[12..16], b"rap ");
assert_eq!(&b[16..20], &7u32.to_be_bytes()); assert_eq!(&b[20..24], &1u32.to_be_bytes()); assert_eq!(&b[24..28], &3u32.to_be_bytes());
assert_eq!(&b[28..32], &2u32.to_be_bytes());
}
#[test]
fn sbgp_zero_entries_legal() {
let s = SampleToGroup {
grouping_type: *b"roll",
grouping_type_parameter: None,
entries: vec![],
};
let b = build_sbgp(&s);
assert_eq!(b.len(), 20);
assert_eq!(&b[16..20], &0u32.to_be_bytes()); }
#[test]
fn sbgp_fragment_local_index_preserved() {
let s = SampleToGroup {
grouping_type: *b"sync",
grouping_type_parameter: None,
entries: vec![(1, 0x1_0001)],
};
let b = build_sbgp(&s);
assert_eq!(&b[24..28], &0x1_0001u32.to_be_bytes());
}
#[test]
fn sgpd_v1_fixed_length_when_entries_share_size() {
let s = SampleGroupDescription {
grouping_type: *b"roll",
default_sample_description_index: None,
entries: vec![vec![0xFF, 0xFB], vec![0x00, 0x05]],
};
let b = build_sgpd(&s);
assert_eq!(b.len(), 28);
assert_eq!(&b[8..12], &[1, 0, 0, 0]); assert_eq!(&b[12..16], b"roll");
assert_eq!(&b[16..20], &2u32.to_be_bytes()); assert_eq!(&b[20..24], &2u32.to_be_bytes()); assert_eq!(&b[24..26], &[0xFF, 0xFB]);
assert_eq!(&b[26..28], &[0x00, 0x05]);
}
#[test]
fn sgpd_v1_variable_length_when_entries_differ() {
let s = SampleGroupDescription {
grouping_type: *b"prol",
default_sample_description_index: None,
entries: vec![vec![0xAA, 0xBB, 0xCC], vec![0xDD]],
};
let b = build_sgpd(&s);
assert_eq!(b.len(), 36);
assert_eq!(&b[8..12], &[1, 0, 0, 0]); assert_eq!(&b[16..20], &0u32.to_be_bytes()); assert_eq!(&b[20..24], &2u32.to_be_bytes()); assert_eq!(&b[24..28], &3u32.to_be_bytes()); assert_eq!(&b[28..31], &[0xAA, 0xBB, 0xCC]);
assert_eq!(&b[31..35], &1u32.to_be_bytes()); assert_eq!(&b[35..36], &[0xDD]);
}
#[test]
fn sgpd_v2_when_default_sample_description_index_set() {
let s = SampleGroupDescription {
grouping_type: *b"alst",
default_sample_description_index: Some(3),
entries: vec![vec![0x01, 0x02], vec![0x03, 0x04]],
};
let b = build_sgpd(&s);
assert_eq!(b.len(), 28);
assert_eq!(&b[8..12], &[2, 0, 0, 0]); assert_eq!(&b[12..16], b"alst");
assert_eq!(&b[16..20], &3u32.to_be_bytes()); assert_eq!(&b[20..24], &2u32.to_be_bytes()); assert_eq!(&b[24..26], &[0x01, 0x02]);
assert_eq!(&b[26..28], &[0x03, 0x04]);
}
#[test]
fn sgpd_v2_falls_back_to_v1_when_entries_differ() {
let s = SampleGroupDescription {
grouping_type: *b"alst",
default_sample_description_index: Some(3),
entries: vec![vec![0x01], vec![0x02, 0x03]],
};
let b = build_sgpd(&s);
assert_eq!(b[8], 1); assert_eq!(&b[16..20], &0u32.to_be_bytes()); }
#[test]
fn sgpd_empty_entries_legal() {
let s = SampleGroupDescription {
grouping_type: *b"roll",
default_sample_description_index: None,
entries: vec![],
};
let b = build_sgpd(&s);
assert_eq!(b.len(), 24);
assert_eq!(&b[8..12], &[1, 0, 0, 0]); assert_eq!(&b[20..24], &0u32.to_be_bytes()); }
#[test]
fn entries_common_length_helper() {
assert_eq!(entries_common_length(&[]), None);
assert_eq!(entries_common_length(&[vec![]]), None);
assert_eq!(entries_common_length(&[vec![1, 2]]), Some(2));
assert_eq!(entries_common_length(&[vec![1, 2], vec![3, 4]]), Some(2));
assert_eq!(entries_common_length(&[vec![1], vec![2, 3]]), None);
}
#[test]
fn size_code_for_picks_narrowest_width() {
assert_eq!(size_code_for(0), 0); assert_eq!(size_code_for(0xF), 0); assert_eq!(size_code_for(0x10), 1); assert_eq!(size_code_for(0xFF), 1);
assert_eq!(size_code_for(0x100), 2); assert_eq!(size_code_for(0xFFFF), 2);
assert_eq!(size_code_for(0x1_0000), 3); assert_eq!(size_code_for(u32::MAX), 3);
}
#[test]
fn bit_writer_msb_first() {
let mut w = BitWriter::new();
w.write(0b101, 3);
w.write(0b01, 2);
let out = w.finish();
assert_eq!(out, vec![0xA8]);
}
#[test]
fn csgp_4bit_widths_byte_exact() {
let c = CompactSampleToGroup {
grouping_type: *b"roll",
grouping_type_parameter: None,
index_msb_indicates_fragment_local_description: false,
patterns: vec![CompactSampleToGroupPattern {
sample_count: 3,
indices: vec![1, 2],
}],
};
let b = build_csgp(&c);
assert_eq!(b.len(), 22);
assert_eq!(&b[0..4], &22u32.to_be_bytes());
assert_eq!(&b[4..8], b"csgp");
assert_eq!(&b[8..12], &[0, 0, 0, 0]); assert_eq!(&b[12..16], b"roll");
assert_eq!(&b[16..20], &1u32.to_be_bytes()); assert_eq!(b[20], 0x23); assert_eq!(b[21], 0x12); }
#[test]
fn csgp_flags_encode_size_codes() {
let c = CompactSampleToGroup {
grouping_type: *b"sync",
grouping_type_parameter: None,
index_msb_indicates_fragment_local_description: false,
patterns: vec![CompactSampleToGroupPattern {
sample_count: 0x100,
indices: vec![0x10; 0x20],
}],
};
let b = build_csgp(&c);
let flags = u32::from_be_bytes([0, b[9], b[10], b[11]]);
assert_eq!(flags & 0x3, 1); assert_eq!((flags >> 2) & 0x3, 2); assert_eq!((flags >> 4) & 0x3, 1); assert_eq!((flags >> 6) & 0x1, 0); }
#[test]
fn csgp_builder_avoids_mixed_4bit_width() {
let c = CompactSampleToGroup {
grouping_type: *b"roll",
grouping_type_parameter: None,
index_msb_indicates_fragment_local_description: false,
patterns: vec![CompactSampleToGroupPattern {
sample_count: 0x100, indices: vec![1, 2], }],
};
let b = build_csgp(&c);
let flags = u32::from_be_bytes([0, b[9], b[10], b[11]]);
let pattern_code = (flags >> 4) & 0x3;
let count_code = (flags >> 2) & 0x3;
assert!(count_code >= 2);
assert_ne!(pattern_code, 0, "pattern_size_code must not stay 4-bit");
let parsed = crate::demux::parse_csgp_box(&b[8..]).expect("must re-parse");
assert_eq!(parsed.patterns.len(), 1);
assert_eq!(parsed.patterns[0].sample_count, 0x100);
assert_eq!(parsed.patterns[0].indices, vec![1, 2]);
}
#[test]
fn csgp_with_grouping_type_parameter_sets_presence_bit() {
let c = CompactSampleToGroup {
grouping_type: *b"rap ",
grouping_type_parameter: Some(7),
index_msb_indicates_fragment_local_description: false,
patterns: vec![CompactSampleToGroupPattern {
sample_count: 1,
indices: vec![1],
}],
};
let b = build_csgp(&c);
let flags = u32::from_be_bytes([0, b[9], b[10], b[11]]);
assert_eq!((flags >> 6) & 0x1, 1); assert_eq!(&b[12..16], b"rap ");
assert_eq!(&b[16..20], &7u32.to_be_bytes()); assert_eq!(&b[20..24], &1u32.to_be_bytes()); }
#[test]
fn csgp_empty_patterns_legal() {
let c = CompactSampleToGroup {
grouping_type: *b"roll",
grouping_type_parameter: None,
index_msb_indicates_fragment_local_description: false,
patterns: vec![],
};
let b = build_csgp(&c);
assert_eq!(b.len(), 20);
assert_eq!(&b[16..20], &0u32.to_be_bytes());
}
#[test]
fn csgp_roundtrip_through_parser() {
let cases = vec![
CompactSampleToGroup {
grouping_type: *b"roll",
grouping_type_parameter: None,
index_msb_indicates_fragment_local_description: false,
patterns: vec![CompactSampleToGroupPattern {
sample_count: 3,
indices: vec![1, 2],
}],
},
CompactSampleToGroup {
grouping_type: *b"rap ",
grouping_type_parameter: Some(42),
index_msb_indicates_fragment_local_description: false,
patterns: vec![
CompactSampleToGroupPattern {
sample_count: 0x100,
indices: vec![0x10, 0, 0xFF],
},
CompactSampleToGroupPattern {
sample_count: 1,
indices: vec![0x1_0000],
},
],
},
CompactSampleToGroup {
grouping_type: *b"sync",
grouping_type_parameter: None,
index_msb_indicates_fragment_local_description: false,
patterns: vec![CompactSampleToGroupPattern {
sample_count: 5,
indices: vec![0x8000_0001],
}],
},
];
for c in &cases {
let bytes = build_csgp(c);
let parsed = crate::demux::parse_csgp_box(&bytes[8..]).unwrap();
assert_eq!(parsed.grouping_type, c.grouping_type);
assert_eq!(parsed.grouping_type_parameter, c.grouping_type_parameter);
assert_eq!(parsed.patterns.len(), c.patterns.len());
for (pp, cp) in parsed.patterns.iter().zip(&c.patterns) {
assert_eq!(pp.sample_count, cp.sample_count);
assert_eq!(pp.indices, cp.indices);
}
}
}
}