use oxideav_core::bits::BitReader;
use oxideav_core::{Error, Result};
use crate::ssf_ac::{
decode_envelope_indices, decode_predictor_gain, AcError, AcState, SsfRandGenState,
};
use crate::ssf_tables::{AC_COEFF_MAX_INDEX, ENVELOPE_CDF_LUT, STEP_SIZES_Q4_15};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StrideFlag {
LongStride,
ShortStride,
}
impl StrideFlag {
pub fn from_bit(v: u32) -> Self {
if v == 0 {
Self::LongStride
} else {
Self::ShortStride
}
}
pub fn num_blocks(self) -> usize {
match self {
Self::LongStride => 1,
Self::ShortStride => 4,
}
}
}
pub const SSF_BANDWIDTH_BLOCK_LENGTHS: [u32; 8] = [192, 240, 256, 384, 512, 768, 960, 1024];
pub static SSF_BANDWIDTHS: [[u8; 8]; 19] = [
[2, 3, 3, 5, 6, 9, 11, 12],
[2, 3, 3, 5, 6, 9, 11, 12],
[2, 3, 3, 5, 6, 9, 11, 12],
[2, 3, 3, 5, 6, 9, 11, 12],
[2, 3, 3, 5, 6, 9, 11, 12],
[2, 3, 3, 5, 6, 9, 11, 12],
[2, 3, 3, 5, 6, 9, 11, 12],
[2, 3, 3, 5, 6, 9, 11, 12],
[2, 3, 3, 5, 6, 9, 11, 12],
[2, 3, 3, 5, 6, 9, 11, 12],
[3, 4, 4, 6, 8, 12, 15, 16],
[3, 4, 4, 6, 8, 12, 15, 16],
[3, 4, 4, 6, 8, 12, 15, 16],
[4, 5, 5, 8, 10, 15, 19, 20],
[4, 5, 5, 8, 10, 15, 19, 20],
[5, 6, 6, 9, 12, 18, 23, 24],
[5, 7, 7, 11, 14, 21, 26, 28],
[5, 7, 7, 11, 14, 21, 26, 28],
[6, 8, 8, 12, 16, 24, 30, 32],
];
pub const MAX_NUM_BANDS: usize = 19;
pub fn ssf_bandwidth_column(n_mdct: u32) -> Option<usize> {
SSF_BANDWIDTH_BLOCK_LENGTHS
.iter()
.position(|&v| v == n_mdct)
}
pub fn ssf_band_widths_for(n_mdct: u32) -> Option<[u8; MAX_NUM_BANDS]> {
let col = ssf_bandwidth_column(n_mdct)?;
let mut out = [0u8; MAX_NUM_BANDS];
for (band, slot) in out.iter_mut().enumerate() {
*slot = SSF_BANDWIDTHS[band][col];
}
Some(out)
}
#[derive(Debug, Clone)]
pub struct SsfBinLayout {
pub start_bin: Vec<u32>,
pub end_bin: Vec<u32>,
pub num_bins: u32,
}
impl SsfBinLayout {
pub fn build(num_bands: usize, n_mdct: u32) -> Option<Self> {
if num_bands == 0 || num_bands > MAX_NUM_BANDS {
return None;
}
let widths = ssf_band_widths_for(n_mdct)?;
let mut start_bin = vec![0u32; MAX_NUM_BANDS];
let mut end_bin = vec![0u32; MAX_NUM_BANDS];
end_bin[0] = u32::from(widths[0]).saturating_sub(1);
for i in 1..MAX_NUM_BANDS {
start_bin[i] = start_bin[i - 1] + u32::from(widths[i - 1]);
end_bin[i] = start_bin[i] + u32::from(widths[i]).saturating_sub(1);
}
let num_bins = end_bin[num_bands - 1] + 1;
Some(SsfBinLayout {
start_bin,
end_bin,
num_bins,
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SsfFrameConfig {
pub frame_length: u32,
pub granule_length: u32,
pub num_granules: u32,
pub max_num_blocks: u32,
}
impl SsfFrameConfig {
pub fn from_frame_len_base(frame_len_base: u32) -> Option<Self> {
let (gl, ng, mb) = match frame_len_base {
1920 | 1536 | 2048 => (frame_len_base / 2, 2, 4),
960 | 1024 | 768 => (frame_len_base, 1, 4),
512 | 384 => (frame_len_base, 1, 1),
_ => return None,
};
Some(Self {
frame_length: frame_len_base,
granule_length: gl,
num_granules: ng,
max_num_blocks: mb,
})
}
pub fn from_toc(fs_index: u32, frame_rate_index: u32, frame_length: u32) -> Option<Self> {
match (fs_index, frame_rate_index) {
(1, 0..=4) | (1, 13) => Some(Self {
frame_length,
granule_length: frame_length / 2,
num_granules: 2,
max_num_blocks: 4,
}),
(1, 5..=9) => Some(Self {
frame_length,
granule_length: frame_length,
num_granules: 1,
max_num_blocks: 4,
}),
(1, 10..=12) => Some(Self {
frame_length,
granule_length: frame_length,
num_granules: 1,
max_num_blocks: 1,
}),
(0, 13) => Some(Self {
frame_length,
granule_length: frame_length / 2,
num_granules: 2,
max_num_blocks: 4,
}),
_ => None,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct SsfBlock {
pub predictor_presence: bool,
pub delta_flag: bool,
pub gain_bits: u8,
pub predictor_lag_bits: u16,
pub predictor_lag_delta_bits: u8,
pub variance_preserving: bool,
pub alloc_offset_bits: u8,
pub pred_gain_idx: Option<i32>,
pub quant_idx: Vec<i32>,
}
impl SsfBlock {
pub fn gain_idx(&self) -> i32 {
i32::from(self.gain_bits) - 8
}
pub fn i_alloc_offset(&self) -> i32 {
const MIN_ALLOC_OFFSET: i32 = -21;
i32::from(self.alloc_offset_bits) + MIN_ALLOC_OFFSET
}
}
#[derive(Debug, Clone)]
pub struct SsfGranule {
pub b_iframe: bool,
pub stride_flag: StrideFlag,
pub num_bands: u32,
pub start_block: u32,
pub end_block: u32,
pub num_blocks: u32,
pub n_mdct: u32,
pub num_bins: u32,
pub env_curr_band0_bits: u8,
pub env_startup_band0_bits: Option<u8>,
pub env_curr: Vec<i32>,
pub env_startup: Option<Vec<i32>>,
pub blocks: Vec<SsfBlock>,
pub ac_bits_used: u32,
}
#[derive(Debug, Clone, Default)]
pub struct SsfData {
pub granules: Vec<SsfGranule>,
}
#[derive(Debug, Clone, Default)]
pub struct SsfChannelState {
pub dither_rng: SsfRandGenState,
pub noise_rng: SsfRandGenState,
pub prev_pred_lag_idx: i32,
pub last_num_bands: u32,
pub last_n_mdct: u32,
pub env_prev: Vec<i32>,
}
impl SsfChannelState {
pub fn new() -> Self {
Self::default()
}
pub fn reset_rngs(&mut self) {
self.dither_rng.reset();
self.noise_rng.reset();
}
}
pub fn parse_ssf_data(
br: &mut BitReader<'_>,
b_iframe: bool,
cfg: &SsfFrameConfig,
state: &mut SsfChannelState,
) -> Result<SsfData> {
let mut data = SsfData::default();
let g0_iframe = if b_iframe { true } else { br.read_bit()? };
if g0_iframe {
state.reset_rngs();
}
let g0 = parse_ssf_granule(br, g0_iframe, cfg, state)?;
data.granules.push(g0);
if cfg.frame_length >= 1536 && cfg.num_granules >= 2 {
let g1 = parse_ssf_granule(br, false, cfg, state)?;
data.granules.push(g1);
}
Ok(data)
}
pub fn parse_ssf_granule(
br: &mut BitReader<'_>,
b_iframe: bool,
cfg: &SsfFrameConfig,
state: &mut SsfChannelState,
) -> Result<SsfGranule> {
let stride_bit = br.read_u32(1)?;
let stride_flag = StrideFlag::from_bit(stride_bit);
if matches!(stride_flag, StrideFlag::ShortStride) && cfg.max_num_blocks < 4 {
return Err(Error::invalid(
"ac4 SSF: SHORT_STRIDE not allowed for this configuration",
));
}
let num_bands = if b_iframe {
let nb_minus = br.read_u32(3)?;
let nb = nb_minus + 12;
state.last_num_bands = nb;
nb
} else if state.last_num_bands == 0 {
return Err(Error::invalid(
"ac4 SSF: P-granule before any I-granule (no num_bands inherited)",
));
} else {
state.last_num_bands
};
let num_blocks = stride_flag.num_blocks() as u32;
let n_mdct = cfg.granule_length / num_blocks;
state.last_n_mdct = n_mdct;
let mut start_block: u32 = 0;
let mut end_block: u32 = 0;
if matches!(stride_flag, StrideFlag::LongStride) && !b_iframe {
end_block = 1;
}
if matches!(stride_flag, StrideFlag::ShortStride) {
end_block = 4;
if b_iframe {
start_block = 1;
}
}
let mut blocks: Vec<SsfBlock> = (0..num_blocks).map(|_| SsfBlock::default()).collect();
for block in start_block..end_block {
let pres = br.read_bit()?;
blocks[block as usize].predictor_presence = pres;
if pres {
if start_block == 1 && block == 1 {
blocks[block as usize].delta_flag = false;
} else {
blocks[block as usize].delta_flag = br.read_bit()?;
}
}
}
let (env_curr_band0_bits, env_startup_band0_bits) = parse_ssf_st_data(
br,
b_iframe,
stride_flag,
start_block,
end_block,
&mut blocks,
)?;
let layout = SsfBinLayout::build(num_bands as usize, n_mdct).ok_or_else(|| {
Error::invalid("ac4 SSF: unsupported (num_bands, n_mdct) for SSF_BANDWIDTHS")
})?;
let (env_curr, env_startup, ac_bits_used) = parse_ssf_ac_data(
br,
b_iframe,
stride_flag,
start_block,
end_block,
num_bands as usize,
env_curr_band0_bits,
env_startup_band0_bits,
&layout,
&mut blocks,
)?;
state.env_prev = env_curr.clone();
Ok(SsfGranule {
b_iframe,
stride_flag,
num_bands,
start_block,
end_block,
num_blocks,
n_mdct,
num_bins: layout.num_bins,
env_curr_band0_bits,
env_startup_band0_bits,
env_curr,
env_startup,
blocks,
ac_bits_used,
})
}
pub fn parse_ssf_st_data(
br: &mut BitReader<'_>,
b_iframe: bool,
stride_flag: StrideFlag,
start_block: u32,
end_block: u32,
blocks: &mut [SsfBlock],
) -> Result<(u8, Option<u8>)> {
let env_curr_band0_bits = br.read_u32(5)? as u8;
let env_startup_band0_bits = if b_iframe && matches!(stride_flag, StrideFlag::ShortStride) {
Some(br.read_u32(5)? as u8)
} else {
None
};
if matches!(stride_flag, StrideFlag::ShortStride) {
for blk in blocks.iter_mut().take(4) {
blk.gain_bits = br.read_u32(4)? as u8;
}
}
let num_blocks = stride_flag.num_blocks();
for (block, blk) in blocks.iter_mut().enumerate().take(num_blocks) {
let in_live = block as u32 >= start_block && (block as u32) < end_block;
if in_live && blk.predictor_presence {
if blk.delta_flag {
blk.predictor_lag_delta_bits = br.read_u32(4)? as u8;
} else {
blk.predictor_lag_bits = br.read_u32(9)? as u16;
}
}
blk.variance_preserving = br.read_bit()?;
blk.alloc_offset_bits = br.read_u32(5)? as u8;
}
Ok((env_curr_band0_bits, env_startup_band0_bits))
}
#[allow(clippy::too_many_arguments)]
pub fn parse_ssf_ac_data(
br: &mut BitReader<'_>,
b_iframe: bool,
stride_flag: StrideFlag,
start_block: u32,
end_block: u32,
num_bands: usize,
env_curr_band0_bits: u8,
env_startup_band0_bits: Option<u8>,
layout: &SsfBinLayout,
blocks: &mut [SsfBlock],
) -> Result<(Vec<i32>, Option<Vec<i32>>, u32)> {
const ENV_BAND0_MIN: i32 = -28;
let env_cdf: Vec<u32> = ENVELOPE_CDF_LUT.iter().map(|&x| x as u32).collect();
let _ = env_cdf; let mut ac = AcState::init(br).map_err(map_ac_err)?;
let bit_cursor_start = ac.bits_consumed; let mut env_curr = vec![0i32; num_bands];
env_curr[0] = i32::from(env_curr_band0_bits) + ENV_BAND0_MIN;
if num_bands > 1 {
decode_envelope_indices(&mut ac, num_bands, &mut env_curr, br).map_err(map_ac_err)?;
}
let env_startup = if b_iframe && matches!(stride_flag, StrideFlag::ShortStride) {
let mut env_st = vec![0i32; num_bands];
env_st[0] = i32::from(env_startup_band0_bits.unwrap_or(0)) + ENV_BAND0_MIN;
if num_bands > 1 {
decode_envelope_indices(&mut ac, num_bands, &mut env_st, br).map_err(map_ac_err)?;
}
Some(env_st)
} else {
None
};
let num_blocks = stride_flag.num_blocks();
let bands: Vec<(usize, usize)> = (0..num_bands)
.map(|b| (layout.start_bin[b] as usize, layout.end_bin[b] as usize))
.collect();
let zero_dither = vec![0i32; layout.num_bins as usize];
for block in 0..num_blocks {
let in_live = (block as u32) >= start_block && (block as u32) < end_block;
let blk_idx = block;
let block_iframe_first = b_iframe && block == 0;
if !block_iframe_first && in_live {
if blocks[blk_idx].predictor_presence {
let gidx = decode_predictor_gain(&mut ac, br).map_err(map_ac_err)?;
blocks[blk_idx].pred_gain_idx = Some(gidx);
}
}
let alloc_offset = blocks[blk_idx].i_alloc_offset();
let i_alloc = alloc_offset.clamp(0, 20) as u32;
let i_alloc_table: Vec<u32> = vec![i_alloc; num_bands];
let mut quant = vec![0i32; layout.num_bins as usize];
if i_alloc == 0 {
} else {
let i_alloc_us = i_alloc as usize;
if i_alloc_us >= STEP_SIZES_Q4_15.len() || i_alloc_us >= AC_COEFF_MAX_INDEX.len() {
return Err(Error::invalid("ac4 SSF: i_alloc out of table bounds"));
}
crate::ssf_ac::decode_coefficient_indices(
&mut ac,
&i_alloc_table,
&bands,
&zero_dither,
&mut quant,
br,
)
.map_err(map_ac_err)?;
}
blocks[blk_idx].quant_idx = quant;
}
let total_bits = ac.decode_finish();
let _ = bit_cursor_start;
Ok((env_curr, env_startup, total_bits))
}
fn map_ac_err(e: AcError) -> Error {
match e {
AcError::BitstreamUnderflow => Error::invalid("ac4 SSF AC: bitstream underflow"),
AcError::SymbolNotFound => Error::invalid("ac4 SSF AC: symbol not found in CDF"),
}
}
#[cfg(test)]
mod tests {
use super::*;
use oxideav_core::bits::BitWriter;
#[test]
fn stride_flag_block_count() {
assert_eq!(StrideFlag::LongStride.num_blocks(), 1);
assert_eq!(StrideFlag::ShortStride.num_blocks(), 4);
}
#[test]
fn ssf_bandwidths_anchor() {
assert_eq!(SSF_BANDWIDTHS[0][7], 12);
assert_eq!(SSF_BANDWIDTHS[18][0], 6);
assert_eq!(SSF_BANDWIDTHS[10][7], 16);
}
#[test]
fn ssf_bin_layout_long_stride_48k_24fps() {
let layout = SsfBinLayout::build(12, 960).unwrap();
assert_eq!(layout.start_bin[0], 0);
assert_eq!(layout.end_bin[0], 10);
assert_eq!(layout.start_bin[1], 11);
assert_eq!(layout.num_bins, 140);
}
#[test]
fn frame_config_48k_24fps() {
let cfg = SsfFrameConfig::from_toc(1, 1, 1920).unwrap();
assert_eq!(cfg.granule_length, 960);
assert_eq!(cfg.num_granules, 2);
assert_eq!(cfg.max_num_blocks, 4);
}
#[test]
fn frame_config_48k_50fps_one_granule() {
let cfg = SsfFrameConfig::from_toc(1, 7, 1024).unwrap();
assert_eq!(cfg.granule_length, 1024);
assert_eq!(cfg.num_granules, 1);
assert_eq!(cfg.max_num_blocks, 4);
}
#[test]
fn frame_config_48k_120fps_no_short_stride() {
let cfg = SsfFrameConfig::from_toc(1, 12, 384).unwrap();
assert_eq!(cfg.max_num_blocks, 1);
}
#[test]
fn frame_config_44_1k_only_index_13() {
assert!(SsfFrameConfig::from_toc(0, 0, 2048).is_none());
let cfg = SsfFrameConfig::from_toc(0, 13, 2048).unwrap();
assert_eq!(cfg.granule_length, 1024);
assert_eq!(cfg.num_granules, 2);
}
#[test]
fn parse_ssf_data_long_stride_iframe_smoke() {
let mut bw = BitWriter::new();
bw.write_u32(0, 1); bw.write_u32(0, 3); bw.write_u32(0, 5);
bw.write_u32(0, 1);
bw.write_u32(0, 5);
for _ in 0..30 {
bw.write_bit(false);
}
for _ in 0..256 {
bw.write_bit(false);
}
bw.align_to_byte();
let bytes = bw.finish();
let mut br = BitReader::new(&bytes);
let cfg = SsfFrameConfig::from_toc(1, 5, 960).unwrap();
let mut state = SsfChannelState::new();
let data = parse_ssf_data(&mut br, true, &cfg, &mut state).expect("parse OK");
assert_eq!(data.granules.len(), 1);
let g = &data.granules[0];
assert!(g.b_iframe);
assert_eq!(g.stride_flag, StrideFlag::LongStride);
assert_eq!(g.num_bands, 12);
assert_eq!(g.start_block, 0);
assert_eq!(g.end_block, 0);
assert_eq!(g.num_blocks, 1);
assert_eq!(g.n_mdct, 960);
assert_eq!(g.env_curr.len(), 12);
assert!(g.env_startup.is_none());
}
#[test]
fn parse_ssf_short_stride_iframe_smoke() {
let mut bw = BitWriter::new();
bw.write_u32(1, 1); bw.write_u32(0, 3); for _ in 1..4 {
bw.write_bit(false);
}
bw.write_u32(0, 5); bw.write_u32(0, 5); for _ in 0..4 {
bw.write_u32(0, 4);
}
for _ in 0..4 {
bw.write_u32(0, 1);
bw.write_u32(0, 5);
}
for _ in 0..(30 + 512) {
bw.write_bit(false);
}
bw.align_to_byte();
let bytes = bw.finish();
let mut br = BitReader::new(&bytes);
let cfg = SsfFrameConfig::from_toc(1, 7, 1024).unwrap();
let mut state = SsfChannelState::new();
let data = parse_ssf_data(&mut br, true, &cfg, &mut state).expect("parse OK");
assert_eq!(data.granules.len(), 1);
let g = &data.granules[0];
assert_eq!(g.stride_flag, StrideFlag::ShortStride);
assert_eq!(g.start_block, 1);
assert_eq!(g.end_block, 4);
assert_eq!(g.num_blocks, 4);
assert_eq!(g.n_mdct, 256);
assert_eq!(g.blocks.len(), 4);
assert!(g.env_startup.is_some());
}
}