use crate::error::{Error, Result};
use crate::hevc_config::HEVCDecoderConfigurationRecord;
use alloc::vec::Vec;
use broadcast_common::{Parse, Serialize};
pub const HEVC_PREFIX_SEI_NUT: u8 = 39;
pub const SEI_PAYLOAD_MASTERING_DISPLAY_COLOUR_VOLUME: u32 = 137;
pub const SEI_PAYLOAD_CONTENT_LIGHT_LEVEL_INFO: u32 = 144;
pub const MDCV_FOURCC: [u8; 4] = *b"mdcv";
pub const CLLI_FOURCC: [u8; 4] = *b"clli";
const HEVC_NAL_HEADER_LEN: usize = 2;
const CIE_PRIMARY_COUNT: usize = 3;
const MDCV_PAYLOAD_LEN: usize = 24;
const CLLI_PAYLOAD_LEN: usize = 4;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ChromaticityXy {
pub x: u16,
pub y: u16,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MasteringDisplayColourVolume {
pub display_primaries: [ChromaticityXy; CIE_PRIMARY_COUNT],
pub white_point: ChromaticityXy,
pub max_display_mastering_luminance: u32,
pub min_display_mastering_luminance: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ContentLightLevel {
pub max_content_light_level: u16,
pub max_pic_average_light_level: u16,
}
fn be16(b: &[u8], at: usize) -> u16 {
u16::from_be_bytes([b[at], b[at + 1]])
}
fn be32(b: &[u8], at: usize) -> u32 {
u32::from_be_bytes([b[at], b[at + 1], b[at + 2], b[at + 3]])
}
fn check_exact(bytes: &[u8], need: usize, what: &'static str) -> Result<()> {
if bytes.len() < need {
return Err(Error::BufferTooShort {
need,
have: bytes.len(),
what,
});
}
if bytes.len() > need {
return Err(Error::InvalidValue {
field: what,
value: bytes.len() as u64,
reason: "payload longer than the defined syntax",
});
}
Ok(())
}
fn check_out(buf: &[u8], need: usize) -> Result<()> {
if buf.len() < need {
return Err(Error::OutputBufferTooSmall {
need,
have: buf.len(),
});
}
Ok(())
}
impl<'a> Parse<'a> for MasteringDisplayColourVolume {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
check_exact(bytes, MDCV_PAYLOAD_LEN, "mdcv payload")?;
let mut display_primaries = [ChromaticityXy { x: 0, y: 0 }; CIE_PRIMARY_COUNT];
for (c, p) in display_primaries.iter_mut().enumerate() {
p.x = be16(bytes, c * 4);
p.y = be16(bytes, c * 4 + 2);
}
let wp_at = CIE_PRIMARY_COUNT * 4;
Ok(Self {
display_primaries,
white_point: ChromaticityXy {
x: be16(bytes, wp_at),
y: be16(bytes, wp_at + 2),
},
max_display_mastering_luminance: be32(bytes, wp_at + 4),
min_display_mastering_luminance: be32(bytes, wp_at + 8),
})
}
}
impl Serialize for MasteringDisplayColourVolume {
type Error = Error;
fn serialized_len(&self) -> usize {
MDCV_PAYLOAD_LEN
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
check_out(buf, MDCV_PAYLOAD_LEN)?;
for (c, p) in self.display_primaries.iter().enumerate() {
buf[c * 4..c * 4 + 2].copy_from_slice(&p.x.to_be_bytes());
buf[c * 4 + 2..c * 4 + 4].copy_from_slice(&p.y.to_be_bytes());
}
let wp_at = CIE_PRIMARY_COUNT * 4;
buf[wp_at..wp_at + 2].copy_from_slice(&self.white_point.x.to_be_bytes());
buf[wp_at + 2..wp_at + 4].copy_from_slice(&self.white_point.y.to_be_bytes());
buf[wp_at + 4..wp_at + 8]
.copy_from_slice(&self.max_display_mastering_luminance.to_be_bytes());
buf[wp_at + 8..wp_at + 12]
.copy_from_slice(&self.min_display_mastering_luminance.to_be_bytes());
Ok(MDCV_PAYLOAD_LEN)
}
}
impl<'a> Parse<'a> for ContentLightLevel {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
check_exact(bytes, CLLI_PAYLOAD_LEN, "clli payload")?;
Ok(Self {
max_content_light_level: be16(bytes, 0),
max_pic_average_light_level: be16(bytes, 2),
})
}
}
impl Serialize for ContentLightLevel {
type Error = Error;
fn serialized_len(&self) -> usize {
CLLI_PAYLOAD_LEN
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
check_out(buf, CLLI_PAYLOAD_LEN)?;
buf[..2].copy_from_slice(&self.max_content_light_level.to_be_bytes());
buf[2..4].copy_from_slice(&self.max_pic_average_light_level.to_be_bytes());
Ok(CLLI_PAYLOAD_LEN)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct HdrStaticMetadata {
pub mastering_display: Option<MasteringDisplayColourVolume>,
pub content_light_level: Option<ContentLightLevel>,
}
impl HdrStaticMetadata {
pub fn is_empty(&self) -> bool {
self.mastering_display.is_none() && self.content_light_level.is_none()
}
pub fn absorb_hevc_sei_nal(&mut self, nal: &[u8]) {
if nal.len() <= HEVC_NAL_HEADER_LEN
|| crate::nal::nal_unit_type(crate::nal::NalCodec::Hevc, nal)
!= Some(HEVC_PREFIX_SEI_NUT)
{
return;
}
for (payload_type, payload) in crate::nal::sei_messages(&nal[HEVC_NAL_HEADER_LEN..]) {
match payload_type {
SEI_PAYLOAD_MASTERING_DISPLAY_COLOUR_VOLUME if self.mastering_display.is_none() => {
self.mastering_display = MasteringDisplayColourVolume::parse(&payload).ok();
}
SEI_PAYLOAD_CONTENT_LIGHT_LEVEL_INFO if self.content_light_level.is_none() => {
self.content_light_level = ContentLightLevel::parse(&payload).ok();
}
_ => {}
}
}
}
pub fn to_hevc_declarative_sei_nal(&self) -> Result<Option<Vec<u8>>> {
let mut payloads: Vec<(u32, Vec<u8>)> = Vec::new();
if let Some(m) = &self.mastering_display {
payloads.push((
SEI_PAYLOAD_MASTERING_DISPLAY_COLOUR_VOLUME,
m.try_to_bytes()?,
));
}
if let Some(c) = &self.content_light_level {
payloads.push((SEI_PAYLOAD_CONTENT_LIGHT_LEVEL_INFO, c.try_to_bytes()?));
}
if payloads.is_empty() {
return Ok(None);
}
let messages: Vec<(u32, &[u8])> =
payloads.iter().map(|(t, p)| (*t, p.as_slice())).collect();
Ok(Some(crate::nal::hevc_prefix_sei_nal(&messages)))
}
pub fn hevc_sei_nal_carries_hdr(nal: &[u8]) -> bool {
let mut m = Self::default();
m.absorb_hevc_sei_nal(nal);
!m.is_empty()
}
pub fn from_hevc_record(record: &HEVCDecoderConfigurationRecord) -> Option<Self> {
let mut m = Self::default();
for array in &record.arrays {
if array.nal_unit_type != HEVC_PREFIX_SEI_NUT {
continue;
}
for nal in &array.nalus {
m.absorb_hevc_sei_nal(&nal.0);
}
}
(!m.is_empty()).then_some(m)
}
pub fn sample_entry_boxes(&self) -> Result<Vec<([u8; 4], Vec<u8>)>> {
let mut out = Vec::new();
if let Some(m) = &self.mastering_display {
out.push((MDCV_FOURCC, m.try_to_bytes()?));
}
if let Some(c) = &self.content_light_level {
out.push((CLLI_FOURCC, c.try_to_bytes()?));
}
Ok(out)
}
pub fn from_sample_entry_boxes<'a>(
boxes: impl IntoIterator<Item = (&'a [u8; 4], &'a [u8])>,
) -> Option<Self> {
let mut m = Self::default();
for (fourcc, body) in boxes {
if *fourcc == MDCV_FOURCC && m.mastering_display.is_none() {
m.mastering_display = MasteringDisplayColourVolume::parse(body).ok();
} else if *fourcc == CLLI_FOURCC && m.content_light_level.is_none() {
m.content_light_level = ContentLightLevel::parse(body).ok();
}
}
(!m.is_empty()).then_some(m)
}
}
#[cfg(test)]
mod tests {
use super::*;
const MDCV_BODY: [u8; 24] = [
0x33, 0xc2, 0x86, 0xc4, 0x1d, 0x4c, 0x0b, 0xb8, 0x84, 0xd0, 0x3e, 0x80, 0x3d, 0x13, 0x40,
0x42, 0x00, 0x98, 0x96, 0x80, 0x00, 0x00, 0x00, 0x01,
];
const CLLI_BODY: [u8; 4] = [0x03, 0xe8, 0x01, 0x90];
#[test]
fn mdcv_round_trips_and_decodes() {
let m = MasteringDisplayColourVolume::parse(&MDCV_BODY).unwrap();
assert_eq!(
m.display_primaries[0],
ChromaticityXy { x: 13250, y: 34500 }
);
assert_eq!(m.white_point, ChromaticityXy { x: 15635, y: 16450 });
assert_eq!(m.max_display_mastering_luminance, 10_000_000);
assert_eq!(m.min_display_mastering_luminance, 1);
assert_eq!(m.try_to_bytes().unwrap(), MDCV_BODY);
let mut m2 = m;
m2.white_point.x += 1;
assert_ne!(m2.try_to_bytes().unwrap(), MDCV_BODY);
}
#[test]
fn clli_round_trips_and_decodes() {
let c = ContentLightLevel::parse(&CLLI_BODY).unwrap();
assert_eq!(c.max_content_light_level, 1000);
assert_eq!(c.max_pic_average_light_level, 400);
assert_eq!(c.try_to_bytes().unwrap(), CLLI_BODY);
}
#[test]
fn wrong_lengths_are_rejected() {
assert!(MasteringDisplayColourVolume::parse(&MDCV_BODY[..23]).is_err());
let mut long = MDCV_BODY.to_vec();
long.push(0);
assert!(MasteringDisplayColourVolume::parse(&long).is_err());
assert!(ContentLightLevel::parse(&CLLI_BODY[..3]).is_err());
}
#[test]
fn sei_nal_with_both_messages_is_absorbed() {
let mut nal = alloc::vec![HEVC_PREFIX_SEI_NUT << 1, 1];
nal.push(137);
nal.push(24);
nal.extend_from_slice(&MDCV_BODY);
nal.push(144);
nal.push(4);
nal.extend_from_slice(&CLLI_BODY);
nal.push(0x80);
let mut m = HdrStaticMetadata::default();
m.absorb_hevc_sei_nal(&nal);
assert_eq!(
m.mastering_display.unwrap().try_to_bytes().unwrap(),
MDCV_BODY
);
assert_eq!(
m.content_light_level.unwrap().try_to_bytes().unwrap(),
CLLI_BODY
);
assert!(HdrStaticMetadata::hevc_sei_nal_carries_hdr(&nal));
}
#[test]
fn unrelated_sei_is_ignored() {
let nal = [HEVC_PREFIX_SEI_NUT << 1, 1, 5, 1, 0xAA, 0x80];
assert!(!HdrStaticMetadata::hevc_sei_nal_carries_hdr(&nal));
}
#[test]
fn declarative_sei_nal_drops_other_messages() {
let mdcv = [0x11u8; MDCV_PAYLOAD_LEN];
let clli = [0x03, 0xE8, 0x01, 0x90];
let other = [0xA5u8; 6];
let bundled = crate::nal::hevc_prefix_sei_nal(&[
(SEI_PAYLOAD_MASTERING_DISPLAY_COLOUR_VOLUME, &mdcv),
(4, &other),
(SEI_PAYLOAD_CONTENT_LIGHT_LEVEL_INFO, &clli),
]);
let mut m = HdrStaticMetadata::default();
m.absorb_hevc_sei_nal(&bundled);
assert!(m.mastering_display.is_some() && m.content_light_level.is_some());
let rebuilt = m.to_hevc_declarative_sei_nal().unwrap().unwrap();
let types: Vec<u32> = crate::nal::sei_messages(&rebuilt[HEVC_NAL_HEADER_LEN..])
.into_iter()
.map(|(t, _)| t)
.collect();
assert_eq!(
types,
[
SEI_PAYLOAD_MASTERING_DISPLAY_COLOUR_VOLUME,
SEI_PAYLOAD_CONTENT_LIGHT_LEVEL_INFO
]
);
assert!(
HdrStaticMetadata::default()
.to_hevc_declarative_sei_nal()
.unwrap()
.is_none()
);
}
}