From 89c06ed32f1933f1b6743da56bd495ec90ae815e Mon Sep 17 00:00:00 2001 From: quietvoid Date: Sun, 10 Mar 2024 12:53:25 -0400 Subject: [PATCH] av1: parse/write EMDF wrapper properly --- .github/workflows/ci.yml | 3 +- dolby_vision/CHANGELOG.md | 1 + dolby_vision/src/av1/emdf.rs | 112 +++++++++++++++++++++ dolby_vision/src/av1/mod.rs | 163 +++++++++---------------------- dolby_vision/src/rpu/dovi_rpu.rs | 5 +- 5 files changed, 164 insertions(+), 120 deletions(-) create mode 100644 dolby_vision/src/av1/emdf.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0b5f099..769e977 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 diff --git a/dolby_vision/CHANGELOG.md b/dolby_vision/CHANGELOG.md index e657c95..171ec07 100644 --- a/dolby_vision/CHANGELOG.md +++ b/dolby_vision/CHANGELOG.md @@ -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. diff --git a/dolby_vision/src/av1/emdf.rs b/dolby_vision/src/av1/emdf.rs new file mode 100644 index 0000000..428514d --- /dev/null +++ b/dolby_vision/src/av1/emdf.rs @@ -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 { + let emdf_version = reader.get_n::(2)?; + ensure!(emdf_version == 0); + + let key_id = reader.get_n::(3)?; + ensure!(key_id == 6); + + let emdf_payload_id = reader.get_n::(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 { + 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(()) +} diff --git a/dolby_vision/src/av1/mod.rs b/dolby_vision/src/av1/mod.rs index 97149a4..2f7c6fa 100644 --- a/dolby_vision/src/av1/mod.rs +++ b/dolby_vision/src/av1/mod.rs @@ -1,12 +1,29 @@ -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 { + 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()); } @@ -14,7 +31,7 @@ fn validated_trimmed_data(data: &mut [u8]) -> Result<&mut [u8]> { let data = if data[0] == 0xB5 { // itu_t_t35_country_code - United States // Remove from buffer - &mut data[1..] + &data[1..] } else { data }; @@ -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> { + 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::(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::(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) -> Result<()> { +pub fn convert_regular_rpu_to_av1_payload(data: &[u8]) -> Result> { 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 { - 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()) } diff --git a/dolby_vision/src/rpu/dovi_rpu.rs b/dolby_vision/src/rpu/dovi_rpu.rs index 0b6b9ae..73b6925 100644 --- a/dolby_vision/src/rpu/dovi_rpu.rs +++ b/dolby_vision/src/rpu/dovi_rpu.rs @@ -224,10 +224,7 @@ impl DoviRpu { /// `itu_t_t35_payload_bytes` pub fn write_av1_rpu_metadata_obu_t35_payload(&self) -> Result> { - 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`