diff --git a/assets/tests/l15_to_l18.bin b/assets/tests/l15_to_l18.bin new file mode 100644 index 00000000..00a64e43 Binary files /dev/null and b/assets/tests/l15_to_l18.bin differ diff --git a/dolby_vision/src/c_structs/extension_metadata.rs b/dolby_vision/src/c_structs/extension_metadata.rs index 316247c2..4f070818 100644 --- a/dolby_vision/src/c_structs/extension_metadata.rs +++ b/dolby_vision/src/c_structs/extension_metadata.rs @@ -22,6 +22,10 @@ pub struct DmData { level9: *const ExtMetadataBlockLevel9, level10: Level10BlockList, level11: *const ExtMetadataBlockLevel11, + level15: *const ExtMetadataBlockLevel15, + level16: *const ExtMetadataBlockLevel16, + level17: *const ExtMetadataBlockLevel17, + level18: *const ExtMetadataBlockLevel18, level254: *const ExtMetadataBlockLevel254, level255: *const ExtMetadataBlockLevel255, } @@ -106,6 +110,22 @@ impl DmData { self.level11 = Box::into_raw(Box::new(b.clone())) as *const ExtMetadataBlockLevel11 } + ExtMetadataBlock::Level15(b) => { + self.level15 = + Box::into_raw(Box::new(b.clone())) as *const ExtMetadataBlockLevel15 + } + ExtMetadataBlock::Level16(b) => { + self.level16 = + Box::into_raw(Box::new(b.clone())) as *const ExtMetadataBlockLevel16 + } + ExtMetadataBlock::Level17(b) => { + self.level17 = + Box::into_raw(Box::new(b.clone())) as *const ExtMetadataBlockLevel17 + } + ExtMetadataBlock::Level18(b) => { + self.level18 = + Box::into_raw(Box::new(b.clone())) as *const ExtMetadataBlockLevel18 + } ExtMetadataBlock::Level254(b) => { self.level254 = Box::into_raw(Box::new(b.clone())) as *const ExtMetadataBlockLevel254 @@ -156,6 +176,18 @@ impl DmData { if !self.level11.is_null() { drop(Box::from_raw(self.level11 as *mut ExtMetadataBlockLevel11)); } + if !self.level15.is_null() { + drop(Box::from_raw(self.level15 as *mut ExtMetadataBlockLevel15)); + } + if !self.level16.is_null() { + drop(Box::from_raw(self.level16 as *mut ExtMetadataBlockLevel16)); + } + if !self.level17.is_null() { + drop(Box::from_raw(self.level17 as *mut ExtMetadataBlockLevel17)); + } + if !self.level18.is_null() { + drop(Box::from_raw(self.level18 as *mut ExtMetadataBlockLevel18)); + } if !self.level254.is_null() { drop(Box::from_raw( self.level254 as *mut ExtMetadataBlockLevel254, @@ -184,6 +216,10 @@ impl Default for DmData { level9: null(), level10: Default::default(), level11: null(), + level15: null(), + level16: null(), + level17: null(), + level18: null(), level254: null(), level255: null(), } diff --git a/dolby_vision/src/rpu/extension_metadata/blocks/level11.rs b/dolby_vision/src/rpu/extension_metadata/blocks/level11.rs index 69d81f56..af4d047b 100644 --- a/dolby_vision/src/rpu/extension_metadata/blocks/level11.rs +++ b/dolby_vision/src/rpu/extension_metadata/blocks/level11.rs @@ -19,6 +19,12 @@ pub struct ExtMetadataBlockLevel11 { #[cfg_attr(feature = "serde", serde(default))] pub reserved_byte2: u8, + /// Byte 3 of the L11 block. In DV2 this carries Authentic Motion / + /// FRC metadata (see Dolby Vision Metadata Spec v1.5.1 ยง6, HDMI + /// Transmission Spec v5.1.1 PB[13]): bits [3:0] = `frc_strength`, + /// bit 4 = `frc_type` (0 = De-judder, 1 = Smoothness). Stored as a + /// raw byte for round-trip fidelity; decoded via [`Self::frc_strength`] + /// and [`Self::frc_type`]. #[cfg_attr(feature = "serde", serde(default))] pub reserved_byte3: u8, } @@ -51,11 +57,23 @@ impl ExtMetadataBlockLevel11 { pub fn validate(&self) -> Result<()> { ensure!(self.content_type <= 15); ensure!(self.whitepoint <= 15); - ensure!(self.reserved_byte3 == 0); Ok(()) } + /// FRC strength (Authentic Motion), bits [3:0] of byte 3. + /// Range 0..=15. Only meaningful when `DV2_Motion_Control` is set + /// (byte 1, bit 5); otherwise this is expected to be 0. + pub const fn frc_strength(&self) -> u8 { + self.reserved_byte3 & 0x0F + } + + /// FRC type (Authentic Motion), bit 4 of byte 3. + /// `false` (0) = De-judder (<=30fps film), `true` (1) = Smoothness (>=50fps sports). + pub const fn frc_type(&self) -> bool { + (self.reserved_byte3 >> 4) & 0x01 == 1 + } + /// Cinema, D65 whitepoint pub const fn default_cinema() -> Self { Self { diff --git a/dolby_vision/src/rpu/extension_metadata/blocks/level15.rs b/dolby_vision/src/rpu/extension_metadata/blocks/level15.rs new file mode 100644 index 00000000..6066500f --- /dev/null +++ b/dolby_vision/src/rpu/extension_metadata/blocks/level15.rs @@ -0,0 +1,92 @@ +use anyhow::{Result, ensure}; +use bitvec_helpers::{ + bitstream_io_reader::BsIoSliceReader, bitstream_io_writer::BitstreamIoWriter, +}; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +use super::{ExtMetadataBlock, ExtMetadataBlockInfo}; + +/// Consumer look metadata, Precision Rendering/Detail +#[repr(C)] +#[derive(Debug, Default, Clone)] +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +pub struct ExtMetadataBlockLevel15 { + pub confidence: u8, + pub precision_rendering_strength: u8, + pub d_local_contrast: u8, + pub d_brightness: u8, + pub d_saturation_plus_one: u8, + pub d_contrast_plus_one: u8, + + pub confidence_no_pr: u8, + pub d_brightness_no_pr: u8, + pub d_saturation_plus_one_no_pr: u8, + pub d_contrast_plus_one_no_pr: u8, + + /// 4 bits + pub revision: u8, + /// 4 bits + pub reserved: u8, +} + +impl ExtMetadataBlockLevel15 { + pub(crate) fn parse(reader: &mut BsIoSliceReader) -> Result { + let block = Self { + confidence: reader.read::<8, u8>()?, + precision_rendering_strength: reader.read::<8, u8>()?, + d_local_contrast: reader.read::<8, u8>()?, + d_brightness: reader.read::<8, u8>()?, + d_saturation_plus_one: reader.read::<8, u8>()?, + d_contrast_plus_one: reader.read::<8, u8>()?, + confidence_no_pr: reader.read::<8, u8>()?, + d_brightness_no_pr: reader.read::<8, u8>()?, + d_saturation_plus_one_no_pr: reader.read::<8, u8>()?, + d_contrast_plus_one_no_pr: reader.read::<8, u8>()?, + revision: reader.read::<4, u8>()?, + reserved: reader.read::<4, u8>()?, + }; + + Ok(ExtMetadataBlock::Level15(block)) + } + + pub fn write(&self, writer: &mut BitstreamIoWriter) -> Result<()> { + self.validate()?; + + writer.write::<8, u8>(self.confidence)?; + writer.write::<8, u8>(self.precision_rendering_strength)?; + writer.write::<8, u8>(self.d_local_contrast)?; + writer.write::<8, u8>(self.d_brightness)?; + writer.write::<8, u8>(self.d_saturation_plus_one)?; + writer.write::<8, u8>(self.d_contrast_plus_one)?; + writer.write::<8, u8>(self.confidence_no_pr)?; + writer.write::<8, u8>(self.d_brightness_no_pr)?; + writer.write::<8, u8>(self.d_saturation_plus_one_no_pr)?; + writer.write::<8, u8>(self.d_contrast_plus_one_no_pr)?; + writer.write::<4, u8>(self.revision)?; + writer.write::<4, u8>(self.reserved)?; + + Ok(()) + } + + pub fn validate(&self) -> Result<()> { + ensure!(self.reserved == 0); + + Ok(()) + } +} + +impl ExtMetadataBlockInfo for ExtMetadataBlockLevel15 { + fn level(&self) -> u8 { + 15 + } + + fn bytes_size(&self) -> u64 { + 11 + } + + fn required_bits(&self) -> u64 { + 88 + } +} diff --git a/dolby_vision/src/rpu/extension_metadata/blocks/level16.rs b/dolby_vision/src/rpu/extension_metadata/blocks/level16.rs new file mode 100644 index 00000000..179e5913 --- /dev/null +++ b/dolby_vision/src/rpu/extension_metadata/blocks/level16.rs @@ -0,0 +1,92 @@ +use anyhow::{Result, ensure}; +use bitvec_helpers::{ + bitstream_io_reader::BsIoSliceReader, bitstream_io_writer::BitstreamIoWriter, +}; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +use super::{ExtMetadataBlock, ExtMetadataBlockInfo}; + +/// Local tone mapping metadata +#[repr(C)] +#[derive(Debug, Default, Clone)] +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +pub struct ExtMetadataBlockLevel16 { + pub revision: u8, + pub count: usize, + + pub params: Vec, +} + +#[repr(C)] +#[derive(Debug, Default, Clone)] +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +pub struct Level16Params { + pub contrast_target: u8, + pub precision_rendering_strength: u8, + pub d_local_contrast: u8, + pub max_d_brightness: u8, + pub max_d_saturation_plus_one: u8, +} + +impl ExtMetadataBlockLevel16 { + pub(crate) fn parse(reader: &mut BsIoSliceReader) -> Result { + let mut block = Self { + revision: reader.read::<4, u8>()?, + count: reader.read::<4, u8>()? as usize, + ..Default::default() + }; + + block.params.reserve(block.count); + + for _ in 0..block.count { + block.params.push(Level16Params { + contrast_target: reader.read::<8, u8>()?, + precision_rendering_strength: reader.read::<8, u8>()?, + d_local_contrast: reader.read::<8, u8>()?, + max_d_brightness: reader.read::<8, u8>()?, + max_d_saturation_plus_one: reader.read::<8, u8>()?, + }); + } + + Ok(ExtMetadataBlock::Level16(block)) + } + + pub fn write(&self, writer: &mut BitstreamIoWriter) -> Result<()> { + self.validate()?; + + writer.write::<4, u8>(self.revision)?; + writer.write::<4, u8>(self.count as u8)?; + + for params in &self.params { + writer.write::<8, u8>(params.contrast_target)?; + writer.write::<8, u8>(params.precision_rendering_strength)?; + writer.write::<8, u8>(params.d_local_contrast)?; + writer.write::<8, u8>(params.max_d_brightness)?; + writer.write::<8, u8>(params.max_d_saturation_plus_one)?; + } + + Ok(()) + } + + pub fn validate(&self) -> Result<()> { + ensure!(self.count == self.params.len()); + + Ok(()) + } +} + +impl ExtMetadataBlockInfo for ExtMetadataBlockLevel16 { + fn level(&self) -> u8 { + 16 + } + + fn bytes_size(&self) -> u64 { + 1 + (self.count as u64 * 5) + } + + fn required_bits(&self) -> u64 { + self.bytes_size() * 8 + } +} diff --git a/dolby_vision/src/rpu/extension_metadata/blocks/level17.rs b/dolby_vision/src/rpu/extension_metadata/blocks/level17.rs new file mode 100644 index 00000000..8df75a8d --- /dev/null +++ b/dolby_vision/src/rpu/extension_metadata/blocks/level17.rs @@ -0,0 +1,85 @@ +use anyhow::Result; +use bitvec_helpers::{ + bitstream_io_reader::BsIoSliceReader, bitstream_io_writer::BitstreamIoWriter, +}; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +use super::{ExtMetadataBlock, ExtMetadataBlockInfo}; + +/// Up mapping metadata +#[repr(C)] +#[derive(Debug, Default, Clone)] +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +pub struct ExtMetadataBlockLevel17 { + pub mid_boost: u8, + pub highlight_stretch: u8, + pub shadow_drop: u8, + pub contrast_boost: u8, + pub saturation_boost: u8, + pub detail_boost: u8, + pub chroma_indicator: u8, + + /// 12 bits + pub intensity_indicator_pq: u16, + + /// 4 bits + pub revision: u8, + + pub chroma_lift: u8, +} + +impl ExtMetadataBlockLevel17 { + pub(crate) fn parse(reader: &mut BsIoSliceReader) -> Result { + let block = Self { + mid_boost: reader.read::<8, u8>()?, + highlight_stretch: reader.read::<8, u8>()?, + shadow_drop: reader.read::<8, u8>()?, + contrast_boost: reader.read::<8, u8>()?, + saturation_boost: reader.read::<8, u8>()?, + detail_boost: reader.read::<8, u8>()?, + chroma_indicator: reader.read::<8, u8>()?, + intensity_indicator_pq: reader.read::<12, u16>()?, + revision: reader.read::<4, u8>()?, + chroma_lift: reader.read::<8, u8>()?, + }; + + Ok(ExtMetadataBlock::Level17(block)) + } + + pub fn write(&self, writer: &mut BitstreamIoWriter) -> Result<()> { + self.validate()?; + + writer.write::<8, u8>(self.mid_boost)?; + writer.write::<8, u8>(self.highlight_stretch)?; + writer.write::<8, u8>(self.shadow_drop)?; + writer.write::<8, u8>(self.contrast_boost)?; + writer.write::<8, u8>(self.saturation_boost)?; + writer.write::<8, u8>(self.detail_boost)?; + writer.write::<8, u8>(self.chroma_indicator)?; + writer.write::<12, u16>(self.intensity_indicator_pq)?; + writer.write::<4, u8>(self.revision)?; + writer.write::<8, u8>(self.chroma_lift)?; + + Ok(()) + } + + pub fn validate(&self) -> Result<()> { + Ok(()) + } +} + +impl ExtMetadataBlockInfo for ExtMetadataBlockLevel17 { + fn level(&self) -> u8 { + 17 + } + + fn bytes_size(&self) -> u64 { + 10 + } + + fn required_bits(&self) -> u64 { + 80 + } +} diff --git a/dolby_vision/src/rpu/extension_metadata/blocks/level18.rs b/dolby_vision/src/rpu/extension_metadata/blocks/level18.rs new file mode 100644 index 00000000..4b9db2a8 --- /dev/null +++ b/dolby_vision/src/rpu/extension_metadata/blocks/level18.rs @@ -0,0 +1,73 @@ +use anyhow::{Result, ensure}; +use bitvec_helpers::{ + bitstream_io_reader::BsIoSliceReader, bitstream_io_writer::BitstreamIoWriter, +}; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +use super::{ExtMetadataBlock, ExtMetadataBlockInfo}; + +/// Creative environment metadata +#[repr(C)] +#[derive(Debug, Default, Clone)] +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +pub struct ExtMetadataBlockLevel18 { + pub surround_luminance_pq: u16, + pub min_preserved_luminance_pq: u16, + pub adaptation_luminance_pq: u16, + pub max_preserved_luminance_pq: u16, + + /// 4 bits + pub revision: u8, + /// 4 bits + pub reserved: u8, +} + +impl ExtMetadataBlockLevel18 { + pub(crate) fn parse(reader: &mut BsIoSliceReader) -> Result { + let block = Self { + surround_luminance_pq: reader.read::<12, u16>()?, + min_preserved_luminance_pq: reader.read::<12, u16>()?, + adaptation_luminance_pq: reader.read::<12, u16>()?, + max_preserved_luminance_pq: reader.read::<12, u16>()?, + revision: reader.read::<4, u8>()?, + reserved: reader.read::<4, u8>()?, + }; + + Ok(ExtMetadataBlock::Level18(block)) + } + + pub fn write(&self, writer: &mut BitstreamIoWriter) -> Result<()> { + self.validate()?; + + writer.write::<12, u16>(self.surround_luminance_pq)?; + writer.write::<12, u16>(self.min_preserved_luminance_pq)?; + writer.write::<12, u16>(self.adaptation_luminance_pq)?; + writer.write::<12, u16>(self.max_preserved_luminance_pq)?; + writer.write::<4, u8>(self.revision)?; + writer.write::<4, u8>(self.reserved)?; + + Ok(()) + } + + pub fn validate(&self) -> Result<()> { + ensure!(self.reserved == 0); + + Ok(()) + } +} + +impl ExtMetadataBlockInfo for ExtMetadataBlockLevel18 { + fn level(&self) -> u8 { + 18 + } + + fn bytes_size(&self) -> u64 { + 7 + } + + fn required_bits(&self) -> u64 { + 56 + } +} diff --git a/dolby_vision/src/rpu/extension_metadata/blocks/mod.rs b/dolby_vision/src/rpu/extension_metadata/blocks/mod.rs index 3707dd2d..eab26056 100644 --- a/dolby_vision/src/rpu/extension_metadata/blocks/mod.rs +++ b/dolby_vision/src/rpu/extension_metadata/blocks/mod.rs @@ -9,6 +9,10 @@ use serde::{Deserialize, Serialize, Serializer}; pub mod level1; pub mod level10; pub mod level11; +pub mod level15; +pub mod level16; +pub mod level17; +pub mod level18; pub mod level2; pub mod level254; pub mod level255; @@ -30,6 +34,10 @@ pub use level8::ExtMetadataBlockLevel8; pub use level9::ExtMetadataBlockLevel9; pub use level10::ExtMetadataBlockLevel10; pub use level11::ExtMetadataBlockLevel11; +pub use level15::ExtMetadataBlockLevel15; +pub use level16::ExtMetadataBlockLevel16; +pub use level17::ExtMetadataBlockLevel17; +pub use level18::ExtMetadataBlockLevel18; pub use level254::ExtMetadataBlockLevel254; pub use level255::ExtMetadataBlockLevel255; pub use reserved::ReservedExtMetadataBlock; @@ -52,6 +60,10 @@ pub enum ExtMetadataBlock { Level9(ExtMetadataBlockLevel9), Level10(ExtMetadataBlockLevel10), Level11(ExtMetadataBlockLevel11), + Level15(ExtMetadataBlockLevel15), + Level16(ExtMetadataBlockLevel16), + Level17(ExtMetadataBlockLevel17), + Level18(ExtMetadataBlockLevel18), Level254(ExtMetadataBlockLevel254), Level255(ExtMetadataBlockLevel255), Reserved(ReservedExtMetadataBlock), @@ -84,6 +96,10 @@ impl ExtMetadataBlock { ExtMetadataBlock::Level9(b) => b.bytes_size(), ExtMetadataBlock::Level10(b) => b.bytes_size(), ExtMetadataBlock::Level11(b) => b.bytes_size(), + ExtMetadataBlock::Level15(b) => b.bytes_size(), + ExtMetadataBlock::Level16(b) => b.bytes_size(), + ExtMetadataBlock::Level17(b) => b.bytes_size(), + ExtMetadataBlock::Level18(b) => b.bytes_size(), ExtMetadataBlock::Level254(b) => b.bytes_size(), ExtMetadataBlock::Level255(b) => b.bytes_size(), ExtMetadataBlock::Reserved(b) => b.bytes_size(), @@ -102,6 +118,10 @@ impl ExtMetadataBlock { ExtMetadataBlock::Level9(b) => b.bits_size(), ExtMetadataBlock::Level10(b) => b.bits_size(), ExtMetadataBlock::Level11(b) => b.bits_size(), + ExtMetadataBlock::Level15(b) => b.bits_size(), + ExtMetadataBlock::Level16(b) => b.bits_size(), + ExtMetadataBlock::Level17(b) => b.bits_size(), + ExtMetadataBlock::Level18(b) => b.bits_size(), ExtMetadataBlock::Level254(b) => b.bits_size(), ExtMetadataBlock::Level255(b) => b.bits_size(), ExtMetadataBlock::Reserved(b) => b.bits_size(), @@ -120,6 +140,10 @@ impl ExtMetadataBlock { ExtMetadataBlock::Level9(b) => b.required_bits(), ExtMetadataBlock::Level10(b) => b.required_bits(), ExtMetadataBlock::Level11(b) => b.required_bits(), + ExtMetadataBlock::Level15(b) => b.required_bits(), + ExtMetadataBlock::Level16(b) => b.required_bits(), + ExtMetadataBlock::Level17(b) => b.required_bits(), + ExtMetadataBlock::Level18(b) => b.required_bits(), ExtMetadataBlock::Level254(b) => b.required_bits(), ExtMetadataBlock::Level255(b) => b.required_bits(), ExtMetadataBlock::Reserved(b) => b.required_bits(), @@ -138,6 +162,10 @@ impl ExtMetadataBlock { ExtMetadataBlock::Level9(b) => b.level(), ExtMetadataBlock::Level10(b) => b.level(), ExtMetadataBlock::Level11(b) => b.level(), + ExtMetadataBlock::Level15(b) => b.level(), + ExtMetadataBlock::Level16(b) => b.level(), + ExtMetadataBlock::Level17(b) => b.level(), + ExtMetadataBlock::Level18(b) => b.level(), ExtMetadataBlock::Level254(b) => b.level(), ExtMetadataBlock::Level255(b) => b.level(), ExtMetadataBlock::Reserved(b) => b.level(), @@ -156,6 +184,10 @@ impl ExtMetadataBlock { ExtMetadataBlock::Level9(b) => b.sort_key(), ExtMetadataBlock::Level10(b) => b.sort_key(), ExtMetadataBlock::Level11(b) => b.sort_key(), + ExtMetadataBlock::Level15(b) => b.sort_key(), + ExtMetadataBlock::Level16(b) => b.sort_key(), + ExtMetadataBlock::Level17(b) => b.sort_key(), + ExtMetadataBlock::Level18(b) => b.sort_key(), ExtMetadataBlock::Level254(b) => b.sort_key(), ExtMetadataBlock::Level255(b) => b.sort_key(), ExtMetadataBlock::Reserved(b) => b.sort_key(), @@ -174,6 +206,10 @@ impl ExtMetadataBlock { ExtMetadataBlock::Level9(b) => b.write(writer), ExtMetadataBlock::Level10(b) => b.write(writer), ExtMetadataBlock::Level11(b) => b.write(writer), + ExtMetadataBlock::Level15(b) => b.write(writer), + ExtMetadataBlock::Level16(b) => b.write(writer), + ExtMetadataBlock::Level17(b) => b.write(writer), + ExtMetadataBlock::Level18(b) => b.write(writer), ExtMetadataBlock::Level254(b) => b.write(writer), ExtMetadataBlock::Level255(b) => b.write(writer), ExtMetadataBlock::Reserved(b) => b.write(writer), @@ -235,6 +271,10 @@ impl ExtMetadataBlock { ExtMetadataBlock::Level9(b) => b.serialize(serializer), ExtMetadataBlock::Level10(b) => b.serialize(serializer), ExtMetadataBlock::Level11(b) => b.serialize(serializer), + ExtMetadataBlock::Level15(b) => b.serialize(serializer), + ExtMetadataBlock::Level16(b) => b.serialize(serializer), + ExtMetadataBlock::Level17(b) => b.serialize(serializer), + ExtMetadataBlock::Level18(b) => b.serialize(serializer), ExtMetadataBlock::Level254(b) => b.serialize(serializer), ExtMetadataBlock::Level255(b) => b.serialize(serializer), ExtMetadataBlock::Reserved(b) => b.serialize(serializer), diff --git a/dolby_vision/src/rpu/extension_metadata/cmv40.rs b/dolby_vision/src/rpu/extension_metadata/cmv40.rs index 0c68252f..b185e914 100644 --- a/dolby_vision/src/rpu/extension_metadata/cmv40.rs +++ b/dolby_vision/src/rpu/extension_metadata/cmv40.rs @@ -16,7 +16,7 @@ pub struct CmV40DmData { impl WithExtMetadataBlocks for CmV40DmData { const VERSION: &'static str = "CM v4.0"; - const ALLOWED_BLOCK_LEVELS: &'static [u8] = &[3, 8, 9, 10, 11, 254]; + const ALLOWED_BLOCK_LEVELS: &'static [u8] = &[3, 8, 9, 10, 11, 15, 16, 17, 18, 254]; fn with_blocks_allocation(num_ext_blocks: u64) -> Self { Self { @@ -51,6 +51,10 @@ impl WithExtMetadataBlocks for CmV40DmData { 9 => level9::ExtMetadataBlockLevel9::parse(reader, ext_block_length)?, 10 => level10::ExtMetadataBlockLevel10::parse(reader, ext_block_length)?, 11 => level11::ExtMetadataBlockLevel11::parse(reader)?, + 15 => level15::ExtMetadataBlockLevel15::parse(reader)?, + 16 => level16::ExtMetadataBlockLevel16::parse(reader)?, + 17 => level17::ExtMetadataBlockLevel17::parse(reader)?, + 18 => level18::ExtMetadataBlockLevel18::parse(reader)?, 254 => level254::ExtMetadataBlockLevel254::parse(reader)?, 1 | 2 | 4 | 5 | 6 | 255 => bail!("Disallowed block level {}", ext_block_level), _ => { diff --git a/dolby_vision/src/rpu/vdr_dm_data.rs b/dolby_vision/src/rpu/vdr_dm_data.rs index 150a5f3e..db923721 100644 --- a/dolby_vision/src/rpu/vdr_dm_data.rs +++ b/dolby_vision/src/rpu/vdr_dm_data.rs @@ -389,6 +389,10 @@ impl VdrDmData { } } ExtMetadataBlock::Level11(_) => self.replace_metadata_level(block), + ExtMetadataBlock::Level15(_) => self.replace_metadata_level(block), + ExtMetadataBlock::Level16(_) => self.replace_metadata_level(block), + ExtMetadataBlock::Level17(_) => self.replace_metadata_level(block), + ExtMetadataBlock::Level18(_) => self.replace_metadata_level(block), ExtMetadataBlock::Level254(_) => self.replace_metadata_level(block), ExtMetadataBlock::Level255(_) => self.replace_metadata_level(block), ExtMetadataBlock::Reserved(_) => bail!("Cannot replace specific reserved block"), diff --git a/src/commands/export.rs b/src/commands/export.rs index 0c266cb4..3ae0be3d 100644 --- a/src/commands/export.rs +++ b/src/commands/export.rs @@ -110,6 +110,14 @@ pub enum ExportLevel { Level10, #[value(alias = "l11")] Level11, + #[value(alias = "l15")] + Level15, + #[value(alias = "l16")] + Level16, + #[value(alias = "l17")] + Level17, + #[value(alias = "l18")] + Level18, } impl ExportData { @@ -135,6 +143,10 @@ impl ExportLevel { Self::Level9 => 9, Self::Level10 => 10, Self::Level11 => 11, + Self::Level15 => 15, + Self::Level16 => 16, + Self::Level17 => 17, + Self::Level18 => 18, } } pub fn default_output_file(&self, format: LevelsOutputFormat) -> String { diff --git a/src/dovi/exporter.rs b/src/dovi/exporter.rs index 94b02578..9a51b515 100644 --- a/src/dovi/exporter.rs +++ b/src/dovi/exporter.rs @@ -301,6 +301,10 @@ impl<'a> Serialize for CsvHeaders<'a> { ExtMetadataBlock::Level9(b) => json!(b), ExtMetadataBlock::Level10(b) => json!(b), ExtMetadataBlock::Level11(b) => json!(b), + ExtMetadataBlock::Level15(b) => json!(b), + ExtMetadataBlock::Level16(b) => json!(b), + ExtMetadataBlock::Level17(b) => json!(b), + ExtMetadataBlock::Level18(b) => json!(b), ExtMetadataBlock::Level254(b) => json!(b), ExtMetadataBlock::Level255(b) => json!(b), ExtMetadataBlock::Reserved(b) => json!(b), diff --git a/src/tests/rpu.rs b/src/tests/rpu.rs index e6c02ac9..d75d5a60 100644 --- a/src/tests/rpu.rs +++ b/src/tests/rpu.rs @@ -7,6 +7,7 @@ use dolby_vision::rpu::ConversionMode; use dolby_vision::rpu::dovi_rpu::DoviRpu; use dolby_vision::rpu::extension_metadata::blocks::{ExtMetadataBlock, ExtMetadataBlockLevel6}; use dolby_vision::rpu::extension_metadata::{ColorPrimaries, MasteringDisplayPrimaries}; +use dolby_vision::rpu::extension_metadata::{DmData, WithExtMetadataBlocks}; use dolby_vision::rpu::generate::GenerateConfig; use dolby_vision::rpu::rpu_data_nlq::DoviELType; use hevc_parser::hevc::{NAL_UNSPEC62, NALUnit}; @@ -1145,3 +1146,41 @@ fn profile20_apple() -> Result<()> { Ok(()) } + +/// DV2 (CM v4.0) extension metadata blocks Level 15, 16 and 18. +/// Verifies that RPU streams carrying the new DV2 metadata blocks +/// (Dolby Image Engine / Reference mode engine / Precision Black) +/// parse and round-trip bit-exactly. +#[test] +fn l15_to_l18() -> Result<()> { + let (original_data, dovi_rpu) = _parse_file(PathBuf::from("./assets/tests/l15_to_l18.bin"))?; + + // CM v4.0 metadata must be present + let vdr_dm_data = dovi_rpu.vdr_dm_data.as_ref().unwrap(); + let cmv40 = match vdr_dm_data.cmv40_metadata.as_ref().unwrap() { + DmData::V40(m) => m, + _ => panic!("expected CM v4.0 metadata"), + }; + let blocks = cmv40.blocks_ref(); + + // At least one of each new DV2 block must have been parsed + let has_l15 = blocks + .iter() + .any(|b| matches!(b, ExtMetadataBlock::Level15(_))); + let has_l16 = blocks + .iter() + .any(|b| matches!(b, ExtMetadataBlock::Level16(_))); + let has_l18 = blocks + .iter() + .any(|b| matches!(b, ExtMetadataBlock::Level18(_))); + + assert!(has_l15, "no Level 15 block parsed"); + assert!(has_l16, "no Level 16 block parsed"); + assert!(has_l18, "no Level 18 block parsed"); + + // Round-trip: rewritten bytes must match the original + let parsed_data = dovi_rpu.write_hevc_unspec62_nalu()?; + assert_eq!(&original_data[4..], &parsed_data[2..]); + + Ok(()) +}