Skip to content

Commit

Permalink
[WIP] Add support for AV1 Dolby Vision metadata payloads
Browse files Browse the repository at this point in the history
Parsing and writing RPUs less than 256 bytes
  • Loading branch information
quietvoid committed Aug 7, 2023
1 parent 038a602 commit c8e27be
Show file tree
Hide file tree
Showing 12 changed files with 248 additions and 18 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

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

Binary file added assets/av1-rpu/fel_orig.bin
Binary file not shown.
Binary file added assets/av1-rpu/p5-01-ref.bin
Binary file not shown.
Binary file added assets/av1-rpu/p5-01.bin
Binary file not shown.
2 changes: 1 addition & 1 deletion dolby_vision/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "dolby_vision"
version = "3.1.2"
version = "3.2.0"
authors = ["quietvoid"]
edition = "2021"
rust-version = "1.60.0"
Expand Down
139 changes: 139 additions & 0 deletions dolby_vision/src/av1/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
use anyhow::{bail, ensure, Result};

use crate::rpu::dovi_rpu::DoviRpu;

pub const ITU_T35_DOVI_RPU_PAYLOAD_HEADER: &[u8] =
&[0x00, 0x3B, 0x00, 0x00, 0x08, 0x00, 0x37, 0xCD, 0x08];

fn validated_trimmed_data(data: &[u8]) -> Result<&[u8]> {
if data.len() < 34 {
bail!("Invalid RPU length: {}", data.len());
}

let data = if data[0] == 0xB5 {
// itu_t_t35_country_code - United States
// Remove from buffer
&data[1..]
} else {
data
};

let trimmed_data = match &data[..9] {
ITU_T35_DOVI_RPU_PAYLOAD_HEADER => data,
_ => bail!("Invalid RPU data start_bytes\n{:?}", &data[..9]),
};

Ok(trimmed_data)
}

/// Internal function, use `parse_itu_t35_dovi_metadata_obu`
///
/// Expects the payload to have `ITU_T35_DOVI_RPU_PAYLOAD_HEADER` discarded
pub fn convert_av1_rpu_payload_to_regular(data: &[u8]) -> Result<Vec<u8>> {
let mut converted_buf = vec![0x19];

// 256+ bytes size
if data[1] & 0x10 > 0 {
if data[2] & 0x08 > 0 {
bail!("RPU exceeds 512 bytes");
}

let mut rpu_size: usize = 0x100;
rpu_size |= (data[1] as usize & 0x0F) << 4;
rpu_size |= (data[2] as usize >> 4) & 0x0F;

converted_buf.reserve(rpu_size);

for i in 0..rpu_size {
let mut converted_byte = (data[2 + i] & 0x07) << 5;
converted_byte |= (data[3 + i] >> 3) & 0x1F;
converted_buf.push(converted_byte);
}
} else {
let mut rpu_size: usize = (data[0] as usize & 0x1F) << 3;
rpu_size |= (data[1] as usize >> 5) & 0x07;

converted_buf.reserve(rpu_size);

for i in 0..rpu_size {
let mut converted_byte = (data[1 + i] & 0x0F) << 4;
converted_byte |= (data[2 + i] >> 4) & 0x0F;
converted_buf.push(converted_byte);
}
}

Ok(converted_buf)
}

/// Buffer must start with 0x19 prefix
/// Returns payload for AV1 ITU T-T.35 metadata OBU
pub fn convert_regular_rpu_to_av1_payload(data: &[u8]) -> Result<Vec<u8>> {
ensure!(data[0] == 0x19);

// Exclude 0x19 prefix
let rpu_size = data.len() - 1;
let mut out_buf =
Vec::<u8>::with_capacity(ITU_T35_DOVI_RPU_PAYLOAD_HEADER.len() + data.len() - 1);
out_buf.extend(ITU_T35_DOVI_RPU_PAYLOAD_HEADER);

// 256+ bytes size
if rpu_size > 0xFF {
// FIXME: Figure out size and final bytes to work
out_buf.extend(&[0xFF, (rpu_size >> 4) as u8, ((rpu_size & 0x0F) as u8) << 4]);

for i in 1..rpu_size {
let mut byte = (data[i] & 0x1F) << 3;
byte |= (data[1 + i] >> 5) & 0x07;

out_buf.push(byte);
}

// Last byte
out_buf.push((data[rpu_size] & 0x1F) << 3);
} else {
// Unknown additional diff for first size byte
let size_byte1_diff = 32; // 2^5

out_buf.extend(&[
(rpu_size >> 3) as u8 + size_byte1_diff,
((rpu_size & 0x07) as u8) << 5,
]);

for i in 1..rpu_size {
let mut byte = (data[i] & 0x0F) << 4;
byte |= (data[1 + i] >> 4) & 0x0F;

out_buf.push(byte);
}

// Last byte
out_buf.push((data[rpu_size] & 0x0F) << 4);

// Unknown necessary bytes
out_buf.extend(&[size_byte1_diff, 0]);
}

Ok(out_buf)
}

/// Parse AV1 RPU metadata payload starting with `ITU_T35_DOVI_RPU_PAYLOAD_HEADER`
pub fn parse_itu_t35_dovi_metadata_obu(data: &[u8]) -> Result<DoviRpu> {
let data = validated_trimmed_data(data)?;

if !(data[0] == 0x00
&& data[1] == 0x3B // itu_t_t35_terminal_provider_code - Dolby 0x003B
&& data[2] == 0x00
&& data[3] == 0x00 // itu_t_t35_terminal_provider_oriented_code - 0x0000
&& data[4] == 0x08
&& data[5] == 0x00
&& data[6] == 0x37
&& data[7] == 0xCD
&& data[8] == 0x08)
{
bail!("Invalid AV1 RPU header: {:?}", &data[..9]);
}

let converted_buf = convert_av1_rpu_payload_to_regular(&data[9..])?;

DoviRpu::parse_rpu(&converted_buf)
}
9 changes: 9 additions & 0 deletions dolby_vision/src/c_structs/rpu_data_header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,14 @@ pub struct RpuDataHeader {
/// null pointer if not profile 7
pub(crate) el_type: *const c_char,

/// Deprecated since 3.2.0
/// The field is not actually part of the RPU header
#[deprecated(
since = "3.2.0",
note = "The field is not actually part of the RPU header"
)]
rpu_nal_prefix: u8,

rpu_type: u8,
rpu_format: u16,
vdr_rpu_profile: u8,
Expand Down Expand Up @@ -48,9 +55,11 @@ impl RpuDataHeader {

impl From<&RuRpuDataHeader> for RpuDataHeader {
fn from(header: &RuRpuDataHeader) -> Self {
#[allow(deprecated)]
Self {
guessed_profile: header.get_dovi_profile(),
el_type: null(),
// FIXME: rpu_nal_prefix deprecation
rpu_nal_prefix: header.rpu_nal_prefix,
rpu_type: header.rpu_type,
rpu_format: header.rpu_format,
Expand Down
3 changes: 3 additions & 0 deletions dolby_vision/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
/// Dolby Vision RPU (as found in HEVC type 62 NALUs) module
pub mod rpu;

/// Dolby Vision RPU (as found in AV1 ITU T.35 metadata OBUs)
pub mod av1;

/// SMPTE ST2094-10 metadata module
pub mod st2094_10;

Expand Down
28 changes: 23 additions & 5 deletions dolby_vision/src/rpu/dovi_rpu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,10 @@ use super::profiles::profile84::Profile84;
use super::rpu_data_header::RpuDataHeader;
use super::rpu_data_mapping::{DoviNlqMethod, RpuDataMapping};
use super::rpu_data_nlq::{DoviELType, RpuDataNlq};
use super::vdr_dm_data::VdrDmData;
use super::vdr_dm_data::{vdr_dm_data_payload, VdrDmData};
use super::{compute_crc32, ConversionMode};

use crate::rpu::vdr_dm_data::vdr_dm_data_payload;

use crate::av1::convert_regular_rpu_to_av1_payload;
use crate::utils::{
add_start_code_emulation_prevention_3_byte, clear_start_code_emulation_prevention_3_byte,
};
Expand Down Expand Up @@ -80,6 +79,7 @@ impl DoviRpu {
Ok(trimmed_data)
}

/// HEVC UNSPEC62 NALU, clears start code emulation prevention 3 bytes
pub fn parse_unspec62_nalu(data: &[u8]) -> Result<DoviRpu> {
let trimmed_data = DoviRpu::validated_trimmed_data(data)?;

Expand All @@ -96,7 +96,7 @@ impl DoviRpu {
}

#[inline(always)]
fn parse(data: &[u8]) -> Result<DoviRpu> {
pub(crate) fn parse(data: &[u8]) -> Result<DoviRpu> {
let trailing_zeroes = data.iter().rev().take_while(|b| **b == 0).count();

// Ignore trailing bytes
Expand All @@ -106,6 +106,7 @@ impl DoviRpu {
// Minus 4 bytes for the CRC32, 1 for the 0x80 ending byte
let crc32_start = rpu_end - 5;

// Ignoring the prefix byte
let received_crc32 = compute_crc32(&data[1..crc32_start]);

if last_byte != FINAL_BYTE {
Expand All @@ -132,7 +133,16 @@ impl DoviRpu {
// CRC32 + 0x80 + trailing
let final_length = (32 + 8 + (trailing_zeroes * 8)) as u64;

let header = RpuDataHeader::parse(&mut reader)?;
let rpu_prefix = reader.get_n(8)?;
ensure!(rpu_prefix == 25, "rpu_nal_prefix should be 25");

let mut header = RpuDataHeader::parse(&mut reader)?;

// FIXME: rpu_nal_prefix deprecation
#[allow(deprecated)]
{
header.rpu_nal_prefix = rpu_prefix;
}

// Preliminary header validation
let dovi_profile = header.get_dovi_profile();
Expand Down Expand Up @@ -212,13 +222,21 @@ impl DoviRpu {
self.write_rpu_data()
}

pub fn write_av1_rpu_metadata_obu_t35_payload(&self) -> Result<Vec<u8>> {
let hevc_rpu = self.write_rpu_data()?;
convert_regular_rpu_to_av1_payload(&hevc_rpu)
}

#[inline(always)]
fn write_rpu_data(&self) -> Result<Vec<u8>> {
// Capacity is in bits
let mut writer = BitstreamIoWriter::with_capacity(self.original_payload_size * 8);

self.validate()?;

// RPU prefix
writer.write_n(&0x19, 8)?;

let header = &self.header;
header.write_header(&mut writer)?;

Expand Down
27 changes: 16 additions & 11 deletions dolby_vision/src/rpu/rpu_data_header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,13 @@ use serde::Serialize;
#[derive(Default, Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct RpuDataHeader {
// Must be 25
/// Must be 25 (HEVC) or 8 (AV1)
#[deprecated(
since = "3.2.0",
note = "The field is not actually part of the RPU header"
)]
pub rpu_nal_prefix: u8,

// Must be 2
pub rpu_type: u8,
pub rpu_format: u16,
Expand Down Expand Up @@ -43,9 +48,6 @@ pub struct RpuDataHeader {

impl RpuDataHeader {
pub(crate) fn parse(reader: &mut BsIoSliceReader) -> Result<RpuDataHeader> {
let rpu_nal_prefix = reader.get_n(8)?;
ensure!(rpu_nal_prefix == 25);

let rpu_type = reader.get_n(6)?;
ensure!(rpu_type == 2);

Expand All @@ -57,7 +59,6 @@ impl RpuDataHeader {
let vdr_seq_info_present_flag = reader.get()?;

let mut header = RpuDataHeader {
rpu_nal_prefix,
rpu_type,
rpu_format,
vdr_rpu_profile,
Expand Down Expand Up @@ -110,8 +111,6 @@ impl RpuDataHeader {
}

pub fn validate(&self, profile: u8) -> Result<()> {
ensure!(self.rpu_nal_prefix == 25, "rpu_nal_prefix should be 25");

match profile {
5 => {
ensure!(
Expand Down Expand Up @@ -186,8 +185,6 @@ impl RpuDataHeader {
}

pub fn write_header(&self, writer: &mut BitstreamIoWriter) -> Result<()> {
writer.write_n(&self.rpu_nal_prefix, 8)?;

writer.write_n(&self.rpu_type, 6)?;
writer.write_n(&self.rpu_format, 11)?;

Expand Down Expand Up @@ -236,8 +233,7 @@ impl RpuDataHeader {
}

pub fn p8_default() -> RpuDataHeader {
RpuDataHeader {
rpu_nal_prefix: 25,
let mut header = RpuDataHeader {
rpu_type: 2,
rpu_format: 18,
vdr_rpu_profile: 1,
Expand All @@ -259,6 +255,15 @@ impl RpuDataHeader {
vdr_dm_metadata_present_flag: true,
use_prev_vdr_rpu_flag: false,
prev_vdr_rpu_id: 0,
..Default::default()
};

// FIXME: rpu_nal_prefix deprecation
#[allow(deprecated)]
{
header.rpu_nal_prefix = 25;
}

header
}
}
Loading

0 comments on commit c8e27be

Please sign in to comment.