use oxideav_core::bits::BitReader;
use oxideav_core::{Error, Result};
use crate::drc_huffman::drc_huff_decode_diff;
use crate::toc::variable_bits;
pub const DRC_MAX_DECODER_MODES: usize = 8;
#[derive(Debug, Clone, Default, PartialEq)]
pub struct DrcCompressionCurve {
pub drc_lev_nullband_low: u8,
pub drc_lev_nullband_high: u8,
pub drc_gain_max_boost: u8,
pub drc_lev_max_boost: Option<u8>,
pub drc_nr_boost_sections: Option<u8>,
pub drc_gain_section_boost: Option<u8>,
pub drc_lev_section_boost: Option<u8>,
pub drc_gain_max_cut: u8,
pub drc_lev_max_cut: Option<u8>,
pub drc_nr_cut_sections: Option<u8>,
pub drc_gain_section_cut: Option<u8>,
pub drc_lev_section_cut: Option<u8>,
pub drc_tc_default_flag: bool,
pub time_constants: Option<DrcTimeConstants>,
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct DrcTimeConstants {
pub drc_tc_attack: u8,
pub drc_tc_release: u8,
pub drc_tc_attack_fast: u8,
pub drc_tc_release_fast: u8,
pub drc_adaptive_smoothing_flag: bool,
pub drc_attack_threshold: Option<u8>,
pub drc_release_threshold: Option<u8>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct DrcDecoderMode {
pub drc_decoder_mode_id: u8,
pub drc_output_level_from: Option<u8>,
pub drc_output_level_to: Option<u8>,
pub drc_repeat_profile_flag: bool,
pub drc_repeat_id: Option<u8>,
pub drc_default_profile_flag: Option<bool>,
pub drc_compression_curve_flag: bool,
pub compression_curve: Option<DrcCompressionCurve>,
pub drc_gains_config: Option<u8>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct DrcConfig {
pub drc_decoder_nr_modes: u8,
pub drc_eac3_profile: u8,
pub modes: Vec<DrcDecoderMode>,
}
impl DrcConfig {
pub fn nr_modes(&self) -> usize {
self.drc_decoder_nr_modes as usize + 1
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct DrcGains {
pub mode_id: u8,
pub nr_drc_channels: u8,
pub nr_drc_subframes: u8,
pub nr_drc_bands: u8,
pub drc_gain_val: u8,
pub drc_gain: Vec<u8>,
}
impl DrcGains {
pub fn idx(&self, ch: usize, sf: usize, band: usize) -> usize {
let bands = self.nr_drc_bands as usize;
let sfs = self.nr_drc_subframes as usize;
ch * bands * sfs + band * sfs + sf
}
pub fn gain_db(&self, ch: usize, sf: usize, band: usize) -> i32 {
self.drc_gain[self.idx(ch, sf, band)] as i32 - 64
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct DrcData {
pub gainsets: Vec<DrcGains>,
pub curve_present: bool,
pub drc_reset_flag: bool,
pub drc_reserved: u8,
}
#[derive(Debug, Clone, PartialEq)]
pub struct DrcFrame {
pub b_drc_present: bool,
pub config: Option<DrcConfig>,
pub data: Option<DrcData>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DrcChannelInfo {
pub nr_drc_channels: u8,
pub nr_drc_subframes: u8,
}
impl DrcChannelInfo {
pub fn new(nr_drc_channels: u8, nr_drc_subframes: u8) -> Self {
Self {
nr_drc_channels,
nr_drc_subframes,
}
}
}
pub fn nr_drc_subframes(frame_length: u32) -> Option<u8> {
match frame_length {
384 => Some(1),
512 => Some(2),
768 | 960 => Some(3),
1024 => Some(4),
1536 | 1920 => Some(6),
2048 => Some(8),
_ => None,
}
}
pub fn nr_drc_channels(channel_mode: u32) -> u8 {
match channel_mode {
0 | 1 => 1, _ => 3, }
}
pub fn nr_drc_bands(drc_gains_config: u8) -> u8 {
match drc_gains_config {
0 | 1 => 1,
2 => 2,
3 => 4,
_ => 1, }
}
pub fn parse_drc_frame(
br: &mut BitReader<'_>,
b_iframe: bool,
chan_info: DrcChannelInfo,
prev_config: Option<&DrcConfig>,
) -> Result<DrcFrame> {
let b_drc_present = br.read_bit()?;
if !b_drc_present {
return Ok(DrcFrame {
b_drc_present: false,
config: None,
data: None,
});
}
let config = if b_iframe {
Some(parse_drc_config(br)?)
} else {
None
};
let active = config.as_ref().or(prev_config).ok_or_else(|| {
Error::invalid(
"ac4: drc_frame on non-I-frame with no prior drc_config — \
cannot decode drc_data() without per-mode flags",
)
})?;
let data = parse_drc_data(br, active, chan_info)?;
Ok(DrcFrame {
b_drc_present: true,
config,
data: Some(data),
})
}
pub fn parse_drc_config(br: &mut BitReader<'_>) -> Result<DrcConfig> {
let drc_decoder_nr_modes = br.read_u32(3)? as u8;
let mode_count = drc_decoder_nr_modes as usize + 1;
let mut modes = Vec::with_capacity(mode_count);
for _ in 0..mode_count {
modes.push(parse_drc_decoder_mode_config(br, &modes)?);
}
let drc_eac3_profile = br.read_u32(3)? as u8;
Ok(DrcConfig {
drc_decoder_nr_modes,
drc_eac3_profile,
modes,
})
}
fn parse_drc_decoder_mode_config(
br: &mut BitReader<'_>,
prior_modes: &[DrcDecoderMode],
) -> Result<DrcDecoderMode> {
let drc_decoder_mode_id = br.read_u32(3)? as u8;
let (drc_output_level_from, drc_output_level_to) = if drc_decoder_mode_id > 3 {
(Some(br.read_u32(5)? as u8), Some(br.read_u32(5)? as u8))
} else {
(None, None)
};
let drc_repeat_profile_flag = br.read_bit()?;
if drc_repeat_profile_flag {
let drc_repeat_id = br.read_u32(3)? as u8;
let referenced = prior_modes
.iter()
.find(|m| m.drc_decoder_mode_id == drc_repeat_id)
.ok_or_else(|| Error::invalid("ac4: drc_repeat_id refers to undeclared mode"))?;
Ok(DrcDecoderMode {
drc_decoder_mode_id,
drc_output_level_from,
drc_output_level_to,
drc_repeat_profile_flag: true,
drc_repeat_id: Some(drc_repeat_id),
drc_default_profile_flag: None,
drc_compression_curve_flag: referenced.drc_compression_curve_flag,
compression_curve: None, drc_gains_config: referenced.drc_gains_config,
})
} else {
let drc_default_profile_flag = br.read_bit()?;
if drc_default_profile_flag {
Ok(DrcDecoderMode {
drc_decoder_mode_id,
drc_output_level_from,
drc_output_level_to,
drc_repeat_profile_flag: false,
drc_repeat_id: None,
drc_default_profile_flag: Some(true),
drc_compression_curve_flag: true,
compression_curve: None,
drc_gains_config: None,
})
} else {
let drc_compression_curve_flag = br.read_bit()?;
if drc_compression_curve_flag {
let curve = parse_drc_compression_curve(br)?;
Ok(DrcDecoderMode {
drc_decoder_mode_id,
drc_output_level_from,
drc_output_level_to,
drc_repeat_profile_flag: false,
drc_repeat_id: None,
drc_default_profile_flag: Some(false),
drc_compression_curve_flag: true,
compression_curve: Some(curve),
drc_gains_config: None,
})
} else {
let drc_gains_config = br.read_u32(2)? as u8;
Ok(DrcDecoderMode {
drc_decoder_mode_id,
drc_output_level_from,
drc_output_level_to,
drc_repeat_profile_flag: false,
drc_repeat_id: None,
drc_default_profile_flag: Some(false),
drc_compression_curve_flag: false,
compression_curve: None,
drc_gains_config: Some(drc_gains_config),
})
}
}
}
}
pub fn parse_drc_compression_curve(br: &mut BitReader<'_>) -> Result<DrcCompressionCurve> {
let drc_lev_nullband_low = br.read_u32(4)? as u8;
let drc_lev_nullband_high = br.read_u32(4)? as u8;
let drc_gain_max_boost = br.read_u32(4)? as u8;
let (drc_lev_max_boost, drc_nr_boost_sections, drc_gain_section_boost, drc_lev_section_boost) =
if drc_gain_max_boost > 0 {
let lmb = br.read_u32(5)? as u8;
let nbs = br.read_u32(1)? as u8;
if nbs > 0 {
(
Some(lmb),
Some(nbs),
Some(br.read_u32(4)? as u8),
Some(br.read_u32(5)? as u8),
)
} else {
(Some(lmb), Some(nbs), None, None)
}
} else {
(None, None, None, None)
};
let drc_gain_max_cut = br.read_u32(5)? as u8;
let (drc_lev_max_cut, drc_nr_cut_sections, drc_gain_section_cut, drc_lev_section_cut) =
if drc_gain_max_cut > 0 {
let lmc = br.read_u32(6)? as u8;
let ncs = br.read_u32(1)? as u8;
if ncs > 0 {
(
Some(lmc),
Some(ncs),
Some(br.read_u32(5)? as u8),
Some(br.read_u32(5)? as u8),
)
} else {
(Some(lmc), Some(ncs), None, None)
}
} else {
(None, None, None, None)
};
let drc_tc_default_flag = br.read_bit()?;
let time_constants = if !drc_tc_default_flag {
let drc_tc_attack = br.read_u32(8)? as u8;
let drc_tc_release = br.read_u32(8)? as u8;
let drc_tc_attack_fast = br.read_u32(8)? as u8;
let drc_tc_release_fast = br.read_u32(8)? as u8;
let drc_adaptive_smoothing_flag = br.read_bit()?;
let (drc_attack_threshold, drc_release_threshold) = if drc_adaptive_smoothing_flag {
(Some(br.read_u32(5)? as u8), Some(br.read_u32(5)? as u8))
} else {
(None, None)
};
Some(DrcTimeConstants {
drc_tc_attack,
drc_tc_release,
drc_tc_attack_fast,
drc_tc_release_fast,
drc_adaptive_smoothing_flag,
drc_attack_threshold,
drc_release_threshold,
})
} else {
None
};
Ok(DrcCompressionCurve {
drc_lev_nullband_low,
drc_lev_nullband_high,
drc_gain_max_boost,
drc_lev_max_boost,
drc_nr_boost_sections,
drc_gain_section_boost,
drc_lev_section_boost,
drc_gain_max_cut,
drc_lev_max_cut,
drc_nr_cut_sections,
drc_gain_section_cut,
drc_lev_section_cut,
drc_tc_default_flag,
time_constants,
})
}
pub fn parse_drc_data(
br: &mut BitReader<'_>,
config: &DrcConfig,
chan_info: DrcChannelInfo,
) -> Result<DrcData> {
let mut curve_present = false;
let mut gainsets = Vec::new();
for mode in &config.modes {
let mode_id = mode.drc_decoder_mode_id;
if !mode.drc_compression_curve_flag {
let mut drc_gainset_size = br.read_u32(6)?;
let b_more_bits = br.read_bit()?;
if b_more_bits {
drc_gainset_size += variable_bits(br, 2)? << 6;
}
let drc_version = br.read_u32(2)?;
let bit_pos_before = br.bit_position();
let gainset = if drc_version <= 1 {
let gains =
parse_drc_gains(br, mode_id, mode.drc_gains_config.unwrap_or(0), chan_info)?;
Some(gains)
} else {
None
};
let used_bits = (br.bit_position() - bit_pos_before) as u32;
if drc_version >= 1 {
let bits_left = drc_gainset_size
.checked_sub(2)
.and_then(|v| v.checked_sub(used_bits))
.ok_or_else(|| {
Error::invalid(
"ac4: drc_gainset_size accounting underflow \
(drc_gains used more bits than declared)",
)
})?;
if bits_left > 0 {
br.skip(bits_left)?;
}
}
if let Some(g) = gainset {
gainsets.push(g);
}
} else {
curve_present = true;
}
}
let (drc_reset_flag, drc_reserved) = if curve_present {
let reset = br.read_bit()?;
let reserved = br.read_u32(2)? as u8;
(reset, reserved)
} else {
(false, 0)
};
Ok(DrcData {
gainsets,
curve_present,
drc_reset_flag,
drc_reserved,
})
}
pub fn parse_drc_gains(
br: &mut BitReader<'_>,
mode_id: u8,
drc_gains_config_value: u8,
chan_info: DrcChannelInfo,
) -> Result<DrcGains> {
let drc_gain_val = br.read_u32(7)? as u8;
let nr_drc_bands = nr_drc_bands(drc_gains_config_value);
if drc_gains_config_value == 0 {
return Ok(DrcGains {
mode_id,
nr_drc_channels: 1,
nr_drc_subframes: 1,
nr_drc_bands: 1,
drc_gain_val,
drc_gain: vec![drc_gain_val],
});
}
let nr_ch = chan_info.nr_drc_channels as usize;
let nr_sf = chan_info.nr_drc_subframes as usize;
let nr_bd = nr_drc_bands as usize;
if nr_ch == 0 || nr_sf == 0 || nr_bd == 0 {
return Err(Error::invalid(
"ac4: drc_gains() invalid (ch, sf, band) dimensions",
));
}
let mut gains = vec![0u8; nr_ch * nr_bd * nr_sf];
let mut ref_gain: i32 = drc_gain_val as i32;
let mut first = true;
let idx =
|ch: usize, sf: usize, band: usize| -> usize { ch * nr_bd * nr_sf + band * nr_sf + sf };
for ch in 0..nr_ch {
for band in 0..nr_bd {
for sf in 0..nr_sf {
if first {
gains[idx(ch, sf, band)] = drc_gain_val;
first = false;
} else {
let diff = drc_huff_decode_diff(br)?;
let g = ref_gain + diff;
if !(0..=127).contains(&g) {
return Err(Error::invalid(
"ac4: DRC gain out of 7-bit range after diff",
));
}
gains[idx(ch, sf, band)] = g as u8;
}
ref_gain = gains[idx(ch, sf, band)] as i32;
}
ref_gain = gains[idx(ch, 0, band)] as i32;
}
ref_gain = gains[idx(ch, 0, 0)] as i32;
}
Ok(DrcGains {
mode_id,
nr_drc_channels: chan_info.nr_drc_channels,
nr_drc_subframes: chan_info.nr_drc_subframes,
nr_drc_bands,
drc_gain_val,
drc_gain: gains,
})
}
#[inline]
pub fn drc_raw_to_linear(drc_gain_raw: u8) -> f32 {
let db = drc_gain_raw as i32 - 64;
libm_pow2(db as f32 / 6.0)
}
#[inline]
pub fn dialnorm_correction_linear(lout_db: i32, lin_db: i32) -> f32 {
libm_pow2((lout_db - lin_db) as f32 / 6.0)
}
#[inline]
fn libm_pow2(x: f32) -> f32 {
const LN2: f32 = std::f32::consts::LN_2;
(x * LN2).exp()
}
#[derive(Debug, Clone)]
pub struct DrcChannelMap {
pub chg_for_pcm_channel: Vec<u8>,
}
impl DrcChannelMap {
pub fn wideband_single_group(nr_pcm_channels: usize) -> Self {
Self {
chg_for_pcm_channel: vec![0; nr_pcm_channels],
}
}
pub fn five_one_default(includes_lfe: bool) -> Self {
if includes_lfe {
Self {
chg_for_pcm_channel: vec![0, 0, 1, 0, 2, 2],
}
} else {
Self {
chg_for_pcm_channel: vec![0, 0, 1, 2, 2],
}
}
}
}
pub fn apply_drc_gains_to_pcm(
pcm_planar: &mut [Vec<f32>],
gains: &DrcGains,
map: &DrcChannelMap,
dialnorm_correction: f32,
) -> Result<usize> {
if pcm_planar.len() != map.chg_for_pcm_channel.len() {
return Err(Error::invalid(
"ac4: apply_drc_gains_to_pcm: pcm channel count mismatches DrcChannelMap",
));
}
let nr_chg = gains.nr_drc_channels as usize;
let nr_sf = gains.nr_drc_subframes as usize;
let nr_bands = gains.nr_drc_bands as usize;
if nr_sf == 0 || nr_chg == 0 || nr_bands == 0 {
return Ok(0);
}
for &chg in &map.chg_for_pcm_channel {
if chg as usize >= nr_chg {
return Err(Error::invalid(
"ac4: DrcChannelMap entry out of bounds for DrcGains.nr_drc_channels",
));
}
}
let mut gain_per_chg_sf = vec![0f32; nr_chg * nr_sf];
for chg in 0..nr_chg {
for sf in 0..nr_sf {
let mut acc = 0f32;
for band in 0..nr_bands {
let raw = gains.drc_gain[gains.idx(chg, sf, band)];
acc += drc_raw_to_linear(raw);
}
let avg = acc / nr_bands as f32;
gain_per_chg_sf[chg * nr_sf + sf] = avg * dialnorm_correction;
}
}
let mut total_samples = 0usize;
for (pcm_ch, ch_buf) in pcm_planar.iter_mut().enumerate() {
let chg = map.chg_for_pcm_channel[pcm_ch] as usize;
let n_total = ch_buf.len();
if n_total == 0 {
continue;
}
let sf_len = n_total / nr_sf;
if sf_len == 0 {
let g = gain_per_chg_sf[chg * nr_sf];
for s in ch_buf.iter_mut() {
*s *= g;
}
total_samples += n_total;
continue;
}
for sf in 0..nr_sf {
let g = gain_per_chg_sf[chg * nr_sf + sf];
let start = sf * sf_len;
let end = if sf + 1 == nr_sf {
n_total
} else {
start + sf_len
};
for s in ch_buf[start..end].iter_mut() {
*s *= g;
}
total_samples += end - start;
}
}
Ok(total_samples)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::drc_huffman::{DRC_HCB_CW, DRC_HCB_LEN};
use oxideav_core::bits::BitWriter;
fn write_zero_diff(bw: &mut BitWriter) {
bw.write_u32(DRC_HCB_CW[127], DRC_HCB_LEN[127] as u32);
}
fn write_plus_one_diff(bw: &mut BitWriter) {
bw.write_u32(DRC_HCB_CW[128], DRC_HCB_LEN[128] as u32);
}
fn write_minus_one_diff(bw: &mut BitWriter) {
bw.write_u32(DRC_HCB_CW[126], DRC_HCB_LEN[126] as u32);
}
#[test]
fn nr_drc_subframes_table_169() {
assert_eq!(nr_drc_subframes(384), Some(1));
assert_eq!(nr_drc_subframes(512), Some(2));
assert_eq!(nr_drc_subframes(768), Some(3));
assert_eq!(nr_drc_subframes(960), Some(3));
assert_eq!(nr_drc_subframes(1024), Some(4));
assert_eq!(nr_drc_subframes(1536), Some(6));
assert_eq!(nr_drc_subframes(1920), Some(6));
assert_eq!(nr_drc_subframes(2048), Some(8));
assert_eq!(nr_drc_subframes(999), None);
}
#[test]
fn nr_drc_bands_table_163() {
assert_eq!(nr_drc_bands(0), 1);
assert_eq!(nr_drc_bands(1), 1);
assert_eq!(nr_drc_bands(2), 2);
assert_eq!(nr_drc_bands(3), 4);
}
#[test]
fn parse_drc_frame_absent() {
let mut bw = BitWriter::new();
bw.write_bit(false);
bw.align_to_byte();
let bytes = bw.finish();
let mut br = BitReader::new(&bytes);
let info = DrcChannelInfo::new(1, 1);
let frame = parse_drc_frame(&mut br, true, info, None).unwrap();
assert!(!frame.b_drc_present);
assert!(frame.config.is_none());
assert!(frame.data.is_none());
}
#[test]
fn parse_drc_frame_one_mode_wideband() {
let mut bw = BitWriter::new();
bw.write_bit(true); bw.write_u32(0, 3); bw.write_u32(2, 3); bw.write_bit(false); bw.write_bit(true); bw.write_u32(1, 3); bw.write_bit(false); bw.write_u32(0, 2); bw.align_to_byte();
let bytes = bw.finish();
let mut br = BitReader::new(&bytes);
let info = DrcChannelInfo::new(1, 1);
let frame = parse_drc_frame(&mut br, true, info, None).unwrap();
assert!(frame.b_drc_present);
let cfg = frame.config.as_ref().expect("config present");
assert_eq!(cfg.nr_modes(), 1);
assert_eq!(cfg.modes[0].drc_decoder_mode_id, 2);
assert_eq!(
cfg.modes[0].drc_default_profile_flag,
Some(true),
"default profile"
);
assert!(cfg.modes[0].drc_compression_curve_flag);
assert_eq!(cfg.drc_eac3_profile, 1);
let data = frame.data.as_ref().expect("data present");
assert!(data.curve_present);
assert!(data.gainsets.is_empty());
assert!(!data.drc_reset_flag);
}
#[test]
fn parse_drc_frame_with_gainset_wideband() {
let drc_gain_val: u8 = 64;
let drc_gainset_size: u32 = 2 + 7 + 3 ;
let mut bw = BitWriter::new();
bw.write_bit(true); bw.write_u32(0, 3); bw.write_u32(0, 3); bw.write_bit(false); bw.write_bit(false); bw.write_bit(false); bw.write_u32(1, 2); bw.write_u32(0, 3); bw.write_u32(drc_gainset_size, 6); bw.write_bit(false); bw.write_u32(0, 2); bw.write_u32(drc_gain_val as u32, 7);
write_plus_one_diff(&mut bw);
bw.align_to_byte();
let bytes = bw.finish();
let mut br = BitReader::new(&bytes);
let info = DrcChannelInfo::new(1, 2);
let frame = parse_drc_frame(&mut br, true, info, None).unwrap();
let data = frame.data.as_ref().expect("data");
assert!(!data.curve_present);
assert_eq!(data.gainsets.len(), 1);
let g = &data.gainsets[0];
assert_eq!(g.nr_drc_channels, 1);
assert_eq!(g.nr_drc_subframes, 2);
assert_eq!(g.nr_drc_bands, 1);
assert_eq!(g.drc_gain_val, 64);
assert_eq!(g.drc_gain.len(), 2);
assert_eq!(g.gain_db(0, 0, 0), 0);
assert_eq!(g.gain_db(0, 1, 0), 1);
}
#[test]
fn parse_drc_frame_gainset_2band_2ch_2sf() {
let drc_gain_val: u8 = 64;
let total_diff_bits = 3 + 3 + 2 + 3 + 3 + 2 + 3;
let mut bw = BitWriter::new();
bw.write_bit(true); bw.write_u32(0, 3); bw.write_u32(0, 3); bw.write_bit(false); bw.write_bit(false); bw.write_bit(false); bw.write_u32(2, 2); bw.write_u32(0, 3);
let drc_gainset_size: u32 = 2 + 7 + total_diff_bits;
bw.write_u32(drc_gainset_size, 6);
bw.write_bit(false); bw.write_u32(0, 2); bw.write_u32(drc_gain_val as u32, 7);
write_plus_one_diff(&mut bw);
write_minus_one_diff(&mut bw);
write_zero_diff(&mut bw);
write_plus_one_diff(&mut bw);
write_minus_one_diff(&mut bw);
write_zero_diff(&mut bw);
write_plus_one_diff(&mut bw);
bw.align_to_byte();
let bytes = bw.finish();
let mut br = BitReader::new(&bytes);
let info = DrcChannelInfo::new(2, 2);
let frame = parse_drc_frame(&mut br, true, info, None).unwrap();
let g = &frame.data.as_ref().unwrap().gainsets[0];
assert_eq!(g.nr_drc_bands, 2);
assert_eq!(g.drc_gain.len(), 8);
assert_eq!(g.gain_db(0, 0, 0), 0); assert_eq!(g.gain_db(0, 1, 0), 1); assert_eq!(g.gain_db(0, 0, 1), -1); assert_eq!(g.gain_db(0, 1, 1), -1); assert_eq!(g.gain_db(1, 0, 0), 1); assert_eq!(g.gain_db(1, 1, 0), 0); assert_eq!(g.gain_db(1, 0, 1), 1); assert_eq!(g.gain_db(1, 1, 1), 2); }
#[test]
fn parse_drc_compression_curve_minimal() {
let mut bw = BitWriter::new();
bw.write_u32(0, 4); bw.write_u32(0, 4); bw.write_u32(0, 4); bw.write_u32(0, 5); bw.write_bit(true); bw.align_to_byte();
let bytes = bw.finish();
let mut br = BitReader::new(&bytes);
let curve = parse_drc_compression_curve(&mut br).unwrap();
assert_eq!(curve.drc_lev_nullband_low, 0);
assert_eq!(curve.drc_gain_max_boost, 0);
assert!(curve.drc_lev_max_boost.is_none());
assert!(curve.time_constants.is_none());
assert!(curve.drc_tc_default_flag);
}
#[test]
fn parse_drc_compression_curve_full_with_boost_and_cut() {
let mut bw = BitWriter::new();
bw.write_u32(3, 4); bw.write_u32(5, 4); bw.write_u32(7, 4); bw.write_u32(15, 5); bw.write_u32(1, 1); bw.write_u32(9, 4); bw.write_u32(20, 5); bw.write_u32(11, 5); bw.write_u32(33, 6); bw.write_u32(1, 1); bw.write_u32(13, 5); bw.write_u32(17, 5); bw.write_bit(false); bw.write_u32(20, 8); bw.write_u32(75, 8); bw.write_u32(2, 8); bw.write_u32(50, 8); bw.write_bit(true); bw.write_u32(15, 5); bw.write_u32(20, 5); bw.align_to_byte();
let bytes = bw.finish();
let mut br = BitReader::new(&bytes);
let curve = parse_drc_compression_curve(&mut br).unwrap();
assert_eq!(curve.drc_lev_nullband_low, 3);
assert_eq!(curve.drc_gain_max_boost, 7);
assert_eq!(curve.drc_lev_max_boost, Some(15));
assert_eq!(curve.drc_nr_boost_sections, Some(1));
assert_eq!(curve.drc_gain_section_boost, Some(9));
assert_eq!(curve.drc_lev_section_boost, Some(20));
assert_eq!(curve.drc_gain_max_cut, 11);
assert_eq!(curve.drc_lev_max_cut, Some(33));
let tc = curve.time_constants.as_ref().unwrap();
assert_eq!(tc.drc_tc_attack, 20);
assert!(tc.drc_adaptive_smoothing_flag);
assert_eq!(tc.drc_attack_threshold, Some(15));
assert_eq!(tc.drc_release_threshold, Some(20));
}
#[test]
fn parse_drc_decoder_mode_repeat_profile() {
let mut bw = BitWriter::new();
bw.write_bit(true); bw.write_u32(1, 3);
bw.write_u32(0, 3); bw.write_bit(false); bw.write_bit(true);
bw.write_u32(1, 3); bw.write_bit(true); bw.write_u32(0, 3);
bw.write_u32(0, 3); bw.write_bit(true); bw.write_u32(2, 2); bw.align_to_byte();
let bytes = bw.finish();
let mut br = BitReader::new(&bytes);
let info = DrcChannelInfo::new(1, 1);
let frame = parse_drc_frame(&mut br, true, info, None).unwrap();
let cfg = frame.config.unwrap();
assert_eq!(cfg.nr_modes(), 2);
assert!(cfg.modes[0].drc_compression_curve_flag);
assert!(cfg.modes[1].drc_repeat_profile_flag);
assert_eq!(cfg.modes[1].drc_repeat_id, Some(0));
assert!(cfg.modes[1].drc_compression_curve_flag); let data = frame.data.unwrap();
assert!(data.curve_present);
assert!(data.drc_reset_flag);
assert_eq!(data.drc_reserved, 2);
}
#[test]
fn parse_drc_frame_non_iframe_uses_prev_config() {
let drc_gain_val: u8 = 50;
let drc_gainset_size: u32 = 2 + 7;
let mut bw = BitWriter::new();
bw.write_bit(true); bw.write_u32(drc_gainset_size, 6); bw.write_bit(false); bw.write_u32(0, 2); bw.write_u32(drc_gain_val as u32, 7); bw.align_to_byte();
let bytes = bw.finish();
let prev = DrcConfig {
drc_decoder_nr_modes: 0,
drc_eac3_profile: 0,
modes: vec![DrcDecoderMode {
drc_decoder_mode_id: 0,
drc_output_level_from: None,
drc_output_level_to: None,
drc_repeat_profile_flag: false,
drc_repeat_id: None,
drc_default_profile_flag: Some(false),
drc_compression_curve_flag: false,
compression_curve: None,
drc_gains_config: Some(0),
}],
};
let mut br = BitReader::new(&bytes);
let info = DrcChannelInfo::new(1, 1);
let frame = parse_drc_frame(&mut br, false, info, Some(&prev)).unwrap();
assert!(frame.config.is_none(), "no config on non-I-frame");
let data = frame.data.unwrap();
assert_eq!(data.gainsets.len(), 1);
let g = &data.gainsets[0];
assert_eq!(g.drc_gain_val, 50);
assert_eq!(g.gain_db(0, 0, 0), -14);
}
#[test]
fn parse_drc_frame_non_iframe_without_prev_config_errors() {
let mut bw = BitWriter::new();
bw.write_bit(true);
bw.align_to_byte();
let bytes = bw.finish();
let mut br = BitReader::new(&bytes);
let info = DrcChannelInfo::new(1, 1);
assert!(parse_drc_frame(&mut br, false, info, None).is_err());
}
#[test]
fn drc_raw_to_linear_unity_at_64() {
let g = drc_raw_to_linear(64);
assert!((g - 1.0).abs() < 1e-6, "expected ~1.0, got {g}");
}
#[test]
fn drc_raw_to_linear_plus_six_db_doubles() {
let g_pos = drc_raw_to_linear(64 + 6);
assert!((g_pos - 2.0).abs() < 1e-5, "+6 dB: got {g_pos}");
let g_neg = drc_raw_to_linear(64 - 6);
assert!((g_neg - 0.5).abs() < 1e-5, "-6 dB: got {g_neg}");
}
#[test]
fn dialnorm_correction_zero_is_unity() {
let c = dialnorm_correction_linear(-31, -31);
assert!((c - 1.0).abs() < 1e-6, "got {c}");
}
#[test]
fn drc_channel_map_five_one_default_with_lfe() {
let map = DrcChannelMap::five_one_default(true);
assert_eq!(map.chg_for_pcm_channel, vec![0, 0, 1, 0, 2, 2]);
}
#[test]
fn drc_channel_map_wideband_single_group() {
let map = DrcChannelMap::wideband_single_group(2);
assert_eq!(map.chg_for_pcm_channel, vec![0, 0]);
}
#[test]
fn apply_drc_gains_to_pcm_uniform_subframes_apply_per_subframe_gain() {
let gains = DrcGains {
mode_id: 0,
nr_drc_channels: 1,
nr_drc_subframes: 4,
nr_drc_bands: 1,
drc_gain_val: 64,
drc_gain: vec![64, 70, 76, 82], };
let map = DrcChannelMap::wideband_single_group(1);
let mut pcm = vec![vec![1.0f32; 16]];
let n = apply_drc_gains_to_pcm(&mut pcm, &gains, &map, 1.0).unwrap();
assert_eq!(n, 16);
let expected: Vec<f32> = [1.0, 2.0, 4.0, 8.0]
.iter()
.flat_map(|&g| std::iter::repeat(g).take(4))
.collect();
for (got, want) in pcm[0].iter().zip(expected.iter()) {
assert!((got - want).abs() < 1e-4, "got {got}, want {want}");
}
}
#[test]
fn apply_drc_gains_to_pcm_dialnorm_correction_is_multiplicative() {
let gains = DrcGains {
mode_id: 0,
nr_drc_channels: 1,
nr_drc_subframes: 1,
nr_drc_bands: 1,
drc_gain_val: 64,
drc_gain: vec![64],
};
let map = DrcChannelMap::wideband_single_group(1);
let mut pcm = vec![vec![1.0f32; 4]];
let dialnorm = dialnorm_correction_linear(-37, -31); apply_drc_gains_to_pcm(&mut pcm, &gains, &map, dialnorm).unwrap();
for s in &pcm[0] {
assert!((s - 0.5).abs() < 1e-5, "got {s}");
}
}
#[test]
fn apply_drc_gains_to_pcm_5_1_routes_to_correct_channel_group() {
let gains = DrcGains {
mode_id: 0,
nr_drc_channels: 3,
nr_drc_subframes: 1,
nr_drc_bands: 1,
drc_gain_val: 70,
drc_gain: vec![70, 64, 58],
};
let map = DrcChannelMap::five_one_default(true); let mut pcm: Vec<Vec<f32>> = (0..6).map(|_| vec![1.0f32; 4]).collect();
apply_drc_gains_to_pcm(&mut pcm, &gains, &map, 1.0).unwrap();
let want = [2.0, 2.0, 1.0, 2.0, 0.5, 0.5];
for (i, expected) in want.iter().enumerate() {
for s in &pcm[i] {
assert!(
(s - expected).abs() < 1e-4,
"ch {i}: got {s}, want {expected}"
);
}
}
}
#[test]
fn apply_drc_gains_to_pcm_invalid_chg_rejected() {
let gains = DrcGains {
mode_id: 0,
nr_drc_channels: 1,
nr_drc_subframes: 1,
nr_drc_bands: 1,
drc_gain_val: 64,
drc_gain: vec![64],
};
let map = DrcChannelMap {
chg_for_pcm_channel: vec![5], };
let mut pcm = vec![vec![1.0f32; 4]];
let err = apply_drc_gains_to_pcm(&mut pcm, &gains, &map, 1.0).unwrap_err();
assert!(err.to_string().contains("out of bounds"));
}
#[test]
fn apply_drc_gains_to_pcm_pcm_channel_count_mismatch_rejected() {
let gains = DrcGains {
mode_id: 0,
nr_drc_channels: 1,
nr_drc_subframes: 1,
nr_drc_bands: 1,
drc_gain_val: 64,
drc_gain: vec![64],
};
let map = DrcChannelMap::wideband_single_group(2);
let mut pcm: Vec<Vec<f32>> = vec![vec![1.0f32; 4]]; let err = apply_drc_gains_to_pcm(&mut pcm, &gains, &map, 1.0).unwrap_err();
assert!(err.to_string().contains("mismatches"));
}
}