Skip to content

Commit

Permalink
Implement support for encoding Dolby Vision from RPU file
Browse files Browse the repository at this point in the history
  • Loading branch information
quietvoid committed Aug 8, 2023
1 parent f8b95d9 commit e6d6adb
Show file tree
Hide file tree
Showing 7 changed files with 231 additions and 23 deletions.
80 changes: 78 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ new_debug_unreachable = "1.0.4"
once_cell = "1.18.0"
av1-grain = { version = "0.2.2", features = ["serialize"] }
serde-big-array = { version = "0.5.1", optional = true }
dolby_vision = { version = "3.2.0" }

[dependencies.image]
version = "0.24.6"
Expand Down
33 changes: 27 additions & 6 deletions src/api/internal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -520,12 +520,6 @@ impl<T: Pixel> ContextInner<T> {
return Err(EncoderStatus::NeedMoreData);
}

let t35_metadata = if let Some(t35) = self.t35_q.remove(&input_frameno) {
t35
} else {
Box::new([])
};

if output_frameno_in_gop > 0 {
let next_keyframe_input_frameno = self.next_keyframe_input_frameno(
self.gop_input_frameno_start[&output_frameno],
Expand Down Expand Up @@ -554,6 +548,17 @@ impl<T: Pixel> ContextInner<T> {
*self.gop_input_frameno_start.get_mut(&output_frameno).unwrap() =
next_keyframe_input_frameno;
} else {
let input_frameno = self.inter_cfg.get_input_frameno(
output_frameno_in_gop,
self.gop_input_frameno_start[&output_frameno],
);
let t35_metadata =
if let Some(t35) = self.t35_q.remove(&input_frameno) {
t35
} else {
Box::new([])
};

let fi = FrameInvariants::new_inter_frame(
self.get_previous_coded_fi(output_frameno),
&self.inter_cfg,
Expand Down Expand Up @@ -592,6 +597,12 @@ impl<T: Pixel> ContextInner<T> {
let output_frameno_in_gop =
output_frameno - self.gop_output_frameno_start[&output_frameno];
if output_frameno_in_gop == 0 {
let t35_metadata = if let Some(t35) = self.t35_q.remove(&input_frameno) {
t35
} else {
Box::new([])
};

let fi = FrameInvariants::new_key_frame(
self.config.clone(),
self.seq.clone(),
Expand All @@ -600,6 +611,16 @@ impl<T: Pixel> ContextInner<T> {
);
Ok(Some(fi))
} else {
let input_frameno = self.inter_cfg.get_input_frameno(
output_frameno_in_gop,
self.gop_input_frameno_start[&output_frameno],
);
let t35_metadata = if let Some(t35) = self.t35_q.remove(&input_frameno) {
t35
} else {
Box::new([])
};

let next_keyframe_input_frameno = self.next_keyframe_input_frameno(
self.gop_input_frameno_start[&output_frameno],
false,
Expand Down
28 changes: 28 additions & 0 deletions src/api/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
// PATENTS file, you can obtain it at www.aomedia.org/license/patent.
#![deny(missing_docs)]

use crate::encoder::FrameInvariants;
use crate::frame::*;
use crate::serialize::{Deserialize, Serialize};
use crate::stats::EncoderStats;
Expand Down Expand Up @@ -137,6 +138,12 @@ impl fmt::Display for FrameType {
}
}

/// Dolby Vision T.35 metadata payload expected prefix.
pub const T35_DOVI_PAYLOAD_PREFIX: &[u8] = &[
0x00, 0x03B, // Dolby
0x00, 0x00, 0x08, 0x00, 0x37, 0xCD, 0x08,
];

/// A single T.35 metadata packet.
#[derive(Clone, Debug, Default)]
pub struct T35 {
Expand Down Expand Up @@ -299,3 +306,24 @@ impl<T: Pixel> IntoFrame<T> for (Frame<T>, Option<FrameParameters>) {
(Some(Arc::new(self.0)), self.1)
}
}

impl T35 {
/// Whether the T.35 metadata is Dolby Vision Metadata.
pub fn is_dovi_metadata(&self) -> bool {
self.country_code == 0xB5 && self.data.starts_with(T35_DOVI_PAYLOAD_PREFIX)
}

/// Returns true if the T35 metadata can be added to the frame
pub fn is_valid_placement<T: Pixel>(&self, fi: &FrameInvariants<T>) -> bool {
if self.is_dovi_metadata() {
return fi.show_frame || fi.is_show_existing_frame();

Check warning on line 319 in src/api/util.rs

View check run for this annotation

Codecov / codecov/patch

src/api/util.rs#L319

Added line #L319 was not covered by tests
}

true
}

/// Metadata that can be encoded with SEF frames
pub fn can_be_added_to_show_existing_frame(&self) -> bool {
self.is_dovi_metadata()
}

Check warning on line 328 in src/api/util.rs

View check run for this annotation

Codecov / codecov/patch

src/api/util.rs#L326-L328

Added lines #L326 - L328 were not covered by tests
}
42 changes: 42 additions & 0 deletions src/bin/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ use rav1e::prelude::*;
use scan_fmt::scan_fmt;

use rav1e::config::CpuFeatureLevel;

use std::collections::BTreeMap;
use std::fs::File;
use std::io;
use std::io::prelude::*;
Expand Down Expand Up @@ -195,6 +197,15 @@ pub struct CliOptions {
help_heading = "ENCODE SETTINGS"
)]
pub film_grain_table: Option<PathBuf>,
/// Uses a Dolby Vision RPU file to add as T.35 metadata to the encode.
/// The RPU must be in the same format as for x265
#[clap(
long,
alias = "dolby-vision-rpu",
value_parser,
help_heading = "ENCODE SETTINGS"
)]
pub dovi_rpu: Option<PathBuf>,

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

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

let dovi_payloads = if let Some(rpu_file) = matches.dovi_rpu.as_ref() {
let rpus = dolby_vision::rpu::utils::parse_rpu_file(rpu_file)
.expect("Failed to read Dolby Vision RPU file");

let payloads: BTreeMap<u64, T35> = rpus
.iter()
.filter_map(|rpu| {
rpu
.write_av1_rpu_metadata_obu_t35_payload()
.map(|payload| T35 {
country_code: 0xB5,
country_code_extension_byte: 0x00,
data: payload.into_boxed_slice(),
})
.ok()
})
.zip(0u64..)
.map(|(payload, frame_no)| (frame_no, payload))
.collect();

if !payloads.is_empty() {
Some(payloads)

Check warning on line 502 in src/bin/common.rs

View check run for this annotation

Codecov / codecov/patch

src/bin/common.rs#L482-L502

Added lines #L482 - L502 were not covered by tests
} else {
None

Check warning on line 504 in src/bin/common.rs

View check run for this annotation

Codecov / codecov/patch

src/bin/common.rs#L504

Added line #L504 was not covered by tests
}
} else {
None
};

#[cfg(feature = "unstable")]
let slots = matches.slots;

Expand All @@ -484,6 +525,7 @@ pub fn parse_cli() -> Result<ParsedCliOptions, CliError> {
pass2file_name: matches.second_pass.clone(),
save_config: save_config_path,
photon_noise: matches.photon_noise,
dovi_payloads,
#[cfg(feature = "unstable")]
slots,
})
Expand Down
Loading

0 comments on commit e6d6adb

Please sign in to comment.