use crate::frame::{ContentLightLevel, MasteringDisplay};
#[derive(Debug, Clone, Copy, Default)]
pub struct HevcHdrSei {
pub mastering_display: Option<MasteringDisplay>,
pub content_light_level: Option<ContentLightLevel>,
}
impl HevcHdrSei {
pub fn merge(&mut self, other: HevcHdrSei) {
if other.mastering_display.is_some() {
self.mastering_display = other.mastering_display;
}
if other.content_light_level.is_some() {
self.content_light_level = other.content_light_level;
}
}
pub fn is_empty(&self) -> bool {
self.mastering_display.is_none() && self.content_light_level.is_none()
}
}
pub fn parse_annexb(buf: &[u8]) -> HevcHdrSei {
let mut out = HevcHdrSei::default();
for nal in annexb_split(buf) {
if nal.is_empty() {
continue;
}
if nal.len() < 2 {
continue;
}
let nal_unit_type = (nal[0] >> 1) & 0x3F;
if nal_unit_type != 39 && nal_unit_type != 40 {
continue;
}
let rbsp = strip_emulation_prevention(&nal[2..]);
parse_sei_rbsp(&rbsp, &mut out);
}
out
}
fn annexb_split(buf: &[u8]) -> Vec<&[u8]> {
let mut out = Vec::new();
let mut i = 0;
while i + 2 < buf.len() {
if buf[i] == 0 && buf[i + 1] == 0 && buf[i + 2] == 1 {
i += 3;
break;
}
if i + 3 < buf.len() && buf[i] == 0 && buf[i + 1] == 0 && buf[i + 2] == 0 && buf[i + 3] == 1
{
i += 4;
break;
}
i += 1;
}
let mut nal_start = i;
while i + 2 < buf.len() {
if buf[i] == 0 && buf[i + 1] == 0 && (buf[i + 2] == 1 || buf[i + 2] == 0) {
let is_3byte = buf[i + 2] == 1;
let is_4byte = !is_3byte && i + 3 < buf.len() && buf[i + 3] == 1;
if is_3byte || is_4byte {
let mut end = i;
while end > nal_start && buf[end - 1] == 0 {
end -= 1;
}
if end > nal_start {
out.push(&buf[nal_start..end]);
}
i += if is_3byte { 3 } else { 4 };
nal_start = i;
continue;
}
}
i += 1;
}
if nal_start < buf.len() {
let mut end = buf.len();
while end > nal_start && buf[end - 1] == 0 {
end -= 1;
}
if end > nal_start {
out.push(&buf[nal_start..end]);
}
}
out
}
fn strip_emulation_prevention(ebsp: &[u8]) -> Vec<u8> {
let mut rbsp = Vec::with_capacity(ebsp.len());
let mut i = 0;
while i < ebsp.len() {
if i + 2 < ebsp.len() && ebsp[i] == 0 && ebsp[i + 1] == 0 && ebsp[i + 2] == 0x03 {
rbsp.push(0);
rbsp.push(0);
i += 3;
continue;
}
rbsp.push(ebsp[i]);
i += 1;
}
rbsp
}
fn parse_sei_rbsp(rbsp: &[u8], out: &mut HevcHdrSei) {
let mut cursor = 0;
while cursor < rbsp.len() {
let (payload_type, after_type) = match read_sei_ff_byte_sum(rbsp, cursor) {
Some(v) => v,
None => return,
};
cursor = after_type;
if cursor >= rbsp.len() {
return;
}
let (payload_size, after_size) = match read_sei_ff_byte_sum(rbsp, cursor) {
Some(v) => v,
None => return,
};
cursor = after_size;
if cursor + payload_size > rbsp.len() {
return;
}
let payload = &rbsp[cursor..cursor + payload_size];
cursor += payload_size;
match payload_type {
137 => {
if let Some(mdcv) = parse_mastering_display(payload) {
out.mastering_display = Some(mdcv);
}
}
144 => {
if let Some(clli) = parse_content_light_level(payload) {
out.content_light_level = Some(clli);
}
}
_ => { }
}
if cursor < rbsp.len() && rbsp[cursor] == 0x80 {
break;
}
}
}
fn read_sei_ff_byte_sum(buf: &[u8], mut idx: usize) -> Option<(usize, usize)> {
let mut acc = 0usize;
while idx < buf.len() && buf[idx] == 0xFF {
acc += 0xFF;
idx += 1;
}
if idx >= buf.len() {
return None;
}
acc += buf[idx] as usize;
Some((acc, idx + 1))
}
fn parse_mastering_display(p: &[u8]) -> Option<MasteringDisplay> {
if p.len() < 24 {
return None;
}
let u16be = |o: usize| u16::from_be_bytes([p[o], p[o + 1]]);
let u32be = |o: usize| u32::from_be_bytes([p[o], p[o + 1], p[o + 2], p[o + 3]]);
Some(MasteringDisplay {
primaries_g_x: u16be(0),
primaries_g_y: u16be(2),
primaries_b_x: u16be(4),
primaries_b_y: u16be(6),
primaries_r_x: u16be(8),
primaries_r_y: u16be(10),
white_point_x: u16be(12),
white_point_y: u16be(14),
max_luminance: u32be(16),
min_luminance: u32be(20),
})
}
fn parse_content_light_level(p: &[u8]) -> Option<ContentLightLevel> {
if p.len() < 4 {
return None;
}
Some(ContentLightLevel {
max_cll: u16::from_be_bytes([p[0], p[1]]),
max_fall: u16::from_be_bytes([p[2], p[3]]),
})
}
#[cfg(test)]
mod tests {
use super::*;
fn emit_sei_payload(payload_type: u8, payload: &[u8]) -> Vec<u8> {
let mut out = Vec::new();
out.push(payload_type);
out.push(payload.len() as u8);
out.extend_from_slice(payload);
out.push(0x80);
out
}
fn wrap_as_prefix_sei_nal(rbsp: &[u8]) -> Vec<u8> {
let mut v = Vec::with_capacity(2 + rbsp.len());
v.push(0x4E);
v.push(0x01);
v.extend_from_slice(rbsp);
v
}
fn mastering_display_sei_bytes() -> Vec<u8> {
let mut p = Vec::new();
p.extend_from_slice(&13250u16.to_be_bytes());
p.extend_from_slice(&34500u16.to_be_bytes());
p.extend_from_slice(&7500u16.to_be_bytes());
p.extend_from_slice(&3000u16.to_be_bytes());
p.extend_from_slice(&34000u16.to_be_bytes());
p.extend_from_slice(&16000u16.to_be_bytes());
p.extend_from_slice(&15635u16.to_be_bytes());
p.extend_from_slice(&16450u16.to_be_bytes());
p.extend_from_slice(&10_000_000u32.to_be_bytes());
p.extend_from_slice(&50u32.to_be_bytes());
assert_eq!(p.len(), 24);
p
}
fn content_light_level_sei_bytes() -> Vec<u8> {
let mut p = Vec::new();
p.extend_from_slice(&1000u16.to_be_bytes());
p.extend_from_slice(&400u16.to_be_bytes());
p
}
fn build_annexb(nals: &[&[u8]]) -> Vec<u8> {
let mut out = Vec::new();
for nal in nals {
out.extend_from_slice(&[0, 0, 0, 1]);
out.extend_from_slice(nal);
}
out
}
#[test]
fn parses_mastering_display_sei_from_prefix_nal() {
let rbsp = emit_sei_payload(137, &mastering_display_sei_bytes());
let nal = wrap_as_prefix_sei_nal(&rbsp);
let stream = build_annexb(&[&nal]);
let sei = parse_annexb(&stream);
let md = sei.mastering_display.expect("mastering display populated");
assert_eq!(md.primaries_r_x, 34000);
assert_eq!(md.primaries_r_y, 16000);
assert_eq!(md.primaries_g_x, 13250);
assert_eq!(md.primaries_g_y, 34500);
assert_eq!(md.primaries_b_x, 7500);
assert_eq!(md.primaries_b_y, 3000);
assert_eq!(md.white_point_x, 15635);
assert_eq!(md.white_point_y, 16450);
assert_eq!(md.max_luminance, 10_000_000);
assert_eq!(md.min_luminance, 50);
assert!(sei.content_light_level.is_none());
}
#[test]
fn parses_content_light_level_sei_from_prefix_nal() {
let rbsp = emit_sei_payload(144, &content_light_level_sei_bytes());
let nal = wrap_as_prefix_sei_nal(&rbsp);
let stream = build_annexb(&[&nal]);
let sei = parse_annexb(&stream);
let cll = sei.content_light_level.expect("clli populated");
assert_eq!(cll.max_cll, 1000);
assert_eq!(cll.max_fall, 400);
assert!(sei.mastering_display.is_none());
}
#[test]
fn parses_both_sei_messages_in_same_nal() {
let mut rbsp = emit_sei_payload(137, &mastering_display_sei_bytes());
rbsp.pop();
rbsp.extend(emit_sei_payload(144, &content_light_level_sei_bytes()));
let nal = wrap_as_prefix_sei_nal(&rbsp);
let stream = build_annexb(&[&nal]);
let sei = parse_annexb(&stream);
assert!(sei.mastering_display.is_some());
assert!(sei.content_light_level.is_some());
}
#[test]
fn handles_emulation_prevention_bytes() {
let payload = vec![0x00, 0x00, 0x00, 0x01];
let mut rbsp_without_prevention = Vec::new();
rbsp_without_prevention.push(144); rbsp_without_prevention.push(payload.len() as u8); rbsp_without_prevention.extend_from_slice(&payload);
rbsp_without_prevention.push(0x80);
let mut ebsp = Vec::new();
let mut zero_run = 0;
for &b in &rbsp_without_prevention {
if zero_run >= 2 && b <= 0x03 {
ebsp.push(0x03);
zero_run = 0;
}
ebsp.push(b);
if b == 0 {
zero_run += 1;
} else {
zero_run = 0;
}
}
let mut nal = vec![0x4E, 0x01];
nal.extend_from_slice(&ebsp);
let stream = build_annexb(&[&nal]);
let sei = parse_annexb(&stream);
let cll = sei.content_light_level.expect("clli after emulation strip");
assert_eq!(cll.max_cll, 0);
assert_eq!(cll.max_fall, 1);
}
#[test]
fn returns_empty_when_no_sei_nal_present() {
let mut nal = vec![0x02, 0x01]; nal.extend_from_slice(&[0xFF, 0xFF, 0xFF]);
let stream = build_annexb(&[&nal]);
let sei = parse_annexb(&stream);
assert!(sei.mastering_display.is_none());
assert!(sei.content_light_level.is_none());
assert!(sei.is_empty());
}
#[test]
fn handles_start_code_4byte_variant() {
let rbsp = emit_sei_payload(144, &content_light_level_sei_bytes());
let nal = wrap_as_prefix_sei_nal(&rbsp);
let mut stream = vec![0, 0, 0, 1];
stream.extend_from_slice(&nal);
let sei = parse_annexb(&stream);
assert!(sei.content_light_level.is_some());
}
#[test]
fn suffix_sei_nal_type_40_also_parsed() {
let rbsp = emit_sei_payload(144, &content_light_level_sei_bytes());
let mut nal = vec![0x50, 0x01];
nal.extend_from_slice(&rbsp);
let stream = build_annexb(&[&nal]);
let sei = parse_annexb(&stream);
assert!(sei.content_light_level.is_some());
}
#[test]
fn ff_byte_sum_handles_large_payload_type() {
let mut rbsp = vec![0xFF, 7, 0, 0x80];
rbsp.pop();
rbsp.extend(emit_sei_payload(144, &content_light_level_sei_bytes()));
let nal = wrap_as_prefix_sei_nal(&rbsp);
let stream = build_annexb(&[&nal]);
let sei = parse_annexb(&stream);
assert!(sei.content_light_level.is_some());
}
}