use crate::error::{CodecError, Result};
use crate::types::{AudioCodec, AudioCodecExt, CodecConfig, CodecInfo, SampleRate};
use crate::utils::validate_opus_frame;
use tracing::{debug, trace, warn};
pub use crate::types::OpusApplication;
pub struct OpusCodec {
sample_rate: u32,
channels: u8,
frame_size: usize,
config: OpusConfig,
}
#[derive(Debug, Clone)]
pub struct OpusConfig {
pub application: OpusApplication,
pub bitrate: u32,
pub vbr: bool,
pub cvbr: bool,
pub complexity: u8,
pub inband_fec: bool,
pub dtx: bool,
pub packet_loss_perc: u8,
pub force_mono: bool,
}
impl Default for OpusConfig {
fn default() -> Self {
Self {
application: OpusApplication::Voip,
bitrate: 64000,
vbr: true,
cvbr: false,
complexity: 5,
inband_fec: false,
dtx: false,
packet_loss_perc: 0,
force_mono: false,
}
}
}
impl OpusCodec {
pub fn new(config: CodecConfig) -> Result<Self> {
let sample_rate = config.sample_rate.hz();
if ![8000, 12000, 16000, 24000, 48000].contains(&sample_rate) {
return Err(CodecError::InvalidSampleRate {
rate: sample_rate,
supported: vec![8000, 12000, 16000, 24000, 48000],
});
}
if config.channels == 0 || config.channels > 2 {
return Err(CodecError::InvalidChannelCount {
channels: config.channels,
supported: vec![1, 2],
});
}
let frame_size = if let Some(frame_ms) = config.frame_size_ms {
let samples_per_ms = sample_rate as f32 / 1000.0;
(samples_per_ms * frame_ms) as usize
} else {
(sample_rate * 20 / 1000) as usize
};
let opus_config = OpusConfig {
application: config.parameters.opus.application,
bitrate: config.parameters.opus.bitrate,
vbr: config.parameters.opus.vbr,
cvbr: config.parameters.opus.cvbr,
complexity: config.parameters.opus.complexity,
inband_fec: config.parameters.opus.inband_fec,
dtx: config.parameters.opus.dtx,
packet_loss_perc: config.parameters.opus.packet_loss_perc,
force_mono: config.parameters.opus.force_mono,
};
debug!(
"Creating Opus codec: {}Hz, {}ch, {}bps, {:?} mode",
sample_rate, config.channels, opus_config.bitrate, opus_config.application
);
Ok(Self {
sample_rate,
channels: config.channels,
frame_size,
config: opus_config,
})
}
pub fn compression_ratio(&self) -> f32 {
let uncompressed_bits = self.frame_size as f32 * 16.0 * self.channels as f32;
let compressed_bits =
self.config.bitrate as f32 * (self.frame_size as f32 / self.sample_rate as f32);
compressed_bits / uncompressed_bits
}
pub fn set_bitrate(&mut self, bitrate: u32) -> Result<()> {
if bitrate < 6000 || bitrate > 510000 {
return Err(CodecError::InvalidBitrate {
bitrate,
min: 6000,
max: 510000,
});
}
self.config.bitrate = bitrate;
debug!("Opus bitrate set to {} bps", bitrate);
Ok(())
}
pub fn set_complexity(&mut self, complexity: u8) -> Result<()> {
if complexity > 10 {
return Err(CodecError::invalid_config("Complexity must be 0-10"));
}
self.config.complexity = complexity;
debug!("Opus complexity set to {}", complexity);
Ok(())
}
fn simulate_encode(&mut self, samples: &[i16]) -> Result<Vec<u8>> {
let frame_duration_ms =
(samples.len() as f32 * 1000.0) / (self.sample_rate as f32 * self.channels as f32);
let target_bits = (self.config.bitrate as f32 * frame_duration_ms / 1000.0) as usize;
let target_bytes = target_bits / 8;
let mut encoded = Vec::with_capacity(target_bytes.max(10));
for i in 0..target_bytes {
encoded.push((i % 256) as u8);
}
Ok(encoded)
}
fn simulate_decode(&mut self, data: &[u8]) -> Result<Vec<i16>> {
let mut samples = vec![0i16; self.frame_size * self.channels as usize];
for (i, sample) in samples.iter_mut().enumerate() {
let data_idx = i % data.len();
*sample = ((data[data_idx] as i16) << 8) | (i as i16 & 0xFF);
}
Ok(samples)
}
}
impl AudioCodec for OpusCodec {
fn encode(&mut self, samples: &[i16]) -> Result<Vec<u8>> {
validate_opus_frame(samples, SampleRate::from_hz(self.sample_rate))?;
let encoded = self.simulate_encode(samples)?;
trace!(
"Opus encoded {} samples to {} bytes",
samples.len(),
encoded.len()
);
Ok(encoded)
}
fn decode(&mut self, data: &[u8]) -> Result<Vec<i16>> {
if data.is_empty() {
return Err(CodecError::InvalidPayload {
details: "Empty encoded data".to_string(),
});
}
let decoded = self.simulate_decode(data)?;
trace!(
"Opus decoded {} bytes to {} samples",
data.len(),
decoded.len()
);
Ok(decoded)
}
fn info(&self) -> CodecInfo {
CodecInfo {
name: "Opus",
sample_rate: self.sample_rate,
channels: self.channels,
bitrate: self.config.bitrate,
frame_size: self.frame_size,
payload_type: Some(111), }
}
fn reset(&mut self) -> Result<()> {
debug!("Opus codec reset");
Ok(())
}
fn frame_size(&self) -> usize {
self.frame_size
}
fn supports_variable_frame_size(&self) -> bool {
true }
}
impl AudioCodecExt for OpusCodec {
fn encode_to_buffer(&mut self, samples: &[i16], output: &mut [u8]) -> Result<usize> {
validate_opus_frame(samples, SampleRate::from_hz(self.sample_rate))?;
let encoded = self.simulate_encode(samples)?;
if output.len() < encoded.len() {
return Err(CodecError::BufferTooSmall {
needed: encoded.len(),
actual: output.len(),
});
}
output[..encoded.len()].copy_from_slice(&encoded);
trace!(
"Opus encoded {} samples to {} bytes (zero-alloc)",
samples.len(),
encoded.len()
);
Ok(encoded.len())
}
fn decode_to_buffer(&mut self, data: &[u8], output: &mut [i16]) -> Result<usize> {
if data.is_empty() {
return Err(CodecError::InvalidPayload {
details: "Empty encoded data".to_string(),
});
}
let decoded = self.simulate_decode(data)?;
if output.len() < decoded.len() {
return Err(CodecError::BufferTooSmall {
needed: decoded.len(),
actual: output.len(),
});
}
output[..decoded.len()].copy_from_slice(&decoded);
trace!(
"Opus decoded {} bytes to {} samples (zero-alloc)",
data.len(),
decoded.len()
);
Ok(decoded.len())
}
fn max_encoded_size(&self, input_samples: usize) -> usize {
let bits_per_sample = self.config.bitrate as f32 / self.sample_rate as f32;
let max_bytes = (input_samples as f32 * bits_per_sample / 8.0) as usize;
max_bytes.min(1275)
}
fn max_decoded_size(&self, _input_bytes: usize) -> usize {
let max_frame_ms = 60.0; ((self.sample_rate as f32 * max_frame_ms / 1000.0) as usize) * self.channels as usize
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::{CodecConfig, CodecType, SampleRate};
fn create_test_config() -> CodecConfig {
CodecConfig::new(CodecType::Opus)
.with_sample_rate(SampleRate::Rate48000)
.with_channels(1)
.with_frame_size_ms(20.0)
}
#[test]
fn test_opus_creation() {
let config = create_test_config();
let codec = OpusCodec::new(config);
assert!(codec.is_ok());
let codec = codec.unwrap();
assert_eq!(codec.frame_size(), 960);
let info = codec.info();
assert_eq!(info.name, "Opus");
assert_eq!(info.sample_rate, 48000);
assert_eq!(info.payload_type, Some(111));
}
#[test]
fn test_encoding_decoding_roundtrip() {
let config = create_test_config();
let mut codec = OpusCodec::new(config).unwrap();
let mut samples = Vec::new();
for i in 0..960 {
let t = i as f32 / 48000.0;
let sample = ((2.0 * std::f32::consts::PI * 1000.0 * t).sin() * 16000.0) as i16;
samples.push(sample);
}
let encoded = codec.encode(&samples).unwrap();
assert!(encoded.len() > 0);
let decoded = codec.decode(&encoded).unwrap();
assert_eq!(decoded.len(), samples.len());
}
#[test]
fn test_bitrate_control() {
let config = create_test_config();
let mut codec = OpusCodec::new(config).unwrap();
assert!(codec.set_bitrate(32000).is_ok());
assert!(codec.set_bitrate(128000).is_ok());
assert!(codec.set_bitrate(1000).is_err());
assert!(codec.set_bitrate(1000000).is_err());
}
#[test]
fn test_complexity_control() {
let config = create_test_config();
let mut codec = OpusCodec::new(config).unwrap();
for complexity in 0..=10 {
assert!(codec.set_complexity(complexity).is_ok());
}
assert!(codec.set_complexity(11).is_err());
}
}