use super::capture::AudioFrame;
use crate::errors::CameraError;
const OPUS_FRAME_SAMPLES: usize = 960;
const OPUS_APPLICATION_AUDIO: i32 = 2049;
#[derive(Debug, Clone)]
pub struct EncodedAudio {
pub data: Vec<u8>,
pub timestamp: f64,
pub duration: f64,
}
pub struct OpusEncoder {
encoder: *mut libopus_sys::OpusEncoder,
channels: u16,
sample_rate: u32,
sample_buffer: Vec<f32>,
buffer_start_pts: Option<f64>,
samples_encoded: u64,
}
unsafe impl Send for OpusEncoder {}
impl OpusEncoder {
pub fn new(sample_rate: u32, channels: u16, bitrate: u32) -> Result<Self, CameraError> {
if sample_rate != 48000 {
return Err(CameraError::AudioError(
"Opus requires 48000 Hz sample rate".to_string(),
));
}
if channels != 1 && channels != 2 {
return Err(CameraError::AudioError(
"Opus supports only mono (1) or stereo (2) channels".to_string(),
));
}
let mut error: i32 = 0;
let encoder = unsafe {
libopus_sys::opus_encoder_create(
sample_rate as i32,
channels as i32,
OPUS_APPLICATION_AUDIO,
&mut error,
)
};
if encoder.is_null() || error != 0 {
return Err(CameraError::AudioError(format!(
"Failed to create Opus encoder: error code {}",
error
)));
}
let result = unsafe {
libopus_sys::opus_encoder_ctl(
encoder,
libopus_sys::OPUS_SET_BITRATE_REQUEST as i32,
bitrate as i32,
)
};
if result != 0 {
unsafe { libopus_sys::opus_encoder_destroy(encoder) };
return Err(CameraError::AudioError(format!(
"Failed to set bitrate: error code {}",
result
)));
}
Ok(Self {
encoder,
channels,
sample_rate,
sample_buffer: Vec::with_capacity(OPUS_FRAME_SAMPLES * channels as usize * 2),
buffer_start_pts: None,
samples_encoded: 0,
})
}
pub fn encode(&mut self, frame: &AudioFrame) -> Result<Vec<EncodedAudio>, CameraError> {
if frame.sample_rate != self.sample_rate {
return Err(CameraError::AudioError(format!(
"Sample rate mismatch: expected {}, got {}",
self.sample_rate, frame.sample_rate
)));
}
if frame.channels != self.channels {
return Err(CameraError::AudioError(format!(
"Channel count mismatch: expected {}, got {}",
self.channels, frame.channels
)));
}
if self.buffer_start_pts.is_none() && !frame.samples.is_empty() {
self.buffer_start_pts = Some(frame.timestamp);
}
self.sample_buffer.extend_from_slice(&frame.samples);
let mut encoded_packets = Vec::new();
let samples_per_frame = OPUS_FRAME_SAMPLES * self.channels as usize;
let frame_duration = OPUS_FRAME_SAMPLES as f64 / self.sample_rate as f64;
while self.sample_buffer.len() >= samples_per_frame {
let frame_samples: Vec<f32> = self.sample_buffer.drain(..samples_per_frame).collect();
let pts = self.samples_encoded as f64 / self.sample_rate as f64;
let mut output = vec![0u8; 4000]; let len = unsafe {
libopus_sys::opus_encode_float(
self.encoder,
frame_samples.as_ptr(),
OPUS_FRAME_SAMPLES as i32,
output.as_mut_ptr(),
output.len() as i32,
)
};
if len < 0 {
return Err(CameraError::AudioError(format!(
"Opus encoding failed: error code {}",
len
)));
}
output.truncate(len as usize);
encoded_packets.push(EncodedAudio {
data: output,
timestamp: self.buffer_start_pts.unwrap_or(0.0) + pts,
duration: frame_duration,
});
self.samples_encoded += OPUS_FRAME_SAMPLES as u64;
}
Ok(encoded_packets)
}
pub fn flush(&mut self) -> Result<Vec<EncodedAudio>, CameraError> {
if self.sample_buffer.is_empty() {
return Ok(Vec::new());
}
let samples_per_frame = OPUS_FRAME_SAMPLES * self.channels as usize;
let padding_needed = samples_per_frame - (self.sample_buffer.len() % samples_per_frame);
if padding_needed < samples_per_frame {
self.sample_buffer.extend(vec![0.0f32; padding_needed]);
}
let mut encoded_packets = Vec::new();
let frame_duration = OPUS_FRAME_SAMPLES as f64 / self.sample_rate as f64;
while self.sample_buffer.len() >= samples_per_frame {
let frame_samples: Vec<f32> = self.sample_buffer.drain(..samples_per_frame).collect();
let pts = self.samples_encoded as f64 / self.sample_rate as f64;
let mut output = vec![0u8; 4000];
let len = unsafe {
libopus_sys::opus_encode_float(
self.encoder,
frame_samples.as_ptr(),
OPUS_FRAME_SAMPLES as i32,
output.as_mut_ptr(),
output.len() as i32,
)
};
if len < 0 {
return Err(CameraError::AudioError(format!(
"Opus flush failed: error code {}",
len
)));
}
output.truncate(len as usize);
encoded_packets.push(EncodedAudio {
data: output,
timestamp: self.buffer_start_pts.unwrap_or(0.0) + pts,
duration: frame_duration,
});
self.samples_encoded += OPUS_FRAME_SAMPLES as u64;
}
Ok(encoded_packets)
}
pub fn sample_rate(&self) -> u32 {
self.sample_rate
}
pub fn channels(&self) -> u16 {
self.channels
}
}
impl Drop for OpusEncoder {
fn drop(&mut self) {
if !self.encoder.is_null() {
unsafe {
libopus_sys::opus_encoder_destroy(self.encoder);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_encoder_creation() {
let encoder = OpusEncoder::new(48000, 2, 128000);
assert!(encoder.is_ok());
}
#[test]
fn test_encoder_rejects_wrong_sample_rate() {
let encoder = OpusEncoder::new(44100, 2, 128000);
assert!(encoder.is_err());
}
#[test]
fn test_encoder_rejects_wrong_channels() {
let encoder = OpusEncoder::new(48000, 5, 128000);
assert!(encoder.is_err());
}
#[test]
fn test_encode_full_frame() {
let mut encoder = OpusEncoder::new(48000, 2, 128000).unwrap();
let frame = AudioFrame {
samples: vec![0.0f32; OPUS_FRAME_SAMPLES * 2],
sample_rate: 48000,
channels: 2,
timestamp: 0.0,
};
let encoded = encoder.encode(&frame).unwrap();
assert_eq!(encoded.len(), 1);
assert!(!encoded[0].data.is_empty());
}
#[test]
fn test_encode_partial_frame() {
let mut encoder = OpusEncoder::new(48000, 2, 128000).unwrap();
let frame = AudioFrame {
samples: vec![0.0f32; 100],
sample_rate: 48000,
channels: 2,
timestamp: 0.0,
};
let encoded = encoder.encode(&frame).unwrap();
assert!(
encoded.is_empty(),
"Partial frame should not produce output"
);
}
#[test]
fn test_flush_remaining() {
let mut encoder = OpusEncoder::new(48000, 2, 128000).unwrap();
let frame = AudioFrame {
samples: vec![0.0f32; 100],
sample_rate: 48000,
channels: 2,
timestamp: 0.0,
};
encoder.encode(&frame).unwrap();
let flushed = encoder.flush().unwrap();
assert_eq!(flushed.len(), 1);
}
}