use super::mode::{AmrMode, AmrVariant};
use super::AmrCodec;
use crate::types::{CodecConfig, CodedFrame, FrameKind, VariableRateCodec};
fn soak_seconds() -> u64 {
std::env::var("RVOIP_AMR_SOAK_SECS")
.ok()
.and_then(|value| value.parse().ok())
.unwrap_or(30)
}
fn resident_kb() -> Option<u64> {
#[cfg(target_os = "linux")]
{
let status = std::fs::read_to_string("/proc/self/status").ok()?;
for line in status.lines() {
if let Some(rest) = line.strip_prefix("VmRSS:") {
return rest.split_whitespace().next()?.parse().ok();
}
}
None
}
#[cfg(target_os = "macos")]
{
let pid = std::process::id();
let output = std::process::Command::new("ps")
.args(["-o", "rss=", "-p", &pid.to_string()])
.output()
.ok()?;
String::from_utf8(output.stdout).ok()?.trim().parse().ok()
}
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
{
None
}
}
#[allow(
clippy::cast_precision_loss,
clippy::cast_possible_truncation,
clippy::suboptimal_flops
)]
fn frame_samples(variant: AmrVariant, index: usize) -> Vec<i16> {
let samples = variant.frame_samples();
let rate = f64::from(variant.sample_rate());
if (index / 50) % 8 == 7 {
return vec![0i16; samples];
}
let mut seed =
0x2545_f491u32.wrapping_add(u32::try_from(index % u32::MAX as usize).unwrap_or(0));
(0..samples)
.map(|i| {
let t = ((index * samples + i) as f64) / rate;
seed = seed.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
let noise = f64::from((seed >> 22) as i16 % 128) * 1.5;
let envelope = 0.55 + 0.45 * (t * 0.37 * std::f64::consts::TAU).sin();
let tone = (t * 230.0 * std::f64::consts::TAU).sin() * 7000.0
+ (t * 690.0 * std::f64::consts::TAU).sin() * 2600.0;
((tone * envelope) + noise).clamp(-32000.0, 32000.0) as i16
})
.collect()
}
#[allow(clippy::cast_precision_loss)]
fn soak_variant(variant: AmrVariant) {
let seconds = soak_seconds();
let frames_per_second = 50; let total_frames = usize::try_from(seconds).unwrap_or(usize::MAX) * frames_per_second;
let modes = AmrMode::all(variant);
assert!(!modes.is_empty(), "variant must have modes");
let mut config = match variant {
AmrVariant::NarrowBand => CodecConfig::amr_nb(),
AmrVariant::WideBand => CodecConfig::amr_wb(),
};
config.parameters.amr.mode_set = 0;
config.parameters.amr.dtx = true;
let mut encoder = AmrCodec::new(&config).expect("encoder constructs");
let mut decoder = AmrCodec::new(&config).expect("decoder constructs");
let expected_samples = variant.frame_samples();
let baseline_kb = resident_kb();
let mut peak_kb = baseline_kb.unwrap_or(0);
let mut decoded_frames = 0usize;
let mut concealed_frames = 0usize;
let mut dtx_frames = 0usize;
let mut window_energy: f64 = 0.0;
let mut window_frames = 0usize;
let mut silent_windows = 0usize;
for index in 0..total_frames {
if index % 7 == 0 {
let mode = modes[(index / 7) % modes.len()];
encoder
.set_mode(mode.index())
.expect("every mode in the set is selectable");
}
let pcm = frame_samples(variant, index);
let coded = encoder.encode_frame(&pcm).expect("speech frame encodes");
let isolated = index % 43 == 42;
let burst = (index % 349) >= 346;
let lost = isolated || burst;
let output = if lost {
concealed_frames += 1;
decoder
.decode_frame(&CodedFrame {
kind: FrameKind::Lost,
mode: coded.mode,
quality_ok: false,
data: Vec::new(),
})
.expect("concealment produces a frame")
} else {
if coded.kind != FrameKind::Speech {
dtx_frames += 1;
}
decoder.decode_frame(&coded).expect("coded frame decodes")
};
assert_eq!(
output.len(),
expected_samples,
"frame {index}: decoder must always produce a full frame"
);
decoded_frames += 1;
let energy: f64 = output
.iter()
.map(|&sample| f64::from(sample) * f64::from(sample))
.sum();
window_energy += energy;
window_frames += 1;
if window_frames == frames_per_second {
if window_energy / (window_frames * expected_samples) as f64 <= 1.0 {
silent_windows += 1;
}
window_energy = 0.0;
window_frames = 0;
}
if index % 2_000 == 0 {
if let Some(kb) = resident_kb() {
peak_kb = peak_kb.max(kb);
}
}
}
assert_eq!(decoded_frames, total_frames);
assert!(
concealed_frames > 0,
"the loss pattern must actually have concealed frames"
);
let seconds_run = total_frames / frames_per_second;
assert!(
silent_windows * 3 < seconds_run.max(3),
"decoded audio was silent for {silent_windows} of {seconds_run} seconds — \
the codec appears to have degraded into silence"
);
if let (Some(baseline), true) = (baseline_kb, peak_kb > 0) {
let allowed = baseline + 64 * 1024;
assert!(
peak_kb <= allowed,
"resident memory grew from {baseline} kB to {peak_kb} kB over \
{total_frames} frames — expected a flat steady state"
);
}
println!(
"{variant:?}: {total_frames} frames ({seconds}s), {concealed_frames} concealed, \
{dtx_frames} non-speech, rss {baseline_kb:?} kB -> {peak_kb} kB"
);
}
#[test]
#[ignore = "long-running; see tools/run-amr-soak.sh"]
#[cfg(feature = "amr-nb")]
fn narrowband_survives_a_long_call() {
soak_variant(AmrVariant::NarrowBand);
}
#[test]
#[ignore = "long-running; see tools/run-amr-soak.sh"]
#[cfg(feature = "amr-wb")]
fn wideband_survives_a_long_call() {
soak_variant(AmrVariant::WideBand);
}