use super::DecodeError;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DnxhdProfile {
Dnxhd145,
Dnxhd220,
Dnxhd220x,
Dnxhd145x,
Dnxhd100,
Dnxhd60,
Unknown(u32),
}
impl DnxhdProfile {
fn from_cid(cid: u32) -> Self {
match cid {
1237 => Self::Dnxhd145,
1238 => Self::Dnxhd220,
1235 => Self::Dnxhd220x,
1241 => Self::Dnxhd145x,
1242 => Self::Dnxhd100,
1243 => Self::Dnxhd60,
other => Self::Unknown(other),
}
}
pub fn is_10bit(self) -> bool {
matches!(self, Self::Dnxhd220x | Self::Dnxhd145x)
}
}
impl std::fmt::Display for DnxhdProfile {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Dnxhd145 => write!(f, "DNxHD 145 (CID 1237)"),
Self::Dnxhd220 => write!(f, "DNxHD 220 (CID 1238)"),
Self::Dnxhd220x => write!(f, "DNxHD 220x (CID 1235)"),
Self::Dnxhd145x => write!(f, "DNxHD 145x (CID 1241)"),
Self::Dnxhd100 => write!(f, "DNxHD 100 (CID 1242)"),
Self::Dnxhd60 => write!(f, "DNxHD 60 (CID 1243)"),
Self::Unknown(c) => write!(f, "Unknown CID {c}"),
}
}
}
#[derive(Debug, Clone)]
pub struct FrameHeader {
pub cid: u32,
pub profile: DnxhdProfile,
pub width: u16,
pub height: u16,
pub chroma_format: u8,
pub bits_per_pixel: u8,
pub num_slices: u16,
pub mb_width: u16,
}
pub(crate) const FRAME_MAGIC: [u8; 4] = [0x00, 0x00, 0x02, 0x80];
const FRAME_MARKER: [u8; 4] = [0x00, 0x00, 0x00, 0x01];
const HEADER_MIN_LEN: usize = 26;
pub fn parse_frame_header(data: &[u8]) -> Result<(FrameHeader, usize), DecodeError> {
if data.len() < HEADER_MIN_LEN {
return Err(DecodeError::BufferTooSmall {
need: HEADER_MIN_LEN,
have: data.len(),
});
}
if data[0..4] != FRAME_MAGIC {
return Err(DecodeError::InvalidMagic);
}
if data[4..8] != FRAME_MARKER {
return Err(DecodeError::InvalidMagic);
}
let cid = u32::from_be_bytes([data[8], data[9], data[10], data[11]]);
let profile = DnxhdProfile::from_cid(cid);
let width = u16::from_be_bytes([data[12], data[13]]);
let height = u16::from_be_bytes([data[14], data[15]]);
let chroma_format = data[19]; let bpp_marker = u16::from_be_bytes([data[20], data[21]]);
let bits_per_pixel: u8 = match bpp_marker {
0x5814 => 8,
0x58A4 => 10,
_ if profile.is_10bit() => 10,
_ => 8,
};
let num_slices = u16::from_be_bytes([data[22], data[23]]);
let mb_width = u16::from_be_bytes([data[24], data[25]]);
let header = FrameHeader {
cid,
profile,
width,
height,
chroma_format,
bits_per_pixel,
num_slices,
mb_width,
};
Ok((header, HEADER_MIN_LEN))
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
pub fn make_test_header(cid: u32, width: u16, height: u16, bpp_marker: u16) -> Vec<u8> {
let mut h = vec![0u8; 40];
h[0..4].copy_from_slice(&FRAME_MAGIC);
h[4..8].copy_from_slice(&FRAME_MARKER);
h[8..12].copy_from_slice(&cid.to_be_bytes());
h[12..14].copy_from_slice(&width.to_be_bytes());
h[14..16].copy_from_slice(&height.to_be_bytes());
h[16..18].copy_from_slice(&height.to_be_bytes()); h[19] = 0x58; h[20..22].copy_from_slice(&bpp_marker.to_be_bytes());
let ns = (height / 16).max(1);
h[22..24].copy_from_slice(&ns.to_be_bytes());
let mbw = (width / 16).max(1);
h[24..26].copy_from_slice(&mbw.to_be_bytes());
h
}
#[test]
fn parse_dnxhd145_header() {
let data = make_test_header(1237, 1440, 1080, 0x5814);
let (hdr, consumed) = parse_frame_header(&data).unwrap();
assert_eq!(hdr.profile, DnxhdProfile::Dnxhd145);
assert_eq!(hdr.cid, 1237);
assert_eq!(hdr.width, 1440);
assert_eq!(hdr.height, 1080);
assert_eq!(hdr.bits_per_pixel, 8);
assert_eq!(hdr.chroma_format, 0x58);
assert_eq!(consumed, HEADER_MIN_LEN);
}
#[test]
fn parse_dnxhd220_header() {
let data = make_test_header(1238, 1920, 1080, 0x5814);
let (hdr, _) = parse_frame_header(&data).unwrap();
assert_eq!(hdr.profile, DnxhdProfile::Dnxhd220);
assert_eq!(hdr.bits_per_pixel, 8);
}
#[test]
fn parse_dnxhd220x_10bit() {
let data = make_test_header(1235, 1920, 1080, 0x58A4);
let (hdr, _) = parse_frame_header(&data).unwrap();
assert_eq!(hdr.profile, DnxhdProfile::Dnxhd220x);
assert_eq!(hdr.bits_per_pixel, 10);
}
#[test]
fn parse_dnxhd145x_10bit() {
let data = make_test_header(1241, 1440, 1080, 0x58A4);
let (hdr, _) = parse_frame_header(&data).unwrap();
assert_eq!(hdr.profile, DnxhdProfile::Dnxhd145x);
assert_eq!(hdr.bits_per_pixel, 10);
}
#[test]
fn bad_magic_errors() {
let mut data = make_test_header(1237, 1440, 1080, 0x5814);
data[0] = 0xFF;
assert!(matches!(
parse_frame_header(&data),
Err(DecodeError::InvalidMagic)
));
}
#[test]
fn short_buffer_errors() {
let data = [0u8; 10];
assert!(matches!(
parse_frame_header(&data),
Err(DecodeError::BufferTooSmall { .. })
));
}
#[test]
fn unknown_cid_is_unknown_variant() {
let data = make_test_header(9999, 1920, 1080, 0x5814);
let (hdr, _) = parse_frame_header(&data).unwrap();
assert!(matches!(hdr.profile, DnxhdProfile::Unknown(9999)));
}
#[test]
fn dnxhd_profile_is_10bit_correct() {
assert!(DnxhdProfile::Dnxhd220x.is_10bit());
assert!(DnxhdProfile::Dnxhd145x.is_10bit());
assert!(!DnxhdProfile::Dnxhd145.is_10bit());
assert!(!DnxhdProfile::Dnxhd220.is_10bit());
}
}