use std::num::NonZeroU32;
use crate::corpus::{Class, Item};
use crate::degrade::Degrade;
use crate::libopus::{self, CDecoder, CEncoder};
use crate::signals::{self, REF_RATE, XorShift};
use audiopus_sys as ffi;
use opus1::internals::eval::compare::{Reference, opus_compare};
use opus1::internals::eval::demo::{BitPacket, decode_bitstream};
use opus1::internals::eval::moc::{moc, moc_relative};
use opus1::{
Application, Bandwidth, Bitrate, Channels, ForcedMode, InbandFec, OpusDecoder, OpusEncoder, SampleRate,
};
use std::collections::{BTreeMap, HashMap};
use std::sync::OnceLock;
use std::sync::atomic::{AtomicUsize, Ordering};
const MAX_PACKET: usize = 1276 * 6;
const LOSS_SEED: u64 = 0x105E_0000_DEC0_DE01;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum App {
Voip,
Audio,
LowDelay,
}
impl App {
pub fn name(self) -> &'static str {
match self {
App::Voip => "voip",
App::Audio => "audio",
App::LowDelay => "lowdelay",
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Mode {
Silk,
Hybrid,
Celt,
}
fn bandwidth_name(bw: Bandwidth) -> &'static str {
match bw {
Bandwidth::Narrowband => "nb",
Bandwidth::Mediumband => "mb",
Bandwidth::Wideband => "wb",
Bandwidth::Superwideband => "swb",
Bandwidth::Fullband => "fb",
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Codec {
Opus1,
Libopus,
}
impl Codec {
pub fn name(self) -> &'static str {
match self {
Codec::Opus1 => "opus1",
Codec::Libopus => "libopus",
}
}
}
#[derive(Clone, Debug)]
pub struct Condition {
pub name: String,
pub group: String,
pub app: App,
pub rate: u32,
pub channels: usize,
pub bitrate: i32,
pub cbr: bool,
pub complexity: i32,
pub frame_us: u32,
pub fec: bool,
pub loss_perc: i32,
pub decode_loss: u32,
pub mode: Option<Mode>,
pub bandwidth: Option<Bandwidth>,
pub schedule: Vec<(f64, i32)>,
pub tags: Vec<&'static str>,
}
impl Condition {
fn opus1_format(&self) -> Result<(SampleRate, Channels), String> {
let rate = SampleRate::try_from(self.rate).map_err(|e| format!("{} Hz: {e}", self.rate))?;
let channels = Channels::try_from(self.channels).map_err(|e| format!("{} channels: {e}", self.channels))?;
Ok((rate, channels))
}
pub fn new(group: &str, app: App, rate: u32, channels: usize, bitrate: i32, complexity: i32, frame_us: u32) -> Self {
let mut c = Self {
name: String::new(),
group: group.to_string(),
app,
rate,
channels,
bitrate,
cbr: false,
complexity,
frame_us,
fec: false,
loss_perc: 0,
decode_loss: 0,
mode: None,
bandwidth: None,
schedule: Vec::new(),
tags: Vec::new(),
};
c.rename();
c
}
pub fn cbr(mut self) -> Self {
self.cbr = true;
self.rename();
self
}
pub fn fec(mut self, loss_perc: i32, decode_loss: u32) -> Self {
self.fec = true;
self.loss_perc = loss_perc;
self.decode_loss = decode_loss;
self.rename();
self
}
pub fn forced(mut self, mode: Mode) -> Self {
self.mode = Some(mode);
self.rename();
self
}
pub fn bandwidth(mut self, bandwidth: Bandwidth) -> Self {
self.bandwidth = Some(bandwidth);
self.rename();
self
}
pub fn schedule(mut self, schedule: Vec<(f64, i32)>) -> Self {
self.schedule = schedule;
self.rename();
self
}
pub fn frame(&self) -> usize {
(self.rate as u64 * self.frame_us as u64 / 1_000_000) as usize
}
pub fn bitrate_at(&self, t: f64) -> i32 {
self.schedule.iter().rev().find(|(start, _)| *start <= t).map_or(self.bitrate, |&(_, b)| b)
}
pub fn runs_analysis(&self) -> bool {
self.complexity >= 7 && self.rate >= 16_000 && self.app != App::LowDelay
}
fn rename(&mut self) {
let frame = if self.frame_us.is_multiple_of(1000) {
format!("{}ms", self.frame_us / 1000)
} else {
format!("{}ms", self.frame_us as f64 / 1000.0)
};
let mut name = format!(
"{}_{}k_{}_{}k_{}_c{}_{}",
self.app.name(),
self.rate / 1000,
if self.channels == 2 { "stereo" } else { "mono" },
self.bitrate as f64 / 1000.0,
if self.cbr { "cbr" } else { "vbr" },
self.complexity,
frame
);
if self.fec {
name += &format!("_fec{}", self.loss_perc);
}
if self.decode_loss > 0 {
name += &format!("_loss{}", self.decode_loss);
}
if let Some(mode) = self.mode {
name += match mode {
Mode::Silk => "_silk",
Mode::Hybrid => "_hybrid",
Mode::Celt => "_celt",
};
}
if let Some(bw) = self.bandwidth {
name += "_";
name += bandwidth_name(bw);
}
if !self.schedule.is_empty() {
name += "_switch";
}
self.name = name;
self.tags = if self.runs_analysis() { vec!["analysis"] } else { Vec::new() };
}
}
#[derive(Default)]
pub struct Packets {
data: Vec<u8>,
spans: Vec<(usize, usize)>,
ranges: Vec<u32>,
pub lookahead: usize,
}
impl Packets {
fn push(&mut self, packet: &[u8], range: u32) {
self.spans.push((self.data.len(), packet.len()));
self.data.extend_from_slice(packet);
self.ranges.push(range);
}
pub fn len(&self) -> usize {
self.spans.len()
}
pub fn get(&self, i: usize) -> &[u8] {
let (offset, len) = self.spans[i];
&self.data[offset..offset + len]
}
pub fn bytes(&self) -> usize {
self.data.len()
}
pub fn toc_label(&self) -> String {
let mut histogram: BTreeMap<u8, usize> = BTreeMap::new();
for i in 0..self.len() {
let key = self.get(i).first().map_or(0xFF, |toc| toc >> 2);
*histogram.entry(key).or_insert(0) += 1;
}
let parts: Vec<String> = histogram.iter().map(|(&k, &n)| format!("{} x{n}", toc_name(k))).collect();
parts.join(", ")
}
}
fn toc_name(key: u8) -> String {
if key == 0xFF {
return "empty".to_string();
}
let config = (key >> 1) as usize;
let channels = if key & 1 == 1 { "stereo" } else { "mono" };
const SILK_MS: [&str; 4] = ["10ms", "20ms", "40ms", "60ms"];
const CELT_MS: [&str; 4] = ["2.5ms", "5ms", "10ms", "20ms"];
let (mode, bandwidth, duration) = match config {
0..=3 => ("silk", "nb", SILK_MS[config & 3]),
4..=7 => ("silk", "mb", SILK_MS[config & 3]),
8..=11 => ("silk", "wb", SILK_MS[config & 3]),
12..=13 => ("hybrid", "swb", SILK_MS[config & 1]),
14..=15 => ("hybrid", "fb", SILK_MS[config & 1]),
16..=19 => ("celt", "nb", CELT_MS[config & 3]),
20..=23 => ("celt", "wb", CELT_MS[config & 3]),
24..=27 => ("celt", "swb", CELT_MS[config & 3]),
_ => ("celt", "fb", CELT_MS[config & 3]),
};
format!("{mode}/{bandwidth}/{duration}/{channels}")
}
pub fn encoder_input(item: &Item, rate: u32, channels: usize) -> Vec<f32> {
let parts: Vec<Vec<f64>> = match (item.channels, channels) {
(2, 1) => vec![item.mono_f64()],
(1, 2) => {
let m = item.mono_f64();
vec![m.clone(), m]
}
_ => {
let all: Vec<f64> = item.reference.iter().map(|&s| s as f64 / 32768.0).collect();
signals::deinterleave(&all, item.channels)
}
};
let parts: Vec<Vec<f64>> = if rate == REF_RATE {
parts
} else {
let resampler = signals::Resampler::new(REF_RATE, rate);
parts.iter().map(|p| resampler.run(p)).collect()
};
signals::interleave(&parts).iter().map(|&v| v as f32).collect()
}
pub struct Inputs {
map: HashMap<(usize, u32, usize), OnceLock<Vec<f32>>>,
}
impl Inputs {
pub fn new(keys: impl IntoIterator<Item = (usize, u32, usize)>) -> Self {
Self { map: keys.into_iter().map(|k| (k, OnceLock::new())).collect() }
}
pub fn get(&self, items: &[Item], item: usize, rate: u32, channels: usize) -> &[f32] {
self.map
.get(&(item, rate, channels))
.expect("input registered")
.get_or_init(|| encoder_input(&items[item], rate, channels))
}
}
enum Encoder {
Opus1(Box<OpusEncoder>),
Libopus(CEncoder),
}
impl Encoder {
fn new(codec: Codec, cond: &Condition, degrade: Option<&Degrade>) -> Result<Self, String> {
match codec {
Codec::Opus1 => {
let application = match cond.app {
App::Voip => Application::Voip,
App::Audio => Application::Audio,
App::LowDelay => Application::RestrictedLowDelay,
};
let (rate, channels) = cond.opus1_format()?;
let mut e = OpusEncoder::new(rate, channels, application).map_err(|e| format!("OpusEncoder::new: {e}"))?;
let complexity = u8::try_from(cond.complexity).map_err(|_| "set_complexity: invalid complexity")?;
e.set_complexity(complexity).map_err(|e| format!("set_complexity: {e}"))?;
e.set_vbr(!cond.cbr);
e.set_inband_fec(if cond.fec { InbandFec::On } else { InbandFec::Off });
let loss_perc = u8::try_from(cond.loss_perc).map_err(|_| "set_packet_loss_perc: invalid packet loss percentage")?;
e.set_packet_loss_perc(loss_perc).map_err(|e| format!("set_packet_loss_perc: {e}"))?;
if let Some(mode) = cond.mode {
e.set_force_mode(Some(match mode {
Mode::Silk => ForcedMode::SilkOnly,
Mode::Hybrid => ForcedMode::Hybrid,
Mode::Celt => ForcedMode::CeltOnly,
}));
}
if let Some(bw) = cond.bandwidth {
e.set_bandwidth(Some(bw));
}
if let Some(mask) = degrade.and_then(|d| d.energy_mask(cond)) {
e.set_energy_mask(Some(&mask)).map_err(|e| format!("set_energy_mask: {e}"))?;
}
Ok(Encoder::Opus1(Box::new(e)))
}
Codec::Libopus => {
if degrade.is_some_and(|d| d.energy_mask(cond).is_some()) {
return Err("the energy mask degradation applies to opus1 only".into());
}
let mut e = CEncoder::new(cond.rate, cond.channels, libopus_application(cond.app))?;
e.ctl(ffi::OPUS_SET_COMPLEXITY_REQUEST, cond.complexity)?;
e.ctl(ffi::OPUS_SET_VBR_REQUEST, i32::from(!cond.cbr))?;
e.ctl(ffi::OPUS_SET_INBAND_FEC_REQUEST, i32::from(cond.fec))?;
e.ctl(ffi::OPUS_SET_PACKET_LOSS_PERC_REQUEST, cond.loss_perc)?;
if let Some(mode) = cond.mode {
e.set_force_mode(match mode {
Mode::Silk => libopus::MODE_SILK_ONLY,
Mode::Hybrid => libopus::MODE_HYBRID,
Mode::Celt => libopus::MODE_CELT_ONLY,
})?;
}
if let Some(bw) = cond.bandwidth {
e.ctl(ffi::OPUS_SET_BANDWIDTH_REQUEST, bw.to_raw())?;
}
Ok(Encoder::Libopus(e))
}
}
}
fn set_bitrate(&mut self, bitrate: i32) -> Result<(), String> {
match self {
Encoder::Opus1(e) => {
let bitrate = u32::try_from(bitrate).ok().and_then(NonZeroU32::new).ok_or("set_bitrate: invalid bitrate")?;
e.set_bitrate(Bitrate::BitsPerSecond(bitrate));
Ok(())
}
Encoder::Libopus(e) => e.ctl(ffi::OPUS_SET_BITRATE_REQUEST, bitrate),
}
}
fn lookahead(&self) -> Result<usize, String> {
match self {
Encoder::Opus1(e) => Ok(e.lookahead()),
Encoder::Libopus(e) => Ok(e.get(ffi::OPUS_GET_LOOKAHEAD_REQUEST)? as usize),
}
}
fn encode(&mut self, pcm: &[f32], frame: usize, out: &mut [u8]) -> Result<usize, String> {
match self {
Encoder::Opus1(e) => e.encode(pcm, frame, out).map_err(|e| format!("encode: {e}")),
Encoder::Libopus(e) => e.encode(pcm, frame, out),
}
}
fn final_range(&self) -> u32 {
match self {
Encoder::Opus1(e) => e.final_range(),
Encoder::Libopus(e) => e.final_range(),
}
}
fn settings(&self, cond: &Condition) -> Result<Settings, String> {
Ok(match self {
Encoder::Opus1(e) => Settings {
application: libopus_application(cond.app),
sample_rate: e.sampling_rate().hz() as i32,
channels: e.channels().count() as i32,
bitrate: e.bitrate(),
vbr: e.vbr(),
vbr_constraint: e.vbr_constraint(),
complexity: i32::from(e.complexity()),
inband_fec: e.inband_fec().to_raw(),
packet_loss_perc: i32::from(e.packet_loss_perc()),
force_channels: e.force_channels().map_or(OPUS_AUTO, |c| c.count() as i32),
expert_frame_duration: e.expert_frame_duration() as i32,
prediction_disabled: e.prediction_disabled(),
lookahead: e.lookahead() as i32,
max_bandwidth: OPUS_BANDWIDTH_FULLBAND,
signal: OPUS_AUTO,
lsb_depth: 24,
dtx: false,
phase_inversion_disabled: false,
},
Encoder::Libopus(e) => Settings {
application: e.get(ffi::OPUS_GET_APPLICATION_REQUEST)?,
sample_rate: e.get(ffi::OPUS_GET_SAMPLE_RATE_REQUEST)?,
channels: e.channels() as i32,
bitrate: e.get(ffi::OPUS_GET_BITRATE_REQUEST)?,
vbr: e.get(ffi::OPUS_GET_VBR_REQUEST)? != 0,
vbr_constraint: e.get(ffi::OPUS_GET_VBR_CONSTRAINT_REQUEST)? != 0,
complexity: e.get(ffi::OPUS_GET_COMPLEXITY_REQUEST)?,
inband_fec: e.get(ffi::OPUS_GET_INBAND_FEC_REQUEST)?,
packet_loss_perc: e.get(ffi::OPUS_GET_PACKET_LOSS_PERC_REQUEST)?,
force_channels: e.get(ffi::OPUS_GET_FORCE_CHANNELS_REQUEST)?,
expert_frame_duration: e.get(ffi::OPUS_GET_EXPERT_FRAME_DURATION_REQUEST)?,
prediction_disabled: e.get(ffi::OPUS_GET_PREDICTION_DISABLED_REQUEST)? != 0,
lookahead: e.get(ffi::OPUS_GET_LOOKAHEAD_REQUEST)?,
max_bandwidth: e.get(ffi::OPUS_GET_MAX_BANDWIDTH_REQUEST)?,
signal: e.get(ffi::OPUS_GET_SIGNAL_REQUEST)?,
lsb_depth: e.get(ffi::OPUS_GET_LSB_DEPTH_REQUEST)?,
dtx: e.get(ffi::OPUS_GET_DTX_REQUEST)? != 0,
phase_inversion_disabled: e.get(ffi::OPUS_GET_PHASE_INVERSION_DISABLED_REQUEST)? != 0,
},
})
}
}
const OPUS_AUTO: i32 = -1000;
const OPUS_BANDWIDTH_FULLBAND: i32 = 1105;
fn libopus_application(app: App) -> i32 {
match app {
App::Voip => ffi::OPUS_APPLICATION_VOIP,
App::Audio => ffi::OPUS_APPLICATION_AUDIO,
App::LowDelay => ffi::OPUS_APPLICATION_RESTRICTED_LOWDELAY,
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Settings {
pub application: i32,
pub sample_rate: i32,
pub channels: i32,
pub bitrate: i32,
pub vbr: bool,
pub vbr_constraint: bool,
pub complexity: i32,
pub inband_fec: i32,
pub packet_loss_perc: i32,
pub force_channels: i32,
pub expert_frame_duration: i32,
pub prediction_disabled: bool,
pub lookahead: i32,
pub max_bandwidth: i32,
pub signal: i32,
pub lsb_depth: i32,
pub dtx: bool,
pub phase_inversion_disabled: bool,
}
pub fn encoder_settings(codec: Codec, cond: &Condition) -> Result<Settings, String> {
let mut encoder = Encoder::new(codec, cond, None)?;
encoder.set_bitrate(cond.bitrate_at(0.0))?;
encoder.settings(cond)
}
pub fn check_settings(conditions: &[Condition]) -> Result<(), String> {
for cond in conditions {
let opus1 = encoder_settings(Codec::Opus1, cond)?;
let libopus = encoder_settings(Codec::Libopus, cond)?;
if opus1 != libopus {
return Err(format!("{}: the encoders differ in their settings: opus1 {opus1:?}, libopus {libopus:?}", cond.name));
}
}
Ok(())
}
pub fn encode(codec: Codec, cond: &Condition, input: &[f32], degrade: Option<&Degrade>) -> Result<Packets, String> {
let mut encoder = Encoder::new(codec, cond, degrade)?;
let scale = degrade.map_or(1.0, |d| d.bitrate_scale());
let frame = cond.frame();
let c = cond.channels;
let n = input.len() / c;
let lookahead = encoder.lookahead()?;
let frames = (n + lookahead).div_ceil(frame) + 1;
let mut padded = input.to_vec();
padded.resize(frames * frame * c, 0.0);
let mut out = vec![0u8; MAX_PACKET];
let mut packets = Packets { lookahead, ..Packets::default() };
let mut current = None;
for (k, pcm) in padded.chunks_exact(frame * c).enumerate() {
let t = (k * frame) as f64 / cond.rate as f64;
let bitrate = (cond.bitrate_at(t) as f64 * scale).round() as i32;
if current != Some(bitrate) {
encoder.set_bitrate(bitrate)?;
current = Some(bitrate);
}
let len = encoder.encode(pcm, frame, &mut out)?;
packets.push(&out[..len], encoder.final_range());
}
Ok(packets)
}
#[derive(Clone, Debug)]
pub struct Stream {
pub bytes: usize,
pub packets: usize,
pub kbps: f64,
pub toc: String,
pub lost: usize,
pub fec_calls: usize,
pub range_checks: usize,
}
pub fn loss_pattern(cond: &Condition, packets: usize) -> Vec<bool> {
let mut rng = XorShift(LOSS_SEED);
(0..packets)
.map(|i| {
let draw = rng.below(100) < cond.decode_loss as u64;
draw && i > 0 && i + 2 < packets
})
.collect()
}
pub fn decode(decoder: Codec, cond: &Condition, packets: &Packets, n: usize) -> Result<(Vec<i16>, Stream), String> {
let lost = loss_pattern(cond, packets.len());
let records: Vec<BitPacket<'_>> = (0..packets.len())
.map(|i| {
if lost[i] {
BitPacket { data: &[], enc_final_range: 0 }
} else {
BitPacket { data: packets.get(i), enc_final_range: packets.ranges[i] }
}
})
.collect();
let output = match decoder {
Codec::Libopus => decode_bitstream(&mut CDecoder::new(cond.rate, cond.channels)?, &records),
Codec::Opus1 => {
let (rate, channels) = cond.opus1_format()?;
let mut d = OpusDecoder::new(rate, channels).map_err(|e| format!("OpusDecoder::new: {e}"))?;
decode_bitstream(&mut d, &records)
}
};
if let Some(m) = output.range_mismatch {
return Err(format!(
"final range mismatch at packet {}: encoder {:08x}, decoder {:08x}",
m.packet, m.encoder, m.decoder
));
}
if output.decode_errors > 0 {
return Err(format!("{} decode error(s)", output.decode_errors));
}
let c = cond.channels;
let first = packets.lookahead * c;
let last = first + n * c;
if output.pcm.len() < last {
return Err(format!("decoded {} samples, need {}", output.pcm.len() / c, last / c));
}
let seconds = packets.len() as f64 * cond.frame_us as f64 / 1e6;
let stream = Stream {
bytes: packets.bytes(),
packets: packets.len(),
kbps: packets.bytes() as f64 * 8.0 / seconds.max(1e-9) / 1000.0,
toc: packets.toc_label(),
lost: output.lost,
fec_calls: output.fec_calls,
range_checks: output.range_checks,
};
Ok((output.pcm[first..last].to_vec(), stream))
}
#[derive(Clone, Debug)]
pub struct Side {
pub stream: Stream,
pub lookahead: usize,
pub err: f64,
pub q: f32,
pub worst_frame: usize,
pub snr_db: f64,
pub best_shift: i32,
pub moc: Option<f64>,
pub moc_vad: Option<f64>,
pub pcm: Option<Vec<i16>>,
}
pub fn measure(
cond: &Condition,
item: &Item,
input: &[f32],
pcm: Vec<i16>,
stream: Stream,
lookahead: usize,
keep_pcm: bool,
) -> Result<Side, String> {
let c = cond.channels;
let reference = if item.channels == 2 { Reference::Stereo(&item.reference) } else { Reference::Mono(&item.reference) };
let cmp = opus_compare(reference, &pcm, c, cond.rate).map_err(|e| format!("opus_compare: {e}"))?;
let snr_db = snr_db(input, &pcm);
let best_shift = best_shift(input, &pcm, c, cond.rate);
let (mut moc_plain, mut moc_vad) = (None, None);
if item.class == Class::Speech && c == 1 {
let reference = item.moc_reference();
let test: Vec<f64> = pcm.iter().map(|&s| s as f64 / 32768.0).collect();
let test16: Vec<f32> = if cond.rate == 16_000 {
test.iter().map(|&v| v as f32).collect()
} else {
signals::Resampler::new(cond.rate, 16_000).run(&test).iter().map(|&v| v as f32).collect()
};
moc_plain = moc(reference, &test16, false).ok();
moc_vad = moc(reference, &test16, true).ok();
}
Ok(Side {
stream,
lookahead,
err: cmp.err,
q: cmp.quality,
worst_frame: cmp.worst_frame,
snr_db,
best_shift,
moc: moc_plain,
moc_vad,
pcm: keep_pcm.then_some(pcm),
})
}
pub fn snr_db(reference: &[f32], test: &[i16]) -> f64 {
let (mut signal, mut noise) = (0.0f64, 0.0f64);
for (&r, &t) in reference.iter().zip(test) {
let r = r as f64;
let d = r - t as f64 / 32768.0;
signal += r * r;
noise += d * d;
}
if noise == 0.0 { 999.0 } else { 10.0 * (signal.max(1e-30) / noise).log10() }
}
pub fn best_shift(reference: &[f32], test: &[i16], channels: usize, rate: u32) -> i32 {
let max = (rate / 1000) as usize;
let window = (rate / 4) as usize;
let frames = reference.len().min(test.len()) / channels;
if frames < window + 2 * max + 1 {
return 0;
}
let energy = |start: usize| -> f64 { (start..start + window).map(|i| (reference[i * channels] as f64).powi(2)).sum() };
let mut start = max;
let mut loudest = f64::MIN;
let mut s = max;
while s + window + max <= frames {
let e = energy(s);
if e > loudest {
loudest = e;
start = s;
}
s += window / 2;
}
let correlation = |delay: isize| -> f64 {
(start..start + window)
.map(|i| reference[i * channels] as f64 * test[(i as isize + delay) as usize * channels] as f64)
.sum()
};
let mut best = (0isize, correlation(0));
for d in 1..=max as isize {
for delay in [-d, d] {
let v = correlation(delay);
if v > best.1 {
best = (delay, v);
}
}
}
best.0 as i32
}
pub fn run_side(
codec: Codec,
decoder: Codec,
cond: &Condition,
item: &Item,
input: &[f32],
degrade: Option<&Degrade>,
keep_pcm: bool,
) -> Result<Side, String> {
let packets = encode(codec, cond, input, degrade)?;
let n = input.len() / cond.channels;
let (mut pcm, stream) = decode(decoder, cond, &packets, n)?;
if let Some(d) = degrade {
d.apply_post(&mut pcm, cond.rate, cond.channels);
}
measure(cond, item, input, pcm, stream, packets.lookahead, keep_pcm)
}
pub fn degrade_side(side: &Side, d: &Degrade, cond: &Condition, item: &Item, input: &[f32]) -> Result<Side, String> {
let mut pcm = side.pcm.clone().ok_or("the side did not keep its signal")?;
d.apply_post(&mut pcm, cond.rate, cond.channels);
measure(cond, item, input, pcm, side.stream.clone(), side.lookahead, true)
}
#[derive(Clone, Debug)]
pub struct Pair {
pub opus1: Side,
pub libopus: Side,
pub ratio_err: f64,
pub delta_snr_db: f64,
pub ratio_bytes: f64,
pub moc_d: Option<f64>,
}
pub fn pair(opus1: Side, libopus: Side) -> Result<Pair, String> {
if opus1.lookahead != libopus.lookahead {
return Err(format!("look-ahead differs: opus1 {}, libopus {}", opus1.lookahead, libopus.lookahead));
}
let ratio_err = (opus1.err + 1e-6) / (libopus.err + 1e-6);
let delta_snr_db = opus1.snr_db - libopus.snr_db;
let ratio_bytes = opus1.stream.bytes as f64 / libopus.stream.bytes.max(1) as f64;
let moc_d = match (libopus.moc, opus1.moc) {
(Some(r), Some(t)) => Some(moc_relative(r, t)),
_ => None,
};
Ok(Pair { opus1, libopus, ratio_err, delta_snr_db, ratio_bytes, moc_d })
}
#[derive(Clone, Debug)]
pub struct Row {
pub cond: usize,
pub item: usize,
pub result: Result<Pair, String>,
}
pub fn parallel_map<T: Send>(count: usize, threads: usize, label: &str, f: impl Fn(usize) -> T + Sync) -> Vec<T> {
let next = AtomicUsize::new(0);
let done = AtomicUsize::new(0);
let slots: Vec<std::sync::Mutex<Option<T>>> = (0..count).map(|_| std::sync::Mutex::new(None)).collect();
let start = std::time::Instant::now();
std::thread::scope(|scope| {
for _ in 0..threads.max(1).min(count.max(1)) {
scope.spawn(|| {
loop {
let i = next.fetch_add(1, Ordering::Relaxed);
if i >= count {
break;
}
let value = f(i);
*slots[i].lock().unwrap() = Some(value);
let d = done.fetch_add(1, Ordering::Relaxed) + 1;
if !label.is_empty() && count >= 20 && d.is_multiple_of((count / 10).max(1)) {
eprintln!("{label}: {d}/{count} jobs, {:.1} s", start.elapsed().as_secs_f64());
}
}
});
}
});
slots.into_iter().map(|s| s.into_inner().unwrap().expect("every job ran")).collect()
}