Skip to content

Commit e6d6adb

Browse files
committed
Implement support for encoding Dolby Vision from RPU file
1 parent f8b95d9 commit e6d6adb

File tree

7 files changed

+231
-23
lines changed

7 files changed

+231
-23
lines changed

Cargo.lock

+78-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ new_debug_unreachable = "1.0.4"
108108
once_cell = "1.18.0"
109109
av1-grain = { version = "0.2.2", features = ["serialize"] }
110110
serde-big-array = { version = "0.5.1", optional = true }
111+
dolby_vision = { version = "3.2.0" }
111112

112113
[dependencies.image]
113114
version = "0.24.6"

src/api/internal.rs

+27-6
Original file line numberDiff line numberDiff line change
@@ -520,12 +520,6 @@ impl<T: Pixel> ContextInner<T> {
520520
return Err(EncoderStatus::NeedMoreData);
521521
}
522522

523-
let t35_metadata = if let Some(t35) = self.t35_q.remove(&input_frameno) {
524-
t35
525-
} else {
526-
Box::new([])
527-
};
528-
529523
if output_frameno_in_gop > 0 {
530524
let next_keyframe_input_frameno = self.next_keyframe_input_frameno(
531525
self.gop_input_frameno_start[&output_frameno],
@@ -554,6 +548,17 @@ impl<T: Pixel> ContextInner<T> {
554548
*self.gop_input_frameno_start.get_mut(&output_frameno).unwrap() =
555549
next_keyframe_input_frameno;
556550
} else {
551+
let input_frameno = self.inter_cfg.get_input_frameno(
552+
output_frameno_in_gop,
553+
self.gop_input_frameno_start[&output_frameno],
554+
);
555+
let t35_metadata =
556+
if let Some(t35) = self.t35_q.remove(&input_frameno) {
557+
t35
558+
} else {
559+
Box::new([])
560+
};
561+
557562
let fi = FrameInvariants::new_inter_frame(
558563
self.get_previous_coded_fi(output_frameno),
559564
&self.inter_cfg,
@@ -592,6 +597,12 @@ impl<T: Pixel> ContextInner<T> {
592597
let output_frameno_in_gop =
593598
output_frameno - self.gop_output_frameno_start[&output_frameno];
594599
if output_frameno_in_gop == 0 {
600+
let t35_metadata = if let Some(t35) = self.t35_q.remove(&input_frameno) {
601+
t35
602+
} else {
603+
Box::new([])
604+
};
605+
595606
let fi = FrameInvariants::new_key_frame(
596607
self.config.clone(),
597608
self.seq.clone(),
@@ -600,6 +611,16 @@ impl<T: Pixel> ContextInner<T> {
600611
);
601612
Ok(Some(fi))
602613
} else {
614+
let input_frameno = self.inter_cfg.get_input_frameno(
615+
output_frameno_in_gop,
616+
self.gop_input_frameno_start[&output_frameno],
617+
);
618+
let t35_metadata = if let Some(t35) = self.t35_q.remove(&input_frameno) {
619+
t35
620+
} else {
621+
Box::new([])
622+
};
623+
603624
let next_keyframe_input_frameno = self.next_keyframe_input_frameno(
604625
self.gop_input_frameno_start[&output_frameno],
605626
false,

src/api/util.rs

+28
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
// PATENTS file, you can obtain it at www.aomedia.org/license/patent.
99
#![deny(missing_docs)]
1010

11+
use crate::encoder::FrameInvariants;
1112
use crate::frame::*;
1213
use crate::serialize::{Deserialize, Serialize};
1314
use crate::stats::EncoderStats;
@@ -137,6 +138,12 @@ impl fmt::Display for FrameType {
137138
}
138139
}
139140

141+
/// Dolby Vision T.35 metadata payload expected prefix.
142+
pub const T35_DOVI_PAYLOAD_PREFIX: &[u8] = &[
143+
0x00, 0x03B, // Dolby
144+
0x00, 0x00, 0x08, 0x00, 0x37, 0xCD, 0x08,
145+
];
146+
140147
/// A single T.35 metadata packet.
141148
#[derive(Clone, Debug, Default)]
142149
pub struct T35 {
@@ -299,3 +306,24 @@ impl<T: Pixel> IntoFrame<T> for (Frame<T>, Option<FrameParameters>) {
299306
(Some(Arc::new(self.0)), self.1)
300307
}
301308
}
309+
310+
impl T35 {
311+
/// Whether the T.35 metadata is Dolby Vision Metadata.
312+
pub fn is_dovi_metadata(&self) -> bool {
313+
self.country_code == 0xB5 && self.data.starts_with(T35_DOVI_PAYLOAD_PREFIX)
314+
}
315+
316+
/// Returns true if the T35 metadata can be added to the frame
317+
pub fn is_valid_placement<T: Pixel>(&self, fi: &FrameInvariants<T>) -> bool {
318+
if self.is_dovi_metadata() {
319+
return fi.show_frame || fi.is_show_existing_frame();
320+
}
321+
322+
true
323+
}
324+
325+
/// Metadata that can be encoded with SEF frames
326+
pub fn can_be_added_to_show_existing_frame(&self) -> bool {
327+
self.is_dovi_metadata()
328+
}
329+
}

src/bin/common.rs

+42
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ use rav1e::prelude::*;
1818
use scan_fmt::scan_fmt;
1919

2020
use rav1e::config::CpuFeatureLevel;
21+
22+
use std::collections::BTreeMap;
2123
use std::fs::File;
2224
use std::io;
2325
use std::io::prelude::*;
@@ -195,6 +197,15 @@ pub struct CliOptions {
195197
help_heading = "ENCODE SETTINGS"
196198
)]
197199
pub film_grain_table: Option<PathBuf>,
200+
/// Uses a Dolby Vision RPU file to add as T.35 metadata to the encode.
201+
/// The RPU must be in the same format as for x265
202+
#[clap(
203+
long,
204+
alias = "dolby-vision-rpu",
205+
value_parser,
206+
help_heading = "ENCODE SETTINGS"
207+
)]
208+
pub dovi_rpu: Option<PathBuf>,
198209

199210
/// Pixel range
200211
#[clap(long, value_parser, help_heading = "VIDEO METADATA")]
@@ -339,6 +350,7 @@ pub struct ParsedCliOptions {
339350
pub photon_noise: u8,
340351
#[cfg(feature = "unstable")]
341352
pub slots: usize,
353+
pub dovi_payloads: Option<BTreeMap<u64, T35>>,
342354
}
343355

344356
#[cfg(feature = "serialize")]
@@ -466,6 +478,35 @@ pub fn parse_cli() -> Result<ParsedCliOptions, CliError> {
466478
panic!("A limit cannot be set above 1 in still picture mode");
467479
}
468480

481+
let dovi_payloads = if let Some(rpu_file) = matches.dovi_rpu.as_ref() {
482+
let rpus = dolby_vision::rpu::utils::parse_rpu_file(rpu_file)
483+
.expect("Failed to read Dolby Vision RPU file");
484+
485+
let payloads: BTreeMap<u64, T35> = rpus
486+
.iter()
487+
.filter_map(|rpu| {
488+
rpu
489+
.write_av1_rpu_metadata_obu_t35_payload()
490+
.map(|payload| T35 {
491+
country_code: 0xB5,
492+
country_code_extension_byte: 0x00,
493+
data: payload.into_boxed_slice(),
494+
})
495+
.ok()
496+
})
497+
.zip(0u64..)
498+
.map(|(payload, frame_no)| (frame_no, payload))
499+
.collect();
500+
501+
if !payloads.is_empty() {
502+
Some(payloads)
503+
} else {
504+
None
505+
}
506+
} else {
507+
None
508+
};
509+
469510
#[cfg(feature = "unstable")]
470511
let slots = matches.slots;
471512

@@ -484,6 +525,7 @@ pub fn parse_cli() -> Result<ParsedCliOptions, CliError> {
484525
pass2file_name: matches.second_pass.clone(),
485526
save_config: save_config_path,
486527
photon_noise: matches.photon_noise,
528+
dovi_payloads,
487529
#[cfg(feature = "unstable")]
488530
slots,
489531
})

0 commit comments

Comments
 (0)