use crate::error::{Error, Result};
use alloc::string::String;
use alloc::vec::Vec;
use broadcast_common::error::InvalidValue;
use broadcast_common::len::{need_in, need_out};
use broadcast_common::{Parse, Serialize};
pub const DVCC_FOURCC: [u8; 4] = *b"dvcC";
pub const DVVC_FOURCC: [u8; 4] = *b"dvvC";
pub const DVH1_FOURCC: [u8; 4] = *b"dvh1";
pub const DVA1_FOURCC: [u8; 4] = *b"dva1";
pub const DVHE_FOURCC: [u8; 4] = *b"dvhe";
pub const DOVI_VIDEO_STREAM_DESCRIPTOR_TAG: u8 = 0xB0;
pub const REGISTRATION_DESCRIPTOR_TAG: u8 = 0x05;
pub const DOVI_FORMAT_IDENTIFIER: [u8; 4] = *b"DOVI";
pub const MAX_DVCC_PROFILE: u8 = 7;
pub const AVC_PROFILE: u8 = 9;
const HEVC_PROFILES: [u8; 4] = [4, 5, 7, 8];
pub const DOVI_RECORD_LEN: usize = 24;
const TS_DESCRIPTOR_BASE_LEN: usize = 5;
const TS_DESCRIPTOR_DEPENDENCY_LEN: usize = 2;
const LEVEL_BITS: u32 = 6;
const LEVEL_MASK: u16 = (1 << LEVEL_BITS) - 1;
const PROFILE_SHIFT: u32 = 9;
const LEVEL_SHIFT: u32 = 3;
const RPU_FLAG: u16 = 0b100;
const EL_FLAG: u16 = 0b010;
const BL_FLAG: u16 = 0b001;
const CCID_SHIFT: u32 = 4;
const CCID_MASK: u8 = 0x0F;
const PID_SHIFT: u32 = 3;
const PID_MAX: u16 = 0x1FFF;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum DoviBlSignalCompatibilityId {
None,
Hdr10,
Sdr,
Hlg,
UhdBdHdr,
Reserved(u8),
}
impl DoviBlSignalCompatibilityId {
pub fn code(self) -> u8 {
match self {
Self::None => 0,
Self::Hdr10 => 1,
Self::Sdr => 2,
Self::Hlg => 4,
Self::UhdBdHdr => 6,
Self::Reserved(v) => v,
}
}
pub fn from_code(code: u8) -> Self {
match code & CCID_MASK {
0 => Self::None,
1 => Self::Hdr10,
2 => Self::Sdr,
4 => Self::Hlg,
6 => Self::UhdBdHdr,
v => Self::Reserved(v),
}
}
pub fn name(&self) -> &'static str {
match self {
Self::None => "none",
Self::Hdr10 => "HDR10",
Self::Sdr => "SDR",
Self::Hlg => "HLG",
Self::UhdBdHdr => "UHD BD HDR",
Self::Reserved(_) => "reserved",
}
}
}
broadcast_common::impl_spec_display!(DoviBlSignalCompatibilityId, Reserved);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum DoviSampleEntryKind {
CompatibleBaseLayer,
DolbyVisionEntry,
}
impl DoviSampleEntryKind {
pub fn name(&self) -> &'static str {
match self {
Self::CompatibleBaseLayer => "compatible base layer",
Self::DolbyVisionEntry => "Dolby Vision sample entry",
}
}
}
broadcast_common::impl_spec_display!(DoviSampleEntryKind);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DoviDecoderConfigurationRecord {
pub version_major: u8,
pub version_minor: u8,
pub profile: u8,
pub level: u8,
pub rpu_present: bool,
pub el_present: bool,
pub bl_present: bool,
pub bl_signal_compatibility_id: DoviBlSignalCompatibilityId,
pub reserved: [u8; DOVI_RESERVED_LEN],
}
pub const DOVI_RESERVED_LEN: usize = DOVI_RECORD_LEN - RESERVED_OFFSET;
pub const DOVI_RESERVED_ZERO: [u8; DOVI_RESERVED_LEN] = [0; DOVI_RESERVED_LEN];
const RESERVED_OFFSET: usize = 4;
fn invalid(field: &'static str, value: u64, reason: &'static str) -> Error {
Error::InvalidValue(InvalidValue {
field,
value,
reason,
})
}
fn pack_config_word(r: &DoviDecoderConfigurationRecord) -> u16 {
(u16::from(r.profile) << PROFILE_SHIFT)
| ((u16::from(r.level) & LEVEL_MASK) << LEVEL_SHIFT)
| if r.rpu_present { RPU_FLAG } else { 0 }
| if r.el_present { EL_FLAG } else { 0 }
| if r.bl_present { BL_FLAG } else { 0 }
}
fn unpack_config_word(
version_major: u8,
version_minor: u8,
word: u16,
ccid: u8,
reserved: [u8; DOVI_RESERVED_LEN],
) -> DoviDecoderConfigurationRecord {
DoviDecoderConfigurationRecord {
version_major,
version_minor,
profile: (word >> PROFILE_SHIFT) as u8,
level: ((word >> LEVEL_SHIFT) & LEVEL_MASK) as u8,
rpu_present: word & RPU_FLAG != 0,
el_present: word & EL_FLAG != 0,
bl_present: word & BL_FLAG != 0,
bl_signal_compatibility_id: DoviBlSignalCompatibilityId::from_code(ccid),
reserved,
}
}
impl DoviDecoderConfigurationRecord {
pub fn box_fourcc(&self) -> [u8; 4] {
if self.profile <= MAX_DVCC_PROFILE {
DVCC_FOURCC
} else {
DVVC_FOURCC
}
}
pub fn is_base_layer_compatible(&self) -> bool {
self.bl_signal_compatibility_id != DoviBlSignalCompatibilityId::None
}
pub fn is_single_layer(&self) -> bool {
self.bl_present && self.rpu_present && !self.el_present
}
pub fn is_hevc_profile(&self) -> bool {
HEVC_PROFILES.contains(&self.profile)
}
pub fn is_avc_profile(&self) -> bool {
self.profile == AVC_PROFILE
}
pub fn uses_dolby_hevc_entry(&self) -> bool {
self.is_single_layer()
&& self.is_hevc_profile()
&& self.sample_entry_kind() == DoviSampleEntryKind::DolbyVisionEntry
}
pub fn sample_entry_kind(&self) -> DoviSampleEntryKind {
if self.is_base_layer_compatible() {
DoviSampleEntryKind::CompatibleBaseLayer
} else {
DoviSampleEntryKind::DolbyVisionEntry
}
}
pub fn from_sample_entry_boxes<'a>(
boxes: impl IntoIterator<Item = (&'a [u8; 4], &'a [u8])>,
) -> Option<Self> {
boxes
.into_iter()
.filter(|(fourcc, _)| **fourcc == DVCC_FOURCC || **fourcc == DVVC_FOURCC)
.find_map(|(_, body)| Self::parse(body).ok())
}
pub fn codec_string(&self) -> Option<String> {
let fourcc = if self.is_hevc_profile() {
DVH1_FOURCC
} else if self.is_avc_profile() {
DVA1_FOURCC
} else {
return None;
};
Some(alloc::format!(
"{}.{:02}.{:02}",
core::str::from_utf8(&fourcc).ok()?,
self.profile,
self.level
))
}
}
impl<'a> Parse<'a> for DoviDecoderConfigurationRecord {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
need_in(bytes, DOVI_RECORD_LEN, "DOVIDecoderConfigurationRecord")?;
if bytes.len() > DOVI_RECORD_LEN {
return Err(invalid(
"DOVIDecoderConfigurationRecord",
bytes.len() as u64,
"longer than the defined syntax",
));
}
let word = u16::from_be_bytes([bytes[2], bytes[3]]);
let mut reserved = [0u8; DOVI_RESERVED_LEN];
reserved.copy_from_slice(&bytes[RESERVED_OFFSET..]);
reserved[0] &= CCID_MASK;
Ok(unpack_config_word(
bytes[0],
bytes[1],
word,
bytes[4] >> CCID_SHIFT,
reserved,
))
}
}
impl Serialize for DoviDecoderConfigurationRecord {
type Error = Error;
fn serialized_len(&self) -> usize {
DOVI_RECORD_LEN
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
need_out(buf, DOVI_RECORD_LEN)?;
if self.profile >= 1 << (16 - PROFILE_SHIFT) {
return Err(invalid(
"dv_profile",
u64::from(self.profile),
"does not fit 7 bits",
));
}
if u16::from(self.level) > LEVEL_MASK {
return Err(invalid(
"dv_level",
u64::from(self.level),
"does not fit 6 bits",
));
}
let ccid = self.bl_signal_compatibility_id.code();
if ccid > CCID_MASK {
return Err(invalid(
"dv_bl_signal_compatibility_id",
u64::from(ccid),
"does not fit 4 bits",
));
}
if self.reserved[0] & !CCID_MASK != 0 {
return Err(invalid(
"DOVIDecoderConfigurationRecord.reserved",
u64::from(self.reserved[0]),
"the first reserved byte holds only 4 bits",
));
}
buf[0] = self.version_major;
buf[1] = self.version_minor;
buf[2..4].copy_from_slice(&pack_config_word(self).to_be_bytes());
buf[RESERVED_OFFSET..DOVI_RECORD_LEN].copy_from_slice(&self.reserved);
buf[4] |= ccid << CCID_SHIFT;
Ok(DOVI_RECORD_LEN)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DoviVideoStreamDescriptor {
pub record: DoviDecoderConfigurationRecord,
pub dependency_pid: Option<u16>,
pub dependency_reserved: u8,
pub reserved: u8,
pub extension: Vec<u8>,
}
impl<'a> Parse<'a> for DoviVideoStreamDescriptor {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
need_in(
bytes,
TS_DESCRIPTOR_BASE_LEN,
"DOVI_video_stream_descriptor",
)?;
let word = u16::from_be_bytes([bytes[2], bytes[3]]);
let bl_present = word & BL_FLAG != 0;
let mut dependency_reserved = 0u8;
let (dependency_pid, tail) = if bl_present {
(None, 4)
} else {
need_in(
bytes,
TS_DESCRIPTOR_BASE_LEN + TS_DESCRIPTOR_DEPENDENCY_LEN,
"DOVI_video_stream_descriptor dependency_pid",
)?;
let raw = u16::from_be_bytes([bytes[4], bytes[5]]);
dependency_reserved = (raw & ((1 << PID_SHIFT) - 1)) as u8;
(Some(raw >> PID_SHIFT), 6)
};
need_in(bytes, tail + 1, "DOVI_video_stream_descriptor")?;
Ok(Self {
record: unpack_config_word(
bytes[0],
bytes[1],
word,
bytes[tail] >> CCID_SHIFT,
DOVI_RESERVED_ZERO,
),
dependency_pid,
dependency_reserved,
reserved: bytes[tail] & CCID_MASK,
extension: bytes[tail + 1..].to_vec(),
})
}
}
impl Serialize for DoviVideoStreamDescriptor {
type Error = Error;
fn serialized_len(&self) -> usize {
TS_DESCRIPTOR_BASE_LEN
+ if self.record.bl_present {
0
} else {
TS_DESCRIPTOR_DEPENDENCY_LEN
}
+ self.extension.len()
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let need = self.serialized_len();
need_out(buf, need)?;
let ccid = self.record.bl_signal_compatibility_id.code();
if ccid > CCID_MASK || self.record.level > LEVEL_MASK as u8 || self.record.profile >= 1 << 7
{
return Err(invalid(
"DOVI_video_stream_descriptor",
u64::from(ccid),
"field does not fit its bit width",
));
}
buf[0] = self.record.version_major;
buf[1] = self.record.version_minor;
buf[2..4].copy_from_slice(&pack_config_word(&self.record).to_be_bytes());
let tail = match (self.record.bl_present, self.dependency_pid) {
(true, None) => 4,
(false, Some(pid)) if pid <= PID_MAX => {
if self.dependency_reserved >= 1 << PID_SHIFT {
return Err(invalid(
"dependency_reserved",
u64::from(self.dependency_reserved),
"does not fit 3 bits",
));
}
buf[4..6].copy_from_slice(
&((pid << PID_SHIFT) | u16::from(self.dependency_reserved)).to_be_bytes(),
);
6
}
_ => {
return Err(invalid(
"dependency_pid",
self.dependency_pid.map_or(0, u64::from),
"present exactly when bl_present_flag is 0, and 13 bits",
));
}
};
if self.reserved > CCID_MASK {
return Err(invalid(
"DOVI_video_stream_descriptor.reserved",
u64::from(self.reserved),
"does not fit 4 bits",
));
}
buf[tail] = (ccid << CCID_SHIFT) | self.reserved;
buf[tail + 1..need].copy_from_slice(&self.extension);
Ok(need)
}
}
pub fn find_video_stream_descriptor(es_info: &[u8]) -> Option<DoviVideoStreamDescriptor> {
descriptor_bodies(es_info)
.find(|(tag, _)| *tag == DOVI_VIDEO_STREAM_DESCRIPTOR_TAG)
.and_then(|(_, body)| DoviVideoStreamDescriptor::parse(body).ok())
}
pub fn video_stream_descriptor_bytes(d: &DoviVideoStreamDescriptor) -> Result<Vec<u8>> {
let body = d.try_to_bytes()?;
let length = broadcast_common::len::fit_u8(body.len(), "descriptor_length")?;
let mut out = Vec::with_capacity(2 + body.len());
out.push(DOVI_VIDEO_STREAM_DESCRIPTOR_TAG);
out.push(length);
out.extend_from_slice(&body);
Ok(out)
}
pub fn has_registration_descriptor(es_info: &[u8]) -> bool {
descriptor_bodies(es_info).any(|(tag, body)| {
tag == REGISTRATION_DESCRIPTOR_TAG && body.starts_with(&DOVI_FORMAT_IDENTIFIER)
})
}
fn descriptor_bodies(es_info: &[u8]) -> impl Iterator<Item = (u8, &[u8])> {
let mut off = 0usize;
core::iter::from_fn(move || {
if off + 2 > es_info.len() {
return None;
}
let tag = es_info[off];
let len = usize::from(es_info[off + 1]);
let body = es_info.get(off + 2..off + 2 + len)?;
off += 2 + len;
Some((tag, body))
})
}
#[cfg(test)]
mod tests {
use super::*;
const P81_RECORD: [u8; 24] = [
0x01, 0x00, 0x10, 0x1D, 0x10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
#[test]
fn record_round_trips() {
let r = DoviDecoderConfigurationRecord::parse(&P81_RECORD).unwrap();
assert_eq!(r.profile, 8);
assert_eq!(r.level, 3);
assert_eq!(r.try_to_bytes().unwrap(), P81_RECORD);
assert_eq!(r.box_fourcc(), DVVC_FOURCC);
assert_eq!(r.codec_string().as_deref(), Some("dvh1.08.03"));
}
#[test]
fn reserved_bits_are_preserved_byte_exact() {
let mut bytes = P81_RECORD;
bytes[4] |= 0x05;
bytes[6] = 0xA5;
bytes[23] = 0xFF;
let r = DoviDecoderConfigurationRecord::parse(&bytes).unwrap();
assert_eq!(
r.bl_signal_compatibility_id,
DoviBlSignalCompatibilityId::Hdr10
);
assert_eq!(r.try_to_bytes().unwrap(), bytes);
assert_eq!(r.reserved[0], 0x05);
}
#[test]
fn lengths_are_rejected() {
assert!(matches!(
DoviDecoderConfigurationRecord::parse(&P81_RECORD[..23]),
Err(crate::error::Error::BufferTooShort(..))
));
let mut long = P81_RECORD.to_vec();
long.push(0);
assert!(matches!(
DoviDecoderConfigurationRecord::parse(&long),
Err(crate::error::Error::InvalidValue(..))
));
}
#[test]
fn descriptor_reserved_bits_are_preserved_byte_exact() {
let with_pid = [0x01, 0x00, 0x0E, 0x1A, 0x12, 0x37, 0x4B];
let d = DoviVideoStreamDescriptor::parse(&with_pid).unwrap();
assert_eq!(d.dependency_pid, Some(0x246));
assert_eq!(d.dependency_reserved, 0x7);
assert_eq!(d.reserved, 0xB);
assert_eq!(d.try_to_bytes().unwrap(), with_pid);
let single = [0x01, 0x00, 0x10, 0x1D, 0x1F];
let d = DoviVideoStreamDescriptor::parse(&single).unwrap();
assert_eq!(d.reserved, 0xF);
assert_eq!(
d.record.bl_signal_compatibility_id,
DoviBlSignalCompatibilityId::Hdr10
);
assert_eq!(d.try_to_bytes().unwrap(), single);
}
#[test]
fn descriptor_with_dependency_pid_round_trips() {
let d = DoviVideoStreamDescriptor {
record: DoviDecoderConfigurationRecord {
version_major: 1,
version_minor: 0,
profile: 7,
level: 6,
rpu_present: true,
el_present: true,
bl_present: false,
bl_signal_compatibility_id: DoviBlSignalCompatibilityId::UhdBdHdr,
reserved: DOVI_RESERVED_ZERO,
},
dependency_pid: Some(0x1234),
dependency_reserved: 0,
reserved: 0,
extension: Vec::new(),
};
let bytes = d.try_to_bytes().unwrap();
assert_eq!(bytes.len(), 7);
assert_eq!(DoviVideoStreamDescriptor::parse(&bytes).unwrap(), d);
}
#[test]
fn profile_84_is_representable() {
let r = DoviDecoderConfigurationRecord {
version_major: 1,
version_minor: 0,
profile: 8,
level: 7,
rpu_present: true,
el_present: false,
bl_present: true,
bl_signal_compatibility_id: DoviBlSignalCompatibilityId::Hlg,
reserved: DOVI_RESERVED_ZERO,
};
let back = DoviDecoderConfigurationRecord::parse(&r.try_to_bytes().unwrap()).unwrap();
assert_eq!(back, r);
assert_eq!(back.codec_string().as_deref(), Some("dvh1.08.07"));
}
#[test]
fn compatibility_id_labels() {
assert_eq!(DoviBlSignalCompatibilityId::from_code(4).name(), "HLG");
assert_eq!(DoviBlSignalCompatibilityId::Reserved(9).code(), 9);
}
#[test]
fn profile_classification_is_table_1_only() {
let mut r = DoviDecoderConfigurationRecord::parse(&P81_RECORD).unwrap();
for (profile, hevc, avc) in [
(4, true, false),
(5, true, false),
(7, true, false),
(8, true, false),
(9, false, true),
(6, false, false),
(10, false, false),
(20, false, false),
] {
r.profile = profile;
assert_eq!(r.is_hevc_profile(), hevc, "profile {profile}");
assert_eq!(r.is_avc_profile(), avc, "profile {profile}");
}
}
#[test]
fn codec_string_follows_profile_family() {
let mut r = DoviDecoderConfigurationRecord::parse(&P81_RECORD).unwrap();
r.profile = 5;
r.level = 3;
assert_eq!(r.codec_string().as_deref(), Some("dvh1.05.03"));
r.profile = 9;
r.level = 4;
assert_eq!(r.codec_string().as_deref(), Some("dva1.09.04"));
r.profile = 10;
assert_eq!(r.codec_string(), None);
}
#[test]
fn descriptor_extension_bytes_are_tolerated_and_preserved() {
let body = [0x01, 0x00, 0x10, 0x1D, 0x10, 0xAA, 0xBB];
let d = DoviVideoStreamDescriptor::parse(&body).unwrap();
assert_eq!(d.extension, [0xAA, 0xBB]);
assert_eq!(d.record.profile, 8);
assert_eq!(d.try_to_bytes().unwrap(), body);
let mut es_info = alloc::vec![DOVI_VIDEO_STREAM_DESCRIPTOR_TAG, body.len() as u8];
es_info.extend_from_slice(&body);
assert!(find_video_stream_descriptor(&es_info).is_some());
}
#[test]
fn video_stream_descriptor_bytes_frames_the_body_and_find_recovers_it() {
let with_pid = [0x01, 0x00, 0x0E, 0x1A, 0x12, 0x37, 0x4B];
let d = DoviVideoStreamDescriptor::parse(&with_pid).unwrap();
let es_info = video_stream_descriptor_bytes(&d).unwrap();
assert_eq!(es_info[0], DOVI_VIDEO_STREAM_DESCRIPTOR_TAG);
assert_eq!(usize::from(es_info[1]), with_pid.len());
assert_eq!(&es_info[2..], &with_pid);
assert_eq!(find_video_stream_descriptor(&es_info), Some(d));
let mut after_other = alloc::vec![0x05, 0x01, 0xAA];
after_other.extend_from_slice(&es_info);
assert_eq!(
find_video_stream_descriptor(&after_other),
find_video_stream_descriptor(&es_info)
);
}
}