Skip to content

Commit

Permalink
Implement support for encoding HDR10+ from JSON metadata file
Browse files Browse the repository at this point in the history
  • Loading branch information
quietvoid committed Aug 25, 2022
1 parent 73f0b6e commit 85f734b
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 0 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ new_debug_unreachable = "1.0.4"
once_cell = "1.13.0"
av1-grain = { version = "0.1.1", features = ["serialize"] }
serde-big-array = { version = "0.4.1", optional = true }
hdr10plus = { version = "1.1.1", features = ["json"] }

[dependencies.image]
version = "0.23"
Expand Down
8 changes: 8 additions & 0 deletions src/api/config/encoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ use crate::api::{Rational, SpeedSettings};
use crate::encoder::Tune;
use crate::serialize::{Deserialize, Serialize};

#[cfg(feature = "unstable")]
use std::collections::BTreeMap;

use std::fmt;

// We add 1 to rdo_lookahead_frames in a bunch of places.
Expand Down Expand Up @@ -85,6 +88,9 @@ pub struct EncoderConfig {
pub tune: Tune,
/// Parameters for grain synthesis.
pub film_grain_params: Option<Vec<GrainTableSegment>>,
/// HDR10+ T.35 metadata payload map, by frame index.
#[cfg(feature = "unstable")]
pub hdr10plus_payloads: Option<BTreeMap<u64, Vec<u8>>>,
/// Number of tiles horizontally. Must be a power of two.
///
/// Overridden by [`tiles`], if present.
Expand Down Expand Up @@ -162,6 +168,8 @@ impl EncoderConfig {
bitrate: 0,
tune: Tune::default(),
film_grain_params: None,
#[cfg(feature = "unstable")]
hdr10plus_payloads: None,
tile_cols: 0,
tile_rows: 0,
tiles: 0,
Expand Down
76 changes: 76 additions & 0 deletions src/api/internal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -349,14 +349,35 @@ impl<T: Pixel> ContextInner<T> {
}
self.frame_q.insert(input_frameno, frame);

#[cfg(feature = "unstable")]
// Update T.35 metadata from encoder config
let maybe_updated_t35_metadata = self.get_maybe_updated_t35_metadata(
input_frameno,
params.as_ref().map(|params| params.t35_metadata.as_ref()),
);

if let Some(params) = params {
if params.frame_type_override == FrameTypeOverride::Key {
self.keyframes_forced.insert(input_frameno);
}
if let Some(op) = params.opaque {
self.opaque_q.insert(input_frameno, op);
}

#[cfg(not(feature = "unstable"))]
self.t35_q.insert(input_frameno, params.t35_metadata);

#[cfg(feature = "unstable")]
if let Some(new_t35_metadata) = maybe_updated_t35_metadata {
self.t35_q.insert(input_frameno, new_t35_metadata.into_boxed_slice());
} else {
self.t35_q.insert(input_frameno, params.t35_metadata);
}
} else {
#[cfg(feature = "unstable")]
if let Some(new_t35_metadata) = maybe_updated_t35_metadata {
self.t35_q.insert(input_frameno, new_t35_metadata.into_boxed_slice());
}
}

if !self.needs_more_frame_q_lookahead(self.next_lookahead_frame) {
Expand Down Expand Up @@ -1688,4 +1709,59 @@ impl<T: Pixel> ContextInner<T> {
(prev_keyframe_nframes, prev_keyframe_ntus)
}
}

#[cfg(feature = "unstable")]
/// Updates the T.35 metadata to be added to the frame.
/// The existing T.35 array may come from `FrameParameters`.
/// New metadata is added from the encoder config:
/// - HDR10+, ST2094-40 in `EncoderConfig.hdr10plus_payloads`
///
/// Returns an `Option`, where `None` means the T.35 metadata is unchanged.
/// Otherwise, the updated T.35 metadata array is returned.
fn get_maybe_updated_t35_metadata(
&self, input_frameno: u64, maybe_existing_t35_metadata: Option<&[T35]>,
) -> Option<Vec<T35>> {
let hdr10plus_payload = self
.config
.hdr10plus_payloads
.as_ref()
.and_then(|list| list.get(&input_frameno));

let update_t35_metadata = hdr10plus_payload.is_some();

let mut new_t35_metadata = if update_t35_metadata {
Some(
maybe_existing_t35_metadata.map_or_else(Vec::new, |t35| t35.to_vec()),
)
} else {
None
};

if let Some(list) = new_t35_metadata.as_mut() {
// HDR10+, ST2094-40
if let Some(payload) = hdr10plus_payload {
// FIXME: Make const
let st2094_40_needle = &[
0x00, 0x03C, // Samsung Electronics America
0x00, 0x01, // ST-2094-40
0x04, // application_identifier = 4
0x01, // application_mode =1
];

let has_existing_hdr10plus_meta = list.iter().any(|t35| {
t35.country_code == 0xB5 && t35.data.starts_with(st2094_40_needle)
});

if !has_existing_hdr10plus_meta {
list.push(T35 {
country_code: 0xB5,
country_code_extension_byte: 0x00,
data: payload.clone().into_boxed_slice(),
});
}
}
}

new_t35_metadata
}
}
4 changes: 4 additions & 0 deletions src/api/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2128,6 +2128,8 @@ fn log_q_exp_overflow() {
bitrate: 1,
tune: Tune::Psychovisual,
film_grain_params: None,
#[cfg(feature = "unstable")]
hdr10plus_payloads: None,
tile_cols: 0,
tile_rows: 0,
tiles: 0,
Expand Down Expand Up @@ -2205,6 +2207,8 @@ fn guess_frame_subtypes_assert() {
bitrate: 16384,
tune: Tune::Psychovisual,
film_grain_params: None,
#[cfg(feature = "unstable")]
hdr10plus_payloads: None,
tile_cols: 0,
tile_rows: 0,
tiles: 0,
Expand Down
43 changes: 43 additions & 0 deletions src/bin/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ use once_cell::sync::Lazy;
use rav1e::prelude::*;
use scan_fmt::scan_fmt;

#[cfg(feature = "unstable")]
use std::collections::BTreeMap;

use std::fs::File;
use std::io;
use std::io::prelude::*;
Expand Down Expand Up @@ -203,6 +206,15 @@ pub struct CliOptions {
help_heading = "ENCODE SETTINGS"
)]
pub film_grain_table: Option<PathBuf>,
/// Uses a HDR10+ metadata JSON file to add as T.35 metadata to the encode.
#[cfg(feature = "unstable")]
#[clap(
long,
alias = "dhdr10-info",
value_parser,
help_heading = "ENCODE SETTINGS"
)]
pub hdr10plus_json: Option<PathBuf>,

/// Pixel range
#[clap(long, value_parser, help_heading = "VIDEO METADATA")]
Expand Down Expand Up @@ -688,11 +700,42 @@ fn parse_config(matches: &CliOptions) -> Result<EncoderConfig, CliError> {
.expect("Failed to read film grain table file");
let table = av1_grain::parse_grain_table(&contents)
.expect("Failed to parse film grain table");

if !table.is_empty() {
cfg.film_grain_params = Some(table);
}
}

#[cfg(feature = "unstable")]
if let Some(json_file) = matches.hdr10plus_json.as_ref() {
let contents = std::fs::read_to_string(json_file)
.expect("Failed to read HDR10+ metadata file");
let metadata_root =
hdr10plus::metadata_json::MetadataJsonRoot::parse(&contents)
.expect("Failed to parse HDR10+ metadata");

let payloads: BTreeMap<u64, Vec<u8>> = metadata_root
.scene_info
.iter()
.filter_map(|meta| {
hdr10plus::metadata::Hdr10PlusMetadata::try_from(meta)
.and_then(|meta| meta.encode(true))
.ok()
.map(|mut bytes| {
// Serialized with country code, which shouldn't be present
bytes.remove(0);
bytes
})
})
.zip(0u64..)
.map(|(payload, frame_no)| (frame_no, payload))
.collect();

if !payloads.is_empty() {
cfg.hdr10plus_payloads = Some(payloads);
}
}

if let Some(frame_rate) = matches.frame_rate {
cfg.time_base = Rational::new(matches.time_scale, frame_rate);
}
Expand Down

0 comments on commit 85f734b

Please sign in to comment.