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
22const 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 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 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 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 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 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}