use crate::error::{Error, Result};
use alloc::vec::Vec;
use broadcast_common::{Parse, Serialize};
pub const MHAC_FOURCC: [u8; 4] = *b"mhaC";
pub const MHA1_FOURCC: [u8; 4] = *b"mha1";
pub const MHA2_FOURCC: [u8; 4] = *b"mha2";
pub const MHM1_FOURCC: [u8; 4] = *b"mhm1";
pub const MHM2_FOURCC: [u8; 4] = *b"mhm2";
pub const MHAC_RECORD_FIXED_LEN: usize = 5;
pub const MHAC_CONFIGURATION_VERSION: u8 = 1;
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct MHADecoderConfigurationRecord {
pub configuration_version: u8,
pub mpegh3da_profile_level_indication: u8,
pub reference_channel_layout: u8,
pub mpegh3da_config: Vec<u8>,
}
impl MHADecoderConfigurationRecord {
pub fn new(
mpegh3da_profile_level_indication: u8,
reference_channel_layout: u8,
mpegh3da_config: Vec<u8>,
) -> Self {
Self {
configuration_version: MHAC_CONFIGURATION_VERSION,
mpegh3da_profile_level_indication,
reference_channel_layout,
mpegh3da_config,
}
}
pub fn rfc6381(&self) -> alloc::string::String {
alloc::format!("mhm1.0x{:02X}", self.mpegh3da_profile_level_indication)
}
}
impl<'a> Parse<'a> for MHADecoderConfigurationRecord {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
if bytes.len() < MHAC_RECORD_FIXED_LEN {
return Err(Error::BufferTooShort {
need: MHAC_RECORD_FIXED_LEN,
have: bytes.len(),
what: "MHADecoderConfigurationRecord",
});
}
let configuration_version = bytes[0];
if configuration_version != MHAC_CONFIGURATION_VERSION {
return Err(Error::InvalidValue {
field: "configurationVersion",
value: configuration_version as u64,
reason: "must be 1 (ISO/IEC 23008-3 §20)",
});
}
let mpegh3da_profile_level_indication = bytes[1];
let reference_channel_layout = bytes[2];
let config_len = u16::from_be_bytes([bytes[3], bytes[4]]) as usize;
let need = MHAC_RECORD_FIXED_LEN + config_len;
if bytes.len() < need {
return Err(Error::BufferTooShort {
need,
have: bytes.len(),
what: "MHADecoderConfigurationRecord.mpegh3daConfig",
});
}
let mpegh3da_config = bytes[MHAC_RECORD_FIXED_LEN..need].to_vec();
Ok(Self {
configuration_version,
mpegh3da_profile_level_indication,
reference_channel_layout,
mpegh3da_config,
})
}
}
impl Serialize for MHADecoderConfigurationRecord {
type Error = Error;
fn serialized_len(&self) -> usize {
MHAC_RECORD_FIXED_LEN + self.mpegh3da_config.len()
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let need = self.serialized_len();
if buf.len() < need {
return Err(Error::OutputBufferTooSmall {
need,
have: buf.len(),
});
}
buf[0] = self.configuration_version;
buf[1] = self.mpegh3da_profile_level_indication;
buf[2] = self.reference_channel_layout;
let config_len = self.mpegh3da_config.len() as u16;
buf[3..5].copy_from_slice(&config_len.to_be_bytes());
buf[5..need].copy_from_slice(&self.mpegh3da_config);
Ok(need)
}
}
pub type MhaCBox = MHADecoderConfigurationRecord;
const MHAS_PACTYP_MPEGH3DACFG: u8 = 1;
const MHAS_TYPE_BITS: (u32, u32, u32) = (3, 8, 8);
const MHAS_LABEL_BITS: (u32, u32, u32) = (2, 8, 32);
const MHAS_LENGTH_BITS: (u32, u32, u32) = (11, 24, 24);
struct BitReader<'a> {
data: &'a [u8],
pos: usize,
}
impl<'a> BitReader<'a> {
fn new(data: &'a [u8]) -> Self {
Self { data, pos: 0 }
}
fn bits_left(&self) -> usize {
self.data.len() * 8 - self.pos
}
fn read(&mut self, n: u32) -> Option<u64> {
if (n as usize) > self.bits_left() {
return None;
}
let mut v = 0u64;
for _ in 0..n {
let byte = self.data[self.pos / 8];
let bit = (byte >> (7 - (self.pos % 8))) & 1;
v = (v << 1) | u64::from(bit);
self.pos += 1;
}
Some(v)
}
fn byte_pos(&self) -> Option<usize> {
(self.pos % 8 == 0).then_some(self.pos / 8)
}
}
fn escaped_value(br: &mut BitReader<'_>, bits: (u32, u32, u32)) -> Option<u64> {
let (n1, n2, n3) = bits;
let mut value = br.read(n1)?;
if value == (1u64 << n1) - 1 {
let v2 = br.read(n2)?;
value += v2;
if v2 == (1u64 << n2) - 1 {
value += br.read(n3)?;
}
}
Some(value)
}
pub(crate) struct MhasPacket<'a> {
pub(crate) packet_type: u8,
pub(crate) payload: &'a [u8],
}
pub(crate) fn walk_mhas_packets(data: &[u8]) -> Vec<MhasPacket<'_>> {
let mut br = BitReader::new(data);
let mut out = Vec::new();
const MIN_HEADER_BITS: usize = 16;
while br.bits_left() >= MIN_HEADER_BITS {
let Some(packet_type) = escaped_value(&mut br, MHAS_TYPE_BITS) else {
break;
};
let Some(_label) = escaped_value(&mut br, MHAS_LABEL_BITS) else {
break;
};
let Some(length) = escaped_value(&mut br, MHAS_LENGTH_BITS) else {
break;
};
let Some(start) = br.byte_pos() else {
break; };
if packet_type > u64::from(u8::MAX) {
break;
}
let Some(end) = start.checked_add(length as usize) else {
break;
};
if end > data.len() {
break;
}
out.push(MhasPacket {
packet_type: packet_type as u8,
payload: &data[start..end],
});
br.pos = end * 8;
}
out
}
pub(crate) fn find_mpegh3da_config(data: &[u8]) -> Option<&[u8]> {
walk_mhas_packets(data)
.into_iter()
.find(|p| p.packet_type == MHAS_PACTYP_MPEGH3DACFG)
.map(|p| p.payload)
}
#[cfg(test)]
mod mhas_tests {
use super::*;
fn build_packet(packet_type: u8, label: u8, payload: &[u8]) -> Vec<u8> {
let len = payload.len() as u16;
assert!(packet_type < 7 && label < 3 && len < 0x7FF);
let word: u32 = ((packet_type as u32) << 13) | ((label as u32) << 11) | len as u32;
let mut out = alloc::vec![(word >> 8) as u8, (word & 0xFF) as u8];
out.extend_from_slice(payload);
out
}
#[test]
fn walks_unescaped_packets() {
let mut data = build_packet(6, 0, &[0xA5]);
data.extend(build_packet(
MHAS_PACTYP_MPEGH3DACFG,
1,
&[0x10, 0x11, 0x12],
));
let packets = walk_mhas_packets(&data);
assert_eq!(packets.len(), 2);
assert_eq!(packets[0].packet_type, 6);
assert_eq!(packets[0].payload, &[0xA5]);
assert_eq!(packets[1].packet_type, MHAS_PACTYP_MPEGH3DACFG);
assert_eq!(packets[1].payload, &[0x10, 0x11, 0x12]);
}
#[test]
fn finds_config_packet() {
let mut data = build_packet(6, 0, &[0xA5]);
data.extend(build_packet(MHAS_PACTYP_MPEGH3DACFG, 1, &[0xAA, 0xBB]));
assert_eq!(find_mpegh3da_config(&data), Some([0xAA, 0xBB].as_slice()));
}
#[test]
fn no_config_packet_returns_none() {
let data = build_packet(6, 0, &[0xA5]);
assert_eq!(find_mpegh3da_config(&data), None);
}
#[test]
fn truncated_header_stops_cleanly() {
let data = [0xFFu8];
assert!(walk_mhas_packets(&data).is_empty());
}
#[test]
fn declared_length_past_end_stops_cleanly() {
let word: u32 = (1u32 << 13) | 5;
let data = [(word >> 8) as u8, (word & 0xFF) as u8, 0xAA];
assert!(walk_mhas_packets(&data).is_empty());
}
}