Skip to content

Commit

Permalink
av1: parse/write EMDF wrapper properly
Browse files Browse the repository at this point in the history
  • Loading branch information
quietvoid committed Mar 10, 2024
1 parent 13f32b3 commit 89c06ed
Show file tree
Hide file tree
Showing 5 changed files with 164 additions and 120 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ jobs:
- name: Test
run: |
cargo test --all-features
cargo test --all-features --bins
cargo test --all-features \
cargo test --all-features --all-targets \
--manifest-path dolby_vision/Cargo.toml
- name: Rustfmt
Expand Down
1 change: 1 addition & 0 deletions dolby_vision/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
## Unreleased
- Changed AV1 function signatures to take slices as input and return a `Vec`.
- Added `write_av1_rpu_metadata_obu_t35_complete` function to encode RPUs in complete metadata OBU payloads.
- XML parser: support decimals when parsing Level6 MaxCLL/MaxFALL values.

Expand Down
112 changes: 112 additions & 0 deletions dolby_vision/src/av1/emdf.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
use anyhow::{ensure, Result};
use bitvec_helpers::{
bitstream_io_reader::BsIoSliceReader, bitstream_io_writer::BitstreamIoWriter,
};

/// Parse the expected EMDF container with fixed values according to spec
/// Returns `emdf_payload_size`
pub(crate) fn parse_emdf_container(reader: &mut BsIoSliceReader) -> Result<usize> {
let emdf_version = reader.get_n::<u8>(2)?;
ensure!(emdf_version == 0);

let key_id = reader.get_n::<u8>(3)?;
ensure!(key_id == 6);

let emdf_payload_id = reader.get_n::<u8>(5)?;
ensure!(emdf_payload_id == 31);

let emdf_payload_id_ext = parse_variable_bits(reader, 5)?;
ensure!(emdf_payload_id_ext == 225);

ensure!(!reader.get()?); // smploffste = 0
ensure!(!reader.get()?); // duratione = 0
ensure!(!reader.get()?); // groupide = 0
ensure!(!reader.get()?); // codecdatae = 0
ensure!(reader.get()?); // discard_unknown_payload = 1

let emdf_payload_size = parse_variable_bits(reader, 8)? as usize;
Ok(emdf_payload_size)
}

/// Write the DOVI RPU EMDF container with payload
pub(crate) fn write_emdf_container_with_dovi_rpu_payload(
writer: &mut BitstreamIoWriter,
payload: &[u8],
) -> Result<()> {
let emdf_payload_size = payload.len() as u32;

write_dovi_rpu_emdf_header(writer)?;
write_variable_bits(writer, emdf_payload_size, 8)?;

for b in payload {
writer.write_n(b, 8)?;
}

writer.write_n(&0, 5)?;
writer.write_n(&1, 2)?;
writer.write_n(&0, 2)?;
writer.write_n(&0, 8)?;

Ok(())
}
fn parse_variable_bits(reader: &mut BsIoSliceReader, n: u32) -> Result<u32> {
let mut value: u32 = 0;

loop {
let tmp: u32 = reader.get_n(n)?;
value += tmp;

// read_more flag
if !reader.get()? {
break;
}

value <<= n;
value += 1 << n;
}

Ok(value)
}

fn write_variable_bits(writer: &mut BitstreamIoWriter, value: u32, n: u32) -> Result<()> {
let max = 1 << n;

if value > max {
let mut remaining = value;

loop {
let tmp = remaining >> n;
let clipped = tmp << n;
remaining -= clipped;

let byte = (clipped - max) >> n;
writer.write_n(&byte, n)?;
writer.write(true)?; // read_more

// Stop once the remaining can be written in N bits
if remaining <= max {
break;
}
}

writer.write_n(&remaining, n)?;
} else {
writer.write_n(&value, n)?;
}

writer.write(false)?;

Ok(())
}

fn write_dovi_rpu_emdf_header(writer: &mut BitstreamIoWriter) -> Result<()> {
writer.write_n(&0, 2)?; // emdf_version
writer.write_n(&6, 3)?; // key_id
writer.write_n(&31, 5)?; // emdf_payload_id
write_variable_bits(writer, 225, 5)?; // emdf_payload_id_ext

writer.write_n(&0, 4)?; // smploffste, duratione, groupide, codecdatae
writer.write(true)?; // discard_unknown_payload

Ok(())
}
163 changes: 48 additions & 115 deletions dolby_vision/src/av1/mod.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,37 @@
use anyhow::{bail, ensure, Result};
use anyhow::{anyhow, bail, ensure, Result};
use bitvec_helpers::{
bitstream_io_reader::BsIoSliceReader, bitstream_io_writer::BitstreamIoWriter,
};

use crate::rpu::dovi_rpu::DoviRpu;
use crate::{
av1::emdf::{parse_emdf_container, write_emdf_container_with_dovi_rpu_payload},
rpu::dovi_rpu::DoviRpu,
};

mod emdf;

pub const ITU_T35_DOVI_RPU_PAYLOAD_HEADER: &[u8] =
&[0x00, 0x3B, 0x00, 0x00, 0x08, 0x00, 0x37, 0xCD, 0x08];
const ITU_T35_DOVI_RPU_PAYLOAD_HEADER_LEN: usize = ITU_T35_DOVI_RPU_PAYLOAD_HEADER.len();

fn validated_trimmed_data(data: &mut [u8]) -> Result<&mut [u8]> {
/// Parse AV1 ITU-T T.35 metadata OBU into a `DoviRpu`
/// The payload is extracted out of the EMDF wrapper
pub fn parse_itu_t35_dovi_metadata_obu(data: &[u8]) -> Result<DoviRpu> {
let data = validated_trimmed_data(data)?;
let converted_buf = convert_av1_rpu_payload_to_regular(data)?;

DoviRpu::parse_rpu(&converted_buf)
}

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
&mut data[1..]
&data[1..]
} else {
data
};
Expand All @@ -32,132 +49,48 @@ fn validated_trimmed_data(data: &mut [u8]) -> Result<&mut [u8]> {

/// Internal function, use `parse_itu_t35_dovi_metadata_obu`
///
/// Expects the payload to have `ITU_T35_DOVI_RPU_PAYLOAD_HEADER` discarded
/// The payload is converted in-place in input slice
///
/// Returns the converted slice truncated to final RPU size
pub fn convert_av1_rpu_payload_to_regular(data: &mut [u8]) -> Result<&[u8]> {
let mut rpu_size;

// 256+ bytes size
if data[1] & 0x10 > 0 {
if data[2] & 0x08 > 0 {
bail!("RPU exceeds 512 bytes");
}
/// Returns the EMDF payload bytes representing the RPU buffer
fn convert_av1_rpu_payload_to_regular(data: &[u8]) -> Result<Vec<u8>> {
let mut reader = BsIoSliceReader::from_slice(data);

rpu_size = 0x100;
rpu_size |= (data[1] as usize & 0x0F) << 4;
rpu_size |= (data[2] as usize >> 4) & 0x0F;
let itu_t_t35_terminal_provider_code = reader.get_n::<u16>(16)?;
ensure!(itu_t_t35_terminal_provider_code == 0x3B);

ensure!(rpu_size + 2 < data.len());
let itu_t_t35_terminal_provider_oriented_code = reader.get_n::<u32>(32)?;
ensure!(itu_t_t35_terminal_provider_oriented_code == 0x800);

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

data[1 + i] = converted_byte;
}
} else {
rpu_size = (data[0] as usize & 0x1F) << 3;
rpu_size |= (data[1] as usize >> 5) & 0x07;
let emdf_payload_size = parse_emdf_container(&mut reader)?;
let mut converted_buf = Vec::with_capacity(emdf_payload_size + 1);
converted_buf.push(0x19);

ensure!(rpu_size + 1 < data.len());

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

data[1 + i] = converted_byte;
}
for _ in 0..emdf_payload_size {
converted_buf.push(reader.get_n(8)?);
}

// Set prefix
data[0] = 0x19;

Ok(&data[..rpu_size + 1])
Ok(converted_buf)
}

/// Buffer must start with 0x19 prefix, the payload is converted in-place
/// 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: &mut Vec<u8>) -> Result<()> {
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;

// Header + size bytes
data.reserve(16);

// 256+ bytes size
if rpu_size > 0xFF {
// Unknown first byte
let size_byte1 = 32;

data.splice(
0..1,
[
size_byte1,
(rpu_size >> 4) as u8,
((rpu_size & 0x0F) as u8) << 4,
],
);
let start_idx = 3;
let end_idx = rpu_size + 2;

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

data[i] = byte;
}

// Last byte
data[end_idx] = (data[end_idx] & 0x1F) << 3;

// Unknown necessary bytes
data.extend(&[16, 0]);
} else {
// Unknown additional diff for first size byte
let size_byte1_diff = 32; // 2^5

data.splice(
0..1,
[
(rpu_size >> 3) as u8 + size_byte1_diff,
((rpu_size & 0x07) as u8) << 5,
],
);
let start_idx = 2;
let end_idx = rpu_size + 1;

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

data[i] = byte;
}

// Last byte
data[end_idx] = (data[end_idx] & 0x0F) << 4;

// Unknown necessary bytes
data.extend(&[size_byte1_diff, 0]);
}
let data = &data[1..];
let rpu_size = data.len();
let capacity = 16 + rpu_size;

// Prefix header
data.splice(0..0, ITU_T35_DOVI_RPU_PAYLOAD_HEADER.iter().copied());
let mut writer = BitstreamIoWriter::with_capacity(capacity * 8);

Ok(())
}
writer.write_n(&0x3B, 16)?; // itu_t_t35_terminal_provider_code
writer.write_n(&0x800, 32)?; // itu_t_t35_terminal_provider_oriented_code

/// Parse AV1 RPU metadata payload starting with `ITU_T35_DOVI_RPU_PAYLOAD_HEADER`
///
/// The payload is converted in-place in input slice, then parsed into a `DoviRpu` struct.
pub fn parse_itu_t35_dovi_metadata_obu(data: &mut [u8]) -> Result<DoviRpu> {
let data = validated_trimmed_data(data)?;
let converted_buf =
convert_av1_rpu_payload_to_regular(&mut data[ITU_T35_DOVI_RPU_PAYLOAD_HEADER_LEN..])?;
write_emdf_container_with_dovi_rpu_payload(&mut writer, data)?;
writer.byte_align()?;

DoviRpu::parse_rpu(converted_buf)
Ok(writer
.as_slice()
.ok_or_else(|| anyhow!("Unaligned bytes"))?
.to_owned())
}
5 changes: 1 addition & 4 deletions dolby_vision/src/rpu/dovi_rpu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,10 +224,7 @@ impl DoviRpu {

/// `itu_t_t35_payload_bytes`
pub fn write_av1_rpu_metadata_obu_t35_payload(&self) -> Result<Vec<u8>> {
let mut encoded_rpu = self.write_rpu_data()?;
convert_regular_rpu_to_av1_payload(encoded_rpu.as_mut())?;

Ok(encoded_rpu)
convert_regular_rpu_to_av1_payload(&self.write_rpu_data()?)
}

/// Complete `metadata_itut_t35()`, including `itu_t_t35_country_code`
Expand Down

0 comments on commit 89c06ed

Please sign in to comment.