use std::default::Default;
use std::mem::transmute;
use crate::utils::bitstream_io::BsIoSliceReader;
use anyhow::{Result, bail};
use log::{trace, warn};
pub const MAX_OBJECT_COUNT: usize = 159;
pub const MAX_OBJ_INFO_BLOCKS: usize = 8;
pub const GAIN_MINUS_INFINITY: i8 = -128;
#[derive(Clone, Debug)]
#[repr(C)]
struct OAMDParserState {
object_count: usize,
program_assignment: ProgramAssignment,
b_alternate_object_data_present: bool,
prev_object_gain: [i8; MAX_OBJ_INFO_BLOCKS],
prev_object_basic_info: ObjectBasicInfo,
prev_object_render_info: ObjectRenderInfo,
object_element: Option<ObjectElement>,
trim_element: Option<TrimElement>,
extended_object_element: Option<ExtendedObjectElement>,
headphone_element: Option<HeadphoneElement>,
object_description_element: Option<ObjectDescriptionElement>,
}
impl Default for OAMDParserState {
fn default() -> Self {
Self {
object_count: 0,
program_assignment: ProgramAssignment::default(),
b_alternate_object_data_present: false,
prev_object_gain: [0; MAX_OBJ_INFO_BLOCKS],
prev_object_basic_info: ObjectBasicInfo::default(),
prev_object_render_info: ObjectRenderInfo::default(),
object_element: None,
trim_element: None,
extended_object_element: None,
headphone_element: None,
object_description_element: None,
}
}
}
#[derive(Copy, Clone, Debug, Default, PartialEq)]
#[repr(C)]
pub struct BedAssignment(pub [bool; 17]);
impl BedAssignment {
pub fn from_non_std(value: u32) -> Self {
let mut ret = Self::default();
ret.0
.iter_mut()
.enumerate()
.for_each(|(i, x)| *x = (value >> i) & 1 == 1);
ret
}
pub fn from_std(value: u16) -> Self {
let mut ret = Self::default();
for (i, &bed) in STD_BED_LIST.iter().enumerate() {
if (value >> i) & 1 == 1 {
for &n in bed {
ret.0[n] = true
}
}
}
ret
}
pub fn with_lfe_only() -> Self {
let mut ret = Self::default();
ret.0[SpeakerLabels::LFE as usize] = true;
ret
}
pub fn to_index_vec(&self) -> Vec<usize> {
self.0
.iter()
.enumerate()
.filter_map(|(i, &sign)| if sign { Some(i) } else { None })
.collect()
}
pub fn is_lfe_only(&self) -> bool {
*self == Self::with_lfe_only()
}
pub fn count_beds(&self) -> usize {
self.0.iter().fold(0, |count, &sign| count + sign as usize)
}
}
pub const STD_BED_LIST: [&[usize]; 10] = [
&[0, 1],
&[2],
&[3],
&[4, 5],
&[6, 7],
&[8, 9],
&[10, 11],
&[12, 13],
&[14, 15],
&[16],
];
pub const ISF_COUNT_LIST: [usize; 8] = [4, 8, 10, 14, 15, 30, 0, 0];
#[derive(Clone, Debug, Default)]
#[repr(C)]
pub struct ProgramAssignment {
pub b_bed_chan_distribute: bool,
pub bed_assignment: Vec<BedAssignment>,
pub num_bed_objects: usize,
pub num_isf_objects: usize,
pub num_dynamic_objects: usize,
}
impl ProgramAssignment {
pub fn b_dyn_object_only_program(&self) -> bool {
self.num_bed_objects <= 1 && self.num_dynamic_objects == 0 && self.num_isf_objects > 0
}
pub fn beds_or_isf_count(&self) -> usize {
self.num_bed_objects + self.num_isf_objects
}
fn read(state: &mut OAMDParserState, reader: &mut BsIoSliceReader) -> Result<Self> {
let mut prog = Self::default();
if reader.get()? {
prog.num_dynamic_objects = state.object_count;
let b_lfe_present = reader.get()?;
if b_lfe_present {
prog.bed_assignment.push(BedAssignment::with_lfe_only());
prog.num_dynamic_objects -= 1;
}
} else {
let content_description = reader.get_n::<u8>(4)?;
if content_description & 1 != 0 {
prog.b_bed_chan_distribute = reader.get()?;
let num_bed_instances = if reader.get()? {
reader.get_n::<u8>(3)? + 2
} else {
1
};
for _ in 0..num_bed_instances {
let bed_assignment = if reader.get()? {
BedAssignment::with_lfe_only()
} else {
if reader.get()? {
BedAssignment::from_std(reader.get_n::<u16>(10)?)
} else {
BedAssignment::from_non_std(reader.get_n::<u32>(17)?)
}
};
prog.bed_assignment.push(bed_assignment)
}
}
if content_description & 2 != 0 {
let intermediate_spatial_format_idx = reader.get_n::<u8>(3)?;
prog.num_isf_objects = ISF_COUNT_LIST[intermediate_spatial_format_idx as usize];
}
if content_description & 4 != 0 {
let mut num_dynamic_objects = reader.get_n::<u8>(5)?;
if num_dynamic_objects == 31 {
num_dynamic_objects += reader.get_n::<u8>(7)?;
};
prog.num_dynamic_objects = num_dynamic_objects as usize + 1;
}
if content_description & 8 != 0 {
let reserved_data_size = (reader.get_n::<u32>(4)? + 1) << 3;
reader.skip_n(reserved_data_size)?;
}
};
for &a in prog.bed_assignment.iter() {
prog.num_bed_objects += a.count_beds();
}
Ok(prog)
}
}
#[derive(Clone, Debug, Default)]
#[repr(C)]
pub struct ObjectAudioMetadataPayload {
pub evo_sample_offset: u64,
pub oamd_version: u8,
pub object_count: usize,
pub program_assignment: ProgramAssignment,
pub b_alternate_object_data_present: bool,
pub object_element: Option<ObjectElement>,
pub trim_element: Option<TrimElement>,
pub extended_object_element: Option<ExtendedObjectElement>,
pub headphone_element: Option<HeadphoneElement>,
pub object_description_element: Option<ObjectDescriptionElement>,
pub oa_element_md: Vec<OAElementMD>,
}
impl ObjectAudioMetadataPayload {
pub fn read(bytes: &[u8]) -> Result<Self> {
let state = &mut OAMDParserState::default();
let reader = &mut BsIoSliceReader::from_slice(bytes);
let mut oamd_version = reader.get_n::<u8>(2)?;
if oamd_version == 3 {
oamd_version += reader.get_n::<u8>(3)?;
}
if oamd_version != 0 {
bail!("unsupported OAMD version {oamd_version}");
}
let mut object_count_bits = reader.get_n::<u8>(5)?;
if object_count_bits == 31 {
object_count_bits += reader.get_n::<u8>(7)?;
}
let object_count = (object_count_bits + 1) as usize;
state.object_count = object_count;
let program_assignment = ProgramAssignment::read(state, reader)?;
state.program_assignment = program_assignment.clone();
let b_alternate_object_data_present = reader.get()?;
state.b_alternate_object_data_present = b_alternate_object_data_present;
let mut oa_element_count = reader.get_n::<u8>(4)?;
if oa_element_count == 15 {
oa_element_count += reader.get_n::<u8>(5)?;
}
let oa_element_md = (0..oa_element_count)
.map(|_| OAElementMD::read(state, reader))
.collect::<Result<Vec<_>>>()?;
let payload = Self {
evo_sample_offset: 0,
oamd_version,
object_count,
program_assignment,
b_alternate_object_data_present,
object_element: state.object_element.clone(),
trim_element: state.trim_element.clone(),
extended_object_element: state.extended_object_element.clone(),
headphone_element: state.headphone_element.clone(),
object_description_element: state.object_description_element.clone(),
oa_element_md,
};
Ok(payload)
}
pub fn get_damf_pos(&self) -> Vec<Vec<[f64; 3]>> {
let mut damf_pos = vec![vec![]; self.object_count];
if let Some(object_element) = &self.object_element {
for (object_index, object_data) in object_element.object_data.iter().enumerate() {
for block in object_data {
damf_pos[object_index].push(block.object_render_info.pos3d);
}
}
}
if let Some(extended_object_element) = &self.extended_object_element {
for (object_index, object) in extended_object_element
.ext_prec_pos_block
.iter()
.enumerate()
{
let pos3d_object = &mut damf_pos[object_index];
for (block_index, block) in object.iter().enumerate() {
let pos3d = &mut pos3d_object[block_index];
pos3d[0] += block.ext_prec_pos3d_x;
pos3d[1] += block.ext_prec_pos3d_y;
pos3d[2] += block.ext_prec_pos3d_z;
}
}
}
damf_pos.iter_mut().for_each(|pos3d_object| {
pos3d_object.iter_mut().for_each(|pos3d| {
pos3d[0] = (pos3d[0].clamp(0.0, 1.0) - 0.5) * 2.0;
pos3d[1] = (0.5 - pos3d[1].clamp(0.0, 1.0)) * 2.0;
pos3d[2] = pos3d[2].clamp(-1.0, 1.0);
})
});
damf_pos
}
}
#[derive(Clone, Debug, Default)]
#[repr(C)]
pub struct OAElementMD {
pub oa_element_id_idx: u8,
pub alternate_object_data_id_idx: Option<u8>,
pub b_discard_unknown_element: bool,
}
#[derive(Debug, Clone, PartialEq)]
#[repr(u8)]
pub enum OAElementType {
Object = 1,
Trim = 2,
Headphone = 3,
ObjectDescription = 4,
ExtendObject = 5,
BedObject = 6,
Reserved(u8),
}
impl OAElementType {
pub fn from_u8(n: u8) -> Self {
match n {
1 => Self::Object,
2 => Self::Trim,
3 => Self::Headphone,
4 => Self::ObjectDescription,
5 => Self::ExtendObject,
6 => Self::BedObject,
_ => Self::Reserved(n),
}
}
}
impl OAElementMD {
fn read(state: &mut OAMDParserState, reader: &mut BsIoSliceReader) -> Result<Self> {
let mut md = OAElementMD {
oa_element_id_idx: reader.get_n::<u8>(4)?,
..Default::default()
};
let oa_element_size_bits = reader.get_variable_bits_max(4, 4)? as u64;
let oa_element_size = (oa_element_size_bits + 1) << 3;
let pos_start = reader.position()?;
let pos_end = if oa_element_size > reader.available()? {
warn!("Truncated oa_element_md with id {}", md.oa_element_id_idx);
reader.available()?
} else {
pos_start + oa_element_size
};
if state.b_alternate_object_data_present {
md.alternate_object_data_id_idx = Some(reader.get_n::<u8>(4)?);
}
md.b_discard_unknown_element = reader.get()?;
match OAElementType::from_u8(md.oa_element_id_idx) {
OAElementType::Object => {
let object_element = ObjectElement::read(state, reader)?;
state.object_element = Some(object_element);
}
OAElementType::Trim => {
let trim_element = TrimElement::read(state, reader)?;
state.trim_element = Some(trim_element);
}
OAElementType::ExtendObject => {
let extended_object_element = ExtendedObjectElement::read(state, reader)?;
state.extended_object_element = Some(extended_object_element);
}
OAElementType::Headphone => {
state.headphone_element = Some(HeadphoneElement::read(state, reader)?);
}
OAElementType::ObjectDescription => {
state.object_description_element =
Some(ObjectDescriptionElement::read(state, reader)?);
}
_ => {
warn!(
"Unimplemented oa_element_md type {} with size={oa_element_size}, pos_end={pos_end}, please submit a sample",
md.oa_element_id_idx
)
}
}
let pos_current = reader.position()?;
if pos_end > pos_current {
reader.skip_n((pos_end - pos_current) as u32)?;
}
trace!(
"OAMD Element ID: {}, size: {}, start: {}, expected_end: {}, actual_end: {}",
md.oa_element_id_idx, oa_element_size, pos_start, pos_end, pos_current
);
Ok(md)
}
}
pub type ObjectData = Vec<ObjectInfoBlock>;
#[derive(Clone, Debug)]
#[repr(C)]
pub struct ObjectElement {
pub md_update_info: MDUpdateInfo,
pub b_reserved_data_not_present: bool,
pub reserved_data: u8,
pub object_data: Vec<ObjectData>,
}
impl Default for ObjectElement {
fn default() -> Self {
Self {
md_update_info: MDUpdateInfo::default(),
b_reserved_data_not_present: false,
reserved_data: 32, object_data: Vec::new(),
}
}
}
impl ObjectElement {
fn read(state: &mut OAMDParserState, reader: &mut BsIoSliceReader) -> Result<Self> {
let mut element = Self {
md_update_info: MDUpdateInfo::read(reader)?,
b_reserved_data_not_present: reader.get()?,
object_data: Vec::with_capacity(state.object_count),
..Default::default()
};
if !element.b_reserved_data_not_present {
element.reserved_data = reader.get_n::<u8>(5)?;
}
let block_count = element.md_update_info.num_obj_info_blocks;
for object_index in 0..state.object_count {
let mut object_data = ObjectData::default();
for block_index in 0..block_count {
object_data.push(ObjectInfoBlock::read(
state,
reader,
object_index,
block_index,
)?);
}
element.object_data.push(object_data.clone());
}
Ok(element)
}
}
#[derive(Clone, Debug, Default)]
#[repr(C)]
pub struct MDUpdateInfo {
pub sample_offset: usize,
pub num_obj_info_blocks: usize,
pub block_update_info: Vec<BlockUpdateInfo>,
}
impl MDUpdateInfo {
pub fn read(reader: &mut BsIoSliceReader) -> Result<Self> {
let sample_offset = match reader.get_n::<u8>(2)? {
0 => 0,
1 => match reader.get_n::<u8>(2)? {
0 => 8,
1 => 16,
2 => 18,
3 => 24,
_ => unreachable!(),
},
2 => reader.get_n::<u8>(5)? as usize,
_ => unreachable!(),
};
let num_obj_info_blocks = (reader.get_n::<u8>(3)? + 1) as usize;
let mut info = Self {
sample_offset,
num_obj_info_blocks,
block_update_info: Vec::with_capacity(num_obj_info_blocks),
};
for _block in 0..num_obj_info_blocks {
info.block_update_info.push(BlockUpdateInfo::read(reader)?);
}
Ok(info)
}
}
#[derive(Clone, Debug, Default)]
#[repr(C)]
pub struct BlockUpdateInfo {
pub block_offset_factor_bits: u8,
pub ramp_duration_code: u8,
pub ramp_duration: u16,
}
impl BlockUpdateInfo {
pub const RAMP_DURATION_LIST: [u16; 16] = [
32, 64, 128, 256, 320, 480, 1000, 1001, 1024, 1600, 1601, 1602, 1920, 2000, 2002, 2048,
];
pub fn read(reader: &mut BsIoSliceReader) -> Result<Self> {
let mut info = Self {
block_offset_factor_bits: reader.get_n(6)?,
ramp_duration_code: reader.get_n(2)?,
..Default::default()
};
info.ramp_duration = match info.ramp_duration_code {
0 => 0,
1 => 512,
2 => 1536,
3 => unsafe {
if reader.get()? {
*Self::RAMP_DURATION_LIST.get_unchecked(reader.get_n::<u64>(4)? as usize)
} else {
reader.get_n(11)?
}
},
_ => unreachable!(),
};
Ok(info)
}
}
#[derive(Clone, Debug, Default)]
#[repr(C)]
pub struct ObjectInfoBlock {
pub b_object_not_active: bool,
pub object_basic_info: ObjectBasicInfo,
pub b_object_in_bed_or_isf: bool,
pub object_render_info: ObjectRenderInfo,
}
impl ObjectInfoBlock {
fn read(
state: &mut OAMDParserState,
reader: &mut BsIoSliceReader,
object_index: usize,
block_index: usize,
) -> Result<Self> {
let mut info = Self {
b_object_not_active: reader.get()?,
..Default::default()
};
let object_basic_info_status_idx = if info.b_object_not_active {
0
} else if block_index == 0 {
1
} else {
reader.get_n::<u8>(2)?
};
let prev_object_basic_info = if block_index == 0 {
ObjectBasicInfo::default()
} else {
state.prev_object_basic_info.clone()
};
info.object_basic_info = match object_basic_info_status_idx {
0 => ObjectBasicInfo::default(),
1 | 3 => ObjectBasicInfo::read(
&prev_object_basic_info,
state,
reader,
object_index,
block_index,
object_basic_info_status_idx,
)?,
_ => prev_object_basic_info,
};
state.prev_object_basic_info = info.object_basic_info.clone();
info.b_object_in_bed_or_isf = object_index < state.program_assignment.beds_or_isf_count();
let object_render_info_status_idx = if info.b_object_not_active {
0
} else if !info.b_object_in_bed_or_isf {
if block_index == 0 {
1
} else {
reader.get_n(2)?
}
} else {
0
};
let prev_object_render_info = if block_index == 0 {
ObjectRenderInfo::default()
} else {
state.prev_object_render_info.clone()
};
info.object_render_info = match object_render_info_status_idx {
0 => ObjectRenderInfo::default(),
1 | 3 => ObjectRenderInfo::read(
&prev_object_render_info,
reader,
object_render_info_status_idx,
block_index,
)?,
_ => prev_object_render_info,
};
state.prev_object_render_info = info.object_render_info.clone();
if reader.get()? {
let additional_table_data_size = (reader.get_n::<u32>(4)? + 1) << 3;
reader.skip_n(additional_table_data_size)?;
}
Ok(info)
}
}
#[derive(Clone, Debug)]
#[repr(C)]
pub struct ObjectBasicInfo {
pub object_gain: i8,
pub object_priority: f64,
}
impl Default for ObjectBasicInfo {
fn default() -> Self {
Self {
object_gain: GAIN_MINUS_INFINITY,
object_priority: 0.0,
}
}
}
impl ObjectBasicInfo {
pub fn gain_string(&self) -> String {
match self.object_gain {
GAIN_MINUS_INFINITY => "-inf".to_string(),
_ => format!("{}", self.object_gain),
}
}
fn read(
prev: &Self,
state: &mut OAMDParserState,
reader: &mut BsIoSliceReader,
object_index: usize,
block_index: usize,
object_basic_info_status_idx: u8,
) -> Result<Self> {
let mut basic = prev.clone();
let obj_basic_info_bits = if object_basic_info_status_idx == 1 {
3u8
} else {
reader.get_n(2)?
};
if obj_basic_info_bits & 1 != 0 {
let prev_object_gain = if object_index == 0 {
0
} else {
state.prev_object_gain[block_index]
};
basic.object_gain = match reader.get_n::<u8>(2)? {
0 => 0,
1 => GAIN_MINUS_INFINITY,
2 => {
let object_gain_idx = reader.get_n::<u8>(6)?;
if object_gain_idx <= 14 {
15 - object_gain_idx as i8
} else {
14 - object_gain_idx as i8
}
}
3 => prev_object_gain,
_ => unreachable!(),
};
state.prev_object_gain[block_index] = basic.object_gain;
}
if obj_basic_info_bits & 2 != 0 {
basic.object_priority = if reader.get()? {
1.0
} else {
reader.get_n::<u8>(5)? as f64 / 32.0
}
}
Ok(basic)
}
}
#[derive(Clone, Debug)]
#[repr(C)]
pub struct ObjectRenderInfo {
pub b_differential_position_specified: bool,
pub pos3d: [f64; 3],
pub b_object_distance_specified: bool,
pub distance_factor: Option<f64>,
pub zone_constraints_idx: u8,
pub b_enable_elevation: bool,
pub object_size: [f64; 3],
pub b_object_use_screen_ref: bool,
pub screen_factor: f64,
pub depth_factor: f64,
pub b_object_snap: bool,
}
impl Default for ObjectRenderInfo {
fn default() -> Self {
Self {
b_differential_position_specified: false,
pos3d: [0.5, 0.5, 0.0],
b_object_distance_specified: false,
distance_factor: None,
zone_constraints_idx: 0,
b_enable_elevation: true,
object_size: [0.0, 0.0, 0.0],
b_object_use_screen_ref: false,
screen_factor: 0.0,
depth_factor: 0.25,
b_object_snap: false,
}
}
}
impl ObjectRenderInfo {
fn read(
prev: &Self,
reader: &mut BsIoSliceReader,
object_render_info_status_idx: u8,
block_index: usize,
) -> Result<Self> {
let mut render = prev.clone();
let object_render_info_bits = if object_render_info_status_idx == 1 {
15
} else {
reader.get_n::<u8>(4)?
};
if object_render_info_bits & 1 != 0 {
render.b_differential_position_specified = if block_index == 0 {
false
} else {
reader.get()?
};
let [x, y, z] = if render.b_differential_position_specified {
let (prev_x, prev_y, prev_z) = (prev.pos3d[0], prev.pos3d[1], prev.pos3d[2]);
let x = prev_x + reader.get_s::<i8>(3)? as f64 / 62.0;
let y = prev_y + reader.get_s::<i8>(3)? as f64 / 62.0;
let z = prev_z + reader.get_s::<i8>(3)? as f64 / 15.0;
[x, y, z]
} else {
let x = reader.get_n::<u64>(6)? as f64 / 62.0;
let y = reader.get_n::<u64>(6)? as f64 / 62.0;
let sign_z = if reader.get()? { 1.0 } else { -1.0 };
let z = reader.get_n::<u64>(4)? as f64 / 15.0 * sign_z;
[x, y, z]
};
render.pos3d = [x.clamp(0.0, 1.0), y.clamp(0.0, 1.0), z.clamp(-1.0, 1.0)];
render.b_object_distance_specified = reader.get()?;
render.distance_factor = if render.b_object_distance_specified {
if reader.get()? {
Some(f64::INFINITY)
} else {
let idx = reader.get_n::<u8>(4)? as usize;
Some(DISTANCE_FACTORS[idx])
}
} else {
None
};
}
if object_render_info_bits & 2 != 0 {
render.zone_constraints_idx = match reader.get_n(3)? {
7 => 0,
idx => idx,
};
render.b_enable_elevation = reader.get()?;
}
if object_render_info_bits & 4 != 0 {
render.object_size = match reader.get_n::<u8>(2)? {
1 => {
let object_size = reader.get_n::<u8>(5)? as f64 / 31.0;
[object_size; 3]
}
2 => {
let width = reader.get_n::<u8>(5)? as f64 / 31.0;
let depth = reader.get_n::<u8>(5)? as f64 / 31.0;
let height = reader.get_n::<u8>(5)? as f64 / 31.0;
[width, depth, height]
}
_ => [0.0; 3],
};
}
if object_render_info_bits & 8 != 0 {
render.b_object_use_screen_ref = reader.get()?;
if render.b_object_use_screen_ref {
render.screen_factor = (reader.get_n::<u8>(3)? + 1) as f64 / 8.0;
render.depth_factor = 0.25 * (reader.get_n::<u8>(2)? + 1) as f64;
} else {
render.screen_factor = 0.0
}
}
render.b_object_snap = reader.get()?;
Ok(render)
}
}
pub const NUM_TRIM_CONFIGS: usize = 9;
#[rustfmt::skip]
pub const DISTANCE_FACTORS: [f64; 16] = [
1.1, 1.3, 1.6, 2.0, 2.5, 3.2, 4.0, 5.0, 6.3, 7.9, 10.0, 12.6, 15.8, 20.0, 25.1, 50.1,
];
#[rustfmt::skip]
pub const TRIM_LUT: [f64; 16] = [
6.0, 3.0, 1.5, 0.75, -0.75, -1.5, -3.0, -4.5, -6.0, -7.5, -9.0, -10.5, -12.0, -13.5, -15.0, -36.0,
];
#[derive(Clone, Copy, Debug, Default)]
#[repr(C)]
pub struct HeadphoneBlock {
pub hp_render_mode: u8,
pub hp_head_track_disable: bool,
}
#[derive(Clone, Debug, Default)]
#[repr(C)]
pub struct HeadphoneElement {
pub hp_mode: u8,
pub num_obj_info_blocks: usize,
pub headphone_data: Vec<Vec<HeadphoneBlock>>,
}
impl HeadphoneElement {
fn read(state: &mut OAMDParserState, reader: &mut BsIoSliceReader) -> Result<Self> {
let mut element = Self {
hp_mode: reader.get_n(3)?,
num_obj_info_blocks: reader.get_n::<u8>(3)? as usize + 1,
..Default::default()
};
let (objects, blocks) = (state.object_count, element.num_obj_info_blocks);
let (mode, head_track_disable) = match element.hp_mode {
0 => (0, true),
1 | 2 => (element.hp_mode, reader.get()?),
3 => {
element.headphone_data = Self::read_per_object(state, reader, blocks)?;
return Ok(element);
}
_ => return Ok(element),
};
element.headphone_data = vec![
vec![
HeadphoneBlock {
hp_render_mode: mode,
hp_head_track_disable: head_track_disable,
};
blocks
];
objects
];
Ok(element)
}
fn read_per_object(
state: &mut OAMDParserState,
reader: &mut BsIoSliceReader,
blocks: usize,
) -> Result<Vec<Vec<HeadphoneBlock>>> {
let prog = &state.program_assignment;
let isf = prog.num_bed_objects..prog.num_bed_objects + prog.num_isf_objects;
let mut data: Vec<Vec<HeadphoneBlock>> = Vec::with_capacity(state.object_count);
for object_index in 0..state.object_count {
if isf.contains(&object_index)
&& object_index != isf.start
&& let Some(first) = data.get(isf.start).cloned()
{
data.push(first);
continue;
}
let mut object = Vec::with_capacity(blocks);
for _ in 0..blocks {
object.push(HeadphoneBlock {
hp_render_mode: reader.get_n::<u8>(2)?.min(2),
hp_head_track_disable: reader.get()?,
});
}
data.push(object);
}
Ok(data)
}
}
#[derive(Clone, Debug, Default)]
#[repr(C)]
pub struct ObjectDescriptionElement {
pub num_obj_info_blocks: usize,
pub b_present: bool,
pub sub_mode: u8,
pub object_dialog_indication: Vec<Vec<u8>>,
}
impl ObjectDescriptionElement {
const DIALOG_MAP: [u8; 4] = [2, 1, 3, 0];
fn read(state: &mut OAMDParserState, reader: &mut BsIoSliceReader) -> Result<Self> {
let mut element = Self {
num_obj_info_blocks: reader.get_n::<u8>(3)? as usize + 1,
b_present: reader.get()?,
..Default::default()
};
if !element.b_present {
return Ok(element);
}
element.sub_mode = reader.get_n(2)?;
let (objects, blocks) = (state.object_count, element.num_obj_info_blocks);
match element.sub_mode {
0 => {
element.object_dialog_indication = Self::per_object(
objects,
blocks,
|r| Ok(if r.get()? { 1 } else { 2 }),
reader,
)?;
}
1 => {
let b_extra = reader.get()?;
element.object_dialog_indication = Self::per_object(
objects,
blocks,
|r| {
let code = r.get_n::<u8>(2)? as usize;
if code == 2 && b_extra {
r.skip_n(5)?;
}
Ok(Self::DIALOG_MAP[code])
},
reader,
)?;
}
_ => {
let mut size = reader.get_n::<u32>(5)? + 1;
if reader.get()? {
size += reader.get_variable_bits_max(2, 3)?;
}
element.object_dialog_indication = vec![vec![0; blocks]; objects];
reader.skip_n(size * 8)?;
}
}
Ok(element)
}
fn per_object(
objects: usize,
blocks: usize,
mut one: impl FnMut(&mut BsIoSliceReader) -> Result<u8>,
reader: &mut BsIoSliceReader,
) -> Result<Vec<Vec<u8>>> {
(0..objects)
.map(|_| (0..blocks).map(|_| one(reader)).collect())
.collect()
}
}
#[derive(Clone, Debug, Default)]
#[repr(C)]
pub struct TrimElement {
pub warp_mode: u8,
pub reserved: u8,
pub global_trim_mode: u8,
pub trims: [Option<Trim>; NUM_TRIM_CONFIGS],
pub b_disable_trim_per_obj: bool,
pub b_disable_trim: Vec<bool>,
}
#[derive(Clone, Debug)]
#[repr(C)]
pub struct Trim {
pub b_default_trim: bool,
pub b_disable_trim: bool,
pub trim_centre: Option<f64>,
pub trim_surround: Option<f64>,
pub trim_height: Option<f64>,
pub bal3d_y_tb: Option<f64>,
pub bal3d_y_lis: Option<f64>,
}
impl Default for Trim {
fn default() -> Trim {
Trim {
b_default_trim: true,
b_disable_trim: false,
trim_centre: None,
trim_surround: None,
trim_height: None,
bal3d_y_tb: None,
bal3d_y_lis: None,
}
}
}
impl TrimElement {
fn read(state: &OAMDParserState, reader: &mut BsIoSliceReader) -> Result<Self> {
let mut element = Self {
warp_mode: reader.get_n(2)?,
reserved: reader.get_n(2)?,
global_trim_mode: reader.get_n(2)?,
..Default::default()
};
if element.global_trim_mode == 2 {
for trim in &mut element.trims {
let mut t = Trim {
b_default_trim: reader.get()?,
..Default::default()
};
if t.b_default_trim {
*trim = Some(t);
continue;
}
t.b_disable_trim = reader.get()?;
if !t.b_disable_trim {
let trim_balance_presence = reader.get_n::<u8>(5)?;
if trim_balance_presence & 0x1 != 0 {
t.trim_centre = Some(TRIM_LUT[reader.get_n::<u8>(4)? as usize]);
}
if trim_balance_presence & 0x2 != 0 {
t.trim_surround = Some(TRIM_LUT[reader.get_n::<u8>(4)? as usize]);
}
if trim_balance_presence & 0x4 != 0 {
t.trim_height = Some(TRIM_LUT[reader.get_n::<u8>(4)? as usize]);
}
if trim_balance_presence & 0x8 != 0 {
let sign = if reader.get()? { 1.0 } else { -1.0 };
t.bal3d_y_tb = Some((reader.get_n::<u8>(4)? + 1) as f64 / 16.0 * sign);
}
if trim_balance_presence & 0x10 != 0 {
let sign = if reader.get()? { 1.0 } else { -1.0 };
t.bal3d_y_lis = Some((reader.get_n::<u8>(4)? + 1) as f64 / 16.0 * sign);
}
}
*trim = Some(t)
}
}
element.b_disable_trim_per_obj = reader.get()?;
if element.b_disable_trim_per_obj {
for _ in 0..state.object_count {
element.b_disable_trim.push(reader.get()?);
}
}
Ok(element)
}
}
#[derive(Clone, Debug, Default)]
#[repr(C)]
pub struct ExtendedObjectElement {
pub b_obj_div_block: bool,
pub object_div_block: Vec<Vec<ObjectDivergenceBlock>>,
pub b_ext_prec_pos_block: bool,
pub ext_prec_pos_block: Vec<Vec<ExtendedPrecisionPositionBlock>>,
}
impl ExtendedObjectElement {
fn read(state: &mut OAMDParserState, reader: &mut BsIoSliceReader) -> Result<Self> {
let mut element = Self {
b_obj_div_block: reader.get()?,
..Default::default()
};
let object_count = state.object_count;
let Some(object_element) = &state.object_element else {
return Ok(element);
};
let block_count = object_element.md_update_info.num_obj_info_blocks;
if element.b_obj_div_block {
let mut obj_blk = Vec::with_capacity(object_count);
for object_index in 0..object_count {
let mut blk_blk = Vec::with_capacity(block_count);
let object_data = &object_element.object_data[object_index];
for (block_index, object_info_block) in object_data.iter().enumerate() {
let mut blk = ObjectDivergenceBlock::default();
if !object_info_block.b_object_not_active
&& !object_info_block.b_object_in_bed_or_isf
{
blk.b_object_divergence = reader.get()?;
if blk.b_object_divergence {
match reader.get_n::<u8>(2)? {
0 => {
blk.object_divergence =
ObjectDivergenceBlock::OBJECT_DIV_TABLE_TABLE
[reader.get_n::<u8>(2)? as usize];
}
1 => {
if let Some(prev_blk) = blk_blk.last().cloned() {
blk = prev_blk;
} else {
warn!(
"No previous block for object {object_index} block {block_index}"
);
}
}
_ => {
blk.object_div_code = reader.get_n(6)?;
if let Some(div) = ObjectDivergenceBlock::OBJECT_DIV_CODE_TABLE
[blk.object_div_code as usize]
{
blk.object_divergence = div;
} else {
warn!(
"Invalid object_div_code for object {object_index} block {block_index}"
)
}
}
}
}
}
blk_blk.push(blk);
}
obj_blk.push(blk_blk);
}
element.object_div_block = obj_blk;
}
element.b_ext_prec_pos_block = reader.get()?;
if element.b_ext_prec_pos_block {
let mut pos_blk = Vec::with_capacity(object_count);
for object_index in 0..object_count {
let mut blk_blk = Vec::with_capacity(block_count);
let object_data = &object_element.object_data[object_index]; for object_info_block in object_data.iter() {
let mut blk = ExtendedPrecisionPositionBlock::default();
if !object_info_block.b_object_not_active {
if !object_info_block.b_object_in_bed_or_isf && reader.get()? {
let ext_prec_pos_presence = reader.get_n::<u8>(3)?;
if ext_prec_pos_presence & 1 != 0 {
blk.ext_prec_pos3d_x =
ExtendedPrecisionPositionBlock::EXT_PREC_POS3D_LUT
[reader.get_n::<u8>(2)? as usize]
/ 310.0;
}
if ext_prec_pos_presence & 2 != 0 {
blk.ext_prec_pos3d_y =
ExtendedPrecisionPositionBlock::EXT_PREC_POS3D_LUT
[reader.get_n::<u8>(2)? as usize]
/ 310.0;
}
if ext_prec_pos_presence & 4 != 0 {
blk.ext_prec_pos3d_z =
ExtendedPrecisionPositionBlock::EXT_PREC_POS3D_LUT
[reader.get_n::<u8>(2)? as usize]
/ 75.0;
}
}
}
blk_blk.push(blk);
}
pos_blk.push(blk_blk);
}
element.ext_prec_pos_block = pos_blk;
}
Ok(element)
}
}
#[derive(Clone, Debug, Default)]
#[repr(C)]
pub struct ObjectDivergenceBlock {
pub object_divergence: f64,
pub b_object_divergence: bool,
pub object_div_mode: u8,
pub object_div_table: u8,
pub object_div_code: u8,
}
impl ObjectDivergenceBlock {
pub const OBJECT_DIV_TABLE_TABLE: [f64; 4] = [0.500755, 0.608529, 0.704833, 1.0];
pub const OBJECT_DIV_CODE_TABLE: [Option<f64>; 64] = [
Some(0.0), Some(0.0), Some(0.004026), Some(0.00716), Some(0.012731), Some(0.020173), Some(0.028485), Some(0.04021), Some(0.050582), Some(0.063601), Some(0.079914), Some(0.100299), Some(0.125666), Some(0.140532), Some(0.157027), Some(0.175282), Some(0.195417), Some(0.217536), Some(0.241718), Some(0.268002), Some(0.296377), Some(0.326766), Some(0.359017), Some(0.392895), Some(0.428081), Some(0.464184), Some(0.500755), Some(0.537316), Some(0.573389), Some(0.608529), Some(0.642346), Some(0.674524), Some(0.704833), Some(0.733123), Some(0.75932), Some(0.783416), Some(0.805451), Some(0.825506), Some(0.843686), Some(0.860112), Some(0.874914), Some(0.888222), Some(0.900168), Some(0.910875), Some(0.920461), Some(0.929035), Some(0.936698), Some(0.943544), Some(0.949656), Some(0.955112), Some(0.95998), Some(0.964322), Some(0.968195), Some(0.974729), Some(0.979923), Some(0.98405), Some(0.98733), Some(0.989935), Some(0.992874), Some(0.994955), Some(0.996817), Some(0.99821), Some(0.998993), Some(1.0), ];
}
#[derive(Clone, Debug, Default)]
#[repr(C)]
pub struct ExtendedPrecisionPositionBlock {
pub ext_prec_pos3d_x: f64,
pub ext_prec_pos3d_y: f64,
pub ext_prec_pos3d_z: f64,
}
impl ExtendedPrecisionPositionBlock {
pub const EXT_PREC_POS3D_LUT: [f64; 4] = [1.0, 2.0, -1.0, -2.0];
}
#[allow(clippy::upper_case_acronyms)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum SpeakerLabels {
L,
R,
C,
LFE,
Lss,
Rss,
Lrs,
Rrs,
Lfh,
Rfh,
Lts,
Rts,
Lrh,
Rrh,
Lw,
Rw,
LFE2,
}
impl SpeakerLabels {
const W: f32 = 42.0 / 62.0;
#[rustfmt::skip]
const POSITIONS: [[f32; 3]; 17] = [
[-1.0, 1.0, 0.0],
[ 1.0, 1.0, 0.0],
[ 0.0, 1.0, 0.0],
[-1.0, 1.0, -1.0],
[-1.0, 0.0, 0.0],
[ 1.0, 0.0, 0.0],
[-1.0, -1.0, 0.0],
[ 1.0, -1.0, 0.0],
[-1.0, 1.0, 1.0],
[ 1.0, 1.0, 1.0],
[-1.0, 0.0, 1.0],
[ 1.0, 0.0, 1.0],
[-1.0, -1.0, 1.0],
[ 1.0, -1.0, 1.0],
[-1.0, Self::W, 0.0],
[ 1.0, Self::W, 0.0],
[ 1.0, 1.0, -1.0],
];
pub fn from_u8(n: u8) -> Option<Self> {
if n <= SpeakerLabels::LFE2 as u8 {
Some(unsafe { transmute::<u8, Self>(n) })
} else {
None
}
}
pub fn pos(&self) -> &[f32; 3] {
&Self::POSITIONS[*self as usize]
}
}
#[test]
fn std_bed_list_partitions_the_speaker_labels() {
let mut seen = vec![0usize; SpeakerLabels::LFE2 as usize + 1];
for group in STD_BED_LIST {
for &n in group {
seen[n] += 1;
}
}
assert!(
seen.iter().all(|&c| c == 1),
"each label must appear in exactly one group, got {seen:?}"
);
assert_eq!(
STD_BED_LIST[0],
&[SpeakerLabels::L as usize, SpeakerLabels::R as usize]
);
assert_eq!(STD_BED_LIST[1], &[SpeakerLabels::C as usize]);
assert_eq!(STD_BED_LIST[2], &[SpeakerLabels::LFE as usize]);
assert_eq!(STD_BED_LIST[9], &[SpeakerLabels::LFE2 as usize]);
}
#[test]
fn lfe_only_bed_agrees_between_both_constructors() {
let from_bit = BedAssignment::from_std(1 << 2);
let direct = BedAssignment::with_lfe_only();
assert_eq!(
from_bit.0, direct.0,
"bit 2 of a standard bed assignment is the LFE group"
);
assert_eq!(direct.0.iter().filter(|&&b| b).count(), 1);
}
#[test]
fn distance_factors_are_a_rising_ladder_of_sixteen() {
assert_eq!(DISTANCE_FACTORS.len(), 16);
assert!(
DISTANCE_FACTORS.windows(2).all(|w| w[0] < w[1]),
"must be strictly increasing: {DISTANCE_FACTORS:?}"
);
assert_eq!(DISTANCE_FACTORS[0], 1.1);
assert_eq!(DISTANCE_FACTORS[15], 50.1);
}
#[test]
fn isf_count_list_covers_the_full_three_bit_index() {
assert_eq!(ISF_COUNT_LIST.len(), 8);
assert_eq!(&ISF_COUNT_LIST[..6], &[4, 8, 10, 14, 15, 30]);
assert_eq!(
&ISF_COUNT_LIST[6..],
&[0, 0],
"reserved indices carry no objects"
);
assert!(ISF_COUNT_LIST[..6].windows(2).all(|w| w[0] < w[1]));
}
#[test]
fn extended_precision_offsets_pair_positive_then_negative() {
assert_eq!(
ExtendedPrecisionPositionBlock::EXT_PREC_POS3D_LUT,
[1.0, 2.0, -1.0, -2.0]
);
}
#[test]
fn speaker_positions_are_damf_coordinates() {
use SpeakerLabels::*;
assert_eq!(L.pos(), &[-1.0, 1.0, 0.0]);
assert_eq!(R.pos(), &[1.0, 1.0, 0.0]);
assert_eq!(C.pos(), &[0.0, 1.0, 0.0]);
assert_eq!(Lss.pos(), &[-1.0, 0.0, 0.0]);
assert_eq!(Rss.pos(), &[1.0, 0.0, 0.0]);
assert_eq!(Lrs.pos(), &[-1.0, -1.0, 0.0]);
assert_eq!(Rrs.pos(), &[1.0, -1.0, 0.0]);
assert_eq!(Lfh.pos(), &[-1.0, 1.0, 1.0]);
assert_eq!(Rfh.pos(), &[1.0, 1.0, 1.0]);
assert_eq!(Lts.pos(), &[-1.0, 0.0, 1.0]);
assert_eq!(Rts.pos(), &[1.0, 0.0, 1.0]);
assert_eq!(Lrh.pos(), &[-1.0, -1.0, 1.0]);
assert_eq!(Rrh.pos(), &[1.0, -1.0, 1.0]);
assert_eq!(Lw.pos(), &[-1.0, 42.0 / 62.0, 0.0]);
assert_eq!(Rw.pos(), &[1.0, 42.0 / 62.0, 0.0]);
assert_eq!(LFE.pos(), &[-1.0, 1.0, -1.0]);
assert_eq!(LFE2.pos(), &[1.0, 1.0, -1.0]);
}
#[test]
fn every_speaker_label_round_trips_to_a_position() {
for n in 0..=SpeakerLabels::LFE2 as u8 {
let label = SpeakerLabels::from_u8(n).unwrap_or_else(|| panic!("label {n} must map"));
assert_eq!(label as u8, n, "discriminant {n} must round trip");
let p = label.pos();
assert!(
(-1.0..=1.0).contains(&p[0]) && (-1.0..=1.0).contains(&p[1]),
"{label:?} position {p:?} outside the DAMF range"
);
}
assert!(SpeakerLabels::from_u8(SpeakerLabels::LFE2 as u8 + 1).is_none());
}
pub const TEST_DATA: &[u8] = &[
0x1F, 0x88, 0x4B, 0x80, 0x00, 0xA2, 0x70, 0x00, 0x80, 0x40, 0xE0, 0x01, 0x00, 0x81, 0xC0, 0x02,
0x01, 0x03, 0x80, 0x04, 0x02, 0x07, 0x00, 0x08, 0x04, 0x0E, 0x00, 0x10, 0x08, 0x1C, 0x00, 0x20,
0x10, 0x38, 0x00, 0x40, 0x20, 0x70, 0x00, 0x80, 0x40, 0xE0, 0x01, 0x00, 0x81, 0xC0, 0x02, 0x01,
0x03, 0x80, 0x04, 0x02, 0x07, 0x00, 0x08, 0x04, 0x0E, 0x00, 0x10, 0x08, 0x1C, 0x00, 0x20, 0x10,
0x02, 0x40, 0x24, 0x33, 0x33, 0xF8, 0x00,
];
pub const TEST_DATA_TRIM: &[u8] = &[
0x1F, 0x88, 0x4B, 0x80, 0x00, 0xA2, 0x70, 0x00, 0x80, 0x40, 0xE4, 0x0B, 0x40, 0x81, 0xDF, 0x02,
0x01, 0x03, 0x80, 0xFC, 0x02, 0x07, 0xD4, 0x5A, 0x04, 0x0F, 0xF0, 0x10, 0x08, 0x1C, 0x0F, 0xA0,
0x10, 0x38, 0x00, 0x7C, 0x20, 0x7F, 0x9F, 0x80, 0x40, 0xFF, 0x7D, 0x00, 0x81, 0xFE, 0x03, 0xE1,
0x03, 0x81, 0xF7, 0xC2, 0x07, 0xFB, 0xEF, 0x84, 0x0E, 0x00, 0x10, 0x08, 0x1C, 0x00, 0x20, 0x10,
0x02, 0xB2, 0x20, 0xCC, 0xE6, 0xAB, 0xEF, 0x0C, 0xED, 0x0D, 0x29, 0x86, 0x85, 0x80,
];
pub const TEST_DATA_BROKEN: &[u8] = &[
0x1F, 0x88, 0x4B, 0x80, 0x00, 0xA2, 0x70, 0x00, 0x80, 0x40, 0xE4, 0x0B, 0x40, 0x81, 0xDF, 0x02,
0x01, 0x03, 0x80, 0xFC, 0x02, 0x07, 0xD4, 0x5A, 0x04, 0x0F, 0xF0, 0x10, 0x08, 0x1C, 0x0F, 0xA0,
0x10, 0x38, 0x00, 0x7C, 0x20, 0x7F, 0x9F, 0x80, 0x40, 0xFF, 0x7D, 0x00, 0x81, 0xFE, 0x03, 0xE1,
0x03, 0x81, 0xF7, 0xC2, 0x07, 0xFB, 0xEF, 0x84, 0x0E, 0x00, 0x10, 0x08, 0x1C, 0x00, 0x20, 0x10,
0x02, 0x30, 0x20, 0xCC, 0xFF, 0xE0,
];
#[cfg(test)]
mod tests {
use crate::structs::oamd::{
ObjectAudioMetadataPayload, TEST_DATA, TEST_DATA_BROKEN, TEST_DATA_TRIM,
};
use anyhow::Result;
#[test]
fn test1() -> Result<()> {
let _ = ObjectAudioMetadataPayload::read(TEST_DATA)?;
Ok(())
}
#[test]
fn trim() -> Result<()> {
let _ = ObjectAudioMetadataPayload::read(TEST_DATA_TRIM)?;
Ok(())
}
#[test]
fn broken() -> Result<()> {
let _ = ObjectAudioMetadataPayload::read(TEST_DATA_BROKEN)?;
Ok(())
}
#[derive(Default)]
struct Bits {
data: Vec<u8>,
len: usize,
}
impl Bits {
fn push(&mut self, n: usize, value: u32) {
for i in (0..n).rev() {
if self.len.is_multiple_of(8) {
self.data.push(0);
}
if (value >> i) & 1 == 1 {
let last = self.data.len() - 1;
self.data[last] |= 1 << (7 - (self.len & 7));
}
self.len += 1;
}
}
}
fn payload_with_element(id: u32, body: impl Fn(&mut Bits)) -> Vec<u8> {
let mut element = Bits::default();
body(&mut element);
let mut bits = Bits::default();
bits.push(2, 0); bits.push(5, 0); bits.push(1, 1); bits.push(1, 0); bits.push(1, 0); bits.push(4, 1); bits.push(4, id);
let bytes = (element.len + 1).div_ceil(8) as u32;
bits.push(4, bytes - 1);
bits.push(1, 0); bits.push(1, 0);
for i in 0..element.len {
bits.push(1, ((element.data[i / 8] >> (7 - (i % 8))) & 1) as u32);
}
bits.data
}
#[test]
fn headphone_element_mode_0_applies_to_every_object() -> Result<()> {
let data = payload_with_element(3, |b| {
b.push(3, 0); b.push(3, 0); });
let payload = ObjectAudioMetadataPayload::read(&data)?;
let element = payload.headphone_element.expect("a headphone element");
assert_eq!(element.hp_mode, 0);
assert_eq!(element.num_obj_info_blocks, 1);
assert_eq!(element.headphone_data.len(), 1, "one object");
assert_eq!(element.headphone_data[0][0].hp_render_mode, 0);
assert!(element.headphone_data[0][0].hp_head_track_disable);
Ok(())
}
#[test]
fn headphone_element_mode_1_reads_one_bit() -> Result<()> {
let data = payload_with_element(3, |b| {
b.push(3, 1); b.push(3, 1); b.push(1, 0); });
let element = ObjectAudioMetadataPayload::read(&data)?
.headphone_element
.expect("a headphone element");
assert_eq!(element.num_obj_info_blocks, 2);
assert_eq!(element.headphone_data[0].len(), 2);
for block in &element.headphone_data[0] {
assert_eq!(block.hp_render_mode, 1);
assert!(!block.hp_head_track_disable);
}
Ok(())
}
#[test]
fn headphone_element_mode_3_codes_each_block() -> Result<()> {
let data = payload_with_element(3, |b| {
b.push(3, 3); b.push(3, 1); b.push(2, 2); b.push(1, 1); b.push(2, 3); b.push(1, 0);
});
let element = ObjectAudioMetadataPayload::read(&data)?
.headphone_element
.expect("a headphone element");
assert_eq!(element.headphone_data[0][0].hp_render_mode, 2);
assert!(element.headphone_data[0][0].hp_head_track_disable);
assert_eq!(element.headphone_data[0][1].hp_render_mode, 2);
assert!(!element.headphone_data[0][1].hp_head_track_disable);
Ok(())
}
#[test]
fn headphone_element_leaves_an_undefined_mode_empty() -> Result<()> {
let data = payload_with_element(3, |b| {
b.push(3, 7);
b.push(3, 0);
});
let element = ObjectAudioMetadataPayload::read(&data)?
.headphone_element
.expect("a headphone element");
assert_eq!(element.hp_mode, 7);
assert!(element.headphone_data.is_empty());
Ok(())
}
#[test]
fn object_description_sub_mode_0_maps_its_bit() -> Result<()> {
let data = payload_with_element(4, |b| {
b.push(3, 0); b.push(1, 1); b.push(2, 0); b.push(1, 1); });
let element = ObjectAudioMetadataPayload::read(&data)?
.object_description_element
.expect("an object description element");
assert!(element.b_present);
assert_eq!(element.object_dialog_indication, vec![vec![1]]);
Ok(())
}
#[test]
fn object_description_sub_mode_1_maps_and_consumes_the_extra() -> Result<()> {
let data = payload_with_element(4, |b| {
b.push(3, 1); b.push(1, 1); b.push(2, 1); b.push(1, 1); b.push(2, 2); b.push(5, 0x15); b.push(2, 3); });
let element = ObjectAudioMetadataPayload::read(&data)?
.object_description_element
.expect("an object description element");
assert_eq!(element.object_dialog_indication, vec![vec![3, 0]]);
Ok(())
}
#[test]
fn object_description_absent_when_not_present() -> Result<()> {
let data = payload_with_element(4, |b| {
b.push(3, 0);
b.push(1, 0); });
let element = ObjectAudioMetadataPayload::read(&data)?
.object_description_element
.expect("an object description element");
assert!(!element.b_present);
assert!(element.object_dialog_indication.is_empty());
Ok(())
}
#[test]
fn trim_lut_values() {
use crate::structs::oamd::TRIM_LUT;
assert_eq!(TRIM_LUT[14], -15.0);
for i in 7..14 {
assert_eq!(TRIM_LUT[i] - TRIM_LUT[i + 1], 1.5);
}
}
}