dolby_vision/rpu/
vdr_dm_data.rs

1use anyhow::{Result, bail, ensure};
2use bitvec_helpers::{
3    bitstream_io_reader::BsIoSliceReader, bitstream_io_writer::BitstreamIoWriter,
4};
5
6#[cfg(feature = "serde")]
7use serde::{Deserialize, Serialize};
8
9use super::extension_metadata::blocks::{
10    ExtMetadataBlock, ExtMetadataBlockLevel9, ExtMetadataBlockLevel11,
11};
12use super::extension_metadata::*;
13use super::generate::{GenerateConfig, GenerateProfile};
14use super::profiles::DoviProfile;
15use super::profiles::profile5::Profile5;
16use super::profiles::profile81::Profile81;
17use super::profiles::profile84::Profile84;
18
19use super::extension_metadata::WithExtMetadataBlocks;
20use super::rpu_data_header::RpuDataHeader;
21
22// 16 bits min for required level 254 + CRC32 + 0x80
23const DM_DATA_PAYLOAD2_MIN_BITS: u64 = 56;
24
25#[derive(Debug, Default, Clone)]
26#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
27pub struct VdrDmData {
28    pub compressed: bool,
29
30    pub affected_dm_metadata_id: u64,
31    pub current_dm_metadata_id: u64,
32    pub scene_refresh_flag: u64,
33
34    pub ycc_to_rgb_coef0: i16,
35    pub ycc_to_rgb_coef1: i16,
36    pub ycc_to_rgb_coef2: i16,
37    pub ycc_to_rgb_coef3: i16,
38    pub ycc_to_rgb_coef4: i16,
39    pub ycc_to_rgb_coef5: i16,
40    pub ycc_to_rgb_coef6: i16,
41    pub ycc_to_rgb_coef7: i16,
42    pub ycc_to_rgb_coef8: i16,
43    pub ycc_to_rgb_offset0: u32,
44    pub ycc_to_rgb_offset1: u32,
45    pub ycc_to_rgb_offset2: u32,
46    pub rgb_to_lms_coef0: i16,
47    pub rgb_to_lms_coef1: i16,
48    pub rgb_to_lms_coef2: i16,
49    pub rgb_to_lms_coef3: i16,
50    pub rgb_to_lms_coef4: i16,
51    pub rgb_to_lms_coef5: i16,
52    pub rgb_to_lms_coef6: i16,
53    pub rgb_to_lms_coef7: i16,
54    pub rgb_to_lms_coef8: i16,
55    pub signal_eotf: u16,
56    pub signal_eotf_param0: u16,
57    pub signal_eotf_param1: u16,
58    pub signal_eotf_param2: u32,
59    pub signal_bit_depth: u8,
60    pub signal_color_space: u8,
61    pub signal_chroma_format: u8,
62    pub signal_full_range_flag: u8,
63    pub source_min_pq: u16,
64    pub source_max_pq: u16,
65    pub source_diagonal: u16,
66
67    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
68    pub cmv29_metadata: Option<DmData>,
69    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
70    pub cmv40_metadata: Option<DmData>,
71}
72
73#[derive(Debug, Copy, Clone, PartialEq, Eq)]
74#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
75pub enum CmVersion {
76    V29,
77    V40,
78}
79
80pub(crate) fn vdr_dm_data_payload(
81    reader: &mut BsIoSliceReader,
82    header: &RpuDataHeader,
83) -> Result<VdrDmData> {
84    let compressed_dm_data = header.reserved_zero_3bits == 1;
85
86    let mut vdr_dm_data = if compressed_dm_data {
87        VdrDmData {
88            compressed: true,
89
90            affected_dm_metadata_id: reader.get_ue()?,
91            current_dm_metadata_id: reader.get_ue()?,
92            scene_refresh_flag: reader.get_ue()?,
93            ..Default::default()
94        }
95    } else {
96        VdrDmData::parse(reader)?
97    };
98
99    if let Some(cmv29_dm_data) = DmData::parse::<CmV29DmData>(reader)? {
100        vdr_dm_data.cmv29_metadata = Some(DmData::V29(cmv29_dm_data));
101    }
102
103    if reader.available()? >= DM_DATA_PAYLOAD2_MIN_BITS {
104        if let Some(cmv40_dm_data) = DmData::parse::<CmV40DmData>(reader)? {
105            vdr_dm_data.cmv40_metadata = Some(DmData::V40(cmv40_dm_data));
106        }
107    }
108
109    Ok(vdr_dm_data)
110}
111
112impl VdrDmData {
113    pub(crate) fn parse(reader: &mut BsIoSliceReader) -> Result<VdrDmData> {
114        let data = VdrDmData {
115            affected_dm_metadata_id: reader.get_ue()?,
116            current_dm_metadata_id: reader.get_ue()?,
117            scene_refresh_flag: reader.get_ue()?,
118
119            ycc_to_rgb_coef0: reader.get_n::<u16>(16)? as i16,
120            ycc_to_rgb_coef1: reader.get_n::<u16>(16)? as i16,
121            ycc_to_rgb_coef2: reader.get_n::<u16>(16)? as i16,
122            ycc_to_rgb_coef3: reader.get_n::<u16>(16)? as i16,
123            ycc_to_rgb_coef4: reader.get_n::<u16>(16)? as i16,
124            ycc_to_rgb_coef5: reader.get_n::<u16>(16)? as i16,
125            ycc_to_rgb_coef6: reader.get_n::<u16>(16)? as i16,
126            ycc_to_rgb_coef7: reader.get_n::<u16>(16)? as i16,
127            ycc_to_rgb_coef8: reader.get_n::<u16>(16)? as i16,
128            ycc_to_rgb_offset0: reader.get_n(32)?,
129            ycc_to_rgb_offset1: reader.get_n(32)?,
130            ycc_to_rgb_offset2: reader.get_n(32)?,
131
132            rgb_to_lms_coef0: reader.get_n::<u16>(16)? as i16,
133            rgb_to_lms_coef1: reader.get_n::<u16>(16)? as i16,
134            rgb_to_lms_coef2: reader.get_n::<u16>(16)? as i16,
135            rgb_to_lms_coef3: reader.get_n::<u16>(16)? as i16,
136            rgb_to_lms_coef4: reader.get_n::<u16>(16)? as i16,
137            rgb_to_lms_coef5: reader.get_n::<u16>(16)? as i16,
138            rgb_to_lms_coef6: reader.get_n::<u16>(16)? as i16,
139            rgb_to_lms_coef7: reader.get_n::<u16>(16)? as i16,
140            rgb_to_lms_coef8: reader.get_n::<u16>(16)? as i16,
141
142            signal_eotf: reader.get_n(16)?,
143            signal_eotf_param0: reader.get_n(16)?,
144            signal_eotf_param1: reader.get_n(16)?,
145            signal_eotf_param2: reader.get_n(32)?,
146            signal_bit_depth: reader.get_n(5)?,
147            signal_color_space: reader.get_n(2)?,
148            signal_chroma_format: reader.get_n(2)?,
149            signal_full_range_flag: reader.get_n(2)?,
150            source_min_pq: reader.get_n(12)?,
151            source_max_pq: reader.get_n(12)?,
152            source_diagonal: reader.get_n(10)?,
153            ..Default::default()
154        };
155
156        Ok(data)
157    }
158
159    pub fn validate(&self) -> Result<()> {
160        ensure!(
161            self.affected_dm_metadata_id <= 15,
162            "affected_dm_metadata_id should be <= 15"
163        );
164
165        // FIXME: Compressed DM metadata, should be set from a state somehow
166        if !self.compressed {
167            ensure!(
168                self.signal_bit_depth >= 8 && self.signal_bit_depth <= 16,
169                "signal_bit_depth should be between 8 and 16"
170            );
171
172            if self.signal_eotf_param0 == 0
173                && self.signal_eotf_param1 == 0
174                && self.signal_eotf_param2 == 0
175            {
176                ensure!(self.signal_eotf == 65535, "signal_eotf should be 65535");
177            }
178        }
179
180        if let Some(cmv29) = &self.cmv29_metadata {
181            cmv29.validate()?;
182        }
183
184        if let Some(cmv40) = &self.cmv40_metadata {
185            cmv40.validate()?;
186        }
187
188        Ok(())
189    }
190
191    pub fn write(&self, writer: &mut BitstreamIoWriter) -> Result<()> {
192        writer.write_ue(&self.affected_dm_metadata_id)?;
193        writer.write_ue(&self.current_dm_metadata_id)?;
194        writer.write_ue(&self.scene_refresh_flag)?;
195
196        if !self.compressed {
197            writer.write_signed_n(&self.ycc_to_rgb_coef0, 16)?;
198            writer.write_signed_n(&self.ycc_to_rgb_coef1, 16)?;
199            writer.write_signed_n(&self.ycc_to_rgb_coef2, 16)?;
200            writer.write_signed_n(&self.ycc_to_rgb_coef3, 16)?;
201            writer.write_signed_n(&self.ycc_to_rgb_coef4, 16)?;
202            writer.write_signed_n(&self.ycc_to_rgb_coef5, 16)?;
203            writer.write_signed_n(&self.ycc_to_rgb_coef6, 16)?;
204            writer.write_signed_n(&self.ycc_to_rgb_coef7, 16)?;
205            writer.write_signed_n(&self.ycc_to_rgb_coef8, 16)?;
206
207            writer.write_n(&self.ycc_to_rgb_offset0, 32)?;
208            writer.write_n(&self.ycc_to_rgb_offset1, 32)?;
209            writer.write_n(&self.ycc_to_rgb_offset2, 32)?;
210
211            writer.write_signed_n(&self.rgb_to_lms_coef0, 16)?;
212            writer.write_signed_n(&self.rgb_to_lms_coef1, 16)?;
213            writer.write_signed_n(&self.rgb_to_lms_coef2, 16)?;
214            writer.write_signed_n(&self.rgb_to_lms_coef3, 16)?;
215            writer.write_signed_n(&self.rgb_to_lms_coef4, 16)?;
216            writer.write_signed_n(&self.rgb_to_lms_coef5, 16)?;
217            writer.write_signed_n(&self.rgb_to_lms_coef6, 16)?;
218            writer.write_signed_n(&self.rgb_to_lms_coef7, 16)?;
219            writer.write_signed_n(&self.rgb_to_lms_coef8, 16)?;
220
221            writer.write_n(&self.signal_eotf, 16)?;
222            writer.write_n(&self.signal_eotf_param0, 16)?;
223            writer.write_n(&self.signal_eotf_param1, 16)?;
224            writer.write_n(&self.signal_eotf_param2, 32)?;
225
226            writer.write_n(&self.signal_bit_depth, 5)?;
227            writer.write_n(&self.signal_color_space, 2)?;
228            writer.write_n(&self.signal_chroma_format, 2)?;
229            writer.write_n(&self.signal_full_range_flag, 2)?;
230
231            writer.write_n(&self.source_min_pq, 12)?;
232            writer.write_n(&self.source_max_pq, 12)?;
233            writer.write_n(&self.source_diagonal, 10)?;
234        }
235
236        if let Some(cmv29) = &self.cmv29_metadata {
237            cmv29.write(writer)?;
238        }
239
240        if let Some(cmv40) = &self.cmv40_metadata {
241            cmv40.write(writer)?;
242        }
243
244        Ok(())
245    }
246
247    pub fn with_cmv29_dm_data(mut self) -> Self {
248        self.cmv29_metadata = Some(DmData::V29(CmV29DmData::default()));
249        self
250    }
251
252    pub fn extension_metadata_for_level(&self, level: u8) -> Option<&DmData> {
253        if CmV29DmData::ALLOWED_BLOCK_LEVELS.contains(&level) {
254            return self.cmv29_metadata.as_ref();
255        } else if CmV40DmData::ALLOWED_BLOCK_LEVELS.contains(&level) {
256            return self.cmv40_metadata.as_ref();
257        }
258
259        None
260    }
261
262    pub fn extension_metadata_for_level_mut(&mut self, level: u8) -> Option<&mut DmData> {
263        if CmV29DmData::ALLOWED_BLOCK_LEVELS.contains(&level) {
264            return self.cmv29_metadata.as_mut();
265        } else if CmV40DmData::ALLOWED_BLOCK_LEVELS.contains(&level) {
266            return self.cmv40_metadata.as_mut();
267        }
268
269        None
270    }
271
272    pub fn metadata_blocks(&self, level: u8) -> Option<&Vec<ExtMetadataBlock>> {
273        self.extension_metadata_for_level(level)
274            .map(|dm_data| match dm_data {
275                DmData::V29(meta) => meta.blocks_ref(),
276                DmData::V40(meta) => meta.blocks_ref(),
277            })
278    }
279
280    pub fn metadata_blocks_mut(&mut self, level: u8) -> Option<&mut Vec<ExtMetadataBlock>> {
281        self.extension_metadata_for_level_mut(level)
282            .map(|dm_data| match dm_data {
283                DmData::V29(meta) => meta.blocks_mut(),
284                DmData::V40(meta) => meta.blocks_mut(),
285            })
286    }
287
288    pub fn level_blocks_iter(&self, level: u8) -> impl Iterator<Item = &ExtMetadataBlock> {
289        self.metadata_blocks(level)
290            .into_iter()
291            .flat_map(|e| e.iter())
292            .filter(move |e| e.level() == level)
293    }
294
295    pub fn level_blocks_iter_mut(
296        &mut self,
297        level: u8,
298    ) -> impl Iterator<Item = &mut ExtMetadataBlock> {
299        self.metadata_blocks_mut(level)
300            .into_iter()
301            .flat_map(|e| e.iter_mut())
302            .filter(move |e| e.level() == level)
303    }
304
305    pub fn get_block(&self, level: u8) -> Option<&ExtMetadataBlock> {
306        self.level_blocks_iter(level).next()
307    }
308
309    pub fn get_block_mut(&mut self, level: u8) -> Option<&mut ExtMetadataBlock> {
310        self.level_blocks_iter_mut(level).next()
311    }
312
313    pub fn add_metadata_block(&mut self, block: ExtMetadataBlock) -> Result<()> {
314        let level = block.level();
315
316        if let Some(dm_data) = self.extension_metadata_for_level_mut(level) {
317            match dm_data {
318                DmData::V29(meta) => meta.add_block(block)?,
319                DmData::V40(meta) => meta.add_block(block)?,
320            }
321        }
322
323        Ok(())
324    }
325
326    pub fn remove_metadata_level(&mut self, level: u8) {
327        if let Some(dm_data) = self.extension_metadata_for_level_mut(level) {
328            match dm_data {
329                DmData::V29(meta) => meta.remove_level(level),
330                DmData::V40(meta) => meta.remove_level(level),
331            }
332        }
333    }
334
335    pub fn replace_metadata_level(&mut self, block: ExtMetadataBlock) -> Result<()> {
336        let level = block.level();
337
338        self.remove_metadata_level(level);
339        self.add_metadata_block(block)?;
340
341        Ok(())
342    }
343
344    pub fn replace_metadata_block(&mut self, block: ExtMetadataBlock) -> Result<()> {
345        let level = block.level();
346
347        match &block {
348            ExtMetadataBlock::Level1(_) => self.replace_metadata_level(block),
349            ExtMetadataBlock::Level2(level2) => {
350                if let Some(dm_data) = self.extension_metadata_for_level_mut(level) {
351                    match dm_data {
352                        DmData::V29(cmv29) => cmv29.replace_level2_block(level2),
353                        _ => unreachable!(),
354                    };
355
356                    Ok(())
357                } else {
358                    bail!("Cannot replace L2 metadata, no CM v2.9 DM data")
359                }
360            }
361            ExtMetadataBlock::Level3(_) => self.replace_metadata_level(block),
362            ExtMetadataBlock::Level4(_) => self.replace_metadata_level(block),
363            ExtMetadataBlock::Level5(_) => self.replace_metadata_level(block),
364            ExtMetadataBlock::Level6(_) => self.replace_metadata_level(block),
365            ExtMetadataBlock::Level8(level8) => {
366                if let Some(dm_data) = self.extension_metadata_for_level_mut(level) {
367                    match dm_data {
368                        DmData::V40(cmv40) => cmv40.replace_level8_block(level8),
369                        _ => unreachable!(),
370                    };
371
372                    Ok(())
373                } else {
374                    bail!("Cannot replace L8 metadata, no CM v4.0 DM data")
375                }
376            }
377            ExtMetadataBlock::Level9(_) => self.replace_metadata_level(block),
378            ExtMetadataBlock::Level10(level10) => {
379                if let Some(dm_data) = self.extension_metadata_for_level_mut(level) {
380                    match dm_data {
381                        DmData::V40(cmv40) => cmv40.replace_level10_block(level10),
382                        _ => unreachable!(),
383                    };
384
385                    Ok(())
386                } else {
387                    bail!("Cannot replace L10 metadata, no CM v4.0 DM data")
388                }
389            }
390            ExtMetadataBlock::Level11(_) => self.replace_metadata_level(block),
391            ExtMetadataBlock::Level254(_) => self.replace_metadata_level(block),
392            ExtMetadataBlock::Level255(_) => self.replace_metadata_level(block),
393            ExtMetadataBlock::Reserved(_) => bail!("Cannot replace specific reserved block"),
394        }
395    }
396
397    /// Clones every block to replace
398    pub fn replace_metadata_blocks<'a, I>(&mut self, blocks: I) -> Result<()>
399    where
400        I: Iterator<Item = &'a ExtMetadataBlock>,
401    {
402        for block in blocks {
403            self.replace_metadata_block(block.clone())?;
404        }
405
406        Ok(())
407    }
408
409    pub fn set_p81_coeffs(&mut self) {
410        self.ycc_to_rgb_coef0 = 9574;
411        self.ycc_to_rgb_coef1 = 0;
412        self.ycc_to_rgb_coef2 = 13802;
413        self.ycc_to_rgb_coef3 = 9574;
414        self.ycc_to_rgb_coef4 = -1540;
415        self.ycc_to_rgb_coef5 = -5348;
416        self.ycc_to_rgb_coef6 = 9574;
417        self.ycc_to_rgb_coef7 = 17610;
418        self.ycc_to_rgb_coef8 = 0;
419        self.ycc_to_rgb_offset0 = 16777216;
420        self.ycc_to_rgb_offset1 = 134217728;
421        self.ycc_to_rgb_offset2 = 134217728;
422
423        self.rgb_to_lms_coef0 = 7222;
424        self.rgb_to_lms_coef1 = 8771;
425        self.rgb_to_lms_coef2 = 390;
426        self.rgb_to_lms_coef3 = 2654;
427        self.rgb_to_lms_coef4 = 12430;
428        self.rgb_to_lms_coef5 = 1300;
429        self.rgb_to_lms_coef6 = 0;
430        self.rgb_to_lms_coef7 = 422;
431        self.rgb_to_lms_coef8 = 15962;
432
433        self.signal_color_space = 0;
434    }
435
436    // Source PQ means the mastering display
437    // MDL 1000,1-10 = 7,3079
438    // MDL 4000,50   = 62,3696
439    pub fn change_source_levels(&mut self, min_pq: Option<u16>, max_pq: Option<u16>) {
440        if let Some(v) = min_pq {
441            self.source_min_pq = v;
442        }
443
444        if let Some(v) = max_pq {
445            self.source_max_pq = v;
446        }
447
448        if let Some(ExtMetadataBlock::Level6(level6_block)) = self.get_block(6) {
449            let (derived_min_pq, derived_max_pq) = level6_block.source_meta_from_l6();
450
451            if min_pq.is_none() && self.source_min_pq == 0 {
452                self.source_min_pq = derived_min_pq;
453            }
454
455            if max_pq.is_none() && self.source_max_pq == 0 {
456                self.source_max_pq = derived_max_pq;
457            }
458        }
459    }
460
461    pub fn set_scene_cut(&mut self, is_scene_cut: bool) {
462        self.scene_refresh_flag = is_scene_cut as u64;
463    }
464
465    pub fn default_pq() -> VdrDmData {
466        VdrDmData {
467            signal_eotf: 65535,
468            signal_bit_depth: 12,
469            signal_full_range_flag: 1,
470            source_diagonal: 42,
471            ..Default::default()
472        }
473    }
474
475    /// Sets static metadata (L5/L6/L11) and source levels
476    pub fn from_generate_config(config: &GenerateConfig) -> Result<VdrDmData> {
477        let mut vdr_dm_data = match config.profile {
478            GenerateProfile::Profile5 => Profile5::dm_data(),
479            GenerateProfile::Profile81 => Profile81::dm_data(),
480            GenerateProfile::Profile84 => Profile84::dm_data(),
481        }
482        .with_cmv29_dm_data();
483
484        if config.cm_version == CmVersion::V40 {
485            vdr_dm_data.cmv40_metadata = if let Some(level254) = &config.level254 {
486                Some(DmData::V40(CmV40DmData::new_with_custom_l254(level254)))
487            } else {
488                Some(DmData::V40(CmV40DmData::new_with_l254_402()))
489            }
490        }
491
492        vdr_dm_data.set_static_metadata(config)?;
493        vdr_dm_data.change_source_levels(config.source_min_pq, config.source_max_pq);
494
495        Ok(vdr_dm_data)
496    }
497
498    pub fn set_static_metadata(&mut self, config: &GenerateConfig) -> Result<()> {
499        self.replace_metadata_block(ExtMetadataBlock::Level5(config.level5.clone()))?;
500
501        if let Some(level6) = &config.level6 {
502            self.replace_metadata_block(ExtMetadataBlock::Level6(level6.clone()))?;
503        }
504
505        // Default to inserting both L9 (required) and L11 metadata
506        self.replace_metadata_block(ExtMetadataBlock::Level9(
507            ExtMetadataBlockLevel9::default_dci_p3(),
508        ))?;
509        self.replace_metadata_block(ExtMetadataBlock::Level11(
510            ExtMetadataBlockLevel11::default_reference_cinema(),
511        ))?;
512
513        if !config.default_metadata_blocks.is_empty() {
514            const LEVEL_BLOCK_LIST: &[u8] = &[5, 6];
515
516            let allowed_default_blocks = config
517                .default_metadata_blocks
518                .iter()
519                .filter(|block| !LEVEL_BLOCK_LIST.contains(&block.level()));
520
521            for block in allowed_default_blocks {
522                self.replace_metadata_block(block.clone())?;
523            }
524        }
525
526        Ok(())
527    }
528}
529
530impl CmVersion {
531    pub fn v29() -> Self {
532        CmVersion::V29
533    }
534
535    pub fn v40() -> Self {
536        CmVersion::V40
537    }
538}
539
540#[cfg(test)]
541mod tests {
542    use crate::rpu::extension_metadata::blocks::{ExtMetadataBlock, ExtMetadataBlockLevel6};
543
544    use super::VdrDmData;
545
546    #[test]
547    fn change_source_levels_with_zero() {
548        let mut vdr_dm_data = VdrDmData::default_pq().with_cmv29_dm_data();
549        vdr_dm_data
550            .add_metadata_block(ExtMetadataBlock::Level6(ExtMetadataBlockLevel6 {
551                max_display_mastering_luminance: 1000,
552                min_display_mastering_luminance: 1,
553                max_content_light_level: 1000,
554                max_frame_average_light_level: 400,
555            }))
556            .unwrap();
557
558        vdr_dm_data.change_source_levels(Some(0), Some(1000));
559
560        assert_eq!(vdr_dm_data.source_min_pq, 0);
561        assert_eq!(vdr_dm_data.source_max_pq, 1000);
562    }
563}