#[cfg(feature = "dred")]
use alloc::boxed::Box;
use alloc::vec::Vec;
#[cfg(test)]
extern crate std;
use libm::fmaf;
#[cfg(not(feature = "fixed_point"))]
use crate::analysis::{
TonalityAnalysisState, run_analysis, tonality_analysis_init, tonality_analysis_reset,
tonality_get_info,
};
use crate::celt::AnalysisInfo;
use crate::celt::{
CELT_SIG_SCALE, CeltCoef, CeltEncodeError, CeltEncoderCtlError, CeltEncoderInitError,
EncoderCtlRequest as CeltEncoderCtlRequest, OpusRes, OwnedCeltEncoder, SilkInfo,
canonical_mode, celt_encode_with_ec, celt_exp2, celt_sqrt, frac_div32,
opus_custom_encoder_create, opus_custom_encoder_ctl, opus_select_arch,
};
use crate::opus_multistream::{OPUS_AUTO, OPUS_BITRATE_MAX};
use crate::packet::Bandwidth;
use crate::range::RangeEncoder;
#[cfg(feature = "dred")]
use crate::repacketizer::{OpusExtensionData, opus_packet_pad_with_extensions};
use crate::repacketizer::{OpusRepacketizer, RepacketizerError, opus_packet_pad};
use crate::silk::EncControl as SilkEncControl;
use crate::silk::enc_api::{PrefillMode, silk_encode, silk_init_encoder};
use crate::silk::errors::SilkError;
use crate::silk::lin2log::lin2log;
use crate::silk::log2lin::log2lin;
#[cfg(not(feature = "fixed_point"))]
use crate::silk::tuning_parameters::MAX_CONSECUTIVE_DTX;
use crate::silk::tuning_parameters::{
NB_SPEECH_FRAMES_BEFORE_DTX, VARIABLE_HP_MIN_CUTOFF_HZ, VARIABLE_HP_SMTH_COEF2,
};
#[cfg(feature = "dred")]
use crate::{
dred_constants::{DRED_MAX_DATA_SIZE, DRED_MIN_BYTES},
dred_encoder::{
DredEnc, dred_compute_latents, dred_encode_silk_frame, dred_encoder_init,
dred_encoder_load_model, dred_encoder_reset,
},
};
const MAX_CHANNELS: usize = 2;
const MAX_ENCODER_BUFFER: usize = 480;
const DELAY_BUFFER_SAMPLES: usize = MAX_ENCODER_BUFFER * 2;
const MAX_PACKET_BYTES: i32 = 1276 * 6;
const MAX_REPACKETIZER_BYTES: usize = MAX_PACKET_BYTES as usize + 6;
const MAX_FRAME_SAMPLES: usize = 5760;
const MAX_DELAY_COMPENSATION_SAMPLES: usize = 192;
const MAX_PCM_BUF_SAMPLES: usize =
(MAX_FRAME_SAMPLES + MAX_DELAY_COMPENSATION_SAMPLES) * MAX_CHANNELS;
const MAX_TMP_PREFILL_SAMPLES: usize = (MAX_ENCODER_BUFFER / 4) * MAX_CHANNELS;
#[cfg(test)]
mod opus_mode_trace {
extern crate std;
use core::sync::atomic::{AtomicUsize, Ordering};
use std::env;
use std::sync::OnceLock;
use crate::celt::AnalysisInfo;
pub(crate) struct TraceConfig {
frame: Option<usize>,
}
static TRACE_CONFIG: OnceLock<Option<TraceConfig>> = OnceLock::new();
static FRAME_INDEX: AtomicUsize = AtomicUsize::new(0);
pub(crate) fn begin_frame() -> Option<usize> {
if config().is_some() {
Some(FRAME_INDEX.fetch_add(1, Ordering::Relaxed))
} else {
None
}
}
fn config() -> Option<&'static TraceConfig> {
TRACE_CONFIG
.get_or_init(|| {
let enabled = match env::var("OPUS_TRACE_MODE") {
Ok(value) => !value.is_empty() && value != "0",
Err(_) => false,
};
if !enabled {
return None;
}
let frame = env::var("OPUS_TRACE_MODE_FRAME")
.ok()
.and_then(|value| value.parse::<usize>().ok());
Some(TraceConfig { frame })
})
.as_ref()
}
pub(crate) fn dump_if_match(
frame_idx: usize,
mode: i32,
prev_mode: i32,
equiv_rate: i32,
bandwidth_int: i32,
stream_channels: i32,
voice_ratio: i32,
is_silence: bool,
analysis: &AnalysisInfo,
) {
let Some(cfg) = config() else {
return;
};
if let Some(frame) = cfg.frame {
if frame != frame_idx {
return;
}
}
crate::test_trace::trace_println!("opus_mode[{frame_idx}].mode={mode}");
crate::test_trace::trace_println!("opus_mode[{frame_idx}].prev_mode={prev_mode}");
crate::test_trace::trace_println!("opus_mode[{frame_idx}].equiv_rate={equiv_rate}");
crate::test_trace::trace_println!("opus_mode[{frame_idx}].bandwidth={bandwidth_int}");
crate::test_trace::trace_println!(
"opus_mode[{frame_idx}].stream_channels={stream_channels}"
);
crate::test_trace::trace_println!("opus_mode[{frame_idx}].voice_ratio={voice_ratio}");
crate::test_trace::trace_println!(
"opus_mode[{frame_idx}].is_silence={}",
if is_silence { 1 } else { 0 }
);
crate::test_trace::trace_println!(
"opus_mode[{frame_idx}].analysis.valid={}",
if analysis.valid { 1 } else { 0 }
);
crate::test_trace::trace_println!(
"opus_mode[{frame_idx}].analysis.bandwidth={}",
analysis.bandwidth
);
crate::test_trace::trace_println!(
"opus_mode[{frame_idx}].analysis.activity={:.9e}",
analysis.activity as f64
);
crate::test_trace::trace_println!(
"opus_mode[{frame_idx}].analysis.tonality={:.9e}",
analysis.tonality as f64
);
crate::test_trace::trace_println!(
"opus_mode[{frame_idx}].analysis.tonality_slope={:.9e}",
analysis.tonality_slope as f64
);
crate::test_trace::trace_println!(
"opus_mode[{frame_idx}].analysis.noisiness={:.9e}",
analysis.noisiness as f64
);
crate::test_trace::trace_println!(
"opus_mode[{frame_idx}].analysis.music_prob={:.9e}",
analysis.music_prob as f64
);
crate::test_trace::trace_println!(
"opus_mode[{frame_idx}].analysis.activity_probability={:.9e}",
analysis.activity_probability as f64
);
crate::test_trace::trace_println!(
"opus_mode[{frame_idx}].analysis.max_pitch_ratio={:.9e}",
analysis.max_pitch_ratio as f64
);
}
}
#[cfg(test)]
mod opus_pcm_trace {
extern crate std;
use core::sync::atomic::{AtomicUsize, Ordering};
use std::env;
use std::sync::OnceLock;
use crate::celt::OpusRes;
#[derive(Clone, Copy)]
pub(crate) struct TraceConfig {
pub frame: Option<usize>,
pub want_bits: bool,
pub start: usize,
pub count: usize,
}
static TRACE_CONFIG: OnceLock<Option<TraceConfig>> = OnceLock::new();
static FRAME_INDEX: AtomicUsize = AtomicUsize::new(0);
static CURRENT_FRAME: AtomicUsize = AtomicUsize::new(usize::MAX);
fn config() -> Option<&'static TraceConfig> {
TRACE_CONFIG
.get_or_init(|| {
let enabled = match env::var("OPUS_TRACE_PCM_DUMP") {
Ok(value) => !value.is_empty() && value != "0",
Err(_) => false,
};
if !enabled {
return None;
}
let frame = env::var("OPUS_TRACE_PCM_FRAME")
.ok()
.and_then(|value| value.parse::<usize>().ok());
let want_bits = match env::var("OPUS_TRACE_PCM_BITS") {
Ok(value) => !value.is_empty() && value != "0",
Err(_) => false,
};
let start = env::var("OPUS_TRACE_PCM_START")
.ok()
.and_then(|value| value.parse::<usize>().ok())
.unwrap_or(0);
let count = env::var("OPUS_TRACE_PCM_COUNT")
.ok()
.and_then(|value| value.parse::<usize>().ok())
.unwrap_or(64);
Some(TraceConfig {
frame,
want_bits,
start,
count,
})
})
.as_ref()
}
pub(crate) fn begin_frame() -> Option<usize> {
if config().is_some() {
let idx = FRAME_INDEX.fetch_add(1, Ordering::Relaxed);
CURRENT_FRAME.store(idx, Ordering::Relaxed);
Some(idx)
} else {
None
}
}
pub(crate) fn current_frame() -> Option<usize> {
if config().is_some() {
let idx = CURRENT_FRAME.load(Ordering::Relaxed);
if idx == usize::MAX { None } else { Some(idx) }
} else {
None
}
}
pub(crate) fn config_copy() -> Option<TraceConfig> {
config().copied()
}
fn sample_bits(value: OpusRes) -> u32 {
value.to_bits()
}
fn sample_value(value: OpusRes) -> f64 {
value as f64
}
pub(crate) fn dump(tag: &str, frame_idx: usize, pcm: &[OpusRes], channels: usize, len: usize) {
let cfg = match config() {
Some(cfg) => cfg,
None => return,
};
if channels == 0 {
return;
}
if let Some(frame) = cfg.frame {
if frame != frame_idx {
return;
}
}
let max_len = pcm.len() / channels;
let len = len.min(max_len);
let start = cfg.start.min(len);
let end = start.saturating_add(cfg.count).min(len);
crate::test_trace::trace_println!("opus_pcm[{frame_idx}].{tag}.len={len}");
for ch in 0..channels {
for i in start..end {
let idx = i * channels + ch;
if idx >= pcm.len() {
continue;
}
let value = pcm[idx];
crate::test_trace::trace_println!(
"opus_pcm[{frame_idx}].{tag}.ch[{ch}].sample[{i}]={:.9e}",
sample_value(value)
);
if cfg.want_bits {
crate::test_trace::trace_println!(
"opus_pcm[{frame_idx}].{tag}.ch[{ch}].sample_bits[{i}]=0x{:08x}",
sample_bits(value)
);
}
}
}
}
}
#[cfg(test)]
mod opus_celt_budget_trace {
extern crate std;
use core::sync::atomic::{AtomicUsize, Ordering};
use std::env;
use std::sync::OnceLock;
pub(crate) struct TraceConfig {
frame: Option<usize>,
}
static TRACE_CONFIG: OnceLock<Option<TraceConfig>> = OnceLock::new();
static FRAME_INDEX: AtomicUsize = AtomicUsize::new(0);
pub(crate) fn begin_frame() -> Option<usize> {
if config().is_some() {
Some(FRAME_INDEX.fetch_add(1, Ordering::Relaxed))
} else {
None
}
}
fn config() -> Option<&'static TraceConfig> {
TRACE_CONFIG
.get_or_init(|| {
let enabled = match env::var("OPUS_TRACE_CELT_BUDGET") {
Ok(value) => !value.is_empty() && value != "0",
Err(_) => false,
};
if !enabled {
return None;
}
let frame = env::var("OPUS_TRACE_CELT_BUDGET_FRAME")
.ok()
.and_then(|value| value.parse::<usize>().ok());
Some(TraceConfig { frame })
})
.as_ref()
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn dump_if_match(
frame_idx: usize,
mode: i32,
max_data_bytes: i32,
max_frame_bytes: i32,
max_payload_bytes: i32,
frame_size: i32,
frame_rate: i32,
equiv_rate: i32,
bitrate_bps: i32,
use_vbr: bool,
vbr_constraint: bool,
channels: i32,
stream_channels: i32,
redundancy: bool,
celt_to_silk: bool,
to_celt: bool,
redundancy_bytes: i32,
nb_compr_bytes: i32,
tell_pre: i32,
tell_frac_pre: i32,
tell_post: i32,
tell_frac_post: i32,
) {
let Some(cfg) = config() else {
return;
};
if let Some(frame) = cfg.frame {
if frame != frame_idx {
return;
}
}
crate::test_trace::trace_println!("opus_celt_budget[{frame_idx}].mode={mode}");
crate::test_trace::trace_println!(
"opus_celt_budget[{frame_idx}].max_data_bytes={max_data_bytes}"
);
crate::test_trace::trace_println!(
"opus_celt_budget[{frame_idx}].max_frame_bytes={max_frame_bytes}"
);
crate::test_trace::trace_println!(
"opus_celt_budget[{frame_idx}].max_payload_bytes={max_payload_bytes}"
);
crate::test_trace::trace_println!("opus_celt_budget[{frame_idx}].frame_size={frame_size}");
crate::test_trace::trace_println!("opus_celt_budget[{frame_idx}].frame_rate={frame_rate}");
crate::test_trace::trace_println!("opus_celt_budget[{frame_idx}].equiv_rate={equiv_rate}");
crate::test_trace::trace_println!(
"opus_celt_budget[{frame_idx}].bitrate_bps={bitrate_bps}"
);
crate::test_trace::trace_println!(
"opus_celt_budget[{frame_idx}].use_vbr={}",
if use_vbr { 1 } else { 0 }
);
crate::test_trace::trace_println!(
"opus_celt_budget[{frame_idx}].vbr_constraint={}",
if vbr_constraint { 1 } else { 0 }
);
crate::test_trace::trace_println!("opus_celt_budget[{frame_idx}].channels={channels}");
crate::test_trace::trace_println!(
"opus_celt_budget[{frame_idx}].stream_channels={stream_channels}"
);
crate::test_trace::trace_println!(
"opus_celt_budget[{frame_idx}].redundancy={}",
if redundancy { 1 } else { 0 }
);
crate::test_trace::trace_println!(
"opus_celt_budget[{frame_idx}].celt_to_silk={}",
if celt_to_silk { 1 } else { 0 }
);
crate::test_trace::trace_println!(
"opus_celt_budget[{frame_idx}].to_celt={}",
if to_celt { 1 } else { 0 }
);
crate::test_trace::trace_println!(
"opus_celt_budget[{frame_idx}].redundancy_bytes={redundancy_bytes}"
);
crate::test_trace::trace_println!(
"opus_celt_budget[{frame_idx}].nb_compr_bytes={nb_compr_bytes}"
);
crate::test_trace::trace_println!("opus_celt_budget[{frame_idx}].tell_pre={tell_pre}");
crate::test_trace::trace_println!(
"opus_celt_budget[{frame_idx}].tell_frac_pre={tell_frac_pre}"
);
crate::test_trace::trace_println!("opus_celt_budget[{frame_idx}].tell_post={tell_post}");
crate::test_trace::trace_println!(
"opus_celt_budget[{frame_idx}].tell_frac_post={tell_frac_post}"
);
}
}
#[cfg(test)]
mod celt_pcm_trace {
extern crate std;
use std::env;
use std::sync::OnceLock;
#[derive(Clone, Copy)]
pub(crate) struct TraceConfig {
pub frame: Option<usize>,
pub want_bits: bool,
pub start: usize,
pub count: usize,
}
static TRACE_CONFIG: OnceLock<Option<TraceConfig>> = OnceLock::new();
pub(crate) fn config() -> Option<&'static TraceConfig> {
TRACE_CONFIG
.get_or_init(|| {
let enabled = match env::var("OPUS_TRACE_PCM_DUMP") {
Ok(value) => !value.is_empty() && value != "0",
Err(_) => false,
};
if !enabled {
return None;
}
let frame = env::var("CELT_TRACE_PCM_FRAME")
.ok()
.and_then(|value| value.parse::<usize>().ok());
let want_bits = match env::var("CELT_TRACE_PCM_BITS") {
Ok(value) => !value.is_empty() && value != "0",
Err(_) => false,
};
let start = env::var("CELT_TRACE_PCM_START")
.ok()
.and_then(|value| value.parse::<usize>().ok())
.unwrap_or(0);
let count = env::var("CELT_TRACE_PCM_COUNT")
.ok()
.and_then(|value| value.parse::<usize>().ok())
.unwrap_or(64);
Some(TraceConfig {
frame,
want_bits,
start,
count,
})
})
.as_ref()
}
pub(crate) fn config_copy() -> Option<TraceConfig> {
config().copied()
}
pub(crate) fn dump(
tag: &str,
pcm: &[i16],
frame_size: usize,
channels: usize,
start: usize,
count: usize,
want_bits: bool,
frame_idx: usize,
) {
if channels == 0 {
return;
}
let len = frame_size;
let start = start.min(len);
let end = start.saturating_add(count).min(len);
crate::test_trace::trace_println!("celt_pcm[{}].{}.len={}", frame_idx, tag, len);
for ch in 0..channels {
for i in start..end {
let idx = i * channels + ch;
if idx >= pcm.len() {
continue;
}
let value = pcm[idx];
crate::test_trace::trace_println!(
"celt_pcm[{}].{}.ch[{}].sample[{}]={}",
frame_idx,
tag,
ch,
i,
value
);
if want_bits {
let bits = value as u16;
crate::test_trace::trace_println!(
"celt_pcm[{}].{}.ch[{}].sample_bits[{}]=0x{:04x}",
frame_idx,
tag,
ch,
i,
bits
);
}
}
}
}
}
pub(crate) const MODE_SILK_ONLY: i32 = 1000;
#[allow(dead_code)]
pub(crate) const MODE_HYBRID: i32 = 1001;
pub(crate) const MODE_CELT_ONLY: i32 = 1002;
pub(crate) const OPUS_SIGNAL_VOICE: i32 = 3001;
pub(crate) const OPUS_SIGNAL_MUSIC: i32 = 3002;
pub(crate) const OPUS_BANDWIDTH_NARROWBAND: i32 = 1101;
pub(crate) const OPUS_BANDWIDTH_MEDIUMBAND: i32 = 1102;
pub(crate) const OPUS_BANDWIDTH_WIDEBAND: i32 = 1103;
pub(crate) const OPUS_BANDWIDTH_SUPERWIDEBAND: i32 = 1104;
pub(crate) const OPUS_BANDWIDTH_FULLBAND: i32 = 1105;
pub(crate) const OPUS_FRAMESIZE_ARG: i32 = 5000;
pub(crate) const OPUS_FRAMESIZE_2_5_MS: i32 = 5001;
pub(crate) const OPUS_FRAMESIZE_5_MS: i32 = 5002;
pub(crate) const OPUS_FRAMESIZE_10_MS: i32 = 5003;
pub(crate) const OPUS_FRAMESIZE_20_MS: i32 = 5004;
pub(crate) const OPUS_FRAMESIZE_40_MS: i32 = 5005;
pub(crate) const OPUS_FRAMESIZE_60_MS: i32 = 5006;
pub(crate) const OPUS_FRAMESIZE_80_MS: i32 = 5007;
pub(crate) const OPUS_FRAMESIZE_100_MS: i32 = 5008;
pub(crate) const OPUS_FRAMESIZE_120_MS: i32 = 5009;
const DRED_MAX_FRAMES: i32 = 104;
#[cfg(feature = "dred")]
const DRED_ACTIVITY_MEM_LEN: usize = DRED_MAX_FRAMES as usize * 4;
#[cfg(feature = "dred")]
const DRED_NUM_REDUNDANCY_FRAMES: i32 = DRED_MAX_FRAMES / 2;
#[cfg(feature = "dred")]
const DRED_EXPERIMENTAL_BYTES: i32 = 2;
#[cfg(feature = "dred")]
const DRED_EXTENSION_ID: u8 = 126;
#[cfg(feature = "dred")]
const DRED_EXPERIMENTAL_VERSION: u8 = 10;
#[cfg(feature = "dred")]
const DRED_BITS_TABLE: [f32; 16] = [
73.2, 68.1, 62.5, 57.0, 51.5, 45.7, 39.9, 32.4, 26.4, 20.4, 16.3, 13.0, 9.3, 8.2, 7.2, 6.4,
];
const FEC_THRESHOLDS: [i32; 10] = [
12_000, 1_000, 14_000, 1_000, 16_000, 1_000, 20_000, 1_000, 22_000, 1_000,
];
const FEC_RATE_SCALE_Q16: i32 = 655;
#[allow(dead_code)]
const SILK_RATE_TABLE: [[i32; 5]; 7] = [
[0, 0, 0, 0, 0],
[12_000, 10_000, 10_000, 11_000, 11_000],
[16_000, 13_500, 13_500, 15_000, 15_000],
[20_000, 16_000, 16_000, 18_000, 18_000],
[24_000, 18_000, 18_000, 21_000, 21_000],
[32_000, 22_000, 22_000, 28_000, 28_000],
[64_000, 38_000, 38_000, 50_000, 50_000],
];
const MONO_VOICE_BANDWIDTH_THRESHOLDS: [i32; 8] =
[9_000, 700, 9_000, 700, 13_500, 1_000, 14_000, 2_000];
const MONO_MUSIC_BANDWIDTH_THRESHOLDS: [i32; 8] =
[9_000, 700, 9_000, 700, 11_000, 1_000, 12_000, 2_000];
const STEREO_VOICE_BANDWIDTH_THRESHOLDS: [i32; 8] =
[9_000, 700, 9_000, 700, 13_500, 1_000, 14_000, 2_000];
const STEREO_MUSIC_BANDWIDTH_THRESHOLDS: [i32; 8] =
[9_000, 700, 9_000, 700, 11_000, 1_000, 12_000, 2_000];
const STEREO_VOICE_THRESHOLD: i32 = 19_000;
const STEREO_MUSIC_THRESHOLD: i32 = 17_000;
const MODE_THRESHOLDS: [[i32; 2]; 2] = [[64_000, 10_000], [44_000, 10_000]];
const Q15_ONE: i32 = 1 << 15;
const VARIABLE_HP_SMTH_COEF2_Q16: i32 = (VARIABLE_HP_SMTH_COEF2 * ((1 << 16) as f32) + 0.5) as i32;
const HP_CUTOFF_COEF_Q19: i32 =
(1.5 * core::f32::consts::PI / 1000.0 * ((1 << 19) as f32) + 0.5) as i32;
const HP_CUTOFF_R_COEF_Q9: i32 = (0.92 * ((1 << 9) as f32) + 0.5) as i32;
const VERY_SMALL: f32 = 1.0e-30;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OpusApplication {
Voip,
Audio,
RestrictedLowDelay,
}
impl OpusApplication {
#[inline]
fn from_opus_int(value: i32) -> Option<Self> {
match value {
2048 => Some(Self::Voip),
2049 => Some(Self::Audio),
2051 => Some(Self::RestrictedLowDelay),
_ => None,
}
}
#[inline]
const fn to_opus_int(self) -> i32 {
match self {
Self::Voip => 2048,
Self::Audio => 2049,
Self::RestrictedLowDelay => 2051,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OpusEncoderInitError {
BadArgument,
SilkInit,
CeltInit,
}
impl OpusEncoderInitError {
#[inline]
pub const fn code(self) -> i32 {
match self {
Self::BadArgument => -1,
Self::SilkInit | Self::CeltInit => -3,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum OpusEncoderCtlError {
BadArgument,
Unimplemented,
Silk(SilkError),
InternalError,
}
impl OpusEncoderCtlError {
#[inline]
pub const fn code(&self) -> i32 {
match self {
Self::BadArgument => -1,
Self::Unimplemented => -5,
Self::Silk(_) | Self::InternalError => -3,
}
}
}
impl From<SilkError> for OpusEncoderCtlError {
#[inline]
fn from(value: SilkError) -> Self {
Self::Silk(value)
}
}
impl From<CeltEncoderInitError> for OpusEncoderCtlError {
#[inline]
fn from(value: CeltEncoderInitError) -> Self {
let _ = value;
Self::InternalError
}
}
impl From<CeltEncoderCtlError> for OpusEncoderCtlError {
#[inline]
fn from(value: CeltEncoderCtlError) -> Self {
let _ = value;
Self::InternalError
}
}
pub enum OpusEncoderCtlRequest<'req> {
SetApplication(i32),
GetApplication(&'req mut i32),
SetBitrate(i32),
GetBitrate(&'req mut i32),
SetForceChannels(i32),
GetForceChannels(&'req mut i32),
SetMaxBandwidth(i32),
GetMaxBandwidth(&'req mut i32),
SetBandwidth(i32),
GetBandwidth(&'req mut i32),
SetVbr(bool),
GetVbr(&'req mut bool),
SetVbrConstraint(bool),
GetVbrConstraint(&'req mut bool),
SetComplexity(i32),
GetComplexity(&'req mut i32),
SetSignal(i32),
GetSignal(&'req mut i32),
SetVoiceRatio(i32),
GetVoiceRatio(&'req mut i32),
SetPacketLossPerc(i32),
GetPacketLossPerc(&'req mut i32),
SetInbandFec(bool),
GetInbandFec(&'req mut bool),
SetDtx(bool),
GetDtx(&'req mut bool),
GetInDtx(&'req mut bool),
SetLsbDepth(i32),
GetLsbDepth(&'req mut i32),
SetExpertFrameDuration(i32),
GetExpertFrameDuration(&'req mut i32),
SetPredictionDisabled(bool),
GetPredictionDisabled(&'req mut bool),
SetPhaseInversionDisabled(bool),
GetPhaseInversionDisabled(&'req mut bool),
SetDredDuration(i32),
GetDredDuration(&'req mut i32),
SetDnnBlob(&'req [u8]),
SetForceMode(i32),
GetSampleRate(&'req mut i32),
GetLookahead(&'req mut i32),
GetFinalRange(&'req mut u32),
ResetState,
SetLfe(bool),
GetLfe(&'req mut bool),
}
#[derive(Debug, Clone, Copy, Default)]
pub struct OpusEncodeOptions<'a> {
pub energy_masking: Option<&'a [f32]>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum OpusEncodeError {
BadArgument,
BufferTooSmall,
InternalError,
Unimplemented,
Silk(SilkError),
}
impl OpusEncodeError {
#[inline]
pub const fn code(&self) -> i32 {
match self {
Self::BadArgument => -1,
Self::BufferTooSmall => -2,
Self::InternalError | Self::Silk(_) => -3,
Self::Unimplemented => -5,
}
}
}
impl From<SilkError> for OpusEncodeError {
#[inline]
fn from(value: SilkError) -> Self {
Self::Silk(value)
}
}
#[inline]
fn align(value: usize) -> usize {
#[repr(C)]
struct AlignProbe {
_tag: u8,
_union: AlignUnion,
}
#[repr(C)]
union AlignUnion {
_ptr: *const (),
_i32: i32,
_f32: f32,
}
let alignment = core::mem::align_of::<AlignProbe>();
value.div_ceil(alignment) * alignment
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
struct StereoWidthState {
xx: f32,
xy: f32,
yy: f32,
smoothed_width: f32,
max_follower: f32,
}
#[repr(C)]
struct OpusEncoderLayout {
celt_enc_offset: i32,
silk_enc_offset: i32,
silk_mode: SilkEncControlLayout,
#[cfg(feature = "dred")]
dred_encoder: DredEnc,
application: i32,
channels: i32,
delay_compensation: i32,
force_channels: i32,
signal_type: i32,
user_bandwidth: i32,
max_bandwidth: i32,
user_forced_mode: i32,
voice_ratio: i32,
fs: i32,
use_vbr: i32,
vbr_constraint: i32,
variable_duration: i32,
bitrate_bps: i32,
user_bitrate_bps: i32,
lsb_depth: i32,
encoder_buffer: i32,
lfe: i32,
arch: i32,
use_dtx: i32,
fec_config: i32,
#[cfg(not(feature = "fixed_point"))]
analysis: TonalityAnalysisState,
stream_channels: i32,
hybrid_stereo_width_q14: i16,
variable_hp_smth2_q15: i32,
prev_hb_gain: f32,
hp_mem: [f32; 4],
mode: i32,
prev_mode: i32,
prev_channels: i32,
prev_framesize: i32,
bandwidth: i32,
auto_bandwidth: i32,
silk_bw_switch: i32,
first: i32,
width_mem: StereoWidthState,
delay_buffer: [OpusRes; DELAY_BUFFER_SAMPLES],
#[cfg(not(feature = "fixed_point"))]
detected_bandwidth: i32,
#[cfg(not(feature = "fixed_point"))]
nb_no_activity_ms_q1: i32,
#[cfg(not(feature = "fixed_point"))]
peak_signal_energy: f32,
#[cfg(feature = "dred")]
dred_duration: i32,
#[cfg(feature = "dred")]
dred_q0: i32,
#[cfg(feature = "dred")]
dred_dq: i32,
#[cfg(feature = "dred")]
dred_qmax: i32,
#[cfg(feature = "dred")]
dred_target_chunks: i32,
#[cfg(feature = "dred")]
activity_mem: [u8; DRED_ACTIVITY_MEM_LEN],
nonfinal_frame: i32,
range_final: u32,
}
#[repr(C)]
struct SilkEncControlLayout {
n_channels_api: i32,
n_channels_internal: i32,
api_sample_rate: i32,
max_internal_sample_rate: i32,
min_internal_sample_rate: i32,
desired_internal_sample_rate: i32,
payload_size_ms: i32,
bit_rate: i32,
packet_loss_percentage: i32,
complexity: i32,
use_in_band_fec: i32,
use_dred: i32,
lbrr_coded: i32,
use_dtx: i32,
use_cbr: i32,
max_bits: i32,
to_mono: bool,
opus_can_switch: bool,
reduced_dependency: bool,
internal_sample_rate: i32,
allow_bandwidth_switch: bool,
in_wb_mode_without_variable_lp: bool,
stereo_width_q14: i32,
switch_ready: bool,
signal_type: i32,
offset: i32,
}
#[must_use]
pub fn opus_encoder_get_size(channels: usize) -> Option<usize> {
if channels == 0 || channels > MAX_CHANNELS {
return None;
}
let mut silk_size = 0usize;
crate::silk::get_encoder_size::get_encoder_size(&mut silk_size).ok()?;
let silk_size = align(silk_size);
let celt_size = crate::celt::celt_encoder_get_size(channels)?;
let header_size = align(core::mem::size_of::<OpusEncoderLayout>());
Some(header_size + silk_size + celt_size)
}
#[derive(Debug)]
pub struct OpusEncoder<'mode> {
celt: OwnedCeltEncoder<'mode>,
silk: crate::silk::encoder::state::Encoder,
silk_mode: SilkEncControl,
#[cfg(feature = "dred")]
dred_encoder: Box<DredEnc>,
#[cfg(not(feature = "fixed_point"))]
analysis: TonalityAnalysisState,
analysis_info: AnalysisInfo,
application: OpusApplication,
channels: i32,
stream_channels: i32,
fs: i32,
arch: i32,
use_vbr: bool,
vbr_constraint: bool,
user_bitrate_bps: i32,
bitrate_bps: i32,
packet_loss_perc: i32,
complexity: i32,
inband_fec: bool,
use_dtx: bool,
fec_config: i32,
force_channels: i32,
user_bandwidth: i32,
max_bandwidth: i32,
signal_type: i32,
user_forced_mode: i32,
voice_ratio: i32,
delay_compensation: i32,
encoder_buffer: i32,
lsb_depth: i32,
variable_duration: i32,
prediction_disabled: bool,
hybrid_stereo_width_q14: i16,
variable_hp_smth2_q15: i32,
prev_hb_gain: f32,
hp_mem: [f32; 4],
mode: i32,
prev_mode: i32,
prev_channels: i32,
prev_framesize: i32,
bandwidth: Bandwidth,
auto_bandwidth: i32,
silk_bw_switch: bool,
first: bool,
width_mem: StereoWidthState,
delay_buffer: [OpusRes; DELAY_BUFFER_SAMPLES],
#[cfg(not(feature = "fixed_point"))]
detected_bandwidth: i32,
#[cfg(not(feature = "fixed_point"))]
nb_no_activity_ms_q1: i32,
#[cfg(not(feature = "fixed_point"))]
peak_signal_energy: f32,
nonfinal_frame: bool,
range_final: u32,
dred_duration: i32,
#[cfg(feature = "dred")]
dred_loaded: bool,
#[cfg(feature = "dred")]
dred_latents_buffer_fill: i32,
#[cfg(feature = "dred")]
dred_q0: i32,
#[cfg(feature = "dred")]
dred_dq: i32,
#[cfg(feature = "dred")]
dred_qmax: i32,
#[cfg(feature = "dred")]
dred_target_chunks: i32,
#[cfg(feature = "dred")]
dred_activity_mem: [u8; DRED_ACTIVITY_MEM_LEN],
lfe: bool,
}
impl<'mode> OpusEncoder<'mode> {
pub fn init(
&mut self,
fs: i32,
channels: i32,
application: i32,
) -> Result<(), OpusEncoderInitError> {
if !matches!(fs, 48_000 | 24_000 | 16_000 | 12_000 | 8_000) || !matches!(channels, 1 | 2) {
return Err(OpusEncoderInitError::BadArgument);
}
let application =
OpusApplication::from_opus_int(application).ok_or(OpusEncoderInitError::BadArgument)?;
let mode = canonical_mode().ok_or(OpusEncoderInitError::CeltInit)?;
self.celt = opus_custom_encoder_create(mode, fs, channels as usize, 0)
.map_err(|_| OpusEncoderInitError::CeltInit)?;
self.arch = opus_select_arch();
self.channels = channels;
self.stream_channels = channels;
self.fs = fs;
self.application = application;
silk_init_encoder(&mut self.silk, self.arch, &mut self.silk_mode)
.map_err(|_| OpusEncoderInitError::SilkInit)?;
self.silk_mode.n_channels_api = channels;
self.silk_mode.n_channels_internal = channels;
self.silk_mode.api_sample_rate = fs;
self.silk_mode.max_internal_sample_rate = 16_000;
self.silk_mode.min_internal_sample_rate = 8_000;
self.silk_mode.desired_internal_sample_rate = 16_000;
self.silk_mode.payload_size_ms = 20;
self.silk_mode.bit_rate = 25_000;
self.silk_mode.packet_loss_percentage = 0;
self.silk_mode.complexity = 9;
self.silk_mode.use_in_band_fec = 0;
self.silk_mode.use_dred = 0;
self.silk_mode.use_dtx = 0;
self.silk_mode.use_cbr = 0;
self.silk_mode.reduced_dependency = false;
self.dred_duration = 0;
#[cfg(feature = "dred")]
{
self.dred_loaded = false;
self.dred_latents_buffer_fill = 0;
self.dred_q0 = 0;
self.dred_dq = 0;
self.dred_qmax = 0;
self.dred_target_chunks = 0;
self.dred_activity_mem = [0; DRED_ACTIVITY_MEM_LEN];
}
opus_custom_encoder_ctl(self.celt.encoder(), CeltEncoderCtlRequest::SetSignalling(0))
.map_err(|_| OpusEncoderInitError::CeltInit)?;
opus_custom_encoder_ctl(
self.celt.encoder(),
CeltEncoderCtlRequest::SetComplexity(self.silk_mode.complexity),
)
.map_err(|_| OpusEncoderInitError::CeltInit)?;
#[cfg(feature = "dred")]
{
dred_encoder_init(&mut self.dred_encoder, fs, channels);
self.dred_loaded = self.dred_encoder.loaded;
}
self.use_vbr = true;
self.vbr_constraint = true;
self.user_bitrate_bps = OPUS_AUTO;
self.bitrate_bps = 3000 + fs * channels;
self.packet_loss_perc = 0;
self.complexity = self.silk_mode.complexity;
self.inband_fec = false;
self.use_dtx = false;
self.fec_config = 0;
self.force_channels = OPUS_AUTO;
self.user_bandwidth = OPUS_AUTO;
self.max_bandwidth = OPUS_BANDWIDTH_FULLBAND;
self.signal_type = OPUS_AUTO;
self.user_forced_mode = OPUS_AUTO;
self.voice_ratio = -1;
self.encoder_buffer = fs / 100;
self.lsb_depth = 24;
self.variable_duration = OPUS_FRAMESIZE_ARG;
self.delay_compensation = fs / 250;
self.prediction_disabled = false;
self.hybrid_stereo_width_q14 = 1_i16 << 14;
self.variable_hp_smth2_q15 = lin2log(VARIABLE_HP_MIN_CUTOFF_HZ) << 8;
self.prev_hb_gain = 1.0;
self.hp_mem = [0.0; 4];
self.mode = MODE_HYBRID;
self.prev_mode = 0;
self.prev_channels = 0;
self.prev_framesize = 0;
self.bandwidth = Bandwidth::Full;
self.auto_bandwidth = 0;
self.silk_bw_switch = false;
self.first = true;
self.width_mem = StereoWidthState::default();
self.delay_buffer = [OpusRes::default(); DELAY_BUFFER_SAMPLES];
#[cfg(not(feature = "fixed_point"))]
{
self.detected_bandwidth = 0;
self.nb_no_activity_ms_q1 = 0;
self.peak_signal_energy = 0.0;
}
self.nonfinal_frame = false;
self.range_final = 0;
self.dred_duration = 0;
self.lfe = false;
#[cfg(not(feature = "fixed_point"))]
{
tonality_analysis_init(&mut self.analysis, fs);
}
self.analysis_info = AnalysisInfo::default();
Ok(())
}
fn reset_state(&mut self) -> Result<(), OpusEncoderCtlError> {
silk_init_encoder(&mut self.silk, self.arch, &mut self.silk_mode)?;
opus_custom_encoder_ctl(self.celt.encoder(), CeltEncoderCtlRequest::ResetState)?;
#[cfg(feature = "dred")]
{
dred_encoder_reset(&mut self.dred_encoder);
self.dred_loaded = self.dred_encoder.loaded;
}
#[cfg(not(feature = "fixed_point"))]
{
tonality_analysis_reset(&mut self.analysis);
}
self.analysis_info = AnalysisInfo::default();
self.dred_duration = 0;
self.silk_mode.use_dred = 0;
#[cfg(feature = "dred")]
{
self.dred_latents_buffer_fill = 0;
self.dred_q0 = 0;
self.dred_dq = 0;
self.dred_qmax = 0;
self.dred_target_chunks = 0;
self.dred_activity_mem = [0; DRED_ACTIVITY_MEM_LEN];
}
self.stream_channels = self.channels;
self.hybrid_stereo_width_q14 = 1_i16 << 14;
self.prev_hb_gain = 1.0;
self.first = true;
self.mode = MODE_HYBRID;
self.bandwidth = Bandwidth::Full;
self.variable_hp_smth2_q15 = lin2log(VARIABLE_HP_MIN_CUTOFF_HZ) << 8;
self.hp_mem = [0.0; 4];
self.prev_mode = 0;
self.prev_channels = 0;
self.prev_framesize = 0;
self.auto_bandwidth = 0;
self.silk_bw_switch = false;
self.width_mem = StereoWidthState::default();
self.delay_buffer = [OpusRes::default(); DELAY_BUFFER_SAMPLES];
#[cfg(not(feature = "fixed_point"))]
{
self.detected_bandwidth = 0;
self.nb_no_activity_ms_q1 = 0;
self.peak_signal_energy = 0.0;
}
self.nonfinal_frame = false;
self.range_final = 0;
Ok(())
}
fn configure_silk_control(&mut self, frame_size: i32, max_data_bytes: usize) {
let payload_size_ms = (1000i64 * i64::from(frame_size) / i64::from(self.fs)) as i32;
self.silk_mode.payload_size_ms = payload_size_ms;
self.silk_mode.n_channels_api = self.channels;
self.silk_mode.n_channels_internal = self.stream_channels;
self.silk_mode.api_sample_rate = self.fs;
self.silk_mode.max_internal_sample_rate = 16_000;
self.silk_mode.min_internal_sample_rate = 8_000;
self.silk_mode.desired_internal_sample_rate = 16_000;
self.silk_mode.packet_loss_percentage = self.packet_loss_perc;
self.silk_mode.complexity = self.complexity;
self.silk_mode.use_in_band_fec = i32::from(self.inband_fec);
self.silk_mode.use_dtx = i32::from(self.use_dtx);
self.silk_mode.use_cbr = i32::from(!self.use_vbr);
self.silk_mode.reduced_dependency = self.prediction_disabled;
self.silk_mode.opus_can_switch = false;
self.silk_mode.max_bits = (max_data_bytes.saturating_mul(8)).min(i32::MAX as usize) as i32;
let max_internal =
max_internal_sample_rate_for_bandwidth(self.user_bandwidth, self.max_bandwidth);
self.silk_mode.max_internal_sample_rate = max_internal;
self.silk_mode.desired_internal_sample_rate = max_internal;
let bitrate = match self.user_bitrate_bps {
OPUS_AUTO => self.bitrate_bps,
OPUS_BITRATE_MAX => 80_000,
value => value,
};
self.silk_mode.bit_rate = bitrate.clamp(5_000, 80_000);
}
fn bandwidth_from_silk_control(control: &SilkEncControl) -> Bandwidth {
match control.internal_sample_rate {
8_000 => Bandwidth::Narrow,
12_000 => Bandwidth::Medium,
_ => Bandwidth::Wide,
}
}
}
fn gen_toc(mode: i32, framerate: i32, bandwidth: Bandwidth, channels: i32) -> u8 {
let mut framerate = framerate;
let mut period = 0i32;
while framerate < 400 {
framerate <<= 1;
period += 1;
}
let bw_int = bandwidth.to_opus_int();
let mut toc = if mode == MODE_SILK_ONLY {
let bw_index = (bw_int - Bandwidth::Narrow.to_opus_int()).clamp(0, 3);
let period_index = (period - 2).clamp(0, 3);
((bw_index as u8) << 5) | ((period_index as u8) << 3)
} else if mode == MODE_CELT_ONLY {
let mut tmp = bw_int - Bandwidth::Medium.to_opus_int();
if tmp < 0 {
tmp = 0;
}
let period_index = period.clamp(0, 3);
0x80 | ((tmp as u8) << 5) | ((period_index as u8) << 3)
} else {
let bw_flag = if bandwidth == Bandwidth::Full { 1 } else { 0 };
let period_index = (period - 2).clamp(0, 3);
0x60 | ((bw_flag as u8) << 4) | ((period_index as u8) << 3)
};
if channels == 2 {
toc |= 0x04;
}
toc
}
fn frame_size_select(frame_size: i32, variable_duration: i32, fs: i32) -> Option<i32> {
if frame_size < fs / 400 {
return None;
}
let new_size = if variable_duration == OPUS_FRAMESIZE_ARG {
frame_size
} else if (OPUS_FRAMESIZE_2_5_MS..=OPUS_FRAMESIZE_120_MS).contains(&variable_duration) {
if variable_duration <= OPUS_FRAMESIZE_40_MS {
(fs / 400) << (variable_duration - OPUS_FRAMESIZE_2_5_MS)
} else {
(variable_duration - OPUS_FRAMESIZE_2_5_MS - 2) * fs / 50
}
} else {
return None;
};
if new_size > frame_size {
return None;
}
let valid = 400 * new_size == fs
|| 200 * new_size == fs
|| 100 * new_size == fs
|| 50 * new_size == fs
|| 25 * new_size == fs
|| 50 * new_size == 3 * fs
|| 50 * new_size == 4 * fs
|| 50 * new_size == 5 * fs
|| 50 * new_size == 6 * fs;
valid.then_some(new_size)
}
fn user_bitrate_to_bitrate(
encoder: &OpusEncoder<'_>,
mut frame_size: i32,
max_data_bytes: i32,
) -> i32 {
if frame_size == 0 {
frame_size = encoder.fs / 400;
}
if encoder.user_bitrate_bps == OPUS_AUTO {
let base = i64::from(60 * encoder.fs / frame_size);
let channels = i64::from(encoder.channels * encoder.fs);
(base + channels).clamp(i64::from(i32::MIN), i64::from(i32::MAX)) as i32
} else if encoder.user_bitrate_bps == OPUS_BITRATE_MAX {
let bitrate = i64::from(max_data_bytes) * 8 * i64::from(encoder.fs) / i64::from(frame_size);
bitrate.clamp(i64::from(i32::MIN), i64::from(i32::MAX)) as i32
} else {
encoder.user_bitrate_bps
}
}
fn compute_stereo_width(
pcm: &[i16],
frame_size: usize,
fs: i32,
mem: &mut StereoWidthState,
) -> f32 {
let frame_rate = fs / frame_size as i32;
if frame_rate <= 0 {
return 0.0;
}
let short_alpha = 1.0 - 25.0 / (frame_rate.max(50) as f32);
let mut xx = 0.0f32;
let mut xy = 0.0f32;
let mut yy = 0.0f32;
let scale = 1.0 / CELT_SIG_SCALE;
for i in (0..frame_size.saturating_sub(3)).step_by(4) {
let mut pxx = 0.0f32;
let mut pxy = 0.0f32;
let mut pyy = 0.0f32;
let x0 = f32::from(pcm[2 * i]) * scale;
let y0 = f32::from(pcm[2 * i + 1]) * scale;
pxx += x0 * x0;
pxy += x0 * y0;
pyy += y0 * y0;
let x1 = f32::from(pcm[2 * i + 2]) * scale;
let y1 = f32::from(pcm[2 * i + 3]) * scale;
pxx += x1 * x1;
pxy += x1 * y1;
pyy += y1 * y1;
let x2 = f32::from(pcm[2 * i + 4]) * scale;
let y2 = f32::from(pcm[2 * i + 5]) * scale;
pxx += x2 * x2;
pxy += x2 * y2;
pyy += y2 * y2;
let x3 = f32::from(pcm[2 * i + 6]) * scale;
let y3 = f32::from(pcm[2 * i + 7]) * scale;
pxx += x3 * x3;
pxy += x3 * y3;
pyy += y3 * y3;
xx += pxx;
xy += pxy;
yy += pyy;
}
if xx >= 1.0e9 || xx.is_nan() || yy >= 1.0e9 || yy.is_nan() {
xx = 0.0;
xy = 0.0;
yy = 0.0;
}
mem.xx += short_alpha * (xx - mem.xx);
mem.xy += short_alpha * (xy - mem.xy);
mem.yy += short_alpha * (yy - mem.yy);
mem.xx = mem.xx.max(0.0);
mem.xy = mem.xy.max(0.0);
mem.yy = mem.yy.max(0.0);
const WIDTH_THRESHOLD: f32 = 8.0e-4;
const EPSILON: f32 = 1.0e-15;
if mem.xx.max(mem.yy) > WIDTH_THRESHOLD {
let sqrt_xx = celt_sqrt(mem.xx);
let sqrt_yy = celt_sqrt(mem.yy);
let qrrt_xx = celt_sqrt(sqrt_xx);
let qrrt_yy = celt_sqrt(sqrt_yy);
mem.xy = mem.xy.min(sqrt_xx * sqrt_yy);
let corr = frac_div32(mem.xy, EPSILON + sqrt_xx * sqrt_yy);
let ldiff = (qrrt_xx - qrrt_yy).abs() / (EPSILON + qrrt_xx + qrrt_yy);
let width = celt_sqrt((1.0 - corr * corr).max(0.0)).min(1.0) * ldiff;
mem.smoothed_width += (width - mem.smoothed_width) / frame_rate as f32;
mem.max_follower = (mem.max_follower - 0.02 / frame_rate as f32).max(mem.smoothed_width);
}
(20.0 * mem.max_follower).min(1.0)
}
fn decide_fec(
use_in_band_fec: bool,
packet_loss_perc: i32,
last_fec: bool,
mode: i32,
bandwidth: &mut i32,
rate: i32,
) -> bool {
if !use_in_band_fec || packet_loss_perc == 0 || mode == MODE_CELT_ONLY {
return false;
}
let orig_bandwidth = *bandwidth;
loop {
let idx = usize::try_from(2 * (*bandwidth - OPUS_BANDWIDTH_NARROWBAND)).unwrap_or(0);
let mut lbrr_rate_thres_bps = *FEC_THRESHOLDS.get(idx).unwrap_or(&0);
let hysteresis = *FEC_THRESHOLDS.get(idx + 1).unwrap_or(&0);
if last_fec {
lbrr_rate_thres_bps -= hysteresis;
}
if !last_fec {
lbrr_rate_thres_bps += hysteresis;
}
let loss_scale = 125 - packet_loss_perc.min(25);
let scaled = (i64::from(lbrr_rate_thres_bps)
* i64::from(loss_scale)
* i64::from(FEC_RATE_SCALE_Q16))
>> 16;
lbrr_rate_thres_bps = scaled.clamp(i64::from(i32::MIN), i64::from(i32::MAX)) as i32;
if rate > lbrr_rate_thres_bps {
return true;
}
if packet_loss_perc <= 5 {
return false;
}
if *bandwidth > OPUS_BANDWIDTH_NARROWBAND {
*bandwidth -= 1;
} else {
break;
}
}
*bandwidth = orig_bandwidth;
false
}
#[allow(dead_code)]
fn compute_silk_rate_for_hybrid(
mut rate: i32,
bandwidth: i32,
frame_20ms: bool,
vbr: bool,
fec: bool,
channels: i32,
) -> i32 {
rate /= channels;
let entry = 1 + i32::from(frame_20ms) + 2 * i32::from(fec);
let mut idx = 1;
while idx < SILK_RATE_TABLE.len() && SILK_RATE_TABLE[idx][0] <= rate {
idx += 1;
}
let mut silk_rate = if idx == SILK_RATE_TABLE.len() {
let base = SILK_RATE_TABLE[idx - 1][entry as usize];
base + (rate - SILK_RATE_TABLE[idx - 1][0]) / 2
} else {
let lo = SILK_RATE_TABLE[idx - 1][entry as usize];
let hi = SILK_RATE_TABLE[idx][entry as usize];
let x0 = SILK_RATE_TABLE[idx - 1][0];
let x1 = SILK_RATE_TABLE[idx][0];
let num = i64::from(lo) * i64::from(x1 - rate) + i64::from(hi) * i64::from(rate - x0);
(num / i64::from(x1 - x0)) as i32
};
if !vbr {
silk_rate += 100;
}
if bandwidth == OPUS_BANDWIDTH_SUPERWIDEBAND {
silk_rate += 300;
}
silk_rate *= channels;
if channels == 2 && rate >= 12_000 {
silk_rate -= 1_000;
}
silk_rate
}
fn compute_surround_masking_rate_offset(
energy_masking: &[f32],
bandwidth: i32,
channels: i32,
) -> i32 {
let (end, srate): (usize, i32) = match bandwidth {
OPUS_BANDWIDTH_NARROWBAND => (13, 8000),
OPUS_BANDWIDTH_MEDIUMBAND => (15, 12000),
_ => (17, 16000),
};
let channels_usize = channels as usize;
let mut mask_sum: f32 = 0.0;
for c in 0..channels_usize {
for i in 0..end {
let idx = 21 * c + i;
if idx < energy_masking.len() {
let mut mask = energy_masking[idx].clamp(-2.0, 0.5);
if mask > 0.0 {
mask *= 0.5;
}
mask_sum += mask;
}
}
}
let masking_depth = mask_sum / (end * channels_usize) as f32 + 0.2;
(srate as f32 * masking_depth) as i32
}
fn compute_equiv_rate(
bitrate: i32,
channels: i32,
frame_rate: i32,
vbr: bool,
mode: i32,
complexity: i32,
loss: i32,
) -> i32 {
let mut equiv = i64::from(bitrate);
if frame_rate > 50 {
equiv -= i64::from((40 * channels + 20) * (frame_rate - 50));
}
if !vbr {
equiv -= equiv / 12;
}
equiv = equiv * i64::from(90 + complexity) / 100;
if mode == MODE_SILK_ONLY || mode == MODE_HYBRID {
if complexity < 2 {
equiv = equiv * 4 / 5;
}
equiv -= equiv * i64::from(loss) / i64::from(6 * loss + 10);
} else if mode == MODE_CELT_ONLY {
if complexity < 5 {
equiv = equiv * 9 / 10;
}
} else {
equiv -= equiv * i64::from(loss) / i64::from(12 * loss + 20);
}
equiv.clamp(i64::from(i32::MIN), i64::from(i32::MAX)) as i32
}
#[cfg(feature = "dred")]
fn ec_ilog(value: i32) -> i32 {
if value <= 0 {
0
} else {
32 - (value as u32).leading_zeros() as i32
}
}
#[cfg(feature = "dred")]
fn compute_quantizer(q0: i32, d_q: i32, qmax: i32, index: i32) -> i32 {
const DQ_TABLE: [i32; 8] = [0, 2, 3, 4, 6, 8, 12, 16];
let d_q = d_q as usize;
debug_assert!(d_q < DQ_TABLE.len());
let step = DQ_TABLE[d_q];
let quant = q0 + (step * index + 8) / 16;
quant.min(qmax)
}
#[cfg(feature = "dred")]
fn estimate_dred_bitrate(
q0: i32,
d_q: i32,
qmax: i32,
duration: i32,
target_bits: i32,
target_chunks: &mut i32,
) -> i32 {
let mut bits = 8.0 * (3 + DRED_EXPERIMENTAL_BYTES) as f32;
bits += 50.0 + DRED_BITS_TABLE[q0 as usize];
let dred_chunks = ((duration + 5) / 4).min(DRED_NUM_REDUNDANCY_FRAMES / 2);
*target_chunks = 0;
for i in 0..dred_chunks {
let q = compute_quantizer(q0, d_q, qmax, i);
bits += DRED_BITS_TABLE[q as usize];
if bits < target_bits as f32 {
*target_chunks = i + 1;
}
}
libm::floorf(bits + 0.5) as i32
}
#[cfg(feature = "dred")]
fn compute_dred_bitrate(encoder: &mut OpusEncoder<'_>, bitrate_bps: i32, frame_size: i32) -> i32 {
let (mut dred_frac, bitrate_offset) = if encoder.inband_fec {
(3.0 * encoder.packet_loss_perc as f32 / 100.0, 20_000)
} else if encoder.packet_loss_perc > 5 {
(0.55 + encoder.packet_loss_perc as f32 / 100.0, 12_000)
} else {
(12.0 * encoder.packet_loss_perc as f32 / 100.0, 12_000)
};
dred_frac = dred_frac.min(if encoder.inband_fec { 0.7 } else { 0.8 });
let frame_factor = frame_size as f32 * 50.0 / encoder.fs as f32;
dred_frac = dred_frac / (dred_frac + (1.0 - dred_frac) * frame_factor);
let rate_delta = bitrate_bps - bitrate_offset;
let q0 = (51 - 3 * ec_ilog(rate_delta.max(1))).clamp(4, 15);
let d_q = if rate_delta > 36_000 { 3 } else { 5 };
let qmax = 15;
let target_dred_bitrate = (dred_frac * rate_delta as f32) as i32;
let target_dred_bitrate = target_dred_bitrate.max(0);
let mut target_chunks = 0;
let max_dred_bits = if encoder.dred_duration > 0 {
let target_bits = target_dred_bitrate * frame_size / encoder.fs;
estimate_dred_bitrate(
q0,
d_q,
qmax,
encoder.dred_duration,
target_bits,
&mut target_chunks,
)
} else {
target_chunks = 0;
0
};
let mut dred_bitrate = target_dred_bitrate.min(max_dred_bits * encoder.fs / frame_size.max(1));
if target_chunks < 2 {
dred_bitrate = 0;
}
encoder.dred_q0 = q0;
encoder.dred_dq = d_q;
encoder.dred_qmax = qmax;
encoder.dred_target_chunks = target_chunks;
dred_bitrate
}
#[cfg(feature = "dred")]
fn adjust_nb_compr_bytes_for_dred(
nb_compr_bytes: usize,
range_tell_bits: i32,
frame_rate: i32,
dred_bitrate_bps: i32,
) -> usize {
if dred_bitrate_bps <= 0 || frame_rate <= 0 {
return nb_compr_bytes;
}
let dred_bytes = dred_bitrate_bps / (frame_rate * 8);
let min_celt_bytes = ((range_tell_bits + 7) / 8) + 5;
let max_celt_bytes = (nb_compr_bytes as i32 - dred_bytes * 3 / 4).max(min_celt_bytes);
nb_compr_bytes.min(max_celt_bytes.max(0) as usize)
}
#[cfg(feature = "dred")]
#[allow(dead_code)]
fn update_dred_activity_history(encoder: &mut OpusEncoder<'_>, activity: i32, frame_size: usize) {
if encoder.dred_duration > 0 && encoder.dred_loaded {
let frame_size_400hz = ((frame_size as i32 * 400) / encoder.fs)
.clamp(0, DRED_ACTIVITY_MEM_LEN as i32) as usize;
let mem_len = encoder.dred_activity_mem.len();
if frame_size_400hz < mem_len {
encoder
.dred_activity_mem
.copy_within(0..(mem_len - frame_size_400hz), frame_size_400hz);
}
for value in encoder.dred_activity_mem[..frame_size_400hz.min(mem_len)].iter_mut() {
*value = activity as u8;
}
} else {
encoder.dred_latents_buffer_fill = 0;
encoder.dred_activity_mem.fill(0);
}
}
#[cfg(not(feature = "fixed_point"))]
fn is_digital_silence(pcm: &[i16], frame_size: usize, channels: usize, lsb_depth: i32) -> bool {
let total = frame_size.saturating_mul(channels);
if pcm.len() < total || lsb_depth <= 0 {
return false;
}
let mut sample_max = 0i32;
for &sample in &pcm[..total] {
sample_max = sample_max.max(i32::from(sample).abs());
}
if lsb_depth >= 15 {
sample_max == 0
} else {
let threshold = 1i32 << (15 - lsb_depth);
sample_max <= threshold
}
}
#[allow(dead_code)]
fn compute_redundancy_bytes(
max_data_bytes: i32,
bitrate_bps: i32,
frame_rate: i32,
channels: i32,
) -> i32 {
if frame_rate <= 0 {
return 0;
}
let base_bits = i64::from(40 * channels + 20);
let mut redundancy_rate = i64::from(bitrate_bps) + base_bits * i64::from(200 - frame_rate);
redundancy_rate = 3 * redundancy_rate / 2;
let mut redundancy_bytes = redundancy_rate / 1_600;
let available_bits = i64::from(max_data_bytes) * 8 - 2 * base_bits;
let denom = i64::from(240 + 48_000 / frame_rate);
let redundancy_bytes_cap = (available_bits * 240 / denom + base_bits) / 8;
redundancy_bytes = redundancy_bytes.min(redundancy_bytes_cap);
if redundancy_bytes > i64::from(4 + 8 * channels) {
redundancy_bytes = redundancy_bytes.min(257);
} else {
redundancy_bytes = 0;
}
redundancy_bytes.clamp(i64::from(i32::MIN), i64::from(i32::MAX)) as i32
}
fn finish_encode(encoder: &mut OpusEncoder<'_>, mode: i32, to_celt: bool, frame_size: i32) {
encoder.mode = mode;
encoder.prev_mode = if to_celt { MODE_CELT_ONLY } else { mode };
encoder.prev_channels = encoder.stream_channels;
encoder.prev_framesize = frame_size;
encoder.first = false;
}
fn prepare_pcm_buffer(
encoder: &OpusEncoder<'_>,
pcm: &[i16],
frame_size: usize,
channels: usize,
scratch: &mut [OpusRes],
) -> Result<usize, OpusEncodeError> {
let total_buffer = if matches!(encoder.application, OpusApplication::RestrictedLowDelay) {
0
} else {
encoder.delay_compensation
};
let total_buffer = usize::try_from(total_buffer).map_err(|_| OpusEncodeError::BadArgument)?;
let needed_per_channel = total_buffer
.checked_add(frame_size)
.ok_or(OpusEncodeError::BadArgument)?;
let needed = needed_per_channel
.checked_mul(channels)
.ok_or(OpusEncodeError::BadArgument)?;
if needed > scratch.len() {
return Err(OpusEncodeError::BadArgument);
}
let delay_len = total_buffer
.checked_mul(channels)
.ok_or(OpusEncodeError::BadArgument)?;
if delay_len > 0 {
let encoder_buffer =
usize::try_from(encoder.encoder_buffer).map_err(|_| OpusEncodeError::BadArgument)?;
debug_assert!(encoder_buffer >= total_buffer);
let delay_start = encoder_buffer.saturating_sub(total_buffer);
let delay_start = delay_start
.checked_mul(channels)
.ok_or(OpusEncodeError::BadArgument)?;
let delay_end = delay_start
.checked_add(delay_len)
.ok_or(OpusEncodeError::BadArgument)?;
if delay_end > encoder.delay_buffer.len() {
return Err(OpusEncodeError::BadArgument);
}
scratch[..delay_len].copy_from_slice(&encoder.delay_buffer[delay_start..delay_end]);
}
#[cfg(test)]
if total_buffer > 0 {
if let Some(frame_idx) = opus_pcm_trace::current_frame() {
opus_pcm_trace::dump(
"delay_copy",
frame_idx,
&scratch[..delay_len],
channels,
total_buffer,
);
}
}
let scale = 1.0 / CELT_SIG_SCALE;
let pcm_len = frame_size
.checked_mul(channels)
.ok_or(OpusEncodeError::BadArgument)?;
if pcm.len() < pcm_len {
return Err(OpusEncodeError::BadArgument);
}
for (dst, &sample) in scratch[delay_len..delay_len + pcm_len]
.iter_mut()
.zip(pcm.iter().take(pcm_len))
{
*dst = f32::from(sample) * scale;
}
#[cfg(test)]
if let Some(cfg) = celt_pcm_trace::config_copy() {
if cfg.frame.is_none() {
celt_pcm_trace::dump(
"prepare_pcm",
pcm,
frame_size,
channels,
cfg.start,
cfg.count,
cfg.want_bits,
0,
);
}
}
Ok(needed)
}
fn update_delay_buffer(
encoder: &mut OpusEncoder<'_>,
pcm_buf: &[OpusRes],
frame_size: usize,
total_buffer: usize,
channels: usize,
) -> Result<(), OpusEncodeError> {
debug_assert!(channels == 1 || channels == 2);
let encoder_buffer =
usize::try_from(encoder.encoder_buffer).map_err(|_| OpusEncodeError::BadArgument)?;
let encoder_samples = encoder_buffer
.checked_mul(channels)
.ok_or(OpusEncodeError::BadArgument)?;
if encoder_samples > encoder.delay_buffer.len() {
return Err(OpusEncodeError::BadArgument);
}
let frame_total = frame_size
.checked_add(total_buffer)
.ok_or(OpusEncodeError::BadArgument)?;
let frame_total_samples = frame_total
.checked_mul(channels)
.ok_or(OpusEncodeError::BadArgument)?;
if frame_total_samples > pcm_buf.len() {
return Err(OpusEncodeError::BadArgument);
}
if encoder_buffer > frame_total {
let move_len = (encoder_buffer - frame_total)
.checked_mul(channels)
.ok_or(OpusEncodeError::BadArgument)?;
let src_start = frame_size
.checked_mul(channels)
.ok_or(OpusEncodeError::BadArgument)?;
let src_end = src_start
.checked_add(move_len)
.ok_or(OpusEncodeError::BadArgument)?;
if src_end > encoder_samples {
return Err(OpusEncodeError::BadArgument);
}
encoder.delay_buffer.copy_within(src_start..src_end, 0);
let dst_end = move_len
.checked_add(frame_total_samples)
.ok_or(OpusEncodeError::BadArgument)?;
if dst_end > encoder_samples {
return Err(OpusEncodeError::BadArgument);
}
encoder.delay_buffer[move_len..dst_end].copy_from_slice(&pcm_buf[..frame_total_samples]);
} else {
let offset = frame_total
.checked_sub(encoder_buffer)
.ok_or(OpusEncodeError::BadArgument)?;
let src_start = offset
.checked_mul(channels)
.ok_or(OpusEncodeError::BadArgument)?;
let src_end = src_start
.checked_add(encoder_samples)
.ok_or(OpusEncodeError::BadArgument)?;
if src_end > pcm_buf.len() {
return Err(OpusEncodeError::BadArgument);
}
encoder.delay_buffer[..encoder_samples].copy_from_slice(&pcm_buf[src_start..src_end]);
}
#[cfg(test)]
if let Some(frame_idx) = opus_pcm_trace::current_frame() {
opus_pcm_trace::dump(
"delay_buf",
frame_idx,
&encoder.delay_buffer[..encoder_samples],
channels,
encoder_buffer,
);
}
Ok(())
}
fn smulww(a: i32, b: i32) -> i32 {
((i64::from(a) * i64::from(b)) >> 16) as i32
}
fn smlawb(a: i32, b: i32, c: i32) -> i32 {
a.wrapping_add(((i64::from(b) * i64::from(c as i16)) >> 16) as i32)
}
fn update_high_pass_state(encoder: &mut OpusEncoder<'_>, mode: i32) -> i32 {
let hp_freq_smth1 = if mode == MODE_CELT_ONLY {
lin2log(VARIABLE_HP_MIN_CUTOFF_HZ) << 8
} else {
encoder.silk.state_fxx[0].common().variable_hp_smth1_q15
};
encoder.variable_hp_smth2_q15 = smlawb(
encoder.variable_hp_smth2_q15,
hp_freq_smth1 - encoder.variable_hp_smth2_q15,
VARIABLE_HP_SMTH_COEF2_Q16,
);
log2lin(encoder.variable_hp_smth2_q15 >> 8)
}
fn hp_cutoff(
input: &[i16],
cutoff_hz: i32,
output: &mut [OpusRes],
hp_mem: &mut [f32; 4],
len: usize,
channels: usize,
fs: i32,
) {
assert!(channels == 1 || channels == 2, "unsupported channel count");
let expected = len.saturating_mul(channels);
assert!(input.len() >= expected, "input buffer too small");
assert!(output.len() >= expected, "output buffer too small");
debug_assert!(fs > 0);
let fs_div = fs / 1000;
debug_assert!(fs_div > 0);
debug_assert!(cutoff_hz <= i32::MAX / HP_CUTOFF_COEF_Q19);
let fc_q19 = (HP_CUTOFF_COEF_Q19 * cutoff_hz) / fs_div;
debug_assert!(fc_q19 > 0 && fc_q19 < 32768);
let r_q28 = (1 << 28) - (HP_CUTOFF_R_COEF_Q9 * fc_q19);
let b_q28 = [r_q28, -2 * r_q28, r_q28];
let r_q22 = r_q28 >> 6;
let fc_sq_q22 = smulww(fc_q19, fc_q19);
let a_q28 = [smulww(r_q22, fc_sq_q22 - (2 << 22)), smulww(r_q22, r_q22)];
let scale_q28 = 1.0 / ((1u64 << 28) as f32);
let b0 = b_q28[0] as f32 * scale_q28;
let b1 = b_q28[1] as f32 * scale_q28;
let b2 = b_q28[2] as f32 * scale_q28;
let a0 = a_q28[0] as f32 * scale_q28;
let a1 = a_q28[1] as f32 * scale_q28;
let scale = 1.0 / CELT_SIG_SCALE;
if channels == 2 {
let mut s0 = hp_mem[0];
let mut s1 = hp_mem[1];
let mut s2 = hp_mem[2];
let mut s3 = hp_mem[3];
for i in 0..len {
let idx = i * 2;
let x0 = f32::from(input[idx]) * scale;
let x1 = f32::from(input[idx + 1]) * scale;
let vout0 = s0 + b0 * x0;
s0 = s1 - vout0 * a0 + b1 * x0;
s1 = -vout0 * a1 + b2 * x0 + VERY_SMALL;
output[idx] = vout0;
let vout1 = s2 + b0 * x1;
s2 = s3 - vout1 * a0 + b1 * x1;
s3 = -vout1 * a1 + b2 * x1 + VERY_SMALL;
output[idx + 1] = vout1;
}
hp_mem[0] = s0;
hp_mem[1] = s1;
hp_mem[2] = s2;
hp_mem[3] = s3;
} else {
let mut s0 = hp_mem[0];
let mut s1 = hp_mem[1];
for i in 0..len {
let x = f32::from(input[i]) * scale;
let vout = s0 + b0 * x;
s0 = s1 - vout * a0 + b1 * x;
s1 = -vout * a1 + b2 * x + VERY_SMALL;
output[i] = vout;
}
hp_mem[0] = s0;
hp_mem[1] = s1;
}
}
fn dc_reject(
input: &[i16],
cutoff_hz: i32,
output: &mut [OpusRes],
hp_mem: &mut [f32; 4],
len: usize,
channels: usize,
fs: i32,
) {
assert!(channels == 1 || channels == 2, "unsupported channel count");
let expected = len.saturating_mul(channels);
assert!(input.len() >= expected, "input buffer too small");
assert!(output.len() >= expected, "output buffer too small");
let coef = 6.3f32 * cutoff_hz as f32 / fs as f32;
let coef2 = 1.0 - coef;
let scale = 1.0 / CELT_SIG_SCALE;
#[cfg(test)]
let trace = match (
opus_pcm_trace::config_copy(),
opus_pcm_trace::current_frame(),
) {
(Some(cfg), Some(frame_idx)) => {
if cfg.frame.map_or(false, |want| want != frame_idx) {
None
} else {
let start = cfg.start.min(len);
let end = start.saturating_add(cfg.count).min(len);
Some((cfg, frame_idx, start, end))
}
}
_ => None,
};
if channels == 2 {
let mut m0 = hp_mem[0];
let mut m2 = hp_mem[2];
for i in 0..len {
let idx = i * 2;
let x0 = f32::from(input[idx]) * scale;
let x1 = f32::from(input[idx + 1]) * scale;
#[cfg(test)]
let m0_pre = m0;
#[cfg(test)]
let m2_pre = m2;
let out0 = x0 - m0;
let out1 = x1 - m2;
let acc0 = fmaf(coef, x0, VERY_SMALL);
let acc1 = fmaf(coef, x1, VERY_SMALL);
m0 = fmaf(coef2, m0, acc0);
m2 = fmaf(coef2, m2, acc1);
output[idx] = out0;
output[idx + 1] = out1;
#[cfg(test)]
if let Some((cfg, frame_idx, start, end)) = trace {
if i >= start && i < end {
if i == start {
crate::test_trace::trace_println!(
"opus_dc_reject[{frame_idx}].coef={coef:.9e}"
);
if cfg.want_bits {
crate::test_trace::trace_println!(
"opus_dc_reject[{frame_idx}].coef_bits=0x{:08x}",
coef.to_bits()
);
}
crate::test_trace::trace_println!(
"opus_dc_reject[{frame_idx}].coef2={coef2:.9e}"
);
if cfg.want_bits {
crate::test_trace::trace_println!(
"opus_dc_reject[{frame_idx}].coef2_bits=0x{:08x}",
coef2.to_bits()
);
}
}
crate::test_trace::trace_println!(
"opus_dc_reject[{frame_idx}].ch[0].i[{i}].x={x0:.9e}"
);
crate::test_trace::trace_println!(
"opus_dc_reject[{frame_idx}].ch[0].i[{i}].m0_pre={m0_pre:.9e}"
);
crate::test_trace::trace_println!(
"opus_dc_reject[{frame_idx}].ch[0].i[{i}].out={out0:.9e}"
);
crate::test_trace::trace_println!(
"opus_dc_reject[{frame_idx}].ch[0].i[{i}].m0_post={m0:.9e}"
);
crate::test_trace::trace_println!(
"opus_dc_reject[{frame_idx}].ch[1].i[{i}].x={x1:.9e}"
);
crate::test_trace::trace_println!(
"opus_dc_reject[{frame_idx}].ch[1].i[{i}].m0_pre={m2_pre:.9e}"
);
crate::test_trace::trace_println!(
"opus_dc_reject[{frame_idx}].ch[1].i[{i}].out={out1:.9e}"
);
crate::test_trace::trace_println!(
"opus_dc_reject[{frame_idx}].ch[1].i[{i}].m0_post={m2:.9e}"
);
if cfg.want_bits {
crate::test_trace::trace_println!(
"opus_dc_reject[{frame_idx}].ch[0].i[{i}].x_bits=0x{:08x}",
x0.to_bits()
);
crate::test_trace::trace_println!(
"opus_dc_reject[{frame_idx}].ch[0].i[{i}].m0_pre_bits=0x{:08x}",
m0_pre.to_bits()
);
crate::test_trace::trace_println!(
"opus_dc_reject[{frame_idx}].ch[0].i[{i}].out_bits=0x{:08x}",
out0.to_bits()
);
crate::test_trace::trace_println!(
"opus_dc_reject[{frame_idx}].ch[0].i[{i}].m0_post_bits=0x{:08x}",
m0.to_bits()
);
crate::test_trace::trace_println!(
"opus_dc_reject[{frame_idx}].ch[1].i[{i}].x_bits=0x{:08x}",
x1.to_bits()
);
crate::test_trace::trace_println!(
"opus_dc_reject[{frame_idx}].ch[1].i[{i}].m0_pre_bits=0x{:08x}",
m2_pre.to_bits()
);
crate::test_trace::trace_println!(
"opus_dc_reject[{frame_idx}].ch[1].i[{i}].out_bits=0x{:08x}",
out1.to_bits()
);
crate::test_trace::trace_println!(
"opus_dc_reject[{frame_idx}].ch[1].i[{i}].m0_post_bits=0x{:08x}",
m2.to_bits()
);
}
}
}
}
hp_mem[0] = m0;
hp_mem[2] = m2;
} else {
let mut m0 = hp_mem[0];
for i in 0..len {
let x = f32::from(input[i]) * scale;
#[cfg(test)]
let m0_pre = m0;
let y = x - m0;
let acc = fmaf(coef, x, VERY_SMALL);
m0 = fmaf(coef2, m0, acc);
output[i] = y;
#[cfg(test)]
if let Some((cfg, frame_idx, start, end)) = trace {
if i >= start && i < end {
if i == start {
crate::test_trace::trace_println!(
"opus_dc_reject[{frame_idx}].coef={coef:.9e}"
);
if cfg.want_bits {
crate::test_trace::trace_println!(
"opus_dc_reject[{frame_idx}].coef_bits=0x{:08x}",
coef.to_bits()
);
}
crate::test_trace::trace_println!(
"opus_dc_reject[{frame_idx}].coef2={coef2:.9e}"
);
if cfg.want_bits {
crate::test_trace::trace_println!(
"opus_dc_reject[{frame_idx}].coef2_bits=0x{:08x}",
coef2.to_bits()
);
}
}
crate::test_trace::trace_println!(
"opus_dc_reject[{frame_idx}].ch[0].i[{i}].x={x:.9e}"
);
crate::test_trace::trace_println!(
"opus_dc_reject[{frame_idx}].ch[0].i[{i}].m0_pre={m0_pre:.9e}"
);
crate::test_trace::trace_println!(
"opus_dc_reject[{frame_idx}].ch[0].i[{i}].out={y:.9e}"
);
crate::test_trace::trace_println!(
"opus_dc_reject[{frame_idx}].ch[0].i[{i}].m0_post={m0:.9e}"
);
if cfg.want_bits {
crate::test_trace::trace_println!(
"opus_dc_reject[{frame_idx}].ch[0].i[{i}].x_bits=0x{:08x}",
x.to_bits()
);
crate::test_trace::trace_println!(
"opus_dc_reject[{frame_idx}].ch[0].i[{i}].m0_pre_bits=0x{:08x}",
m0_pre.to_bits()
);
crate::test_trace::trace_println!(
"opus_dc_reject[{frame_idx}].ch[0].i[{i}].out_bits=0x{:08x}",
y.to_bits()
);
crate::test_trace::trace_println!(
"opus_dc_reject[{frame_idx}].ch[0].i[{i}].m0_post_bits=0x{:08x}",
m0.to_bits()
);
}
}
}
}
hp_mem[0] = m0;
}
}
#[allow(clippy::too_many_arguments)]
fn gain_fade(
pcm: &mut [OpusRes],
g1: f32,
g2: f32,
overlap48: usize,
frame_size: usize,
channels: usize,
window: &[CeltCoef],
fs: i32,
) {
debug_assert!(channels == 1 || channels == 2);
debug_assert!(fs > 0);
let inc = (48_000 / fs) as usize;
let overlap = overlap48 / inc;
let overlap_samples = overlap.min(frame_size);
if channels == 1 {
for (sample, pcm_val) in pcm.iter_mut().enumerate().take(overlap_samples) {
let w = window.get(sample * inc).copied().unwrap_or(0.0);
let w_sq = w * w;
let g = w_sq * g2 + (1.0 - w_sq) * g1;
*pcm_val *= g;
}
} else {
for sample in 0..overlap_samples {
let w = window.get(sample * inc).copied().unwrap_or(0.0);
let w_sq = w * w;
let g = w_sq * g2 + (1.0 - w_sq) * g1;
pcm[sample * 2] *= g;
pcm[sample * 2 + 1] *= g;
}
}
for c in 0..channels {
for sample in overlap..frame_size {
pcm[sample * channels + c] *= g2;
}
}
}
#[allow(clippy::too_many_arguments)]
fn stereo_fade(
pcm: &mut [OpusRes],
g1: f32,
g2: f32,
overlap48: usize,
frame_size: usize,
channels: usize,
window: &[CeltCoef],
fs: i32,
) {
debug_assert_eq!(channels, 2, "stereo_fade requires stereo input");
debug_assert!(fs > 0);
let inc = (48_000 / fs) as usize;
let overlap = overlap48 / inc;
let overlap_samples = overlap.min(frame_size);
let g1 = 1.0 - g1;
let g2 = 1.0 - g2;
for sample in 0..overlap_samples {
let w = window.get(sample * inc).copied().unwrap_or(0.0);
let w_sq = w * w;
let g = w_sq * g2 + (1.0 - w_sq) * g1;
let left = pcm[sample * 2];
let right = pcm[sample * 2 + 1];
let diff = (left - right) * 0.5;
pcm[sample * 2] = left - g * diff;
pcm[sample * 2 + 1] = right + g * diff;
}
for sample in overlap_samples..frame_size {
let left = pcm[sample * 2];
let right = pcm[sample * 2 + 1];
let diff = (left - right) * 0.5;
pcm[sample * 2] = left - g2 * diff;
pcm[sample * 2 + 1] = right + g2 * diff;
}
}
fn prepare_silk_prefill(
encoder: &mut OpusEncoder<'_>,
channels: usize,
prefill_buf: &mut [i16],
) -> Result<usize, OpusEncodeError> {
let encoder_buffer =
usize::try_from(encoder.encoder_buffer).map_err(|_| OpusEncodeError::BadArgument)?;
let prefill_len = encoder_buffer
.checked_mul(channels)
.ok_or(OpusEncodeError::BadArgument)?;
if prefill_len > encoder.delay_buffer.len() || prefill_len > prefill_buf.len() {
return Err(OpusEncodeError::BadArgument);
}
let ramp_samples =
usize::try_from(encoder.fs / 400).map_err(|_| OpusEncodeError::BadArgument)?;
let ramp_len = ramp_samples
.checked_mul(channels)
.ok_or(OpusEncodeError::BadArgument)?;
let delay_comp =
usize::try_from(encoder.delay_compensation).map_err(|_| OpusEncodeError::BadArgument)?;
let prefill_offset_samples = encoder_buffer
.checked_sub(delay_comp)
.and_then(|value| value.checked_sub(ramp_samples))
.ok_or(OpusEncodeError::BadArgument)?;
let prefill_offset = prefill_offset_samples
.checked_mul(channels)
.ok_or(OpusEncodeError::BadArgument)?;
let ramp_end = prefill_offset
.checked_add(ramp_len)
.ok_or(OpusEncodeError::BadArgument)?;
if ramp_end > encoder.delay_buffer.len() {
return Err(OpusEncodeError::BadArgument);
}
if ramp_len > 0 {
let overlap48 = encoder.celt.mode.overlap;
let window = encoder.celt.mode.window;
gain_fade(
&mut encoder.delay_buffer[prefill_offset..ramp_end],
0.0,
1.0,
overlap48,
ramp_samples,
channels,
window,
encoder.fs,
);
}
if prefill_offset > 0 {
encoder.delay_buffer[..prefill_offset].fill(0.0);
}
for (dst, &src) in prefill_buf[..prefill_len]
.iter_mut()
.zip(encoder.delay_buffer[..prefill_len].iter())
{
let scaled = libm::roundf(src * CELT_SIG_SCALE);
let clamped = scaled.clamp(f32::from(i16::MIN), f32::from(i16::MAX));
*dst = clamped as i16;
}
Ok(prefill_len)
}
fn prepare_celt_prefill_from_delay(
encoder: &OpusEncoder<'_>,
channels: usize,
total_buffer: usize,
prefill_buf: &mut [OpusRes],
) -> Result<usize, OpusEncodeError> {
let encoder_buffer =
usize::try_from(encoder.encoder_buffer).map_err(|_| OpusEncodeError::BadArgument)?;
let prefill_samples =
usize::try_from(encoder.fs / 400).map_err(|_| OpusEncodeError::BadArgument)?;
let prefill_len = prefill_samples
.checked_mul(channels)
.ok_or(OpusEncodeError::BadArgument)?;
if prefill_len > prefill_buf.len() {
return Err(OpusEncodeError::BadArgument);
}
let delay_start_samples = encoder_buffer
.checked_sub(total_buffer)
.and_then(|value| value.checked_sub(prefill_samples))
.ok_or(OpusEncodeError::BadArgument)?;
let delay_start = delay_start_samples
.checked_mul(channels)
.ok_or(OpusEncodeError::BadArgument)?;
let delay_end = delay_start
.checked_add(prefill_len)
.ok_or(OpusEncodeError::BadArgument)?;
if delay_end > encoder.delay_buffer.len() {
return Err(OpusEncodeError::BadArgument);
}
prefill_buf[..prefill_len].copy_from_slice(&encoder.delay_buffer[delay_start..delay_end]);
Ok(prefill_len)
}
fn encode_frame_native<'mode>(
encoder: &mut OpusEncoder<'mode>,
energy_masking: Option<&[f32]>,
pcm: &[i16],
frame_size: usize,
data: &mut [u8],
lsb_depth: i32,
silk_use_dtx: bool,
is_silence: bool,
redundancy: bool,
celt_to_silk: bool,
prefill: PrefillMode,
bandwidth_int: i32,
mode: i32,
to_celt: bool,
equiv_rate: i32,
first_frame: bool,
dred_bitrate_bps: i32,
) -> Result<usize, OpusEncodeError> {
if data.len() < 2 {
return Err(OpusEncodeError::BufferTooSmall);
}
let channels = usize::try_from(encoder.channels).map_err(|_| OpusEncodeError::BadArgument)?;
let required = channels
.checked_mul(frame_size)
.ok_or(OpusEncodeError::BadArgument)?;
if pcm.len() < required {
return Err(OpusEncodeError::BadArgument);
}
#[cfg(test)]
let _trace_pcm_frame = opus_pcm_trace::begin_frame();
#[cfg(test)]
let trace_budget_frame_idx = opus_celt_budget_trace::begin_frame();
#[cfg(test)]
let trace_range_frame_idx = crate::range::begin_range_done_trace_frame();
#[cfg(test)]
if let Some(frame_idx) = trace_range_frame_idx {
crate::range::set_range_done_trace_frame(frame_idx);
}
let frame_size_i32 = i32::try_from(frame_size).map_err(|_| OpusEncodeError::BadArgument)?;
let frame_rate = encoder
.fs
.checked_div(frame_size_i32)
.ok_or(OpusEncodeError::BadArgument)?;
let mut max_data_bytes = i32::try_from(data.len()).map_err(|_| OpusEncodeError::BadArgument)?;
max_data_bytes = max_data_bytes.min(1276);
let mut pcm_buf_storage = [OpusRes::default(); MAX_PCM_BUF_SAMPLES];
let pcm_buf_len = prepare_pcm_buffer(encoder, pcm, frame_size, channels, &mut pcm_buf_storage)?;
let pcm_buf = &mut pcm_buf_storage[..pcm_buf_len];
let total_buffer = if matches!(encoder.application, OpusApplication::RestrictedLowDelay) {
0
} else {
encoder.delay_compensation
};
let total_buffer = usize::try_from(total_buffer).map_err(|_| OpusEncodeError::BadArgument)?;
let delay_len = total_buffer
.checked_mul(channels)
.ok_or(OpusEncodeError::BadArgument)?;
let cutoff_hz = update_high_pass_state(encoder, mode);
let filtered_pcm = &mut pcm_buf[delay_len..delay_len + required];
if encoder.application == OpusApplication::Voip {
hp_cutoff(
pcm,
cutoff_hz,
filtered_pcm,
&mut encoder.hp_mem,
frame_size,
channels,
encoder.fs,
);
} else {
dc_reject(
pcm,
3,
filtered_pcm,
&mut encoder.hp_mem,
frame_size,
channels,
encoder.fs,
);
}
#[cfg(test)]
if let Some(frame_idx) = opus_pcm_trace::current_frame() {
opus_pcm_trace::dump(
"pcm_buf",
frame_idx,
pcm_buf,
channels,
total_buffer + frame_size,
);
}
#[cfg(feature = "fixed_point")]
let _ = is_silence;
let mut bandwidth = Bandwidth::from_opus_int(bandwidth_int).unwrap_or(Bandwidth::Wide);
let max_frame_bytes_i32 = max_data_bytes.min(1276);
let max_frame_bytes =
usize::try_from(max_frame_bytes_i32).map_err(|_| OpusEncodeError::BadArgument)?;
let max_payload_bytes = max_frame_bytes.saturating_sub(1);
#[cfg(not(feature = "dred"))]
let _ = dred_bitrate_bps;
#[cfg(feature = "fixed_point")]
let activity = 1i32;
#[cfg(not(feature = "fixed_point"))]
let activity = {
if is_silence {
0
} else if encoder.analysis_info.valid && encoder.analysis_info.activity < 0.02 {
0
} else {
1
}
};
#[cfg(feature = "dred")]
{
if encoder.dred_duration > 0 && encoder.dred_loaded {
#[cfg(feature = "fixed_point")]
{
let src = &pcm_buf[delay_len..delay_len + required];
let mut pcm_f32 = Vec::with_capacity(required);
pcm_f32.extend(src.iter().map(|&value| value as f32));
dred_compute_latents(
&mut encoder.dred_encoder,
&pcm_f32,
frame_size,
total_buffer as i32,
encoder.arch,
);
}
#[cfg(not(feature = "fixed_point"))]
dred_compute_latents(
&mut encoder.dred_encoder,
&pcm_buf[delay_len..delay_len + required],
frame_size,
total_buffer as i32,
encoder.arch,
);
let frame_size_400hz = ((frame_size_i32 * 400) / encoder.fs)
.clamp(0, DRED_ACTIVITY_MEM_LEN as i32) as usize;
let mem_len = encoder.dred_activity_mem.len();
if frame_size_400hz < mem_len {
encoder
.dred_activity_mem
.copy_within(0..(mem_len - frame_size_400hz), frame_size_400hz);
}
for value in encoder.dred_activity_mem[..frame_size_400hz.min(mem_len)].iter_mut() {
*value = activity as u8;
}
encoder.dred_latents_buffer_fill = encoder.dred_encoder.latents_buffer_fill;
} else {
encoder.dred_encoder.latents_buffer_fill = 0;
encoder.dred_latents_buffer_fill = 0;
encoder.dred_activity_mem.fill(0);
}
}
let mut ret = match mode {
MODE_SILK_ONLY => {
encoder.configure_silk_control(frame_size_i32, max_payload_bytes);
encoder.silk_mode.use_dtx = i32::from(silk_use_dtx);
encoder.silk_mode.desired_internal_sample_rate = match bandwidth_int {
OPUS_BANDWIDTH_NARROWBAND => 8_000,
OPUS_BANDWIDTH_MEDIUMBAND => 12_000,
_ => 16_000,
};
encoder.silk_mode.min_internal_sample_rate = 8_000;
if encoder.silk.state_fxx[0].resampler_state.fs_in_khz() == 0
|| encoder.silk.state_fxx[0].resampler_state.fs_out_khz() == 0
{
encoder.silk.state_fxx[0]
.resampler_state
.silk_resampler_init(
encoder.silk_mode.api_sample_rate,
encoder.silk_mode.desired_internal_sample_rate,
true,
)
.map_err(|_| OpusEncodeError::InternalError)?;
if encoder.silk_mode.n_channels_internal == 2 {
encoder.silk.state_fxx[1].resampler_state =
encoder.silk.state_fxx[0].resampler_state.clone();
}
}
let mut range_encoder = RangeEncoder::new();
let mut bytes_out = 0i32;
silk_encode(
&mut encoder.silk,
&mut encoder.silk_mode,
&pcm[..required],
&mut range_encoder,
&mut bytes_out,
PrefillMode::None,
activity,
)?;
let range_final = range_encoder.range_final();
let bytes_out =
usize::try_from(bytes_out).map_err(|_| OpusEncodeError::InternalError)?;
bandwidth = OpusEncoder::bandwidth_from_silk_control(&encoder.silk_mode);
let toc = gen_toc(mode, frame_rate, bandwidth, encoder.stream_channels) & 0xFC;
data[0] = toc;
if bytes_out == 0 {
encoder.bandwidth = bandwidth;
encoder.range_final = 0;
finish_encode(encoder, mode, to_celt, frame_size_i32);
1
} else {
update_delay_buffer(encoder, pcm_buf, frame_size, total_buffer, channels)?;
let payload = range_encoder.finish();
let payload_len = payload.len();
if payload_len > max_payload_bytes {
return Err(OpusEncodeError::BufferTooSmall);
}
data[1..1 + payload_len].copy_from_slice(&payload);
encoder.bandwidth = bandwidth;
encoder.range_final = range_final;
finish_encode(encoder, mode, to_celt, frame_size_i32);
1 + payload_len
}
}
MODE_CELT_ONLY => {
if bandwidth == Bandwidth::Medium {
bandwidth = Bandwidth::Wide;
}
let end_band = match bandwidth {
Bandwidth::Narrow => 13,
Bandwidth::Medium | Bandwidth::Wide => 17,
Bandwidth::SuperWide => 19,
Bandwidth::Full => 21,
};
opus_custom_encoder_ctl(
encoder.celt.encoder(),
CeltEncoderCtlRequest::SetEndBand(end_band),
)
.map_err(|_| OpusEncodeError::InternalError)?;
opus_custom_encoder_ctl(
encoder.celt.encoder(),
CeltEncoderCtlRequest::SetStartBand(0),
)
.map_err(|_| OpusEncodeError::InternalError)?;
opus_custom_encoder_ctl(
encoder.celt.encoder(),
CeltEncoderCtlRequest::SetLsbDepth(lsb_depth),
)
.map_err(|_| OpusEncodeError::InternalError)?;
opus_custom_encoder_ctl(
encoder.celt.encoder(),
CeltEncoderCtlRequest::SetChannels(
usize::try_from(encoder.stream_channels)
.map_err(|_| OpusEncodeError::BadArgument)?,
),
)
.map_err(|_| OpusEncodeError::InternalError)?;
opus_custom_encoder_ctl(
encoder.celt.encoder(),
CeltEncoderCtlRequest::SetVbr(encoder.use_vbr),
)
.map_err(|_| OpusEncodeError::InternalError)?;
opus_custom_encoder_ctl(
encoder.celt.encoder(),
CeltEncoderCtlRequest::SetVbrConstraint(encoder.vbr_constraint),
)
.map_err(|_| OpusEncodeError::InternalError)?;
opus_custom_encoder_ctl(
encoder.celt.encoder(),
CeltEncoderCtlRequest::SetBitrate(encoder.bitrate_bps),
)
.map_err(|_| OpusEncodeError::InternalError)?;
#[cfg(not(feature = "fixed_point"))]
{
encoder.celt.encoder().analysis = encoder.analysis_info.clone();
}
update_delay_buffer(encoder, pcm_buf, frame_size, total_buffer, channels)?;
let celt_frame_size = frame_size;
let celt_pcm = &pcm_buf[..required];
let mut range_encoder = RangeEncoder::with_capacity(max_payload_bytes);
#[cfg(test)]
if let Some(frame_idx) = trace_budget_frame_idx {
let tell_pre = range_encoder.tell();
let tell_frac_pre =
crate::celt::ec_tell_frac(range_encoder.encoder_mut().ctx()) as i32;
let nb_compr_bytes = i32::try_from(max_payload_bytes).unwrap_or(i32::MAX);
opus_celt_budget_trace::dump_if_match(
frame_idx,
mode,
max_data_bytes,
max_frame_bytes_i32,
i32::try_from(max_payload_bytes).unwrap_or(i32::MAX),
frame_size_i32,
frame_rate,
equiv_rate,
encoder.bitrate_bps,
encoder.use_vbr,
encoder.vbr_constraint,
encoder.channels,
encoder.stream_channels,
false,
false,
to_celt,
0,
nb_compr_bytes,
tell_pre,
tell_frac_pre,
tell_pre,
tell_frac_pre,
);
}
let bytes = celt_encode_with_ec(
encoder.celt.encoder(),
Some(celt_pcm),
celt_frame_size,
None,
Some(range_encoder.encoder_mut()),
)
.map_err(|err| match err {
CeltEncodeError::MissingOutput => OpusEncodeError::BufferTooSmall,
_ => OpusEncodeError::InternalError,
})?;
let range_final = range_encoder.range_final();
let payload = range_encoder.finish_without_done();
let mut payload_len = payload.len();
if payload_len > max_payload_bytes {
return Err(OpusEncodeError::BufferTooSmall);
}
if bytes == 0 {
if max_payload_bytes == 0 {
return Err(OpusEncodeError::BufferTooSmall);
}
data[1] = 0;
payload_len = 1;
} else {
data[1..1 + payload_len].copy_from_slice(&payload);
}
let toc = gen_toc(mode, frame_rate, bandwidth, encoder.stream_channels) & 0xFC;
data[0] = toc;
encoder.bandwidth = bandwidth;
encoder.range_final = range_final;
finish_encode(encoder, mode, to_celt, frame_size_i32);
1 + payload_len
}
MODE_HYBRID => {
let mut redundancy = redundancy;
let mut celt_to_silk = celt_to_silk;
let mut prefill = prefill;
let mut redundancy_bytes = 0i32;
let mut redundant_rng = 0u32;
if encoder.silk_bw_switch {
redundancy = true;
celt_to_silk = true;
encoder.silk_bw_switch = false;
prefill = PrefillMode::PrefillWithState;
}
if redundancy {
redundancy_bytes = compute_redundancy_bytes(
max_frame_bytes_i32,
encoder.bitrate_bps,
frame_rate,
encoder.stream_channels,
);
if redundancy_bytes == 0 {
redundancy = false;
}
}
let bytes_target = (max_frame_bytes_i32 - redundancy_bytes)
.min(encoder.bitrate_bps * frame_size_i32 / (encoder.fs * 8))
- 1;
let bytes_target = bytes_target.max(0);
encoder.silk_mode.payload_size_ms =
(i64::from(frame_size_i32) * 1000 / i64::from(encoder.fs)) as i32;
encoder.silk_mode.n_channels_api = encoder.channels;
encoder.silk_mode.n_channels_internal = encoder.stream_channels;
encoder.silk_mode.api_sample_rate = encoder.fs;
encoder.silk_mode.packet_loss_percentage = encoder.packet_loss_perc;
encoder.silk_mode.complexity = encoder.complexity;
encoder.silk_mode.use_in_band_fec = i32::from(encoder.inband_fec);
encoder.silk_mode.use_dtx = i32::from(silk_use_dtx);
encoder.silk_mode.use_cbr = i32::from(!encoder.use_vbr);
encoder.silk_mode.reduced_dependency = encoder.prediction_disabled;
encoder.silk_mode.opus_can_switch = false;
encoder.silk_mode.desired_internal_sample_rate = match bandwidth_int {
OPUS_BANDWIDTH_NARROWBAND => 8_000,
OPUS_BANDWIDTH_MEDIUMBAND => 12_000,
_ => 16_000,
};
debug_assert!(
mode == MODE_HYBRID || bandwidth_int == OPUS_BANDWIDTH_WIDEBAND,
"unexpected SILK internal bandwidth selection",
);
encoder.silk_mode.min_internal_sample_rate = 16_000;
encoder.silk_mode.max_internal_sample_rate = 16_000;
encoder.silk_mode.max_bits = (max_frame_bytes_i32 - 1).saturating_mul(8);
if redundancy && redundancy_bytes >= 2 {
encoder.silk_mode.max_bits = encoder
.silk_mode
.max_bits
.saturating_sub(redundancy_bytes * 8 + 1);
encoder.silk_mode.max_bits = encoder.silk_mode.max_bits.saturating_sub(20);
}
let frame_20ms = frame_size_i32 * 50 == encoder.fs;
let total_bitrate = (i64::from(bytes_target) * 8 * i64::from(frame_rate))
.clamp(i64::from(i32::MIN), i64::from(i32::MAX))
as i32;
encoder.silk_mode.bit_rate = compute_silk_rate_for_hybrid(
total_bitrate,
bandwidth_int,
frame_20ms,
encoder.use_vbr,
encoder.silk_mode.lbrr_coded != 0,
encoder.stream_channels,
)
.clamp(5_000, 80_000);
if let Some(mask) = energy_masking.filter(|_| encoder.use_vbr && !encoder.lfe) {
let rate_offset = compute_surround_masking_rate_offset(
mask,
bandwidth_int,
encoder.stream_channels,
);
let rate_offset = rate_offset.max(-2 * encoder.silk_mode.bit_rate / 3);
if bandwidth_int == OPUS_BANDWIDTH_SUPERWIDEBAND
|| bandwidth_int == OPUS_BANDWIDTH_FULLBAND
{
encoder.silk_mode.bit_rate += 3 * rate_offset / 5;
} else {
encoder.silk_mode.bit_rate += rate_offset;
}
}
let hb_gain = if energy_masking.is_none() {
let celt_rate = total_bitrate - encoder.silk_mode.bit_rate;
1.0 - 0.5 * celt_exp2(-celt_rate as f32 / 1024.0)
} else {
1.0
};
if encoder.silk_mode.use_cbr != 0 {
let other_bits = (encoder.silk_mode.max_bits
- encoder.silk_mode.bit_rate * frame_size_i32 / encoder.fs)
.max(0);
encoder.silk_mode.max_bits =
(encoder.silk_mode.max_bits - other_bits * 3 / 4).max(0);
encoder.silk_mode.use_cbr = 0;
} else {
let max_bit_rate = compute_silk_rate_for_hybrid(
encoder.silk_mode.max_bits * encoder.fs / frame_size_i32,
bandwidth_int,
frame_20ms,
encoder.use_vbr,
encoder.silk_mode.lbrr_coded != 0,
encoder.stream_channels,
);
encoder.silk_mode.max_bits = max_bit_rate * frame_size_i32 / encoder.fs;
}
if !matches!(prefill, PrefillMode::None) {
let mut prefill_buf = [0i16; DELAY_BUFFER_SAMPLES];
let prefill_len = prepare_silk_prefill(encoder, channels, &mut prefill_buf)?;
if prefill_len > 0 {
let mut prefill_encoder = RangeEncoder::new();
let mut prefill_bytes = 0i32;
silk_encode(
&mut encoder.silk,
&mut encoder.silk_mode,
&prefill_buf[..prefill_len],
&mut prefill_encoder,
&mut prefill_bytes,
prefill,
activity,
)?;
encoder.silk_mode.opus_can_switch = false;
}
}
let mut range_encoder = RangeEncoder::with_capacity(max_payload_bytes);
let mut bytes_out = 0i32;
silk_encode(
&mut encoder.silk,
&mut encoder.silk_mode,
&pcm[..required],
&mut range_encoder,
&mut bytes_out,
PrefillMode::None,
activity,
)?;
if bytes_out == 0 {
let toc = gen_toc(mode, frame_rate, bandwidth, encoder.stream_channels) & 0xFC;
data[0] = toc;
encoder.bandwidth = bandwidth;
encoder.range_final = 0;
finish_encode(encoder, mode, to_celt, frame_size_i32);
1
} else {
debug_assert!(
encoder.silk_mode.internal_sample_rate == 16_000,
"hybrid SILK internal sample rate must remain at 16 kHz",
);
encoder.silk_mode.opus_can_switch =
encoder.silk_mode.switch_ready && !encoder.nonfinal_frame;
if encoder.silk_mode.opus_can_switch {
redundancy_bytes = compute_redundancy_bytes(
max_frame_bytes_i32,
encoder.bitrate_bps,
frame_rate,
encoder.stream_channels,
);
redundancy = redundancy_bytes != 0;
celt_to_silk = false;
encoder.silk_bw_switch = true;
}
if range_encoder.tell() + 17 + 20 <= 8 * (max_frame_bytes_i32 - 1) {
range_encoder.encode_bit_logp(i32::from(redundancy), 12);
if redundancy {
range_encoder.encode_bit_logp(i32::from(celt_to_silk), 1);
let max_redundancy =
(max_frame_bytes_i32 - 1) - ((range_encoder.tell() + 8 + 3 + 7) >> 3);
redundancy_bytes = redundancy_bytes.min(max_redundancy);
redundancy_bytes = redundancy_bytes.clamp(2, 257);
range_encoder.encode_uint((redundancy_bytes - 2) as u32, 256);
}
} else {
redundancy = false;
}
if !redundancy {
encoder.silk_bw_switch = false;
redundancy_bytes = 0;
}
let mut nb_compr_bytes =
usize::try_from((max_frame_bytes_i32 - 1 - redundancy_bytes).max(0))
.map_err(|_| OpusEncodeError::BadArgument)?;
#[cfg(test)]
let (tell_pre, tell_frac_pre) = if trace_budget_frame_idx.is_some() {
(
range_encoder.tell(),
crate::celt::ec_tell_frac(range_encoder.encoder_mut().ctx()) as i32,
)
} else {
(0, 0)
};
#[cfg(feature = "dred")]
if encoder.dred_duration > 0 {
nb_compr_bytes = adjust_nb_compr_bytes_for_dred(
nb_compr_bytes,
range_encoder.tell(),
frame_rate,
dred_bitrate_bps,
);
}
if nb_compr_bytes < 2 {
return Err(OpusEncodeError::BufferTooSmall);
}
range_encoder.shrink(nb_compr_bytes);
#[cfg(test)]
if let Some(frame_idx) = trace_budget_frame_idx {
let tell_post = range_encoder.tell();
let tell_frac_post =
crate::celt::ec_tell_frac(range_encoder.encoder_mut().ctx()) as i32;
let nb_compr_bytes_i32 = i32::try_from(nb_compr_bytes).unwrap_or(i32::MAX);
opus_celt_budget_trace::dump_if_match(
frame_idx,
mode,
max_data_bytes,
max_frame_bytes_i32,
i32::try_from(max_payload_bytes).unwrap_or(i32::MAX),
frame_size_i32,
frame_rate,
equiv_rate,
encoder.bitrate_bps,
encoder.use_vbr,
encoder.vbr_constraint,
encoder.channels,
encoder.stream_channels,
redundancy,
celt_to_silk,
to_celt,
redundancy_bytes,
nb_compr_bytes_i32,
tell_pre,
tell_frac_pre,
tell_post,
tell_frac_post,
);
}
let end_band = match bandwidth {
Bandwidth::Narrow => 13,
Bandwidth::Medium | Bandwidth::Wide => 17,
Bandwidth::SuperWide => 19,
Bandwidth::Full => 21,
};
opus_custom_encoder_ctl(
encoder.celt.encoder(),
CeltEncoderCtlRequest::SetEndBand(end_band),
)
.map_err(|_| OpusEncodeError::InternalError)?;
opus_custom_encoder_ctl(
encoder.celt.encoder(),
CeltEncoderCtlRequest::SetChannels(
usize::try_from(encoder.stream_channels)
.map_err(|_| OpusEncodeError::BadArgument)?,
),
)
.map_err(|_| OpusEncodeError::InternalError)?;
opus_custom_encoder_ctl(
encoder.celt.encoder(),
CeltEncoderCtlRequest::SetLsbDepth(lsb_depth),
)
.map_err(|_| OpusEncodeError::InternalError)?;
opus_custom_encoder_ctl(
encoder.celt.encoder(),
CeltEncoderCtlRequest::SetBitrate(OPUS_BITRATE_MAX),
)
.map_err(|_| OpusEncodeError::InternalError)?;
opus_custom_encoder_ctl(
encoder.celt.encoder(),
CeltEncoderCtlRequest::SetPrediction(if encoder.silk_mode.reduced_dependency {
0
} else {
2
}),
)
.map_err(|_| OpusEncodeError::InternalError)?;
#[cfg(not(feature = "fixed_point"))]
{
encoder.celt.encoder().analysis = encoder.analysis_info.clone();
}
encoder.celt.encoder().silk_info = SilkInfo {
signal_type: i32::from(encoder.silk_mode.signal_type),
offset: encoder.silk_mode.offset,
};
let need_tmp_prefill =
mode != MODE_SILK_ONLY && encoder.prev_mode != mode && encoder.prev_mode > 0;
let mut tmp_prefill = [OpusRes::default(); MAX_TMP_PREFILL_SAMPLES];
let mut tmp_prefill_len = 0usize;
if need_tmp_prefill {
tmp_prefill_len = prepare_celt_prefill_from_delay(
encoder,
channels,
total_buffer,
&mut tmp_prefill,
)?;
}
update_delay_buffer(encoder, pcm_buf, frame_size, total_buffer, channels)?;
if encoder.prev_hb_gain < 1.0 || hb_gain < 1.0 {
let overlap48 = encoder.celt.mode.overlap;
let window = encoder.celt.mode.window;
gain_fade(
&mut pcm_buf[..required],
encoder.prev_hb_gain,
hb_gain,
overlap48,
frame_size,
channels,
window,
encoder.fs,
);
}
encoder.prev_hb_gain = hb_gain;
if mode != MODE_HYBRID || encoder.stream_channels == 1 {
encoder.silk_mode.stereo_width_q14 = if equiv_rate > 32000 {
16384
} else if equiv_rate < 16000 {
0
} else {
16384 - 2048 * (32000 - equiv_rate) / (equiv_rate - 14000)
};
}
if energy_masking.is_none() && encoder.channels == 2 {
let prev_width = encoder.hybrid_stereo_width_q14;
let curr_width = encoder.silk_mode.stereo_width_q14 as i16;
if prev_width < (1 << 14) || curr_width < (1 << 14) {
let g1 = f32::from(prev_width) / 16384.0;
let g2 = f32::from(curr_width) / 16384.0;
let overlap48 = encoder.celt.mode.overlap;
let window = encoder.celt.mode.window;
stereo_fade(
&mut pcm_buf[..required],
g1,
g2,
overlap48,
frame_size,
channels,
window,
encoder.fs,
);
encoder.hybrid_stereo_width_q14 = curr_width;
}
}
let celt_pcm = &pcm_buf[..required];
let mut redundancy_buf = [0u8; 257];
let mut redundancy_len = 0usize;
if redundancy && celt_to_silk {
let n2 = usize::try_from(encoder.fs / 200)
.map_err(|_| OpusEncodeError::BadArgument)?;
let red_len = usize::try_from(redundancy_bytes)
.map_err(|_| OpusEncodeError::BadArgument)?;
if n2 > 0 && red_len >= 2 {
debug_assert!(red_len <= redundancy_buf.len());
opus_custom_encoder_ctl(
encoder.celt.encoder(),
CeltEncoderCtlRequest::SetStartBand(0),
)
.map_err(|_| OpusEncodeError::InternalError)?;
opus_custom_encoder_ctl(
encoder.celt.encoder(),
CeltEncoderCtlRequest::SetVbr(false),
)
.map_err(|_| OpusEncodeError::InternalError)?;
opus_custom_encoder_ctl(
encoder.celt.encoder(),
CeltEncoderCtlRequest::SetBitrate(OPUS_BITRATE_MAX),
)
.map_err(|_| OpusEncodeError::InternalError)?;
let used = celt_encode_with_ec(
encoder.celt.encoder(),
Some(&celt_pcm[..n2 * channels]),
n2,
Some(&mut redundancy_buf[..red_len]),
None,
)
.map_err(|_| OpusEncodeError::InternalError)?;
redundancy_len = used;
opus_custom_encoder_ctl(
encoder.celt.encoder(),
CeltEncoderCtlRequest::GetFinalRange(&mut redundant_rng),
)
.map_err(|_| OpusEncodeError::InternalError)?;
opus_custom_encoder_ctl(
encoder.celt.encoder(),
CeltEncoderCtlRequest::ResetState,
)
.map_err(|_| OpusEncodeError::InternalError)?;
}
}
opus_custom_encoder_ctl(
encoder.celt.encoder(),
CeltEncoderCtlRequest::SetStartBand(17),
)
.map_err(|_| OpusEncodeError::InternalError)?;
opus_custom_encoder_ctl(
encoder.celt.encoder(),
CeltEncoderCtlRequest::SetVbr(encoder.use_vbr),
)
.map_err(|_| OpusEncodeError::InternalError)?;
if encoder.use_vbr {
let celt_rate = encoder.bitrate_bps - encoder.silk_mode.bit_rate;
opus_custom_encoder_ctl(
encoder.celt.encoder(),
CeltEncoderCtlRequest::SetBitrate(celt_rate),
)
.map_err(|_| OpusEncodeError::InternalError)?;
opus_custom_encoder_ctl(
encoder.celt.encoder(),
CeltEncoderCtlRequest::SetVbrConstraint(false),
)
.map_err(|_| OpusEncodeError::InternalError)?;
}
#[cfg(feature = "dred")]
if !encoder.use_vbr && encoder.dred_duration > 0 {
let mut celt_bitrate = encoder.bitrate_bps;
if mode == MODE_HYBRID {
celt_bitrate = celt_bitrate.saturating_sub(encoder.silk_mode.bit_rate);
}
opus_custom_encoder_ctl(
encoder.celt.encoder(),
CeltEncoderCtlRequest::SetVbr(true),
)
.map_err(|_| OpusEncodeError::InternalError)?;
opus_custom_encoder_ctl(
encoder.celt.encoder(),
CeltEncoderCtlRequest::SetVbrConstraint(false),
)
.map_err(|_| OpusEncodeError::InternalError)?;
opus_custom_encoder_ctl(
encoder.celt.encoder(),
CeltEncoderCtlRequest::SetBitrate(celt_bitrate),
)
.map_err(|_| OpusEncodeError::InternalError)?;
}
if need_tmp_prefill {
opus_custom_encoder_ctl(
encoder.celt.encoder(),
CeltEncoderCtlRequest::ResetState,
)
.map_err(|_| OpusEncodeError::InternalError)?;
let n4 = usize::try_from(encoder.fs / 400)
.map_err(|_| OpusEncodeError::BadArgument)?;
let prefill_len = n4
.checked_mul(channels)
.ok_or(OpusEncodeError::BadArgument)?;
if n4 > 0 && tmp_prefill_len >= prefill_len {
let mut dummy = [0u8; 2];
let _ = celt_encode_with_ec(
encoder.celt.encoder(),
Some(&tmp_prefill[..prefill_len]),
n4,
Some(&mut dummy),
None,
)
.map_err(|_| OpusEncodeError::InternalError)?;
}
opus_custom_encoder_ctl(
encoder.celt.encoder(),
CeltEncoderCtlRequest::SetPrediction(0),
)
.map_err(|_| OpusEncodeError::InternalError)?;
}
let mut enc_done = false;
if range_encoder.tell() <= (nb_compr_bytes * 8) as i32 {
let _ = celt_encode_with_ec(
encoder.celt.encoder(),
Some(celt_pcm),
frame_size,
None,
Some(range_encoder.encoder_mut()),
)
.map_err(|err| match err {
CeltEncodeError::MissingOutput => OpusEncodeError::BufferTooSmall,
_ => OpusEncodeError::InternalError,
})?;
enc_done = true;
}
if redundancy && !celt_to_silk {
let n2 = usize::try_from(encoder.fs / 200)
.map_err(|_| OpusEncodeError::BadArgument)?;
let n4 = usize::try_from(encoder.fs / 400)
.map_err(|_| OpusEncodeError::BadArgument)?;
let red_len = usize::try_from(redundancy_bytes)
.map_err(|_| OpusEncodeError::BadArgument)?;
if n2 > 0 && red_len >= 2 {
debug_assert!(red_len <= redundancy_buf.len());
opus_custom_encoder_ctl(
encoder.celt.encoder(),
CeltEncoderCtlRequest::ResetState,
)
.map_err(|_| OpusEncodeError::InternalError)?;
opus_custom_encoder_ctl(
encoder.celt.encoder(),
CeltEncoderCtlRequest::SetStartBand(0),
)
.map_err(|_| OpusEncodeError::InternalError)?;
opus_custom_encoder_ctl(
encoder.celt.encoder(),
CeltEncoderCtlRequest::SetPrediction(0),
)
.map_err(|_| OpusEncodeError::InternalError)?;
opus_custom_encoder_ctl(
encoder.celt.encoder(),
CeltEncoderCtlRequest::SetVbr(false),
)
.map_err(|_| OpusEncodeError::InternalError)?;
opus_custom_encoder_ctl(
encoder.celt.encoder(),
CeltEncoderCtlRequest::SetBitrate(OPUS_BITRATE_MAX),
)
.map_err(|_| OpusEncodeError::InternalError)?;
if n4 > 0 {
let prefill_start =
frame_size.saturating_sub(n2 + n4).saturating_mul(channels);
let prefill_end =
prefill_start.saturating_add(n4.saturating_mul(channels));
if prefill_end <= celt_pcm.len() {
let mut dummy = [0u8; 2];
let _ = celt_encode_with_ec(
encoder.celt.encoder(),
Some(&celt_pcm[prefill_start..prefill_end]),
n4,
Some(&mut dummy),
None,
)
.map_err(|_| OpusEncodeError::InternalError)?;
}
}
let red_start = frame_size.saturating_sub(n2).saturating_mul(channels);
let red_end = red_start.saturating_add(n2.saturating_mul(channels));
if red_end <= celt_pcm.len() {
let used = celt_encode_with_ec(
encoder.celt.encoder(),
Some(&celt_pcm[red_start..red_end]),
n2,
Some(&mut redundancy_buf[..red_len]),
None,
)
.map_err(|_| OpusEncodeError::InternalError)?;
redundancy_len = used;
opus_custom_encoder_ctl(
encoder.celt.encoder(),
CeltEncoderCtlRequest::GetFinalRange(&mut redundant_rng),
)
.map_err(|_| OpusEncodeError::InternalError)?;
}
}
}
let range_final = range_encoder.range_final();
let payload = if enc_done {
range_encoder.finish_without_done()
} else {
range_encoder.finish()
};
let payload_len = payload.len();
let total_len = payload_len + redundancy_len;
if total_len > max_payload_bytes {
return Err(OpusEncodeError::BufferTooSmall);
}
let toc = gen_toc(mode, frame_rate, bandwidth, encoder.stream_channels) & 0xFC;
data[0] = toc;
data[1..1 + payload_len].copy_from_slice(&payload);
if redundancy_len != 0 {
data[1 + payload_len..1 + total_len]
.copy_from_slice(&redundancy_buf[..redundancy_len]);
}
encoder.bandwidth = bandwidth;
encoder.range_final = range_final ^ redundant_rng;
finish_encode(encoder, mode, to_celt, frame_size_i32);
1 + total_len
}
}
_ => return Err(OpusEncodeError::BadArgument),
};
#[cfg(not(feature = "fixed_point"))]
if encoder.use_dtx && (encoder.analysis_info.valid || is_silence) {
let frame_size_ms_q1 = 2 * 1000 * frame_size_i32 / encoder.fs;
if decide_dtx_mode(
activity,
&mut encoder.nb_no_activity_ms_q1,
frame_size_ms_q1,
) {
encoder.range_final = 0;
data[0] = gen_toc(mode, frame_rate, bandwidth, encoder.stream_channels);
ret = 1;
}
} else {
encoder.nb_no_activity_ms_q1 = 0;
}
#[cfg(feature = "dred")]
{
if ret > 1 && encoder.dred_duration > 0 && encoder.dred_loaded && first_frame {
let mut dred_chunks =
((encoder.dred_duration + 5) / 4).min(DRED_NUM_REDUNDANCY_FRAMES / 2);
if encoder.use_vbr {
dred_chunks = dred_chunks.min(encoder.dred_target_chunks);
}
let ret_i32 = i32::try_from(ret).map_err(|_| OpusEncodeError::BadArgument)?;
let mut dred_bytes_left = (max_data_bytes - ret_i32 - 3).min(DRED_MAX_DATA_SIZE as i32);
if dred_bytes_left > 0 {
dred_bytes_left -= (dred_bytes_left + 1 + DRED_EXPERIMENTAL_BYTES) / 255;
}
if dred_chunks >= 1
&& dred_bytes_left >= DRED_MIN_BYTES as i32 + DRED_EXPERIMENTAL_BYTES
{
let mut buf = [0u8; DRED_MAX_DATA_SIZE];
buf[0] = b'D';
buf[1] = DRED_EXPERIMENTAL_VERSION;
let dred_bytes = dred_encode_silk_frame(
&mut encoder.dred_encoder,
&mut buf[DRED_EXPERIMENTAL_BYTES as usize..],
dred_chunks,
dred_bytes_left - DRED_EXPERIMENTAL_BYTES,
encoder.dred_q0,
encoder.dred_dq,
encoder.dred_qmax,
&encoder.dred_activity_mem,
);
if dred_bytes > 0 {
let dred_bytes = (dred_bytes + DRED_EXPERIMENTAL_BYTES) as usize;
let extension = OpusExtensionData {
id: DRED_EXTENSION_ID,
frame: 0,
data: &buf[..dred_bytes],
len: dred_bytes as i32,
};
let max_data_bytes_usize = usize::try_from(max_data_bytes)
.map_err(|_| OpusEncodeError::BadArgument)?;
ret = opus_packet_pad_with_extensions(
data,
ret,
max_data_bytes_usize,
!encoder.use_vbr,
&[extension],
)
.map_err(|err| match err {
RepacketizerError::BufferTooSmall => OpusEncodeError::BufferTooSmall,
_ => OpusEncodeError::InternalError,
})?;
}
}
}
}
Ok(ret)
}
pub fn opus_encoder_create<'mode>(
fs: i32,
channels: i32,
application: i32,
) -> Result<OpusEncoder<'mode>, OpusEncoderInitError> {
if !matches!(fs, 48_000 | 24_000 | 16_000 | 12_000 | 8_000)
|| !matches!(channels, 1 | 2)
|| OpusApplication::from_opus_int(application).is_none()
{
return Err(OpusEncoderInitError::BadArgument);
}
let mode = canonical_mode().ok_or(OpusEncoderInitError::CeltInit)?;
let celt = opus_custom_encoder_create(mode, fs, channels as usize, 0)
.map_err(|_| OpusEncoderInitError::CeltInit)?;
let mut encoder = OpusEncoder {
celt,
silk: crate::silk::encoder::state::Encoder::default(),
silk_mode: SilkEncControl::default(),
#[cfg(feature = "dred")]
dred_encoder: Box::new(DredEnc::default()),
#[cfg(not(feature = "fixed_point"))]
analysis: TonalityAnalysisState::new(fs),
analysis_info: AnalysisInfo::default(),
application: OpusApplication::Voip,
channels,
stream_channels: channels,
fs,
arch: opus_select_arch(),
use_vbr: true,
vbr_constraint: true,
user_bitrate_bps: OPUS_AUTO,
bitrate_bps: 0,
packet_loss_perc: 0,
complexity: 9,
inband_fec: false,
use_dtx: false,
fec_config: 0,
force_channels: OPUS_AUTO,
user_bandwidth: OPUS_AUTO,
max_bandwidth: OPUS_BANDWIDTH_FULLBAND,
signal_type: OPUS_AUTO,
user_forced_mode: OPUS_AUTO,
voice_ratio: 0,
delay_compensation: 0,
encoder_buffer: 0,
lsb_depth: 24,
variable_duration: OPUS_FRAMESIZE_ARG,
prediction_disabled: false,
hybrid_stereo_width_q14: 0,
variable_hp_smth2_q15: 0,
prev_hb_gain: 0.0,
hp_mem: [0.0; 4],
mode: MODE_SILK_ONLY,
prev_mode: 0,
prev_channels: 0,
prev_framesize: 0,
bandwidth: Bandwidth::Wide,
auto_bandwidth: 0,
silk_bw_switch: false,
first: false,
width_mem: StereoWidthState::default(),
delay_buffer: [OpusRes::default(); DELAY_BUFFER_SAMPLES],
#[cfg(not(feature = "fixed_point"))]
detected_bandwidth: 0,
#[cfg(not(feature = "fixed_point"))]
nb_no_activity_ms_q1: 0,
#[cfg(not(feature = "fixed_point"))]
peak_signal_energy: 0.0,
nonfinal_frame: false,
range_final: 0,
dred_duration: 0,
#[cfg(feature = "dred")]
dred_loaded: false,
#[cfg(feature = "dred")]
dred_latents_buffer_fill: 0,
#[cfg(feature = "dred")]
dred_q0: 0,
#[cfg(feature = "dred")]
dred_dq: 0,
#[cfg(feature = "dred")]
dred_qmax: 0,
#[cfg(feature = "dred")]
dred_target_chunks: 0,
#[cfg(feature = "dred")]
dred_activity_mem: [0; DRED_ACTIVITY_MEM_LEN],
lfe: false,
};
encoder.init(fs, channels, application)?;
Ok(encoder)
}
fn max_internal_sample_rate_for_bandwidth(user_bandwidth: i32, max_bandwidth: i32) -> i32 {
let selected = if user_bandwidth == OPUS_AUTO {
max_bandwidth
} else {
user_bandwidth
};
match selected {
OPUS_BANDWIDTH_NARROWBAND => 8_000,
OPUS_BANDWIDTH_MEDIUMBAND => 12_000,
OPUS_BANDWIDTH_WIDEBAND | OPUS_BANDWIDTH_SUPERWIDEBAND | OPUS_BANDWIDTH_FULLBAND => 16_000,
_ => 16_000,
}
}
fn is_valid_bandwidth(value: i32) -> bool {
matches!(
value,
OPUS_BANDWIDTH_NARROWBAND
| OPUS_BANDWIDTH_MEDIUMBAND
| OPUS_BANDWIDTH_WIDEBAND
| OPUS_BANDWIDTH_SUPERWIDEBAND
| OPUS_BANDWIDTH_FULLBAND
)
}
fn is_valid_signal(value: i32) -> bool {
matches!(value, OPUS_AUTO | OPUS_SIGNAL_VOICE | OPUS_SIGNAL_MUSIC)
}
fn is_valid_expert_frame_duration(value: i32) -> bool {
matches!(
value,
OPUS_FRAMESIZE_ARG
| OPUS_FRAMESIZE_2_5_MS
| OPUS_FRAMESIZE_5_MS
| OPUS_FRAMESIZE_10_MS
| OPUS_FRAMESIZE_20_MS
| OPUS_FRAMESIZE_40_MS
| OPUS_FRAMESIZE_60_MS
| OPUS_FRAMESIZE_80_MS
| OPUS_FRAMESIZE_100_MS
| OPUS_FRAMESIZE_120_MS
)
}
pub fn opus_encoder_ctl<'req>(
encoder: &mut OpusEncoder<'_>,
request: OpusEncoderCtlRequest<'req>,
) -> Result<(), OpusEncoderCtlError> {
match request {
OpusEncoderCtlRequest::SetApplication(value) => {
let application =
OpusApplication::from_opus_int(value).ok_or(OpusEncoderCtlError::BadArgument)?;
if !encoder.first && encoder.application != application {
return Err(OpusEncoderCtlError::BadArgument);
}
encoder.application = application;
#[cfg(not(feature = "fixed_point"))]
{
encoder.analysis.application = value;
}
}
OpusEncoderCtlRequest::GetApplication(out) => {
*out = encoder.application.to_opus_int();
}
OpusEncoderCtlRequest::SetBitrate(value) => {
if value != OPUS_AUTO && value != OPUS_BITRATE_MAX && value <= 0 {
return Err(OpusEncoderCtlError::BadArgument);
}
encoder.user_bitrate_bps = value;
if value != OPUS_AUTO {
encoder.bitrate_bps = value;
}
}
OpusEncoderCtlRequest::GetBitrate(out) => {
*out = encoder.user_bitrate_bps;
}
OpusEncoderCtlRequest::SetForceChannels(value) => {
if value != OPUS_AUTO && (value < 1 || value > encoder.channels) {
return Err(OpusEncoderCtlError::BadArgument);
}
encoder.force_channels = value;
}
OpusEncoderCtlRequest::GetForceChannels(out) => {
*out = encoder.force_channels;
}
OpusEncoderCtlRequest::SetMaxBandwidth(value) => {
if !is_valid_bandwidth(value) {
return Err(OpusEncoderCtlError::BadArgument);
}
encoder.max_bandwidth = value;
encoder.silk_mode.max_internal_sample_rate = max_internal_sample_rate_for_bandwidth(
encoder.user_bandwidth,
encoder.max_bandwidth,
);
}
OpusEncoderCtlRequest::GetMaxBandwidth(out) => {
*out = encoder.max_bandwidth;
}
OpusEncoderCtlRequest::SetBandwidth(value) => {
if value != OPUS_AUTO && !is_valid_bandwidth(value) {
return Err(OpusEncoderCtlError::BadArgument);
}
encoder.user_bandwidth = value;
encoder.silk_mode.max_internal_sample_rate = max_internal_sample_rate_for_bandwidth(
encoder.user_bandwidth,
encoder.max_bandwidth,
);
}
OpusEncoderCtlRequest::GetBandwidth(out) => {
*out = encoder.bandwidth.to_opus_int();
}
OpusEncoderCtlRequest::SetVbr(value) => {
encoder.use_vbr = value;
}
OpusEncoderCtlRequest::GetVbr(out) => {
*out = encoder.use_vbr;
}
OpusEncoderCtlRequest::SetVbrConstraint(value) => {
encoder.vbr_constraint = value;
}
OpusEncoderCtlRequest::GetVbrConstraint(out) => {
*out = encoder.vbr_constraint;
}
OpusEncoderCtlRequest::SetComplexity(value) => {
if !(0..=10).contains(&value) {
return Err(OpusEncoderCtlError::BadArgument);
}
encoder.complexity = value;
opus_custom_encoder_ctl(
encoder.celt.encoder(),
CeltEncoderCtlRequest::SetComplexity(value),
)?;
}
OpusEncoderCtlRequest::GetComplexity(out) => {
*out = encoder.complexity;
}
OpusEncoderCtlRequest::SetSignal(value) => {
if !is_valid_signal(value) {
return Err(OpusEncoderCtlError::BadArgument);
}
encoder.signal_type = value;
}
OpusEncoderCtlRequest::GetSignal(out) => {
*out = encoder.signal_type;
}
OpusEncoderCtlRequest::SetVoiceRatio(value) => {
if !(-1..=100).contains(&value) {
return Err(OpusEncoderCtlError::BadArgument);
}
encoder.voice_ratio = value;
}
OpusEncoderCtlRequest::GetVoiceRatio(out) => {
*out = encoder.voice_ratio;
}
OpusEncoderCtlRequest::SetPacketLossPerc(value) => {
if !(0..=100).contains(&value) {
return Err(OpusEncoderCtlError::BadArgument);
}
encoder.packet_loss_perc = value;
}
OpusEncoderCtlRequest::GetPacketLossPerc(out) => {
*out = encoder.packet_loss_perc;
}
OpusEncoderCtlRequest::SetInbandFec(value) => {
encoder.inband_fec = value;
}
OpusEncoderCtlRequest::GetInbandFec(out) => {
*out = encoder.inband_fec;
}
OpusEncoderCtlRequest::SetDtx(value) => {
encoder.use_dtx = value;
}
OpusEncoderCtlRequest::GetDtx(out) => {
*out = encoder.use_dtx;
}
OpusEncoderCtlRequest::GetInDtx(out) => {
*out = encoder_in_dtx(encoder);
}
OpusEncoderCtlRequest::SetLsbDepth(value) => {
if !(8..=24).contains(&value) {
return Err(OpusEncoderCtlError::BadArgument);
}
encoder.lsb_depth = value;
}
OpusEncoderCtlRequest::GetLsbDepth(out) => {
*out = encoder.lsb_depth;
}
OpusEncoderCtlRequest::SetExpertFrameDuration(value) => {
if !is_valid_expert_frame_duration(value) {
return Err(OpusEncoderCtlError::BadArgument);
}
encoder.variable_duration = value;
}
OpusEncoderCtlRequest::GetExpertFrameDuration(out) => {
*out = encoder.variable_duration;
}
OpusEncoderCtlRequest::SetPredictionDisabled(value) => {
encoder.prediction_disabled = value;
encoder.silk_mode.reduced_dependency = value;
}
OpusEncoderCtlRequest::GetPredictionDisabled(out) => {
*out = encoder.prediction_disabled;
}
OpusEncoderCtlRequest::SetPhaseInversionDisabled(value) => {
opus_custom_encoder_ctl(
encoder.celt.encoder(),
CeltEncoderCtlRequest::SetPhaseInversionDisabled(value),
)?;
}
OpusEncoderCtlRequest::GetPhaseInversionDisabled(out) => {
opus_custom_encoder_ctl(
encoder.celt.encoder(),
CeltEncoderCtlRequest::GetPhaseInversionDisabled(out),
)?;
}
OpusEncoderCtlRequest::SetDredDuration(value) => {
if !cfg!(feature = "dred") {
return Err(OpusEncoderCtlError::Unimplemented);
}
if !(0..=DRED_MAX_FRAMES).contains(&value) {
return Err(OpusEncoderCtlError::BadArgument);
}
encoder.dred_duration = value;
encoder.silk_mode.use_dred = if value > 0 { 1 } else { 0 };
}
OpusEncoderCtlRequest::GetDredDuration(out) => {
if !cfg!(feature = "dred") {
return Err(OpusEncoderCtlError::Unimplemented);
}
*out = encoder.dred_duration;
}
OpusEncoderCtlRequest::SetDnnBlob(data) => {
if data.is_empty() {
return Err(OpusEncoderCtlError::BadArgument);
}
#[cfg(feature = "dred")]
{
dred_encoder_load_model(&mut encoder.dred_encoder, data)
.map_err(|_| OpusEncoderCtlError::BadArgument)?;
encoder.dred_loaded = encoder.dred_encoder.loaded;
}
#[cfg(not(feature = "dred"))]
{
let _ = data;
return Err(OpusEncoderCtlError::Unimplemented);
}
}
OpusEncoderCtlRequest::SetForceMode(value) => {
if value != OPUS_AUTO && !(MODE_SILK_ONLY..=MODE_CELT_ONLY).contains(&value) {
return Err(OpusEncoderCtlError::BadArgument);
}
encoder.user_forced_mode = value;
}
OpusEncoderCtlRequest::GetSampleRate(out) => {
*out = encoder.fs;
}
OpusEncoderCtlRequest::GetLookahead(out) => {
let mut lookahead = encoder.fs / 400;
if !matches!(encoder.application, OpusApplication::RestrictedLowDelay) {
lookahead += encoder.delay_compensation;
}
*out = lookahead;
}
OpusEncoderCtlRequest::GetFinalRange(out) => {
*out = encoder.range_final;
}
OpusEncoderCtlRequest::ResetState => {
encoder.reset_state()?;
}
OpusEncoderCtlRequest::SetLfe(value) => {
encoder.lfe = value;
opus_custom_encoder_ctl(encoder.celt.encoder(), CeltEncoderCtlRequest::SetLfe(value))?;
}
OpusEncoderCtlRequest::GetLfe(out) => {
*out = encoder.lfe;
}
}
Ok(())
}
fn encoder_in_dtx(encoder: &OpusEncoder<'_>) -> bool {
if encoder.silk_mode.use_dtx != 0 && matches!(encoder.prev_mode, MODE_SILK_ONLY | MODE_HYBRID) {
let first_in_dtx =
encoder.silk.state_fxx[0].common.no_speech_counter >= NB_SPEECH_FRAMES_BEFORE_DTX;
if !first_in_dtx {
return false;
}
if encoder.silk.n_channels_internal == 2 && !encoder.silk.prev_decode_only_middle {
return encoder.silk.state_fxx[1].common.no_speech_counter
>= NB_SPEECH_FRAMES_BEFORE_DTX;
}
return true;
}
#[cfg(not(feature = "fixed_point"))]
if encoder.use_dtx {
return encoder.nb_no_activity_ms_q1 >= NB_SPEECH_FRAMES_BEFORE_DTX * 20 * 2;
}
false
}
#[cfg(not(feature = "fixed_point"))]
fn decide_dtx_mode(activity: i32, nb_no_activity_ms_q1: &mut i32, frame_size_ms_q1: i32) -> bool {
if activity == 0 {
*nb_no_activity_ms_q1 += frame_size_ms_q1;
if *nb_no_activity_ms_q1 > NB_SPEECH_FRAMES_BEFORE_DTX * 20 * 2 {
if *nb_no_activity_ms_q1 <= (NB_SPEECH_FRAMES_BEFORE_DTX + MAX_CONSECUTIVE_DTX) * 20 * 2
{
return true;
}
*nb_no_activity_ms_q1 = NB_SPEECH_FRAMES_BEFORE_DTX * 20 * 2;
}
} else {
*nb_no_activity_ms_q1 = 0;
}
false
}
pub fn opus_encode_with_options(
encoder: &mut OpusEncoder<'_>,
pcm: &[i16],
frame_size: usize,
data: &mut [u8],
options: OpusEncodeOptions<'_>,
) -> Result<usize, OpusEncodeError> {
let channels = usize::try_from(encoder.channels).map_err(|_| OpusEncodeError::BadArgument)?;
if channels == 0 || channels > MAX_CHANNELS || frame_size == 0 {
return Err(OpusEncodeError::BadArgument);
}
let required_input = channels
.checked_mul(frame_size)
.ok_or(OpusEncodeError::BadArgument)?;
if pcm.len() < required_input {
return Err(OpusEncodeError::BadArgument);
}
if data.len() < 2 {
return Err(OpusEncodeError::BufferTooSmall);
}
let frame_size_i32 = i32::try_from(frame_size).map_err(|_| OpusEncodeError::BadArgument)?;
let frame_size_i32 = frame_size_select(frame_size_i32, encoder.variable_duration, encoder.fs)
.ok_or(OpusEncodeError::BadArgument)?;
let frame_size = usize::try_from(frame_size_i32).map_err(|_| OpusEncodeError::BadArgument)?;
#[cfg(test)]
let trace_frame_idx = opus_mode_trace::begin_frame();
let required = channels
.checked_mul(frame_size)
.ok_or(OpusEncodeError::BadArgument)?;
let frame_rate = encoder
.fs
.checked_div(frame_size_i32)
.ok_or(OpusEncodeError::BadArgument)?;
let mut max_data_bytes = i32::try_from(data.len()).map_err(|_| OpusEncodeError::BadArgument)?;
max_data_bytes = max_data_bytes.min(MAX_PACKET_BYTES);
encoder.bitrate_bps = user_bitrate_to_bitrate(encoder, frame_size_i32, max_data_bytes);
let lsb_depth = encoder.lsb_depth.min(16);
let mut stereo_width = 0.0f32;
if encoder.channels == 2 && encoder.force_channels != 1 {
stereo_width = compute_stereo_width(
&pcm[..required],
frame_size,
encoder.fs,
&mut encoder.width_mem,
);
}
let stereo_width_q15 =
libm::roundf(stereo_width * Q15_ONE as f32).clamp(0.0, Q15_ONE as f32) as i32;
#[cfg(not(feature = "fixed_point"))]
let mut is_silence = false;
#[cfg(feature = "fixed_point")]
let is_silence = false;
#[cfg(not(feature = "fixed_point"))]
let mut analysis_read_state = None;
#[cfg(not(feature = "fixed_point"))]
{
encoder.analysis_info.valid = false;
if encoder.silk_mode.complexity >= 7 && encoder.fs >= 16_000 {
is_silence = is_digital_silence(&pcm[..required], frame_size, channels, lsb_depth);
analysis_read_state = Some(encoder.analysis.snapshot_read_state());
encoder.analysis.application = encoder.application.to_opus_int();
run_analysis(
&mut encoder.analysis,
encoder.celt.mode,
Some(&pcm[..required]),
frame_size,
frame_size,
0,
-2,
encoder.channels,
encoder.fs,
lsb_depth,
&mut encoder.analysis_info,
);
} else {
tonality_analysis_reset(&mut encoder.analysis);
}
if !is_silence {
encoder.voice_ratio = -1;
}
encoder.detected_bandwidth = 0;
if encoder.analysis_info.valid {
if encoder.signal_type == OPUS_AUTO {
let prob = if encoder.prev_mode == 0 {
encoder.analysis_info.music_prob
} else if encoder.prev_mode == MODE_CELT_ONLY {
encoder.analysis_info.music_prob_max
} else {
encoder.analysis_info.music_prob_min
};
encoder.voice_ratio = libm::floorf(0.5 + 100.0 * (1.0 - prob)) as i32;
}
let analysis_bandwidth = encoder.analysis_info.bandwidth;
encoder.detected_bandwidth = if analysis_bandwidth <= 12 {
OPUS_BANDWIDTH_NARROWBAND
} else if analysis_bandwidth <= 14 {
OPUS_BANDWIDTH_MEDIUMBAND
} else if analysis_bandwidth <= 16 {
OPUS_BANDWIDTH_WIDEBAND
} else if analysis_bandwidth <= 18 {
OPUS_BANDWIDTH_SUPERWIDEBAND
} else {
OPUS_BANDWIDTH_FULLBAND
};
}
}
#[cfg(feature = "fixed_point")]
{
encoder.voice_ratio = -1;
}
#[cfg(feature = "dred")]
let dred_bitrate_bps = {
let dred_bitrate = compute_dred_bitrate(encoder, encoder.bitrate_bps, frame_size_i32);
encoder.bitrate_bps = encoder.bitrate_bps.saturating_sub(dred_bitrate);
dred_bitrate
};
#[cfg(not(feature = "dred"))]
let dred_bitrate_bps = 0;
let mut equiv_rate = compute_equiv_rate(
encoder.bitrate_bps,
encoder.channels,
frame_rate,
encoder.use_vbr,
0,
encoder.complexity,
encoder.packet_loss_perc,
);
let voice_est = if encoder.signal_type == OPUS_SIGNAL_VOICE {
127
} else if encoder.signal_type == OPUS_SIGNAL_MUSIC {
0
} else if encoder.voice_ratio >= 0 {
let mut est = (encoder.voice_ratio * 327) >> 8;
if matches!(encoder.application, OpusApplication::Audio) {
est = est.min(115);
}
est
} else if matches!(encoder.application, OpusApplication::Voip) {
115
} else {
48
};
let prev_stream_channels = encoder.stream_channels;
let stream_channels = if encoder.force_channels != OPUS_AUTO && encoder.channels == 2 {
encoder.force_channels
} else if encoder.channels == 2 {
let mut stereo_threshold = STEREO_MUSIC_THRESHOLD
+ ((i64::from(voice_est)
* i64::from(voice_est)
* i64::from(STEREO_VOICE_THRESHOLD - STEREO_MUSIC_THRESHOLD))
>> 14) as i32;
if prev_stream_channels == 2 {
stereo_threshold -= 1000;
} else {
stereo_threshold += 1000;
}
if equiv_rate > stereo_threshold { 2 } else { 1 }
} else {
encoder.channels
};
encoder.stream_channels = stream_channels;
equiv_rate = compute_equiv_rate(
encoder.bitrate_bps,
encoder.stream_channels,
frame_rate,
encoder.use_vbr,
0,
encoder.complexity,
encoder.packet_loss_perc,
);
#[cfg(not(feature = "fixed_point"))]
let silk_use_dtx = encoder.use_dtx && !(encoder.analysis_info.valid || is_silence);
#[cfg(feature = "fixed_point")]
let silk_use_dtx = encoder.use_dtx;
encoder.silk_mode.use_dtx = i32::from(silk_use_dtx);
let mut mode = if matches!(encoder.application, OpusApplication::RestrictedLowDelay) {
MODE_CELT_ONLY
} else if encoder.user_forced_mode != OPUS_AUTO {
encoder.user_forced_mode
} else {
let q15_one_minus = Q15_ONE - stereo_width_q15;
let mode_voice = ((i64::from(q15_one_minus) * i64::from(MODE_THRESHOLDS[0][0])
+ i64::from(stereo_width_q15) * i64::from(MODE_THRESHOLDS[1][0]))
>> 15) as i32;
let mode_music = ((i64::from(q15_one_minus) * i64::from(MODE_THRESHOLDS[1][1])
+ i64::from(stereo_width_q15) * i64::from(MODE_THRESHOLDS[1][1]))
>> 15) as i32;
let mut threshold = mode_music
+ ((i64::from(voice_est) * i64::from(voice_est) * i64::from(mode_voice - mode_music))
>> 14) as i32;
if matches!(encoder.application, OpusApplication::Voip) {
threshold += 8000;
}
if encoder.prev_mode == MODE_CELT_ONLY {
threshold -= 4000;
} else if encoder.prev_mode > 0 {
threshold += 4000;
}
let mut selected = if equiv_rate >= threshold {
MODE_CELT_ONLY
} else {
MODE_SILK_ONLY
};
if encoder.inband_fec
&& encoder.packet_loss_perc > (128 - voice_est) >> 4
&& (encoder.fec_config != 2 || voice_est > 25)
{
selected = MODE_SILK_ONLY;
}
if silk_use_dtx && voice_est > 100 {
selected = MODE_SILK_ONLY;
}
let rate_threshold = if frame_rate > 50 { 9000 } else { 6000 };
let threshold_bytes =
i64::from(rate_threshold) * i64::from(frame_size_i32) / i64::from(encoder.fs * 8);
if i64::from(max_data_bytes) < threshold_bytes {
selected = MODE_CELT_ONLY;
}
selected
};
let min_celt = encoder.fs / 100;
if mode != MODE_CELT_ONLY && frame_size_i32 < min_celt {
mode = MODE_CELT_ONLY;
}
let mut redundancy = false;
let mut celt_to_silk = false;
let mut to_celt = false;
let mut prefill = PrefillMode::None;
if encoder.user_forced_mode != MODE_CELT_ONLY
&& encoder.prev_mode > 0
&& ((mode != MODE_CELT_ONLY && encoder.prev_mode == MODE_CELT_ONLY)
|| (mode == MODE_CELT_ONLY && encoder.prev_mode != MODE_CELT_ONLY))
{
redundancy = true;
celt_to_silk = mode != MODE_CELT_ONLY;
if !celt_to_silk {
if frame_size_i32 >= min_celt {
mode = encoder.prev_mode;
to_celt = true;
} else {
redundancy = false;
}
}
}
if encoder.stream_channels == 1
&& encoder.prev_channels == 2
&& !encoder.silk_mode.to_mono
&& mode != MODE_CELT_ONLY
&& encoder.prev_mode != MODE_CELT_ONLY
{
encoder.silk_mode.to_mono = true;
encoder.stream_channels = 2;
} else {
encoder.silk_mode.to_mono = false;
}
equiv_rate = compute_equiv_rate(
encoder.bitrate_bps,
encoder.stream_channels,
frame_rate,
encoder.use_vbr,
mode,
encoder.complexity,
encoder.packet_loss_perc,
);
if mode != MODE_CELT_ONLY && encoder.prev_mode == MODE_CELT_ONLY {
let mut dummy = SilkEncControl::default();
silk_init_encoder(&mut encoder.silk, encoder.arch, &mut dummy)?;
prefill = PrefillMode::Prefill;
}
let mut bandwidth_int = encoder.bandwidth.to_opus_int();
if mode == MODE_CELT_ONLY || encoder.first || encoder.silk_mode.allow_bandwidth_switch {
let (voice_thresholds, music_thresholds) =
if encoder.channels == 2 && encoder.force_channels != 1 {
(
&STEREO_VOICE_BANDWIDTH_THRESHOLDS,
&STEREO_MUSIC_BANDWIDTH_THRESHOLDS,
)
} else {
(
&MONO_VOICE_BANDWIDTH_THRESHOLDS,
&MONO_MUSIC_BANDWIDTH_THRESHOLDS,
)
};
let mut bandwidth_thresholds = [0i32; 8];
for i in 0..bandwidth_thresholds.len() {
bandwidth_thresholds[i] = music_thresholds[i]
+ ((i64::from(voice_est)
* i64::from(voice_est)
* i64::from(voice_thresholds[i] - music_thresholds[i]))
>> 14) as i32;
}
bandwidth_int = OPUS_BANDWIDTH_FULLBAND;
loop {
let idx = usize::try_from(2 * (bandwidth_int - OPUS_BANDWIDTH_MEDIUMBAND)).unwrap_or(0);
let mut threshold = *bandwidth_thresholds.get(idx).unwrap_or(&0);
let hysteresis = *bandwidth_thresholds.get(idx + 1).unwrap_or(&0);
if !encoder.first {
if encoder.auto_bandwidth >= bandwidth_int {
threshold -= hysteresis;
} else {
threshold += hysteresis;
}
}
if equiv_rate >= threshold {
break;
}
if bandwidth_int <= OPUS_BANDWIDTH_NARROWBAND {
break;
}
bandwidth_int -= 1;
}
if bandwidth_int == OPUS_BANDWIDTH_MEDIUMBAND {
bandwidth_int = OPUS_BANDWIDTH_WIDEBAND;
}
encoder.auto_bandwidth = bandwidth_int;
if !encoder.first
&& mode != MODE_CELT_ONLY
&& !encoder.silk_mode.in_wb_mode_without_variable_lp
&& bandwidth_int > OPUS_BANDWIDTH_WIDEBAND
{
bandwidth_int = OPUS_BANDWIDTH_WIDEBAND;
}
}
if bandwidth_int > encoder.max_bandwidth {
bandwidth_int = encoder.max_bandwidth;
}
if encoder.user_bandwidth != OPUS_AUTO {
bandwidth_int = encoder.user_bandwidth;
}
let max_rate = frame_rate.saturating_mul(max_data_bytes).saturating_mul(8);
if mode != MODE_CELT_ONLY && max_rate < 15_000 {
bandwidth_int = bandwidth_int.min(OPUS_BANDWIDTH_WIDEBAND);
}
if encoder.fs <= 24_000 && bandwidth_int > OPUS_BANDWIDTH_SUPERWIDEBAND {
bandwidth_int = OPUS_BANDWIDTH_SUPERWIDEBAND;
}
if encoder.fs <= 16_000 && bandwidth_int > OPUS_BANDWIDTH_WIDEBAND {
bandwidth_int = OPUS_BANDWIDTH_WIDEBAND;
}
if encoder.fs <= 12_000 && bandwidth_int > OPUS_BANDWIDTH_MEDIUMBAND {
bandwidth_int = OPUS_BANDWIDTH_MEDIUMBAND;
}
if encoder.fs <= 8_000 && bandwidth_int > OPUS_BANDWIDTH_NARROWBAND {
bandwidth_int = OPUS_BANDWIDTH_NARROWBAND;
}
#[cfg(not(feature = "fixed_point"))]
if encoder.detected_bandwidth != 0 && encoder.user_bandwidth == OPUS_AUTO {
let min_detected_bandwidth =
if equiv_rate <= 18_000 * encoder.stream_channels && mode == MODE_CELT_ONLY {
OPUS_BANDWIDTH_NARROWBAND
} else if equiv_rate <= 24_000 * encoder.stream_channels && mode == MODE_CELT_ONLY {
OPUS_BANDWIDTH_MEDIUMBAND
} else if equiv_rate <= 30_000 * encoder.stream_channels {
OPUS_BANDWIDTH_WIDEBAND
} else if equiv_rate <= 44_000 * encoder.stream_channels {
OPUS_BANDWIDTH_SUPERWIDEBAND
} else {
OPUS_BANDWIDTH_FULLBAND
};
encoder.detected_bandwidth = encoder.detected_bandwidth.max(min_detected_bandwidth);
bandwidth_int = bandwidth_int.min(encoder.detected_bandwidth);
}
let use_fec = decide_fec(
encoder.inband_fec,
encoder.packet_loss_perc,
encoder.silk_mode.lbrr_coded != 0,
mode,
&mut bandwidth_int,
equiv_rate,
);
encoder.silk_mode.lbrr_coded = i32::from(use_fec);
if mode == MODE_CELT_ONLY && bandwidth_int == OPUS_BANDWIDTH_MEDIUMBAND {
bandwidth_int = OPUS_BANDWIDTH_WIDEBAND;
}
let curr_bandwidth_int = bandwidth_int;
if mode == MODE_SILK_ONLY && curr_bandwidth_int > OPUS_BANDWIDTH_WIDEBAND {
mode = MODE_HYBRID;
}
if mode == MODE_HYBRID && curr_bandwidth_int <= OPUS_BANDWIDTH_WIDEBAND {
mode = MODE_SILK_ONLY;
}
let bandwidth = Bandwidth::from_opus_int(bandwidth_int).unwrap_or(Bandwidth::Wide);
encoder.bandwidth = bandwidth;
#[cfg(test)]
if let Some(frame_idx) = trace_frame_idx {
opus_mode_trace::dump_if_match(
frame_idx,
mode,
encoder.prev_mode,
equiv_rate,
bandwidth_int,
encoder.stream_channels,
encoder.voice_ratio,
is_silence,
&encoder.analysis_info,
);
}
let max_celt = usize::try_from(encoder.fs / 50).map_err(|_| OpusEncodeError::BadArgument)?;
if mode == MODE_SILK_ONLY && frame_size * 2 != max_celt {
let multiples = frame_size / max_celt;
if !frame_size.is_multiple_of(max_celt) || !(1..=6).contains(&multiples) {
return Err(OpusEncodeError::Unimplemented);
}
}
if (frame_size > max_celt && mode != MODE_SILK_ONLY) || frame_size > max_celt * 3 {
let fs = encoder.fs;
let enc_frame_size_i32 = if mode == MODE_SILK_ONLY {
if frame_size_i32 == 2 * fs / 25 {
fs / 25
} else if frame_size_i32 == 3 * fs / 25 {
3 * fs / 50
} else {
fs / 50
}
} else {
fs / 50
};
if enc_frame_size_i32 <= 0 || frame_size_i32 % enc_frame_size_i32 != 0 {
return Err(OpusEncodeError::Unimplemented);
}
let enc_frame_size =
usize::try_from(enc_frame_size_i32).map_err(|_| OpusEncodeError::BadArgument)?;
let nb_frames = frame_size / enc_frame_size;
if !(1..=6).contains(&nb_frames) {
return Err(OpusEncodeError::Unimplemented);
}
#[cfg(not(feature = "fixed_point"))]
if let Some(read_state) = analysis_read_state {
encoder.analysis.restore_read_state(read_state);
}
let max_header_bytes = if nb_frames == 2 {
3
} else {
2 + (nb_frames - 1) * 2
};
let nb_frames_i32 = i32::try_from(nb_frames).map_err(|_| OpusEncodeError::BadArgument)?;
let max_header_bytes_i32 =
i32::try_from(max_header_bytes).map_err(|_| OpusEncodeError::BadArgument)?;
let max_len_sum = nb_frames_i32
.checked_add(max_data_bytes)
.and_then(|value| value.checked_sub(max_header_bytes_i32))
.ok_or(OpusEncodeError::BufferTooSmall)?;
if max_len_sum < 2 * nb_frames_i32 {
return Err(OpusEncodeError::BufferTooSmall);
}
let max_len_sum_usize =
usize::try_from(max_len_sum).map_err(|_| OpusEncodeError::BadArgument)?;
if max_len_sum_usize > MAX_REPACKETIZER_BYTES {
return Err(OpusEncodeError::BufferTooSmall);
}
let mut tmp_data = [0u8; MAX_REPACKETIZER_BYTES];
let tmp_data = &mut tmp_data[..max_len_sum_usize];
let mut repacketizer = OpusRepacketizer::new();
repacketizer.opus_repacketizer_init();
let bak_to_mono = encoder.silk_mode.to_mono;
if bak_to_mono {
encoder.force_channels = 1;
} else {
encoder.prev_channels = encoder.stream_channels;
}
let mut tot_size = 0i32;
let mut dtx_count = 0usize;
for frame_idx in 0..nb_frames {
let first_frame = frame_idx == 0 || frame_idx == dtx_count;
encoder.silk_mode.to_mono = false;
encoder.nonfinal_frame = frame_idx < nb_frames - 1;
let frame_to_celt = to_celt && frame_idx == nb_frames - 1;
let frame_redundancy = redundancy && (frame_to_celt || (!to_celt && frame_idx == 0));
let frames_left =
i32::try_from(nb_frames - frame_idx).map_err(|_| OpusEncodeError::BadArgument)?;
let max_len_per_frame = (max_len_sum - tot_size) / frames_left;
let mut curr_max = max_len_per_frame;
if encoder.use_vbr {
let rate_bytes = (i64::from(encoder.bitrate_bps) * i64::from(enc_frame_size_i32))
/ (8 * i64::from(encoder.fs));
let rate_bytes = rate_bytes.clamp(i64::from(i32::MIN), i64::from(i32::MAX)) as i32;
curr_max = curr_max.min(rate_bytes);
}
if curr_max < 2 {
return Err(OpusEncodeError::BufferTooSmall);
}
let tot_size_usize =
usize::try_from(tot_size).map_err(|_| OpusEncodeError::BadArgument)?;
let max_len_per_frame_usize =
usize::try_from(max_len_per_frame).map_err(|_| OpusEncodeError::BadArgument)?;
if tot_size_usize + max_len_per_frame_usize > tmp_data.len() {
return Err(OpusEncodeError::BufferTooSmall);
}
let curr_max_usize =
usize::try_from(curr_max).map_err(|_| OpusEncodeError::BadArgument)?;
#[cfg(not(feature = "fixed_point"))]
if analysis_read_state.is_some() {
tonality_get_info(
&mut encoder.analysis,
&mut encoder.analysis_info,
enc_frame_size,
);
}
let start = frame_idx
.checked_mul(enc_frame_size)
.and_then(|value| value.checked_mul(channels))
.ok_or(OpusEncodeError::BadArgument)?;
let end = start
.checked_add(enc_frame_size * channels)
.ok_or(OpusEncodeError::BadArgument)?;
let frame_buf = &mut tmp_data[tot_size_usize..tot_size_usize + max_len_per_frame_usize];
let len = match encode_frame_native(
encoder,
options.energy_masking,
&pcm[start..end],
enc_frame_size,
&mut frame_buf[..curr_max_usize],
lsb_depth,
silk_use_dtx,
is_silence,
frame_redundancy,
celt_to_silk,
prefill,
bandwidth_int,
mode,
frame_to_celt,
equiv_rate,
first_frame,
dred_bitrate_bps,
) {
Ok(len) => len,
Err(OpusEncodeError::BufferTooSmall) if curr_max < max_len_per_frame => {
encode_frame_native(
encoder,
options.energy_masking,
&pcm[start..end],
enc_frame_size,
&mut frame_buf[..max_len_per_frame_usize],
lsb_depth,
silk_use_dtx,
is_silence,
frame_redundancy,
celt_to_silk,
prefill,
bandwidth_int,
mode,
frame_to_celt,
equiv_rate,
first_frame,
dred_bitrate_bps,
)?
}
Err(err) => return Err(err),
};
if len == 1 {
dtx_count += 1;
}
repacketizer
.opus_repacketizer_cat(&frame_buf[..len], len)
.map_err(|err| match err {
RepacketizerError::BufferTooSmall => OpusEncodeError::BufferTooSmall,
_ => OpusEncodeError::InternalError,
})?;
tot_size = tot_size.saturating_add(len as i32);
}
let maxlen = usize::try_from(max_data_bytes).map_err(|_| OpusEncodeError::BadArgument)?;
if maxlen > data.len() {
return Err(OpusEncodeError::BufferTooSmall);
}
let mut written = repacketizer
.opus_repacketizer_out(&mut data[..], maxlen)
.map_err(|err| match err {
RepacketizerError::BufferTooSmall => OpusEncodeError::BufferTooSmall,
_ => OpusEncodeError::InternalError,
})?;
if !encoder.use_vbr && dtx_count != nb_frames {
opus_packet_pad(data, written, maxlen).map_err(|err| match err {
RepacketizerError::BufferTooSmall => OpusEncodeError::BufferTooSmall,
_ => OpusEncodeError::InternalError,
})?;
written = maxlen;
}
encoder.silk_mode.to_mono = bak_to_mono;
encoder.nonfinal_frame = false;
return Ok(written);
}
encoder.nonfinal_frame = false;
encode_frame_native(
encoder,
options.energy_masking,
&pcm[..required],
frame_size,
data,
lsb_depth,
silk_use_dtx,
is_silence,
redundancy,
celt_to_silk,
prefill,
bandwidth_int,
mode,
to_celt,
equiv_rate,
true,
dred_bitrate_bps,
)
}
pub fn opus_encode(
encoder: &mut OpusEncoder<'_>,
pcm: &[i16],
frame_size: usize,
data: &mut [u8],
) -> Result<usize, OpusEncodeError> {
opus_encode_with_options(encoder, pcm, frame_size, data, OpusEncodeOptions::default())
}
pub fn opus_encode24_with_options(
encoder: &mut OpusEncoder<'_>,
pcm: &[i32],
frame_size: usize,
data: &mut [u8],
options: OpusEncodeOptions<'_>,
) -> Result<usize, OpusEncodeError> {
let channels = usize::try_from(encoder.channels).map_err(|_| OpusEncodeError::BadArgument)?;
let required = channels
.checked_mul(frame_size)
.ok_or(OpusEncodeError::BadArgument)?;
if pcm.len() < required {
return Err(OpusEncodeError::BadArgument);
}
let mut tmp = Vec::with_capacity(required);
for &sample in pcm.iter().take(required) {
let scaled = libm::roundf(sample as f32 / 256.0);
tmp.push(scaled.clamp(f32::from(i16::MIN), f32::from(i16::MAX)) as i16);
}
opus_encode_with_options(encoder, &tmp, frame_size, data, options)
}
pub fn opus_encode24(
encoder: &mut OpusEncoder<'_>,
pcm: &[i32],
frame_size: usize,
data: &mut [u8],
) -> Result<usize, OpusEncodeError> {
opus_encode24_with_options(encoder, pcm, frame_size, data, OpusEncodeOptions::default())
}
pub fn opus_encode_float_with_options(
encoder: &mut OpusEncoder<'_>,
pcm: &[f32],
frame_size: usize,
data: &mut [u8],
options: OpusEncodeOptions<'_>,
) -> Result<usize, OpusEncodeError> {
let channels = usize::try_from(encoder.channels).map_err(|_| OpusEncodeError::BadArgument)?;
let required = channels
.checked_mul(frame_size)
.ok_or(OpusEncodeError::BadArgument)?;
if pcm.len() < required {
return Err(OpusEncodeError::BadArgument);
}
let mut tmp = Vec::with_capacity(required);
for &sample in pcm.iter().take(required) {
let scaled = libm::roundf(sample * 32768.0);
tmp.push(scaled.clamp(f32::from(i16::MIN), f32::from(i16::MAX)) as i16);
}
opus_encode_with_options(encoder, &tmp, frame_size, data, options)
}
pub fn opus_encode_float(
encoder: &mut OpusEncoder<'_>,
pcm: &[f32],
frame_size: usize,
data: &mut [u8],
) -> Result<usize, OpusEncodeError> {
opus_encode_float_with_options(encoder, pcm, frame_size, data, OpusEncodeOptions::default())
}
#[cfg(test)]
mod tests {
extern crate std;
use super::{
CELT_SIG_SCALE, DELAY_BUFFER_SAMPLES, DRED_MAX_FRAMES, MAX_PCM_BUF_SAMPLES,
MAX_TMP_PREFILL_SAMPLES, MODE_CELT_ONLY, MODE_HYBRID, MODE_SILK_ONLY, OPUS_AUTO,
OPUS_BANDWIDTH_NARROWBAND, OPUS_BANDWIDTH_SUPERWIDEBAND, OPUS_BANDWIDTH_WIDEBAND,
OPUS_BITRATE_MAX, OPUS_FRAMESIZE_20_MS, OPUS_FRAMESIZE_40_MS, OPUS_SIGNAL_MUSIC,
OpusEncodeError, OpusEncodeOptions, OpusEncoderCtlError, OpusEncoderCtlRequest,
OpusEncoderInitError, OpusRes, StereoWidthState, VARIABLE_HP_MIN_CUTOFF_HZ,
VARIABLE_HP_SMTH_COEF2_Q16, VERY_SMALL, compute_equiv_rate, compute_redundancy_bytes,
compute_silk_rate_for_hybrid, compute_stereo_width, dc_reject, decide_fec, hp_cutoff,
lin2log, log2lin, opus_encode, opus_encode_with_options, opus_encode24,
opus_encoder_create, opus_encoder_ctl, opus_encoder_get_size,
prepare_celt_prefill_from_delay, prepare_silk_prefill, update_high_pass_state,
user_bitrate_to_bitrate,
};
#[cfg(feature = "dred")]
use super::{
adjust_nb_compr_bytes_for_dred, compute_dred_bitrate, update_dred_activity_history,
};
use crate::packet::{
Bandwidth, opus_packet_get_bandwidth, opus_packet_get_mode, opus_packet_get_nb_frames,
opus_packet_get_samples_per_frame,
};
use alloc::vec;
use alloc::vec::Vec;
#[test]
fn encoder_get_size_matches_components() {
let size = opus_encoder_get_size(1).expect("size");
assert!(size > 0);
}
#[test]
fn create_rejects_invalid_arguments() {
assert_eq!(
opus_encoder_create(44_100, 1, 2048).unwrap_err(),
OpusEncoderInitError::BadArgument
);
assert_eq!(
opus_encoder_create(48_000, 3, 2048).unwrap_err(),
OpusEncoderInitError::BadArgument
);
assert_eq!(
opus_encoder_create(48_000, 1, 123).unwrap_err(),
OpusEncoderInitError::BadArgument
);
}
#[test]
fn init_sets_hybrid_state_defaults() {
let enc = opus_encoder_create(48_000, 1, 2048).expect("encoder");
assert_eq!(enc.voice_ratio, -1);
assert_eq!(enc.encoder_buffer, enc.fs / 100);
assert_eq!(enc.hybrid_stereo_width_q14, 1_i16 << 14);
assert_eq!(
enc.variable_hp_smth2_q15,
lin2log(VARIABLE_HP_MIN_CUTOFF_HZ) << 8
);
assert_eq!(enc.prev_hb_gain, 1.0);
assert!(enc.hp_mem.iter().all(|&value| value == 0.0));
assert_eq!(enc.delay_compensation, enc.fs / 250);
assert_eq!(enc.mode, MODE_HYBRID);
assert_eq!(enc.bandwidth, Bandwidth::Full);
assert_eq!(enc.auto_bandwidth, 0);
assert_eq!(enc.prev_mode, 0);
assert_eq!(enc.prev_channels, 0);
assert_eq!(enc.prev_framesize, 0);
assert!(!enc.silk_bw_switch);
assert!(enc.first);
assert_eq!(enc.delay_buffer.len(), DELAY_BUFFER_SAMPLES);
assert!(enc.delay_buffer.iter().all(|&value| value == 0.0));
assert_eq!(enc.width_mem.xx, 0.0);
assert_eq!(enc.width_mem.xy, 0.0);
assert_eq!(enc.width_mem.yy, 0.0);
assert_eq!(enc.width_mem.smoothed_width, 0.0);
assert_eq!(enc.width_mem.max_follower, 0.0);
assert!(!enc.nonfinal_frame);
#[cfg(not(feature = "fixed_point"))]
assert_eq!(enc.detected_bandwidth, 0);
#[cfg(feature = "dred")]
{
assert_eq!(enc.dred_duration, 0);
assert!(enc.dred_loaded);
assert_eq!(enc.dred_latents_buffer_fill, 0);
assert!(enc.dred_activity_mem.iter().all(|&value| value == 0));
}
}
#[test]
fn prepare_pcm_buffer_includes_delay_history() {
let mut enc = opus_encoder_create(48_000, 1, 2048).expect("encoder");
for (idx, sample) in enc.delay_buffer.iter_mut().enumerate() {
*sample = idx as f32;
}
let frame_size = 480;
let channels = 1usize;
let pcm = vec![1000i16; frame_size * channels];
let mut scratch = [OpusRes::default(); MAX_PCM_BUF_SAMPLES];
let used = super::prepare_pcm_buffer(&enc, &pcm, frame_size, channels, &mut scratch)
.expect("prepare pcm");
let total_buffer = enc.delay_compensation as usize;
let delay_len = total_buffer * channels;
let delay_start = (enc.encoder_buffer as usize - total_buffer) * channels;
assert_eq!(used, (total_buffer + frame_size) * channels);
assert_eq!(
&scratch[..delay_len],
&enc.delay_buffer[delay_start..delay_start + delay_len]
);
let expected = f32::from(pcm[0]) * (1.0 / CELT_SIG_SCALE);
assert_eq!(scratch[delay_len], expected);
}
#[test]
fn silk_prefill_ramps_delay_buffer_and_converts() {
let mut enc = opus_encoder_create(48_000, 1, 2048).expect("encoder");
for sample in enc.delay_buffer.iter_mut() {
*sample = 0.5;
}
let channels = 1usize;
let mut prefill_buf = [0i16; DELAY_BUFFER_SAMPLES];
let prefill_len =
prepare_silk_prefill(&mut enc, channels, &mut prefill_buf).expect("prefill");
let encoder_buffer = enc.encoder_buffer as usize;
assert_eq!(prefill_len, encoder_buffer * channels);
let ramp_samples = (enc.fs / 400) as usize;
let delay_comp = enc.delay_compensation as usize;
let prefill_offset = (encoder_buffer - delay_comp - ramp_samples) * channels;
assert!(
enc.delay_buffer[..prefill_offset]
.iter()
.all(|&value| value == 0.0)
);
if delay_comp > 0 {
let tail_index = prefill_len - 1;
assert!((enc.delay_buffer[tail_index] - 0.5).abs() < 1e-6);
let expected = libm::roundf(0.5 * CELT_SIG_SCALE) as i16;
assert_eq!(prefill_buf[tail_index], expected);
}
assert_eq!(prefill_buf[0], 0);
}
#[test]
fn celt_prefill_copies_delay_buffer_tail() {
let mut enc = opus_encoder_create(48_000, 2, 2048).expect("encoder");
for (idx, sample) in enc.delay_buffer.iter_mut().enumerate() {
*sample = idx as f32;
}
let channels = 2usize;
let total_buffer = enc.delay_compensation as usize;
let mut tmp_prefill = [OpusRes::default(); MAX_TMP_PREFILL_SAMPLES];
let prefill_len =
prepare_celt_prefill_from_delay(&enc, channels, total_buffer, &mut tmp_prefill)
.expect("prefill");
let encoder_buffer = enc.encoder_buffer as usize;
let prefill_samples = (enc.fs / 400) as usize;
let expected_start = (encoder_buffer - total_buffer - prefill_samples) * channels;
assert_eq!(prefill_len, prefill_samples * channels);
assert_eq!(
&tmp_prefill[..prefill_len],
&enc.delay_buffer[expected_start..expected_start + prefill_len]
);
}
#[test]
fn update_delay_buffer_shifts_when_frame_smaller_than_encoder_buffer() {
let mut enc = opus_encoder_create(48_000, 1, 2048).expect("encoder");
let channels = 1usize;
let frame_size = 240usize;
let total_buffer = enc.delay_compensation as usize;
let encoder_buffer = enc.encoder_buffer as usize;
assert!(encoder_buffer > frame_size + total_buffer);
for (idx, sample) in enc.delay_buffer.iter_mut().enumerate() {
*sample = idx as f32;
}
let original = enc.delay_buffer;
let frame_total = frame_size + total_buffer;
let mut pcm_buf = vec![0.0f32; frame_total * channels];
for (idx, sample) in pcm_buf.iter_mut().enumerate() {
*sample = 1000.0 + idx as f32;
}
super::update_delay_buffer(&mut enc, &pcm_buf, frame_size, total_buffer, channels)
.expect("update delay buffer");
let move_len = (encoder_buffer - frame_total) * channels;
let src_start = frame_size * channels;
let src_end = src_start + move_len;
assert_eq!(&enc.delay_buffer[..move_len], &original[src_start..src_end]);
let dst_end = move_len + frame_total * channels;
assert_eq!(
&enc.delay_buffer[move_len..dst_end],
&pcm_buf[..frame_total * channels]
);
let encoder_samples = encoder_buffer * channels;
assert_eq!(
&enc.delay_buffer[encoder_samples..],
&original[encoder_samples..]
);
}
#[test]
fn update_delay_buffer_copies_tail_when_frame_exceeds_encoder_buffer() {
let mut enc = opus_encoder_create(48_000, 2, 2048).expect("encoder");
let channels = 2usize;
let frame_size = 480usize;
let total_buffer = enc.delay_compensation as usize;
let encoder_buffer = enc.encoder_buffer as usize;
assert!(encoder_buffer <= frame_size + total_buffer);
let frame_total = frame_size + total_buffer;
let mut pcm_buf = vec![0.0f32; frame_total * channels];
for (idx, sample) in pcm_buf.iter_mut().enumerate() {
*sample = -500.0 + idx as f32;
}
super::update_delay_buffer(&mut enc, &pcm_buf, frame_size, total_buffer, channels)
.expect("update delay buffer");
let encoder_samples = encoder_buffer * channels;
let expected_start = (frame_total - encoder_buffer) * channels;
assert_eq!(
&enc.delay_buffer[..encoder_samples],
&pcm_buf[expected_start..expected_start + encoder_samples]
);
}
#[test]
fn update_high_pass_state_smooths_towards_silk_target() {
let mut enc = opus_encoder_create(48_000, 1, 2048).expect("encoder");
let prev = lin2log(VARIABLE_HP_MIN_CUTOFF_HZ) << 8;
let target = lin2log(80) << 8;
enc.variable_hp_smth2_q15 = prev;
enc.silk.state_fxx[0].common_mut().variable_hp_smth1_q15 = target;
let expected = prev
+ (((i64::from(target - prev) * i64::from(VARIABLE_HP_SMTH_COEF2_Q16 as i16)) >> 16)
as i32);
let cutoff = update_high_pass_state(&mut enc, MODE_HYBRID);
assert_eq!(enc.variable_hp_smth2_q15, expected);
assert_eq!(cutoff, log2lin(expected >> 8));
}
#[test]
fn update_high_pass_state_uses_min_cutoff_for_celt() {
let mut enc = opus_encoder_create(48_000, 1, 2048).expect("encoder");
let prev = lin2log(80) << 8;
let target = lin2log(VARIABLE_HP_MIN_CUTOFF_HZ) << 8;
enc.variable_hp_smth2_q15 = prev;
enc.silk.state_fxx[0].common_mut().variable_hp_smth1_q15 = lin2log(100) << 8;
let expected = prev
+ (((i64::from(target - prev) * i64::from(VARIABLE_HP_SMTH_COEF2_Q16 as i16)) >> 16)
as i32);
let cutoff = update_high_pass_state(&mut enc, MODE_CELT_ONLY);
assert_eq!(enc.variable_hp_smth2_q15, expected);
assert_eq!(cutoff, log2lin(expected >> 8));
}
#[test]
fn dc_reject_matches_reference_steps() {
let pcm = [1000i16; 3];
let mut out = [0.0f32; 3];
let mut mem = [0.0f32; 4];
dc_reject(&pcm, 3, &mut out, &mut mem, 3, 1, 48_000);
let scale = 1.0 / CELT_SIG_SCALE;
let x = 1000.0 * scale;
let coef = 6.3f32 * 3.0 / 48_000.0;
let coef2 = 1.0 - coef;
let mut m0 = 0.0f32;
let mut expected = [0.0f32; 3];
for value in expected.iter_mut() {
*value = x - m0;
m0 = coef * x + VERY_SMALL + coef2 * m0;
}
for idx in 0..3 {
assert!((out[idx] - expected[idx]).abs() < 1.0e-6);
}
assert!((mem[0] - m0).abs() < 1.0e-6);
assert_eq!(mem[2], 0.0);
}
#[test]
fn hp_cutoff_modifies_signal_and_state() {
let pcm = [1000i16; 16];
let mut out = [0.0f32; 16];
let mut mem = [0.0f32; 4];
hp_cutoff(
&pcm,
VARIABLE_HP_MIN_CUTOFF_HZ,
&mut out,
&mut mem,
16,
1,
48_000,
);
let reference = 1000.0 * (1.0 / CELT_SIG_SCALE);
assert!(out.iter().any(|&value| (value - reference).abs() > 1.0e-6));
assert!(mem[0] != 0.0 || mem[1] != 0.0);
}
#[test]
fn encodes_silk_only_frame_with_valid_toc() {
let mut enc = opus_encoder_create(48_000, 1, 2048).expect("encoder");
opus_encoder_ctl(&mut enc, OpusEncoderCtlRequest::SetBitrate(12_000)).unwrap();
let pcm = [0i16; 960];
let mut out = [0u8; 4000];
let len = opus_encode(&mut enc, &pcm, 960, &mut out).expect("encode");
assert!(len > 1);
assert_eq!(
opus_packet_get_mode(&out[..len]).unwrap(),
crate::packet::Mode::SILK
);
assert_eq!(
opus_packet_get_bandwidth(&out[..len]).unwrap(),
Bandwidth::Wide
);
}
#[test]
fn encodes_celt_only_frame_with_valid_toc() {
let mut enc = opus_encoder_create(48_000, 1, 2048).expect("encoder");
opus_encoder_ctl(
&mut enc,
OpusEncoderCtlRequest::SetForceMode(MODE_CELT_ONLY),
)
.unwrap();
let pcm = [0i16; 480];
let mut out = [0u8; 4000];
let len = opus_encode(&mut enc, &pcm, 480, &mut out).expect("encode");
assert!(len >= 1);
assert_eq!(
opus_packet_get_mode(&out[..len]).unwrap(),
crate::packet::Mode::CELT
);
assert_eq!(
opus_packet_get_samples_per_frame(&out[..len], 48_000).unwrap(),
480
);
}
#[test]
fn encodes_hybrid_frame_with_valid_toc() {
let mut enc = opus_encoder_create(48_000, 1, 2048).expect("encoder");
opus_encoder_ctl(&mut enc, OpusEncoderCtlRequest::SetForceMode(MODE_HYBRID)).unwrap();
let pcm = [0i16; 960];
let mut out = [0u8; 4000];
let len = opus_encode(&mut enc, &pcm, 960, &mut out).expect("encode");
assert!(len >= 1);
assert_eq!(
opus_packet_get_mode(&out[..len]).unwrap(),
crate::packet::Mode::HYBRID
);
assert_eq!(
opus_packet_get_samples_per_frame(&out[..len], 48_000).unwrap(),
960
);
}
#[test]
fn celt_start_band_resets_when_switching_to_celt_only() {
let mut enc = opus_encoder_create(48_000, 1, 2048).expect("encoder");
opus_encoder_ctl(&mut enc, OpusEncoderCtlRequest::SetForceMode(MODE_HYBRID)).unwrap();
let pcm = [0i16; 960];
let mut out = [0u8; 4000];
opus_encode(&mut enc, &pcm, 960, &mut out).expect("hybrid encode");
assert_eq!(enc.celt.encoder().start_band, 17);
opus_encoder_ctl(
&mut enc,
OpusEncoderCtlRequest::SetForceMode(MODE_CELT_ONLY),
)
.unwrap();
opus_encode(&mut enc, &pcm, 960, &mut out).expect("celt encode");
assert_eq!(enc.celt.encoder().start_band, 0);
}
#[test]
fn encodes_hybrid_multiframe_packet() {
let mut enc = opus_encoder_create(48_000, 1, 2048).expect("encoder");
opus_encoder_ctl(&mut enc, OpusEncoderCtlRequest::SetForceMode(MODE_HYBRID)).unwrap();
opus_encoder_ctl(
&mut enc,
OpusEncoderCtlRequest::SetExpertFrameDuration(OPUS_FRAMESIZE_40_MS),
)
.unwrap();
let pcm = [0i16; 1920];
let mut out = [0u8; 4000];
let len = opus_encode(&mut enc, &pcm, 1920, &mut out).expect("encode");
assert!(len >= 1);
assert_eq!(
opus_packet_get_mode(&out[..len]).unwrap(),
crate::packet::Mode::HYBRID
);
assert_eq!(opus_packet_get_nb_frames(&out[..len], len).unwrap(), 2);
assert_eq!(
opus_packet_get_samples_per_frame(&out[..len], 48_000).unwrap(),
960
);
}
#[test]
fn restricted_low_delay_forces_celt() {
let mut enc = opus_encoder_create(48_000, 1, 2051).expect("encoder");
let pcm = [0i16; 960];
let mut out = [0u8; 4000];
let len = opus_encode(&mut enc, &pcm, 960, &mut out).expect("encode");
assert_eq!(
opus_packet_get_mode(&out[..len]).unwrap(),
crate::packet::Mode::CELT
);
}
#[test]
fn bandwidth_clamps_to_sample_rate_limits() {
let mut enc = opus_encoder_create(16_000, 1, 2048).expect("encoder");
opus_encoder_ctl(
&mut enc,
OpusEncoderCtlRequest::SetForceMode(MODE_CELT_ONLY),
)
.unwrap();
let pcm = [0i16; 320];
let mut out = [0u8; 4000];
let _ = opus_encode(&mut enc, &pcm, 320, &mut out).expect("encode");
assert!(enc.bandwidth.to_opus_int() <= OPUS_BANDWIDTH_WIDEBAND);
}
#[test]
fn ctl_round_trips_basic_settings() {
let mut enc = opus_encoder_create(48_000, 1, 2048).expect("encoder");
opus_encoder_ctl(&mut enc, OpusEncoderCtlRequest::SetVbr(false)).unwrap();
let mut vbr = true;
opus_encoder_ctl(&mut enc, OpusEncoderCtlRequest::GetVbr(&mut vbr)).unwrap();
assert!(!vbr);
}
#[test]
fn ctl_round_trips_extended_settings() {
let mut enc = opus_encoder_create(48_000, 2, 2048).expect("encoder");
opus_encoder_ctl(&mut enc, OpusEncoderCtlRequest::SetForceChannels(1)).unwrap();
let mut force_channels = 0;
opus_encoder_ctl(
&mut enc,
OpusEncoderCtlRequest::GetForceChannels(&mut force_channels),
)
.unwrap();
assert_eq!(force_channels, 1);
opus_encoder_ctl(
&mut enc,
OpusEncoderCtlRequest::SetMaxBandwidth(OPUS_BANDWIDTH_SUPERWIDEBAND),
)
.unwrap();
let mut max_bandwidth = 0;
opus_encoder_ctl(
&mut enc,
OpusEncoderCtlRequest::GetMaxBandwidth(&mut max_bandwidth),
)
.unwrap();
assert_eq!(max_bandwidth, OPUS_BANDWIDTH_SUPERWIDEBAND);
opus_encoder_ctl(
&mut enc,
OpusEncoderCtlRequest::SetBandwidth(OPUS_BANDWIDTH_NARROWBAND),
)
.unwrap();
opus_encoder_ctl(
&mut enc,
OpusEncoderCtlRequest::SetSignal(OPUS_SIGNAL_MUSIC),
)
.unwrap();
let mut signal = 0;
opus_encoder_ctl(&mut enc, OpusEncoderCtlRequest::GetSignal(&mut signal)).unwrap();
assert_eq!(signal, OPUS_SIGNAL_MUSIC);
opus_encoder_ctl(&mut enc, OpusEncoderCtlRequest::SetLsbDepth(16)).unwrap();
let mut lsb_depth = 0;
opus_encoder_ctl(&mut enc, OpusEncoderCtlRequest::GetLsbDepth(&mut lsb_depth)).unwrap();
assert_eq!(lsb_depth, 16);
opus_encoder_ctl(
&mut enc,
OpusEncoderCtlRequest::SetExpertFrameDuration(OPUS_FRAMESIZE_20_MS),
)
.unwrap();
let mut frame_duration = 0;
opus_encoder_ctl(
&mut enc,
OpusEncoderCtlRequest::GetExpertFrameDuration(&mut frame_duration),
)
.unwrap();
assert_eq!(frame_duration, OPUS_FRAMESIZE_20_MS);
opus_encoder_ctl(&mut enc, OpusEncoderCtlRequest::SetPredictionDisabled(true)).unwrap();
let mut prediction_disabled = false;
opus_encoder_ctl(
&mut enc,
OpusEncoderCtlRequest::GetPredictionDisabled(&mut prediction_disabled),
)
.unwrap();
assert!(prediction_disabled);
opus_encoder_ctl(
&mut enc,
OpusEncoderCtlRequest::SetPhaseInversionDisabled(true),
)
.unwrap();
let mut phase_inversion_disabled = false;
opus_encoder_ctl(
&mut enc,
OpusEncoderCtlRequest::GetPhaseInversionDisabled(&mut phase_inversion_disabled),
)
.unwrap();
assert!(phase_inversion_disabled);
opus_encoder_ctl(
&mut enc,
OpusEncoderCtlRequest::SetForceMode(MODE_SILK_ONLY),
)
.unwrap();
}
#[cfg(feature = "dred")]
#[test]
fn ctl_round_trips_dred_duration() {
let mut enc = opus_encoder_create(48_000, 1, 2048).expect("encoder");
opus_encoder_ctl(&mut enc, OpusEncoderCtlRequest::SetDredDuration(12)).unwrap();
let mut duration = 0;
opus_encoder_ctl(
&mut enc,
OpusEncoderCtlRequest::GetDredDuration(&mut duration),
)
.unwrap();
assert_eq!(duration, 12);
}
#[cfg(feature = "dred")]
#[test]
fn ctl_rejects_invalid_dred_duration() {
let mut enc = opus_encoder_create(48_000, 1, 2048).expect("encoder");
assert_eq!(
opus_encoder_ctl(&mut enc, OpusEncoderCtlRequest::SetDredDuration(-1)).unwrap_err(),
OpusEncoderCtlError::BadArgument
);
assert_eq!(
opus_encoder_ctl(
&mut enc,
OpusEncoderCtlRequest::SetDredDuration(DRED_MAX_FRAMES + 1),
)
.unwrap_err(),
OpusEncoderCtlError::BadArgument
);
}
#[cfg(feature = "dred")]
#[test]
fn ctl_rejects_invalid_dnn_blob() {
let mut enc = opus_encoder_create(48_000, 1, 2048).expect("encoder");
assert!(enc.dred_loaded);
assert_eq!(
opus_encoder_ctl(&mut enc, OpusEncoderCtlRequest::SetDnnBlob(&[1, 2, 3])).unwrap_err(),
OpusEncoderCtlError::BadArgument
);
assert!(enc.dred_loaded);
}
#[cfg(feature = "dred")]
#[test]
fn dred_activity_history_updates_when_loaded() {
let mut enc = opus_encoder_create(48_000, 1, 2048).expect("encoder");
enc.dred_duration = 10;
enc.dred_loaded = true;
for (idx, value) in enc.dred_activity_mem.iter_mut().enumerate() {
*value = idx as u8;
}
let frame_size = 480usize;
update_dred_activity_history(&mut enc, 1, frame_size);
let frame_size_400hz = frame_size * 400 / enc.fs as usize;
assert!(
enc.dred_activity_mem[..frame_size_400hz]
.iter()
.all(|&value| value == 1)
);
assert_eq!(enc.dred_activity_mem[frame_size_400hz], 0);
assert_eq!(enc.dred_activity_mem[frame_size_400hz + 1], 1);
}
#[cfg(feature = "dred")]
#[test]
fn dred_activity_history_clears_when_unloaded() {
let mut enc = opus_encoder_create(48_000, 1, 2048).expect("encoder");
enc.dred_duration = 10;
enc.dred_loaded = false;
enc.dred_latents_buffer_fill = 5;
enc.dred_activity_mem.fill(7);
update_dred_activity_history(&mut enc, 1, 480);
assert_eq!(enc.dred_latents_buffer_fill, 0);
assert!(enc.dred_activity_mem.iter().all(|&value| value == 0));
}
#[cfg(feature = "dred")]
#[test]
fn dred_celt_bytes_adjustment_caps_budget() {
let adjusted = adjust_nb_compr_bytes_for_dred(100, 80, 50, 8_000);
assert_eq!(adjusted, 85);
let adjusted = adjust_nb_compr_bytes_for_dred(40, 320, 50, 8_000);
assert_eq!(adjusted, 40);
}
#[cfg(feature = "dred")]
#[test]
fn dred_bitrate_zero_when_duration_unset() {
let mut enc = opus_encoder_create(48_000, 1, 2048).expect("encoder");
enc.dred_duration = 0;
enc.inband_fec = true;
enc.packet_loss_perc = 20;
let dred_bitrate = compute_dred_bitrate(&mut enc, 64_000, 960);
assert_eq!(dred_bitrate, 0);
assert_eq!(enc.dred_target_chunks, 0);
}
#[test]
fn ctl_rejects_invalid_extended_settings() {
let mut enc = opus_encoder_create(48_000, 1, 2048).expect("encoder");
assert_eq!(
opus_encoder_ctl(&mut enc, OpusEncoderCtlRequest::SetForceChannels(3)).unwrap_err(),
OpusEncoderCtlError::BadArgument
);
assert_eq!(
opus_encoder_ctl(&mut enc, OpusEncoderCtlRequest::SetMaxBandwidth(999)).unwrap_err(),
OpusEncoderCtlError::BadArgument
);
assert_eq!(
opus_encoder_ctl(&mut enc, OpusEncoderCtlRequest::SetSignal(42)).unwrap_err(),
OpusEncoderCtlError::BadArgument
);
assert_eq!(
opus_encoder_ctl(&mut enc, OpusEncoderCtlRequest::SetLsbDepth(7)).unwrap_err(),
OpusEncoderCtlError::BadArgument
);
assert_eq!(
opus_encoder_ctl(
&mut enc,
OpusEncoderCtlRequest::SetExpertFrameDuration(4242)
)
.unwrap_err(),
OpusEncoderCtlError::BadArgument
);
assert_eq!(
opus_encoder_ctl(
&mut enc,
OpusEncoderCtlRequest::SetForceMode(MODE_CELT_ONLY + 1)
)
.unwrap_err(),
OpusEncoderCtlError::BadArgument
);
}
#[test]
fn encode_rejects_unsupported_frames() {
let mut enc = opus_encoder_create(48_000, 1, 2048).expect("encoder");
let pcm = [0i16; 479];
let mut out = [0u8; 4000];
let err = opus_encode(&mut enc, &pcm, 479, &mut out).unwrap_err();
assert_eq!(err, OpusEncodeError::BadArgument);
}
#[test]
fn opus_encode24_accepts_int24_input() {
let mut enc = opus_encoder_create(48_000, 1, 2051).expect("encoder");
let pcm = vec![0i32; 960];
let mut out = vec![0u8; 1500];
let result = opus_encode24(&mut enc, &pcm, 960, &mut out);
assert!(result.is_ok(), "opus_encode24 should accept int24 input");
}
#[test]
fn user_bitrate_to_bitrate_respects_auto_and_max() {
let mut enc = opus_encoder_create(48_000, 2, 2048).expect("encoder");
let frame_size = 960;
let max_data_bytes = 200;
enc.user_bitrate_bps = OPUS_AUTO;
let expected_auto = 60 * 48_000 / frame_size + 48_000 * 2;
assert_eq!(
user_bitrate_to_bitrate(&enc, frame_size, max_data_bytes),
expected_auto
);
enc.user_bitrate_bps = OPUS_BITRATE_MAX;
let expected_max = max_data_bytes * 8 * 48_000 / frame_size;
assert_eq!(
user_bitrate_to_bitrate(&enc, frame_size, max_data_bytes),
expected_max
);
enc.user_bitrate_bps = 12_345;
assert_eq!(
user_bitrate_to_bitrate(&enc, frame_size, max_data_bytes),
12_345
);
}
#[test]
fn compute_equiv_rate_matches_reference_math() {
let equiv = compute_equiv_rate(10_000, 1, 100, true, MODE_CELT_ONLY, 10, 0);
assert_eq!(equiv, 7_000);
}
#[test]
fn decide_fec_restores_bandwidth_when_unavailable() {
let mut bandwidth = OPUS_BANDWIDTH_WIDEBAND;
let enabled = decide_fec(true, 20, false, MODE_SILK_ONLY, &mut bandwidth, 1_000);
assert!(!enabled);
assert_eq!(bandwidth, OPUS_BANDWIDTH_WIDEBAND);
}
#[test]
fn compute_redundancy_bytes_respects_caps() {
let redundancy = compute_redundancy_bytes(100, 20_000, 50, 1);
assert_eq!(redundancy, 24);
}
#[test]
fn compute_silk_rate_for_hybrid_from_table() {
let silk_rate = compute_silk_rate_for_hybrid(
24_000,
OPUS_BANDWIDTH_SUPERWIDEBAND,
true,
true,
false,
1,
);
assert_eq!(silk_rate, 18_300);
}
#[test]
fn compute_stereo_width_stays_zero_for_silence() {
let pcm = vec![0i16; 2 * 96];
let mut mem = StereoWidthState::default();
let width = compute_stereo_width(&pcm, 96, 48_000, &mut mem);
assert_eq!(width, 0.0);
assert_eq!(mem.xx, 0.0);
assert_eq!(mem.xy, 0.0);
assert_eq!(mem.yy, 0.0);
assert_eq!(mem.smoothed_width, 0.0);
assert_eq!(mem.max_follower, 0.0);
}
#[test]
fn gain_fade_unity_gain_preserves_signal() {
let original = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
let mut pcm = original.clone();
let window = vec![0.5f32; 120];
super::gain_fade(&mut pcm, 1.0, 1.0, 120, 8, 1, &window, 48_000);
for (i, &val) in pcm.iter().enumerate() {
assert!(
(val - original[i]).abs() < 1e-6,
"Sample {} changed from {} to {} with unity gain",
i,
original[i],
val
);
}
}
#[test]
fn gain_fade_applies_constant_gain_after_overlap() {
let mut pcm = vec![1.0; 960]; let window = vec![1.0f32; 120]; let g2 = 0.5;
super::gain_fade(&mut pcm, 1.0, g2, 120, 960, 1, &window, 48_000);
for &val in &pcm[120..] {
assert!(
(val - g2).abs() < 1e-6,
"Post-overlap sample should be {} but got {}",
g2,
val
);
}
}
#[test]
fn gain_fade_stereo_applies_to_both_channels() {
let mut pcm = vec![1.0; 960 * 2]; let window = vec![1.0f32; 120];
let g2 = 0.5;
super::gain_fade(&mut pcm, 1.0, g2, 120, 960, 2, &window, 48_000);
for i in 120..960 {
let left = pcm[i * 2];
let right = pcm[i * 2 + 1];
assert!(
(left - g2).abs() < 1e-6 && (right - g2).abs() < 1e-6,
"Sample {}: left={}, right={}, expected {}",
i,
left,
right,
g2
);
}
}
#[test]
fn gain_fade_interpolates_in_overlap_region() {
let mut pcm = vec![1.0; 240]; let window: Vec<f32> = (0..120).map(|i| i as f32 / 119.0).collect();
let g1 = 1.0;
let g2 = 0.5;
super::gain_fade(&mut pcm, g1, g2, 120, 240, 1, &window, 48_000);
assert!(
(pcm[0] - g1).abs() < 1e-6,
"First sample should be close to g1={}, got {}",
g1,
pcm[0]
);
let last_overlap = 119;
assert!(
(pcm[last_overlap] - g2).abs() < 0.02,
"Last overlap sample should be close to g2={}, got {}",
g2,
pcm[last_overlap]
);
}
#[test]
fn hb_gain_computation_matches_expected_values() {
use crate::celt::celt_exp2;
let high_celt_rate = 32_000i32;
let hb_gain_high = 1.0 - 0.5 * celt_exp2(-high_celt_rate as f32 / 1024.0);
assert!(
hb_gain_high > 0.99,
"High CELT rate should give HB_gain near 1.0, got {}",
hb_gain_high
);
let low_celt_rate = 2_000i32;
let hb_gain_low = 1.0 - 0.5 * celt_exp2(-low_celt_rate as f32 / 1024.0);
assert!(
hb_gain_low < hb_gain_high,
"Low CELT rate ({}) should give lower HB_gain than high rate ({})",
hb_gain_low,
hb_gain_high
);
assert!(
hb_gain_low > 0.3,
"Low CELT rate HB_gain should still be positive, got {}",
hb_gain_low
);
let zero_celt_rate = 0i32;
let hb_gain_zero = 1.0 - 0.5 * celt_exp2(-zero_celt_rate as f32 / 1024.0);
assert!(
(hb_gain_zero - 0.5).abs() < 1e-6,
"Zero CELT rate should give HB_gain=0.5, got {}",
hb_gain_zero
);
}
#[test]
fn stereo_fade_unity_width_preserves_signal() {
let original = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
let mut pcm = original.clone();
let window = vec![0.5f32; 120];
super::stereo_fade(&mut pcm, 1.0, 1.0, 120, 4, 2, &window, 48_000);
for (i, &val) in pcm.iter().enumerate() {
assert!(
(val - original[i]).abs() < 1e-6,
"Sample {} changed from {} to {} with unity width",
i,
original[i],
val
);
}
}
#[test]
fn stereo_fade_zero_width_collapses_to_mono() {
let mut pcm = vec![
4.0, 2.0, 6.0, 0.0, 10.0, 2.0, 8.0, 4.0, ];
let window = vec![1.0f32; 120];
super::stereo_fade(&mut pcm, 0.0, 0.0, 120, 4, 2, &window, 48_000);
for i in 0..4 {
let left = pcm[i * 2];
let right = pcm[i * 2 + 1];
assert!(
(left - right).abs() < 1e-6,
"Sample {}: L={} should equal R={} for mono collapse",
i,
left,
right
);
}
assert!((pcm[0] - 3.0).abs() < 1e-6, "Sample 0 mid should be 3.0");
assert!((pcm[2] - 3.0).abs() < 1e-6, "Sample 1 mid should be 3.0");
assert!((pcm[4] - 6.0).abs() < 1e-6, "Sample 2 mid should be 6.0");
assert!((pcm[6] - 6.0).abs() < 1e-6, "Sample 3 mid should be 6.0");
}
#[test]
fn stereo_fade_applies_constant_after_overlap() {
let mut pcm = vec![0.0; 960 * 2]; for i in 0..960 {
pcm[i * 2] = 1.0;
pcm[i * 2 + 1] = -1.0;
}
let window = vec![1.0f32; 120];
let g2 = 0.5;
super::stereo_fade(&mut pcm, 1.0, g2, 120, 960, 2, &window, 48_000);
for i in 120..960 {
let left = pcm[i * 2];
let right = pcm[i * 2 + 1];
assert!(
(left - 0.5).abs() < 1e-6,
"Sample {} left should be 0.5, got {}",
i,
left
);
assert!(
(right - (-0.5)).abs() < 1e-6,
"Sample {} right should be -0.5, got {}",
i,
right
);
}
}
#[test]
fn stereo_fade_interpolates_in_overlap_region() {
let mut pcm = vec![0.0; 240 * 2];
for i in 0..240 {
pcm[i * 2] = 1.0;
pcm[i * 2 + 1] = -1.0;
}
let window: Vec<f32> = (0..120).map(|i| i as f32 / 119.0).collect();
let g1 = 1.0; let g2 = 0.0;
super::stereo_fade(&mut pcm, g1, g2, 120, 240, 2, &window, 48_000);
assert!(
(pcm[0] - 1.0).abs() < 1e-6,
"First sample L should be 1.0, got {}",
pcm[0]
);
assert!(
(pcm[1] - (-1.0)).abs() < 1e-6,
"First sample R should be -1.0, got {}",
pcm[1]
);
let last = 119;
assert!(
pcm[last * 2].abs() < 0.02,
"Last overlap L should be near 0, got {}",
pcm[last * 2]
);
assert!(
pcm[last * 2 + 1].abs() < 0.02,
"Last overlap R should be near 0, got {}",
pcm[last * 2 + 1]
);
}
#[test]
fn surround_masking_rate_offset_zero_masks() {
let mask = [0.0f32; 42]; let offset = super::compute_surround_masking_rate_offset(&mask, OPUS_BANDWIDTH_WIDEBAND, 2);
assert_eq!(offset, 3200);
}
#[test]
fn surround_masking_rate_offset_clamps_negative_masks() {
let mask = [-10.0f32; 42];
let offset = super::compute_surround_masking_rate_offset(&mask, OPUS_BANDWIDTH_WIDEBAND, 2);
assert_eq!(offset, -28800);
}
#[test]
fn surround_masking_rate_offset_halves_positive_masks() {
let mask = [0.4f32; 42];
let offset = super::compute_surround_masking_rate_offset(&mask, OPUS_BANDWIDTH_WIDEBAND, 2);
assert!((offset - 6400).abs() <= 1, "expected ~6400, got {}", offset);
}
#[test]
fn surround_masking_rate_offset_narrowband_uses_fewer_bands() {
let mask = [0.0f32; 42];
let offset =
super::compute_surround_masking_rate_offset(&mask, OPUS_BANDWIDTH_NARROWBAND, 2);
assert_eq!(offset, 1600);
}
#[test]
fn ctl_lfe_round_trip() {
let mut enc = opus_encoder_create(48_000, 1, 2048).expect("encoder");
assert!(!enc.lfe);
opus_encoder_ctl(&mut enc, OpusEncoderCtlRequest::SetLfe(true)).unwrap();
assert!(enc.lfe);
let mut lfe_out = false;
opus_encoder_ctl(&mut enc, OpusEncoderCtlRequest::GetLfe(&mut lfe_out)).unwrap();
assert!(lfe_out);
opus_encoder_ctl(&mut enc, OpusEncoderCtlRequest::SetLfe(false)).unwrap();
assert!(!enc.lfe);
}
#[test]
fn encode_with_options_accepts_energy_masking_slice() {
let mut enc = opus_encoder_create(48_000, 2, 2048).expect("encoder");
let pcm = vec![0i16; 960 * 2];
let mut out = vec![0u8; 512];
let mask = [0.1f32; 42];
let len = opus_encode_with_options(
&mut enc,
&pcm,
960,
&mut out,
OpusEncodeOptions {
energy_masking: Some(&mask),
},
)
.expect("encode");
assert!(len > 0);
}
#[test]
fn init_sets_energy_masking_defaults() {
let enc = opus_encoder_create(48_000, 2, 2048).expect("encoder");
assert!(!enc.lfe);
}
#[test]
fn opus_mode_trace_output() {
#[cfg(test)]
use crate::opus_encoder::celt_pcm_trace;
let path = match std::env::var("OPUS_TRACE_PCM") {
Ok(value) => value,
Err(_) => return,
};
let frames = std::env::var("OPUS_TRACE_FRAMES")
.ok()
.and_then(|value| value.parse::<usize>().ok())
.unwrap_or(64);
let mut file = std::fs::File::open(&path).expect("open OPUS_TRACE_PCM");
let mut encoder = opus_encoder_create(48_000, 2, 2049).expect("encoder init");
opus_encoder_ctl(&mut encoder, OpusEncoderCtlRequest::SetBitrate(64_000))
.expect("set bitrate");
let channels = 2usize;
let mut input_bytes = vec![0u8; 960 * channels * 2];
let mut input_pcm = vec![0i16; 960 * channels];
let mut packet = vec![0u8; 3 * 1276];
for frame_idx in 0..frames {
match std::io::Read::read_exact(&mut file, &mut input_bytes) {
Ok(()) => {}
Err(err) if err.kind() == std::io::ErrorKind::UnexpectedEof => break,
Err(err) => panic!("read OPUS_TRACE_PCM failed: {err}"),
}
#[cfg(test)]
if let Some(cfg) = celt_pcm_trace::config_copy()
&& cfg.frame.map_or(true, |frame| frame == frame_idx)
{
for (sample, chunk) in input_pcm.iter_mut().zip(input_bytes.chunks_exact(2)) {
*sample = i16::from_le_bytes([chunk[0], chunk[1]]);
}
celt_pcm_trace::dump(
"opus_trace_pcm",
&input_pcm,
960,
channels,
cfg.start,
cfg.count,
cfg.want_bits,
frame_idx,
);
}
for (sample, chunk) in input_pcm.iter_mut().zip(input_bytes.chunks_exact(2)) {
*sample = i16::from_le_bytes([chunk[0], chunk[1]]);
}
let _ = opus_encode(&mut encoder, &input_pcm, 960, &mut packet).expect("opus_encode");
}
}
}