mod raw;
use {crate::error::OperationError, std::ptr::null};
#[derive(Debug, Clone, Copy)]
pub enum OpusApplication {
Audio,
Voip,
RestrictedLowdelay,
}
impl OpusApplication {
fn to_opus_int(&self) -> i32 {
match self {
Self::Audio => raw::OPUS_APPLICATION_AUDIO as i32,
Self::Voip => raw::OPUS_APPLICATION_VOIP as i32,
Self::RestrictedLowdelay => raw::OPUS_APPLICATION_RESTRICTED_LOWDELAY as i32,
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum OpusBandwidth {
Narrowband,
Mediumband,
Wideband,
Superwideband,
Fullband,
Custom(i32),
}
impl OpusBandwidth {
fn to_opus_int(&self) -> i32 {
match self {
Self::Narrowband => raw::OPUS_BANDWIDTH_NARROWBAND as i32,
Self::Mediumband => raw::OPUS_BANDWIDTH_MEDIUMBAND as i32,
Self::Wideband => raw::OPUS_BANDWIDTH_WIDEBAND as i32,
Self::Superwideband => raw::OPUS_BANDWIDTH_SUPERWIDEBAND as i32,
Self::Fullband => raw::OPUS_BANDWIDTH_FULLBAND as i32,
Self::Custom(value) => *value,
}
}
}
pub trait OpusSample {
fn is_f32() -> bool {
false
}
fn is_i16() -> bool {
false
}
fn zero() -> Self;
}
impl OpusSample for i16 {
fn is_i16() -> bool {
true
}
fn zero() -> Self {
0
}
}
impl OpusSample for f32 {
fn is_f32() -> bool {
true
}
fn zero() -> Self {
0f32
}
}
pub struct OpusEncoder {
encoder: *mut raw::OpusEncoder,
channels: usize,
}
impl OpusEncoder {
pub(self) fn new(
sample_rate: usize,
channels: usize,
application: OpusApplication,
) -> Result<Self, OperationError> {
let mut error = 0;
let encoder = unsafe {
raw::opus_encoder_create(
sample_rate as _,
channels as _,
application.to_opus_int(),
&mut error,
)
};
if error != raw::OPUS_OK as i32 {
return Err(OperationError::Opus(format!(
"Failed to create Opus encoder: {}",
error
)));
}
if encoder.is_null() {
return Err(OperationError::Opus("Opus encoder is null".to_owned()));
}
Ok(Self { encoder, channels })
}
pub fn set_bitrate(&mut self, bitrate: usize) -> Result<(), OperationError> {
let result = unsafe {
raw::opus_encoder_ctl(self.encoder, raw::OPUS_SET_BITRATE_REQUEST as _, bitrate)
};
if result != raw::OPUS_OK as i32 {
return Err(OperationError::Opus(format!(
"Failed to set bitrate: {}",
result
)));
}
Ok(())
}
pub fn set_complexity(&mut self, complexity: i32) -> Result<(), OperationError> {
let result = unsafe {
raw::opus_encoder_ctl(
self.encoder,
raw::OPUS_SET_COMPLEXITY_REQUEST as _,
complexity,
)
};
if result != raw::OPUS_OK as i32 {
return Err(OperationError::Opus(format!(
"Failed to set complexity: {}",
result
)));
}
Ok(())
}
pub fn set_bandwidth(&mut self, bandwidth: OpusBandwidth) -> Result<(), OperationError> {
let result = unsafe {
raw::opus_encoder_ctl(
self.encoder,
raw::OPUS_SET_BANDWIDTH_REQUEST as _,
bandwidth.to_opus_int(),
)
};
if result != raw::OPUS_OK as i32 {
return Err(OperationError::Opus(format!(
"Failed to set bandwidth: {}",
result
)));
}
Ok(())
}
pub fn encode<S: OpusSample>(
&self,
pcm: &[S],
frame_size: usize,
max_data_bytes: usize,
) -> Result<Vec<u8>, OperationError> {
let samples_per_channel = pcm.len() / self.channels;
if samples_per_channel < frame_size as usize {
return Err(OperationError::Opus(format!(
"Input data too short for encoding: need {} samples per channel, got {}",
frame_size, samples_per_channel
)));
}
let mut encoded = vec![0u8; max_data_bytes + 1];
let encoded_len = unsafe {
if S::is_i16() {
raw::opus_encode(
self.encoder,
pcm.as_ptr() as _,
frame_size as _,
encoded.as_mut_ptr(),
max_data_bytes as _,
)
} else {
raw::opus_encode_float(
self.encoder,
pcm.as_ptr() as _,
frame_size as _,
encoded.as_mut_ptr(),
max_data_bytes as _,
)
}
};
if encoded_len < 0 {
return Err(OperationError::Opus(format!(
"Failed to encode: {}",
encoded_len
)));
}
encoded.truncate(encoded_len as usize + 1);
Ok(encoded)
}
pub fn channels(&self) -> usize {
self.channels
}
}
impl Drop for OpusEncoder {
fn drop(&mut self) {
if !self.encoder.is_null() {
unsafe {
raw::opus_encoder_destroy(self.encoder);
}
}
}
}
pub struct OpusDecoder {
decoder: *mut raw::OpusDecoder,
channels: usize,
}
impl OpusDecoder {
pub(self) fn new(sample_rate: usize, channels: usize) -> Result<Self, OperationError> {
let mut error = 0;
let decoder =
unsafe { raw::opus_decoder_create(sample_rate as _, channels as _, &mut error) };
if error != raw::OPUS_OK as i32 {
return Err(OperationError::Opus(format!(
"Failed to create Opus decoder: {}",
error
)));
}
if decoder.is_null() {
return Err(OperationError::Opus("Opus decoder is null".to_owned()));
}
Ok(Self { decoder, channels })
}
pub fn decode<S: Clone + OpusSample>(
&self,
data: Option<&[u8]>,
frame_size: usize,
) -> Result<Vec<S>, OperationError> {
let mut decoded = vec![S::zero(); frame_size * self.channels];
let decoded_samples = unsafe {
if let Some(data) = data {
raw::opus_decode(
self.decoder,
data.as_ptr(),
data.len() as _,
decoded.as_mut_ptr() as _,
frame_size as _, 0, )
} else {
raw::opus_decode(
self.decoder,
null(),
0,
decoded.as_mut_ptr() as _,
frame_size as _, 0,
)
}
};
if decoded_samples < 0 {
return Err(OperationError::Opus(format!(
"Failed to decode: {}",
decoded_samples
)));
}
decoded.truncate(decoded_samples as usize * self.channels);
Ok(decoded)
}
}
impl Drop for OpusDecoder {
fn drop(&mut self) {
if !self.decoder.is_null() {
unsafe {
raw::opus_decoder_destroy(self.decoder);
}
}
}
}
pub struct OpusCodec;
impl OpusCodec {
pub const MAX_PACKET_SIZE: usize = 1500;
pub fn new_encoder(
sample_rate: usize,
channels: usize,
application: OpusApplication,
) -> Result<OpusEncoder, OperationError> {
OpusEncoder::new(sample_rate, channels, application)
}
pub fn new_decoder(sample_rate: usize, channels: usize) -> Result<OpusDecoder, OperationError> {
OpusDecoder::new(sample_rate, channels)
}
pub fn calculate_frame_size(sample_rate: usize, duration_ms: u64) -> usize {
(sample_rate * duration_ms as usize) / 1000
}
pub fn get_max_bandwidth_for_sample_rate(sample_rate: usize) -> OpusBandwidth {
match sample_rate {
8000 => OpusBandwidth::Narrowband,
12000 => OpusBandwidth::Mediumband,
16000 => OpusBandwidth::Wideband,
24000 => OpusBandwidth::Superwideband,
_ => OpusBandwidth::Fullband, }
}
pub fn version() -> String {
unsafe {
let version_ptr = raw::opus_get_version_string();
let c_str = std::ffi::CStr::from_ptr(version_ptr);
c_str.to_string_lossy().to_string()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::{cmp::min, f32::consts::PI};
fn calculate_snr(original: &[i16], decoded: &[i16]) -> f32 {
let len = std::cmp::min(original.len(), decoded.len());
let mut signal_power = 0.0;
let mut noise_power = 0.0;
for i in 0..len {
let signal = original[i] as f32;
let noise = signal - decoded[i] as f32;
signal_power += signal * signal;
noise_power += noise * noise;
}
if noise_power > 0.0 && signal_power > 0.0 {
10.0 * (signal_power / noise_power).log10()
} else {
f32::INFINITY }
}
fn generate_complex_audio(duration_ms: usize, sample_rate: usize) -> Vec<i16> {
let num_samples = sample_rate * duration_ms / 1000;
let mut samples = Vec::with_capacity(num_samples * 2);
let max_freq = (sample_rate as f32 / 2.0) * 0.8;
let frequencies = [261.63, 329.63, 392.00, 523.25, 659.25];
for i in 0..num_samples {
let t = i as f32 / sample_rate as f32;
let mut value = 0.0;
for (j, &freq) in frequencies.iter().enumerate() {
if freq < max_freq {
let amplitude = 0.1 / (j + 1) as f32; value += amplitude * f32::sin(2.0 * PI * freq * t);
}
}
let noise_freq = max_freq * 0.5; let noise = f32::sin(t * noise_freq) * f32::cos(t * (noise_freq * 0.7) * 0.005);
value += noise;
value = value.max(-0.8).min(0.8);
let sample = (value * 30000.0) as i16;
samples.push(sample); samples.push((sample as f32 * 0.95) as i16); }
samples
}
fn test_basic_encoding_decoding(
sample_rate: usize,
channels: usize,
bitrate: usize,
) -> Result<(), OperationError> {
println!("\n--- Testing basic encode/decode ({} Hz) ---", sample_rate);
let frame_size = sample_rate * 20 / 1000;
let input_audio = generate_complex_audio(500, sample_rate);
let mut encoder = OpusCodec::new_encoder(sample_rate, channels, OpusApplication::Audio)?;
encoder.set_bitrate(bitrate)?;
encoder.set_complexity(10)?;
encoder.set_bandwidth(OpusCodec::get_max_bandwidth_for_sample_rate(sample_rate))?;
let decoder = OpusDecoder::new(sample_rate, channels)?;
println!("Encoding and decoding multiple frames...");
let frame_count = 5;
let mut all_input = Vec::new();
let mut all_decoded = Vec::new();
for i in 0..frame_count {
let start = i * frame_size * channels;
let end = start + frame_size * channels;
let end = end.min(input_audio.len());
if start >= input_audio.len() {
break;
}
let frame_input = &input_audio[start..end];
all_input.extend_from_slice(frame_input);
let encoded = encoder.encode(frame_input, frame_size, OpusCodec::MAX_PACKET_SIZE)?;
println!(" Frame {}: encoded {} bytes", i + 1, encoded.len());
let decoded = decoder.decode::<i16>(Some(&encoded), frame_size)?;
println!(" Frame {}: decoded {} samples", i + 1, decoded.len());
all_decoded.extend(decoded);
}
println!("Frame size: {} samples (20 ms)", frame_size);
println!("Total input samples: {}", all_input.len());
println!("Total decoded samples: {}", all_decoded.len());
if !all_decoded.is_empty() {
let min_len = min(all_input.len(), all_decoded.len());
let snr = calculate_snr(&all_input[0..min_len], &all_decoded[0..min_len]);
println!("Signal-to-noise ratio (SNR): {:.2} dB", snr);
}
Ok(())
}
#[test]
fn test_opus() -> anyhow::Result<()> {
println!("Opus version: {}", crate::opus::OpusCodec::version());
println!("\n=== Opus codec test - Basic encoding/decoding functionality ===");
let sample_rates = [8000, 16000, 24000, 48000];
let channels = 2;
let bitrate = 256000;
for &rate in &sample_rates {
test_basic_encoding_decoding(rate, channels, bitrate)?;
}
Ok(())
}
}