#![allow(
clippy::cast_possible_truncation,
clippy::cast_possible_wrap,
clippy::similar_names,
clippy::too_many_arguments,
clippy::too_many_lines
)]
use super::super::bitstream::parameter_widths;
use super::super::codebook::FixedCodebook;
use super::super::lag::{Excitation, PitchLag};
use super::super::lsp::{initial_lsp, interpolate_lsp, interpolate_lsp_mid, AZ_SIZE, M, MP1};
use super::super::synthesis::synthesis_filter;
use super::super::tables::{
SORT_102, SORT_122, SORT_475, SORT_515, SORT_59, SORT_67, SORT_74, SORT_795,
};
use super::super::{L_FRAME, L_INTERPOL, L_SUBFR, PIT_MAX, SHARPMAX};
use super::analysis::{
az_lsp, subframe_targets, LpAnalysis, SpeechBuffer, SubframeTargets, WeightedSpeech, MR122,
MR475,
};
use super::codebook::{search as codebook_search, CodebookInputs};
use super::dtx::{DtxEncoder, TxDecision};
use super::gain_quant::{GainParams, GainQuantiser, SubframeSignals};
use super::lsp_quant::LsfQuantiser;
use super::pitch::{
closed_loop_ltp, convolve, open_loop_lags, ClosedLoopPitch, ToneStability, WeightedOpenLoop,
EXC_ORIGIN,
};
use super::preproc::Preprocessor;
use super::vad::VoiceActivityDetector;
use crate::fixed_point::arith::{add, extract_h, round, sub};
use crate::fixed_point::arith32::{l_mac, l_mult};
use crate::fixed_point::shift::{l_shl, shr};
use crate::fixed_point::types::{DspContext, Word16};
const NB_SUBFR: usize = 4;
const EXC_TOTAL: usize = EXC_ORIGIN + L_FRAME;
const EXC_VIEW: usize = EXC_ORIGIN + L_SUBFR;
const WSP_TOTAL: usize = PIT_MAX as usize + L_FRAME;
const NB_OF_BITS: [usize; 8] = [95, 103, 118, 134, 148, 159, 204, 244];
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Rate(u8);
impl Rate {
#[must_use]
pub const fn from_index(index: u8) -> Option<Self> {
if index <= 7 {
Some(Self(index))
} else {
None
}
}
#[must_use]
pub const fn index(self) -> u8 {
self.0
}
#[must_use]
pub const fn bits(self) -> usize {
NB_OF_BITS[self.0 as usize]
}
#[must_use]
pub const fn packed_bytes(self) -> usize {
self.bits().div_ceil(8)
}
#[must_use]
pub const fn toc_byte(self) -> u8 {
(self.0 << 3) | 0x04
}
const fn sort_table(self) -> &'static [u16] {
match self.0 {
0 => &SORT_475,
1 => &SORT_515,
2 => &SORT_59,
3 => &SORT_67,
4 => &SORT_74,
5 => &SORT_795,
6 => &SORT_102,
_ => &SORT_122,
}
}
const fn widths(self) -> &'static [usize] {
parameter_widths(self.0)
}
}
#[derive(Debug, Default)]
struct Parameters {
words: Vec<u16>,
}
impl Parameters {
const fn new() -> Self {
Self { words: Vec::new() }
}
fn push(&mut self, value: u16) {
self.words.push(value);
}
fn reserve(&mut self) -> usize {
self.words.push(0);
self.words.len() - 1
}
fn fill(&mut self, at: usize, value: u16) {
self.words[at] = value;
}
fn pack(&self, rate: Rate) -> Vec<u8> {
let widths = rate.widths();
assert_eq!(
self.words.len(),
widths.len(),
"mode {}: wrote {} parameters, the layout has {}",
rate.index(),
self.words.len(),
widths.len()
);
let mut bits = Vec::with_capacity(rate.bits());
for (&value, &width) in self.words.iter().zip(widths.iter()) {
debug_assert!(
u32::from(value) < (1u32 << width),
"mode {}: parameter {value} does not fit in {width} bits",
rate.index()
);
for i in (0..width).rev() {
bits.push(((value >> i) & 1) as u8);
}
}
assert_eq!(bits.len(), rate.bits(), "the layout must fill the frame");
let sort = rate.sort_table();
let mut out = vec![0u8; rate.packed_bytes()];
for (i, &source) in sort.iter().enumerate() {
out[i / 8] |= bits[source as usize] << (7 - (i % 8));
}
out
}
fn pack_sid(&self) -> Vec<u8> {
let widths = parameter_widths(8);
assert_eq!(self.words.len(), widths.len(), "a SID is five parameters");
let mut bits = Vec::with_capacity(35);
for (&value, &width) in self.words.iter().zip(widths.iter()) {
debug_assert!(
u32::from(value) < (1u32 << width),
"SID parameter {value} does not fit in {width} bits"
);
for i in (0..width).rev() {
bits.push(u8::try_from((value >> i) & 1).expect("one bit"));
}
}
assert_eq!(bits.len(), 35, "the SID layout must fill 35 bits");
let sort = super::super::tables::SORT_SID;
let mut out = vec![0u8; 5];
for (i, &source) in sort.iter().enumerate() {
out[i / 8] |= bits[source as usize] << (7 - (i % 8));
}
out
}
}
fn codebook_words(book: FixedCodebook, out: &mut Parameters) {
match book {
FixedCodebook::TwoPulses9Bit {
signs, positions, ..
}
| FixedCodebook::TwoPulses11Bit { signs, positions }
| FixedCodebook::ThreePulses14Bit { signs, positions }
| FixedCodebook::FourPulses17Bit { signs, positions } => {
out.push(positions);
out.push(signs);
}
FixedCodebook::EightPulses31Bit(fields) => {
for field in fields {
out.push(field);
}
}
FixedCodebook::TenPulses35Bit(fields) => {
for field in fields {
out.push(field);
}
}
}
}
#[derive(Clone, Debug, Default)]
pub struct EncoderTrace {
rows: Vec<(usize, i32, &'static str, Vec<i32>)>,
}
impl EncoderTrace {
#[must_use]
pub fn rows(&self) -> &[(usize, i32, &'static str, Vec<i32>)] {
&self.rows
}
#[must_use]
pub fn row(&self, frame: usize, subframe: i32, name: &str) -> Option<&[i32]> {
self.rows
.iter()
.find(|r| r.0 == frame && r.1 == subframe && r.2 == name)
.map(|r| r.3.as_slice())
}
}
#[derive(Debug, Clone)]
pub struct NbEncoder {
preprocessor: Preprocessor,
speech: SpeechBuffer,
lp: LpAnalysis,
lsp_old: [Word16; M],
lsp_old_q: [Word16; M],
lsf: LsfQuantiser,
weighting: WeightedSpeech,
old_wsp: [Word16; WSP_TOTAL],
weighted_open_loop: WeightedOpenLoop,
old_lags: [Word16; 5],
ol_gain_flg: [bool; 2],
pitch: ClosedLoopPitch,
tone: ToneStability,
gains: GainQuantiser,
old_exc: [Word16; EXC_TOTAL],
mem_syn: [Word16; M],
mem_w0: [Word16; M],
mem_err: [Word16; M],
sharp: Word16,
vad: VoiceActivityDetector,
dtx: DtxEncoder,
dtx_enabled: bool,
trace: Option<EncoderTrace>,
frame_index: usize,
}
impl Default for NbEncoder {
fn default() -> Self {
Self::new()
}
}
impl NbEncoder {
#[must_use]
pub fn new() -> Self {
Self {
vad: VoiceActivityDetector::new(),
dtx: DtxEncoder::new(),
dtx_enabled: false,
preprocessor: Preprocessor::new(),
speech: SpeechBuffer::new(),
lp: LpAnalysis::new(),
lsp_old: initial_lsp(),
lsp_old_q: initial_lsp(),
lsf: LsfQuantiser::new(),
weighting: WeightedSpeech::new(),
old_wsp: [Word16(0); WSP_TOTAL],
weighted_open_loop: WeightedOpenLoop::new(),
old_lags: [Word16(40); 5],
ol_gain_flg: [false; 2],
pitch: ClosedLoopPitch::new(),
tone: ToneStability::new(),
gains: GainQuantiser::new(),
old_exc: [Word16(0); EXC_TOTAL],
mem_syn: [Word16(0); M],
mem_w0: [Word16(0); M],
mem_err: [Word16(0); M],
sharp: Word16(0),
trace: None,
frame_index: 0,
}
}
pub fn record_trace(&mut self) {
self.trace = Some(EncoderTrace::default());
}
#[must_use]
pub fn take_trace(&mut self) -> Option<EncoderTrace> {
self.trace.replace(EncoderTrace::default())
}
fn trc(&mut self, subframe: i32, name: &'static str, values: &[Word16]) {
let frame = self.frame_index;
if let Some(trace) = self.trace.as_mut() {
let row = values.iter().map(|v| i32::from(v.0)).collect();
trace.rows.push((frame, subframe, name, row));
}
}
fn trc1(&mut self, subframe: i32, name: &'static str, value: i32) {
let frame = self.frame_index;
if let Some(trace) = self.trace.as_mut() {
trace.rows.push((frame, subframe, name, vec![value]));
}
}
pub fn encode_frame(&mut self, pcm: &[i16; L_FRAME], rate: Rate) -> Vec<u8> {
self.encode_frame_typed(pcm, rate).1
}
pub const fn set_allow_dtx(&mut self, allow: bool) {
self.dtx_enabled = allow;
}
pub fn encode_frame_typed(&mut self, pcm: &[i16; L_FRAME], rate: Rate) -> (bool, Vec<u8>) {
let mut prms = Parameters::new();
let comfort_noise = self.encode_into(pcm, rate, &mut prms);
let payload = if comfort_noise {
prms.pack_sid()
} else {
prms.pack(rate)
};
self.frame_index += 1;
(comfort_noise, payload)
}
fn encode_into(&mut self, pcm: &[i16; L_FRAME], rate: Rate, prms: &mut Parameters) -> bool {
let mode = rate.index();
let mut ctx = DspContext::default();
let mut frame = [Word16(0); L_FRAME];
for (slot, &sample) in frame.iter_mut().zip(pcm.iter()) {
*slot = Word16(sample);
}
self.preprocessor.condition(&mut ctx, &mut frame);
self.speech.push(&frame);
self.trc(-1, "speech", &frame);
let (comfort_noise, compute_sid) = if self.dtx_enabled {
let window = *self.speech.vad_window();
let voice_active = self.vad.process(&mut ctx, &window);
let (decision, compute) = self.dtx.classify(&mut ctx, voice_active);
(decision == TxDecision::ComfortNoise, compute)
} else {
(false, false)
};
let mut az = [Word16(0); AZ_SIZE];
self.lp.analyse(&mut ctx, mode, &self.speech, &mut az);
let azq = self.spectrum(&mut ctx, mode, &mut az, prms, comfort_noise);
self.trc(-1, "A_t", &az);
if self.dtx_enabled {
let lsp_new = self.lsp_old;
let newest = *self.speech.newest();
self.dtx.buffer(&mut ctx, &lsp_new, &newest);
}
if comfort_noise {
self.build_sid(&mut ctx, compute_sid, prms);
}
let lsp_new = self.lsp_old;
let lsp_flag = if comfort_noise {
false
} else {
self.tone.check_lsp(&mut ctx, &lsp_new)
};
let mut wsp = [Word16(0); L_FRAME];
for half in 0..2 {
self.weighting.half_frame(
&mut ctx,
mode,
&az,
half * (L_FRAME / 2),
&self.speech,
&mut wsp,
);
}
self.old_wsp[PIT_MAX as usize..].copy_from_slice(&wsp);
let t_op = open_loop_lags(
&mut ctx,
mode,
&mut self.weighted_open_loop,
&self.old_wsp,
PIT_MAX as usize,
&mut self.old_lags,
&mut self.ol_gain_flg,
self.dtx_enabled.then_some(&mut self.vad),
);
if self.dtx_enabled {
self.vad
.observe_pitch(&mut ctx, [Word16(t_op[0]), Word16(t_op[1])]);
}
if comfort_noise {
self.old_wsp.copy_within(L_FRAME.., 0);
self.speech.shift();
return true;
}
if mode <= 1 {
self.trc1(-1, "T_op0", i32::from(t_op[0]));
}
let mut exc = self.old_exc;
let frame_state = FrameState {
mode,
az,
azq,
t_op,
lsp_flag,
lsp_new,
};
if mode == MR475 {
self.subframe_loop_joint(&mut ctx, &frame_state, &mut exc, prms);
} else {
self.subframe_loop(&mut ctx, &frame_state, &mut exc, prms);
}
exc.copy_within(L_FRAME.., 0);
self.old_exc = exc;
self.old_wsp.copy_within(L_FRAME.., 0);
self.speech.shift();
false
}
fn build_sid(&mut self, ctx: &mut DspContext, compute_sid: bool, prms: &mut Parameters) {
if compute_sid {
let (lsp, _) = self.dtx.average_history(ctx);
let mut lsf = super::lsp_quant::lsp_to_lsf(ctx, &lsp);
super::super::lsp::reorder_lsf(ctx, &mut lsf, Word16(205));
let lsp = super::super::lsp::lsf_to_lsp(ctx, &lsf);
let quantised = self.lsf.quantise_sid(ctx, &lsp);
self.dtx.set_indices(
Word16(i16::try_from(quantised.seed_index).expect("three bits")),
quantised
.indices
.map(|i| Word16(i16::try_from(i).expect("nine bits"))),
);
let (ordinary, mr122) = self.dtx.predictor_reset(ctx);
self.gains.reseed_predictors(ordinary, mr122);
}
for word in self.dtx.sid_parameters() {
prms.push(u16::try_from(word.0).expect("a SID parameter is non-negative"));
}
let lsp_new = self.lsp_old;
self.old_exc = [Word16(0); EXC_TOTAL];
self.mem_w0 = [Word16(0); M];
self.mem_err = [Word16(0); M];
self.lsf = LsfQuantiser::new();
self.lsp_old = lsp_new;
self.lsp_old_q = lsp_new;
self.pitch = ClosedLoopPitch::new();
self.sharp = Word16(0);
}
fn spectrum(
&mut self,
ctx: &mut DspContext,
mode: u8,
az: &mut [Word16; AZ_SIZE],
prms: &mut Parameters,
comfort_noise: bool,
) -> [Word16; AZ_SIZE] {
if mode == MR122 {
let mid = az_lsp(ctx, slot(az, 1), &self.lsp_old);
let new = az_lsp(ctx, slot(az, 3), &mid);
let interpolated = interpolate_lsp_mid(ctx, &self.lsp_old, &mid, &new);
copy_slot(az, 0, &interpolated);
copy_slot(az, 2, &interpolated);
if comfort_noise {
self.lsp_old = new;
return [Word16(0); AZ_SIZE];
}
let quantised = self.lsf.quantise_pair(ctx, &mid, &new);
for index in quantised.indices {
prms.push(index);
}
let azq = interpolate_lsp_mid(ctx, &self.lsp_old_q, &quantised.mid, &quantised.new);
self.lsp_old = new;
self.lsp_old_q = quantised.new;
azq
} else {
let new = az_lsp(ctx, slot(az, 3), &self.lsp_old);
let interpolated = interpolate_lsp(ctx, &self.lsp_old, &new);
copy_slot(az, 0, &interpolated);
copy_slot(az, 1, &interpolated);
copy_slot(az, 2, &interpolated);
if comfort_noise {
self.lsp_old = new;
return [Word16(0); AZ_SIZE];
}
let quantised = self.lsf.quantise(ctx, mode, &new);
for index in quantised.indices {
prms.push(index);
}
let azq = interpolate_lsp(ctx, &self.lsp_old_q, &quantised.lsp);
self.lsp_old = new;
self.lsp_old_q = quantised.lsp;
azq
}
}
fn subframe_loop(
&mut self,
ctx: &mut DspContext,
frame: &FrameState,
exc: &mut [Word16; EXC_TOTAL],
prms: &mut Parameters,
) {
for subfr in 0..NB_SUBFR {
let mem_w0 = self.mem_w0;
let searched = self.search_subframe(ctx, frame, subfr, exc, prms, &mem_w0);
let quantised = self.quantise_gains(ctx, frame.mode, subfr, &searched, prms, None);
self.tone.update(ctx, quantised.gains.pitch);
let mut memories = Memories {
syn: self.mem_syn,
err: self.mem_err,
w0: self.mem_w0,
sharp: self.sharp,
};
post_process(
ctx,
&PostProc {
mode: frame.mode,
subfr,
gain_pit: quantised.gains.pitch,
gain_code: quantised.gains.code,
aq: slot(&frame.azq, subfr),
speech: self.speech.coded(),
xn: &searched.xn,
code: &searched.code,
y1: &searched.y1,
y2: &searched.y2,
},
exc,
&mut memories,
);
self.mem_syn = memories.syn;
self.mem_err = memories.err;
self.mem_w0 = memories.w0;
self.sharp = memories.sharp;
}
}
fn subframe_loop_joint(
&mut self,
ctx: &mut DspContext,
frame: &FrameState,
exc: &mut [Word16; EXC_TOTAL],
prms: &mut Parameters,
) {
let mode = frame.mode;
for pair in 0..NB_SUBFR / 2 {
let even = pair * 2;
let odd = even + 1;
let mem_err_save = self.mem_err;
let sharp_save = self.sharp;
let mut mem_w0_save = self.mem_w0;
let even_search = self.search_subframe(ctx, frame, even, exc, prms, &mem_w0_save);
let h1_sf0 = even_search.h1;
let even_quantised = self.quantise_gains(ctx, mode, even, &even_search, prms, None);
let gain_slot = even_quantised
.reserved
.expect("4.75 kbit/s reserves a gain slot on the even subframe");
self.tone.update(ctx, even_quantised.gains.pitch);
let mut provisional = Memories {
syn: self.mem_syn,
err: self.mem_err,
w0: mem_w0_save,
sharp: self.sharp,
};
post_process(
ctx,
&PostProc {
mode,
subfr: even,
gain_pit: even_quantised.gains.pitch,
gain_code: even_quantised.gains.code,
aq: slot(&frame.azq, even),
speech: self.speech.coded(),
xn: &even_search.xn,
code: &even_search.code,
y1: &even_search.y1,
y2: &even_search.y2,
},
exc,
&mut provisional,
);
mem_w0_save = provisional.w0;
self.mem_err = provisional.err;
self.sharp = sharp_save;
let odd_search = self.search_subframe(ctx, frame, odd, exc, prms, &mem_w0_save);
let odd_quantised =
self.quantise_gains(ctx, mode, odd, &odd_search, prms, Some(gain_slot));
let even_gains = odd_quantised
.previous
.expect("the joint quantiser returns the even subframe's gains too");
self.tone.update(ctx, odd_quantised.gains.pitch);
self.mem_err = mem_err_save;
predict_into(ctx, exc, even, even_search.lag);
let rebuilt = subframe_of(exc, even);
let y1 = convolve(ctx, &rebuilt, &h1_sf0);
let mut memories = Memories {
syn: self.mem_syn,
err: self.mem_err,
w0: self.mem_w0,
sharp: sharp_save,
};
post_process(
ctx,
&PostProc {
mode,
subfr: even,
gain_pit: even_gains.pitch,
gain_code: even_gains.code,
aq: slot(&frame.azq, even),
speech: self.speech.coded(),
xn: &even_search.xn,
code: &even_search.code,
y1: &y1,
y2: &even_search.y2,
},
exc,
&mut memories,
);
self.mem_syn = memories.syn;
self.mem_err = memories.err;
self.mem_w0 = memories.w0;
let targets = subframe_targets(
ctx,
mode,
slot(&frame.az, odd),
slot(&frame.azq, odd),
self.speech.with_history(odd * L_SUBFR, L_SUBFR),
&self.mem_err,
&self.mem_w0,
);
predict_into(ctx, exc, odd, odd_search.lag);
let rebuilt = subframe_of(exc, odd);
let y1 = convolve(ctx, &rebuilt, &targets.h1);
let mut memories = Memories {
syn: self.mem_syn,
err: self.mem_err,
w0: self.mem_w0,
sharp: self.sharp,
};
post_process(
ctx,
&PostProc {
mode,
subfr: odd,
gain_pit: odd_quantised.gains.pitch,
gain_code: odd_quantised.gains.code,
aq: slot(&frame.azq, odd),
speech: self.speech.coded(),
xn: &targets.xn,
code: &odd_search.code,
y1: &y1,
y2: &odd_search.y2,
},
exc,
&mut memories,
);
self.mem_syn = memories.syn;
self.mem_err = memories.err;
self.mem_w0 = memories.w0;
self.sharp = memories.sharp;
}
}
fn search_subframe(
&mut self,
ctx: &mut DspContext,
frame: &FrameState,
subfr: usize,
exc: &mut [Word16; EXC_TOTAL],
prms: &mut Parameters,
mem_w0: &[Word16; M],
) -> Searched {
let mode = frame.mode;
let i_subfr = subfr * L_SUBFR;
let targets: SubframeTargets = subframe_targets(
ctx,
mode,
slot(&frame.az, subfr),
slot(&frame.azq, subfr),
self.speech.with_history(i_subfr, L_SUBFR),
&self.mem_err,
mem_w0,
);
exc[EXC_ORIGIN + i_subfr..EXC_ORIGIN + i_subfr + L_SUBFR].copy_from_slice(&targets.res);
let sf = subfr as i32;
self.trc(sf, "lsp_new", &frame.lsp_new);
self.trc(sf, "xn", &targets.xn);
self.trc(sf, "h1", &targets.h1);
self.trc(sf, "res", &targets.res);
let mut res2 = targets.res;
let mut xn2 = [Word16(0); L_SUBFR];
let mut y1 = [Word16(0); L_SUBFR];
let mut view = view_at(exc, subfr);
let ltp = closed_loop_ltp(
ctx,
mode,
&mut self.pitch,
&mut self.tone,
frame.t_op,
subfr,
&mut view,
&targets.xn,
&targets.h1,
frame.lsp_flag,
&mut res2,
&mut xn2,
&mut y1,
);
commit_view(exc, subfr, &view);
if subfr == 0 && self.ol_gain_flg[0] {
self.old_lags[1] = ltp.pitch.lag.integer;
}
if subfr == 3 && self.ol_gain_flg[1] {
self.old_lags[0] = ltp.pitch.lag.integer;
}
prms.push(ltp.pitch.index);
if let Some(index) = ltp.gain_index {
prms.push(index);
}
let adaptive = subframe_of(exc, subfr);
self.trc1(sf, "T0", i32::from(ltp.pitch.lag.integer.0));
self.trc1(sf, "T0_frac", i32::from(ltp.pitch.lag.frac.0));
self.trc1(sf, "gain_pit_ol", i32::from(ltp.gain_pitch.0));
self.trc(sf, "xn2", &xn2);
self.trc(sf, "y1", &y1);
self.trc(sf, "adapt", &adaptive);
let innovation = codebook_search(
ctx,
mode,
subfr as u8,
&CodebookInputs {
target: &xn2,
impulse: &targets.h1,
ltp_residual: &res2,
lag: ltp.pitch.lag.integer.0,
pitch_sharp: self.sharp,
gain_pit: ltp.gain_pitch,
},
);
codebook_words(innovation.params, prms);
self.trc(sf, "code", &innovation.code);
self.trc(sf, "y2", &innovation.filtered);
Searched {
xn: targets.xn,
h1: targets.h1,
res: targets.res,
xn2,
y1,
code: innovation.code,
y2: innovation.filtered,
adaptive,
lag: ltp.pitch.lag,
gain_pitch: ltp.gain_pitch,
gain_limit: ltp.gain_limit,
correlations: [
ltp.gain_coefficients.yy,
ltp.gain_coefficients.exp_yy,
ltp.gain_coefficients.xy,
ltp.gain_coefficients.exp_xy,
],
}
}
fn quantise_gains(
&mut self,
ctx: &mut DspContext,
mode: u8,
subfr: usize,
searched: &Searched,
prms: &mut Parameters,
pair_slot: Option<usize>,
) -> Quantised {
let decision = self.gains.quantise(
ctx,
mode,
&SubframeSignals {
residual: &searched.res,
adaptive: &searched.adaptive,
code: &searched.code,
pitch_target: &searched.xn,
code_target: &searched.xn2,
filtered_adaptive: &searched.y1,
filtered_code: &searched.y2,
pitch_correlations: searched.correlations,
gain_pit: searched.gain_pitch,
gp_limit: searched.gain_limit,
even_subframe: subfr.is_multiple_of(2),
},
);
let mut reserved = None;
match decision.params {
GainParams::Reserve => reserved = Some(prms.reserve()),
GainParams::Index(index) => prms.push(index),
GainParams::Pair(index) => prms.fill(
pair_slot.expect("4.75 kbit/s must carry the reserved slot to the odd subframe"),
index,
),
GainParams::PitchAndCode(pitch, code) => {
prms.push(pitch);
prms.push(code);
}
}
let sf = subfr as i32;
self.trc1(sf, "gain_pit", i32::from(decision.gains.pitch.0));
self.trc1(sf, "gain_code", i32::from(decision.gains.code.0));
Quantised {
gains: Gains {
pitch: decision.gains.pitch,
code: decision.gains.code,
},
previous: decision.previous.map(|g| Gains {
pitch: g.pitch,
code: g.code,
}),
reserved,
}
}
}
struct FrameState {
mode: u8,
az: [Word16; AZ_SIZE],
azq: [Word16; AZ_SIZE],
t_op: [i16; 2],
lsp_flag: bool,
lsp_new: [Word16; M],
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct Gains {
pitch: Word16,
code: Word16,
}
struct Quantised {
gains: Gains,
previous: Option<Gains>,
reserved: Option<usize>,
}
#[derive(Clone, Copy, Debug)]
struct Searched {
xn: [Word16; L_SUBFR],
h1: [Word16; L_SUBFR],
res: [Word16; L_SUBFR],
xn2: [Word16; L_SUBFR],
y1: [Word16; L_SUBFR],
code: [Word16; L_SUBFR],
y2: [Word16; L_SUBFR],
adaptive: [Word16; L_SUBFR],
lag: PitchLag,
gain_pitch: Word16,
gain_limit: Word16,
correlations: [Word16; 4],
}
#[derive(Clone, Copy, Debug)]
struct Memories {
syn: [Word16; M],
err: [Word16; M],
w0: [Word16; M],
sharp: Word16,
}
struct PostProc<'a> {
mode: u8,
subfr: usize,
gain_pit: Word16,
gain_code: Word16,
aq: &'a [Word16; MP1],
speech: &'a [Word16; L_FRAME],
xn: &'a [Word16; L_SUBFR],
code: &'a [Word16; L_SUBFR],
y1: &'a [Word16; L_SUBFR],
y2: &'a [Word16; L_SUBFR],
}
fn view_at(exc: &[Word16; EXC_TOTAL], subfr: usize) -> Excitation {
let at = subfr * L_SUBFR;
let mut view = Excitation::new();
view.all_mut().copy_from_slice(&exc[at..at + EXC_VIEW]);
view
}
fn commit_view(exc: &mut [Word16; EXC_TOTAL], subfr: usize, view: &Excitation) {
let at = EXC_ORIGIN + subfr * L_SUBFR;
exc[at..at + L_SUBFR].copy_from_slice(view.subframe());
}
fn predict_into(ctx: &mut DspContext, exc: &mut [Word16; EXC_TOTAL], subfr: usize, lag: PitchLag) {
let mut view = view_at(exc, subfr);
view.predict(ctx, lag);
commit_view(exc, subfr, &view);
}
fn subframe_of(exc: &[Word16; EXC_TOTAL], subfr: usize) -> [Word16; L_SUBFR] {
let at = EXC_ORIGIN + subfr * L_SUBFR;
exc[at..at + L_SUBFR]
.try_into()
.expect("a subframe is L_SUBFR long")
}
fn slot(az: &[Word16; AZ_SIZE], subfr: usize) -> &[Word16; MP1] {
az[subfr * MP1..(subfr + 1) * MP1]
.try_into()
.expect("AZ_SIZE is four MP1 blocks")
}
fn copy_slot(az: &mut [Word16; AZ_SIZE], subfr: usize, from: &[Word16; AZ_SIZE]) {
let range = subfr * MP1..(subfr + 1) * MP1;
az[range.clone()].copy_from_slice(&from[range]);
}
fn post_process(
ctx: &mut DspContext,
inputs: &PostProc<'_>,
exc: &mut [Word16; EXC_TOTAL],
memories: &mut Memories,
) {
let &PostProc {
mode,
subfr,
gain_pit,
gain_code,
aq,
speech,
xn,
code,
y1,
y2,
} = inputs;
let (temp_shift, k_shift, pitch_fac) = if mode == MR122 {
(2, 4, shr(ctx, gain_pit, 1))
} else {
(1, 2, gain_pit)
};
memories.sharp = if sub(ctx, gain_pit, Word16(SHARPMAX)).0 > 0 {
Word16(SHARPMAX)
} else {
gain_pit
};
let i_subfr = subfr * L_SUBFR;
let base = EXC_ORIGIN + i_subfr;
for i in 0..L_SUBFR {
let mut acc = l_mult(ctx, exc[base + i], pitch_fac);
acc = l_mac(ctx, acc, code[i], gain_code);
acc = l_shl(ctx, acc, temp_shift);
exc[base + i] = round(ctx, acc);
}
let total: [Word16; L_SUBFR] = exc[base..base + L_SUBFR]
.try_into()
.expect("a subframe is L_SUBFR long");
let mut synth = [Word16(0); L_SUBFR];
memories.syn = synthesis_filter(ctx, aq, &total, &mut synth, &memories.syn);
for (j, i) in (L_SUBFR - M..L_SUBFR).enumerate() {
memories.err[j] = sub(ctx, speech[i_subfr + i], synth[i]);
let scaled = l_mult(ctx, y1[i], gain_pit);
let temp = extract_h(l_shl(ctx, scaled, 1));
let scaled = l_mult(ctx, y2[i], gain_code);
let k = extract_h(l_shl(ctx, scaled, k_shift));
let both = add(ctx, temp, k);
memories.w0[j] = sub(ctx, xn[i], both);
}
}
const _: () = {
assert!(EXC_ORIGIN == PIT_MAX as usize + L_INTERPOL);
assert!(EXC_TOTAL == EXC_ORIGIN + L_FRAME);
assert!(EXC_VIEW == EXC_ORIGIN + L_SUBFR);
};
#[cfg(test)]
mod tests {
use super::*;
use crate::codecs::amr::nb::bitstream::parse;
const TRACE: &str = include_str!("../../testdata/nb_enc_trace.txt");
const INPUT: &[u8] = include_bytes!("../../testdata/amrnb_enc_input.pcm");
const BITSTREAMS: [&[u8]; 8] = [
include_bytes!("../../testdata/amrnb_enc_mode0.amr"),
include_bytes!("../../testdata/amrnb_enc_mode1.amr"),
include_bytes!("../../testdata/amrnb_enc_mode2.amr"),
include_bytes!("../../testdata/amrnb_enc_mode3.amr"),
include_bytes!("../../testdata/amrnb_enc_mode4.amr"),
include_bytes!("../../testdata/amrnb_enc_mode5.amr"),
include_bytes!("../../testdata/amrnb_enc_mode6.amr"),
include_bytes!("../../testdata/amrnb_enc_mode7.amr"),
];
const FRAMES: usize = 50;
const TRACED_FRAMES: usize = 3;
const TRACE_MODE: u8 = 4;
const MAGIC: usize = 6;
fn input_frame(frame: usize) -> [i16; L_FRAME] {
let mut samples = [0i16; L_FRAME];
let base = frame * L_FRAME * 2;
for (n, slot) in samples.iter_mut().enumerate() {
let lo = u16::from(INPUT[base + n * 2]);
let hi = u16::from(INPUT[base + n * 2 + 1]);
*slot = (lo | (hi << 8)) as i16;
}
samples
}
fn reference_rows() -> Vec<(usize, i32, String, Vec<i32>)> {
let mut rows = Vec::new();
for line in TRACE.lines() {
let mut field = line.split_whitespace();
if field.next() != Some("T") {
continue;
}
let frame: usize = field.next().expect("frame").parse().expect("frame");
let subframe: i32 = field.next().expect("subframe").parse().expect("subframe");
let name = field.next().expect("name").to_owned();
let values = field.map(|v| v.parse().expect("value")).collect();
rows.push((frame, subframe, name, values));
}
assert!(!rows.is_empty(), "the committed trace parsed to nothing");
rows
}
fn run(mode: u8, frames: usize) -> (Vec<Vec<u8>>, EncoderTrace) {
let rate = Rate::from_index(mode).expect("a speech mode");
let mut encoder = NbEncoder::new();
encoder.record_trace();
let mut payloads = Vec::with_capacity(frames);
for frame in 0..frames {
payloads.push(encoder.encode_frame(&input_frame(frame), rate));
}
let trace = encoder.take_trace().expect("recording was enabled");
(payloads, trace)
}
#[test]
fn every_traced_intermediate_is_bit_exact_against_ts26073() {
let (_, got) = run(TRACE_MODE, TRACED_FRAMES);
let want = reference_rows();
let mut compared_rows = 0usize;
let mut compared_values = 0usize;
for (frame, subframe, name, expected) in &want {
let actual = got.row(*frame, *subframe, name).unwrap_or_else(|| {
panic!("frame {frame} subframe {subframe}: this encoder never produced {name}")
});
assert_eq!(
actual.len(),
expected.len(),
"frame {frame} subframe {subframe}: {name} length"
);
for (i, (&a, &e)) in actual.iter().zip(expected.iter()).enumerate() {
assert_eq!(
a, e,
"frame {frame} subframe {subframe}: {name}[{i}] = {a} but TS 26.073 gives {e}"
);
compared_values += 1;
}
compared_rows += 1;
}
assert_eq!(compared_rows, 174, "the committed trace has 174 rows");
assert_eq!(
compared_values, 4632,
"every traced value must have been compared"
);
}
#[test]
fn the_bitstream_is_byte_identical_to_ts26073_at_every_rate() {
let mut exact = Vec::new();
let mut failures = Vec::new();
for mode in 0..8u8 {
let rate = Rate::from_index(mode).expect("a speech mode");
let file = BITSTREAMS[mode as usize];
assert_eq!(&file[..MAGIC], b"#!AMR\n", "mode {mode}: magic number");
let (payloads, _) = run(mode, FRAMES);
let stride = 1 + rate.packed_bytes();
assert_eq!(
file.len(),
MAGIC + FRAMES * stride,
"mode {mode}: fixture is not fifty frames"
);
let mut wrong = 0usize;
let mut first: Option<(usize, usize)> = None;
for (frame, payload) in payloads.iter().enumerate() {
let at = MAGIC + frame * stride;
assert_eq!(
file[at],
rate.toc_byte(),
"mode {mode} frame {frame}: table-of-contents byte"
);
let expected = &file[at + 1..at + 1 + rate.packed_bytes()];
if payload.as_slice() != expected {
wrong += 1;
if first.is_none() {
let byte = payload
.iter()
.zip(expected.iter())
.position(|(a, b)| a != b)
.expect("the frames differ somewhere");
first = Some((frame, byte));
}
}
}
if wrong == 0 {
exact.push(mode);
} else {
let (frame, byte) = first.expect("a first difference");
failures.push(format!(
"mode {mode}: {wrong}/{FRAMES} frames differ, first at frame {frame} byte {byte}"
));
}
}
assert!(
failures.is_empty(),
"byte-exact at modes {exact:?}; {}",
failures.join("; ")
);
}
#[test]
fn a_one_lsb_change_to_the_input_moves_the_bitstream() {
for mode in 0..8u8 {
let rate = Rate::from_index(mode).expect("a speech mode");
let baseline = {
let mut encoder = NbEncoder::new();
(0..FRAMES)
.map(|f| encoder.encode_frame(&input_frame(f), rate))
.collect::<Vec<_>>()
};
let nudged = {
let mut encoder = NbEncoder::new();
(0..FRAMES)
.map(|f| {
let mut pcm = input_frame(f);
if f == 6 {
pcm[100] = pcm[100].wrapping_add(8);
}
encoder.encode_frame(&pcm, rate)
})
.collect::<Vec<_>>()
};
assert_ne!(
baseline, nudged,
"mode {mode}: a one-LSB change to sample 1060 left the bitstream \
unchanged, so the byte-exactness test proves nothing"
);
}
}
#[test]
fn the_parameter_writer_fills_the_frame_exactly() {
for mode in 0..8u8 {
let rate = Rate::from_index(mode).expect("a speech mode");
let mut encoder = NbEncoder::new();
let mut prms = Parameters::new();
encoder.encode_into(&input_frame(0), rate, &mut prms);
assert_eq!(
prms.words.len(),
rate.widths().len(),
"mode {mode}: parameter count"
);
let packed = prms.pack(rate);
assert_eq!(
packed.len(),
rate.packed_bytes(),
"mode {mode}: payload size"
);
}
}
#[test]
fn the_payload_round_trips_through_the_decoder_s_own_unpacking() {
for mode in 0..8u8 {
let rate = Rate::from_index(mode).expect("a speech mode");
let mut encoder = NbEncoder::new();
let mut prms = Parameters::new();
encoder.encode_into(&input_frame(0), rate, &mut prms);
let packed = prms.pack(rate);
let recovered = parse(mode, &packed).expect("the payload parses");
assert_eq!(
recovered, prms.words,
"mode {mode}: packing is not the inverse of unpacking"
);
}
}
#[test]
fn degenerate_input_produces_a_frame_rather_than_a_panic() {
for mode in 0..8u8 {
let rate = Rate::from_index(mode).expect("a speech mode");
let mut encoder = NbEncoder::new();
for frame in 0..8 {
let mut pcm = [0i16; L_FRAME];
for (i, slot) in pcm.iter_mut().enumerate() {
*slot = match frame % 4 {
0 => 0,
1 => i16::MIN,
2 => {
if i % 2 == 0 {
i16::MAX
} else {
i16::MIN
}
}
_ => i16::MAX,
};
}
let payload = encoder.encode_frame(&pcm, rate);
assert_eq!(payload.len(), rate.packed_bytes(), "mode {mode}");
}
}
}
#[test]
fn the_encoder_carries_state_across_frames_and_repeats_itself() {
for mode in 0..8u8 {
let rate = Rate::from_index(mode).expect("a speech mode");
let stream: Vec<Vec<u8>> = {
let mut encoder = NbEncoder::new();
(0..6)
.map(|f| encoder.encode_frame(&input_frame(f), rate))
.collect()
};
let again: Vec<Vec<u8>> = {
let mut encoder = NbEncoder::new();
(0..6)
.map(|f| encoder.encode_frame(&input_frame(f), rate))
.collect()
};
assert_eq!(stream, again, "mode {mode}: encoding is not deterministic");
let alone = NbEncoder::new().encode_frame(&input_frame(4), rate);
assert_ne!(
alone, stream[4],
"mode {mode}: a cold encoder reproduced frame 4 of the stream, so \
nothing is being carried between frames"
);
}
}
#[test]
#[ignore = "needs RVOIP_NB_TRACE_DIR; see the doc comment"]
fn all_rates_all_frames_against_regenerated_traces() {
let dir = std::env::var("RVOIP_NB_TRACE_DIR")
.expect("set RVOIP_NB_TRACE_DIR to the directory holding nbtrace0..7");
println!();
for mode in 0..8u8 {
let path = format!("{dir}/nbtrace{mode}/trace.txt");
let text = std::fs::read_to_string(&path).expect("a regenerated trace");
let (_, got) = run(mode, FRAMES);
let mut rows = 0usize;
let mut values = 0usize;
for line in text.lines() {
let mut field = line.split_whitespace();
if field.next() != Some("T") {
continue;
}
let frame: usize = field.next().expect("frame").parse().expect("frame");
let subframe: i32 = field.next().expect("subframe").parse().expect("subframe");
let name = field.next().expect("name").to_owned();
let expected: Vec<i32> = field.map(|v| v.parse().expect("value")).collect();
let actual = got.row(frame, subframe, &name).unwrap_or_else(|| {
panic!("mode {mode} frame {frame}/{subframe}: never produced {name}")
});
assert_eq!(actual.len(), expected.len(), "{name} length");
for (i, (&a, &e)) in actual.iter().zip(expected.iter()).enumerate() {
assert_eq!(
a, e,
"mode {mode} frame {frame} subframe {subframe}: \
{name}[{i}] = {a} but TS 26.073 gives {e}"
);
values += 1;
}
rows += 1;
}
assert!(rows > 2800, "mode {mode}: only {rows} rows compared");
println!("mode {mode}: {rows} rows, {values} values, all match");
}
}
#[test]
#[ignore = "independent verification"]
fn independently_reproduce_the_reference_files() {
let pcm: Vec<i16> = include_bytes!("../../testdata/amrnb_enc_input.pcm")
.chunks_exact(2)
.map(|b| i16::from_le_bytes([b[0], b[1]]))
.collect();
assert_eq!(pcm.len(), 50 * 160);
println!();
let refs: [&[u8]; 8] = [
include_bytes!("../../testdata/amrnb_enc_mode0.amr"),
include_bytes!("../../testdata/amrnb_enc_mode1.amr"),
include_bytes!("../../testdata/amrnb_enc_mode2.amr"),
include_bytes!("../../testdata/amrnb_enc_mode3.amr"),
include_bytes!("../../testdata/amrnb_enc_mode4.amr"),
include_bytes!("../../testdata/amrnb_enc_mode5.amr"),
include_bytes!("../../testdata/amrnb_enc_mode6.amr"),
include_bytes!("../../testdata/amrnb_enc_mode7.amr"),
];
for mode in 0..8u8 {
let rate = Rate::from_index(mode).expect("a speech mode");
let mut enc = NbEncoder::new();
let mut out: Vec<u8> = b"#!AMR\n".to_vec();
for frame in pcm.chunks_exact(160) {
let mut block = [0i16; 160];
block.copy_from_slice(frame);
out.push((mode << 3) | 0x04);
out.extend_from_slice(&enc.encode_frame(&block, rate));
}
let want = refs[usize::from(mode)];
assert_eq!(out.len(), want.len(), "mode {mode}: file length");
let first = out.iter().zip(want).position(|(a, b)| a != b);
assert!(
first.is_none(),
"mode {mode}: first differs at byte {}",
first.unwrap()
);
let mut nudged = pcm.clone();
nudged[1000] = nudged[1000].wrapping_add(1024);
let mut enc = NbEncoder::new();
let mut other: Vec<u8> = b"#!AMR\n".to_vec();
for frame in nudged.chunks_exact(160) {
let mut block = [0i16; 160];
block.copy_from_slice(frame);
other.push((mode << 3) | 0x04);
other.extend_from_slice(&enc.encode_frame(&block, rate));
}
assert_ne!(
other, out,
"mode {mode}: the input change left the bitstream alone"
);
println!("mode {mode}: {} bytes byte-identical", out.len());
}
}
}