use anyhow::{Context, Result};
use audiopus::coder::{Decoder, Encoder};
use audiopus::{Application, Channels, SampleRate};
use log::{debug, warn};
pub struct OpusDecoder {
decoder: Decoder,
sample_rate: u32,
frame_size_samples: usize,
}
impl OpusDecoder {
pub fn new(sample_rate: u32, frame_duration_ms: u32) -> Result<Self> {
if sample_rate != 16000 {
anyhow::bail!("Opus decoder only supports 16kHz");
}
if frame_duration_ms != 20 {
anyhow::bail!("Opus decoder only supports 20ms frames (must match firmware)");
}
let frame_size_samples = (sample_rate * frame_duration_ms / 1000) as usize;
let decoder = Decoder::new(SampleRate::Hz16000, Channels::Mono)
.context("Failed to create Opus decoder")?;
Ok(Self {
decoder,
sample_rate,
frame_size_samples,
})
}
pub fn decode_frame(&mut self, frame_data: &[u8]) -> Result<Vec<i16>> {
if frame_data.is_empty() {
return Ok(Vec::new());
}
let mut pcm = vec![0i16; self.frame_size_samples];
let samples_decoded = self
.decoder
.decode(Some(frame_data), &mut pcm, false)
.context("Failed to decode Opus frame")?;
pcm.truncate(samples_decoded);
Ok(pcm)
}
pub fn decode_frame_with_fec(&mut self, next_frame_data: &[u8]) -> Result<Vec<i16>> {
if next_frame_data.is_empty() {
return Ok(Vec::new());
}
let mut pcm = vec![0i16; self.frame_size_samples];
let samples_decoded = self
.decoder
.decode(Some(next_frame_data), &mut pcm, true)
.context("Failed to decode FEC frame")?;
pcm.truncate(samples_decoded);
Ok(pcm)
}
pub fn decode_plc(&mut self) -> Result<Vec<i16>> {
let mut pcm = vec![0i16; self.frame_size_samples];
let samples_decoded = self
.decoder
.decode(None::<&[u8]>, &mut pcm, false)
.context("Failed to decode PLC")?;
pcm.truncate(samples_decoded);
Ok(pcm)
}
pub fn decode_bundle(&mut self, bundle_data: &[u8]) -> Result<Vec<i16>> {
if bundle_data.is_empty() {
return Ok(Vec::new());
}
if bundle_data.len() < 1 {
anyhow::bail!("Bundle too short: missing frame count");
}
let num_frames = bundle_data[0] as usize;
debug!("Decoding bundle with {} frames", num_frames);
let mut pcm_samples = Vec::new();
let mut offset = 1;
for frame_idx in 0..num_frames {
if offset >= bundle_data.len() {
warn!("Bundle truncated at frame {}", frame_idx);
break;
}
let frame_size = bundle_data[offset] as usize;
offset += 1;
if offset + frame_size > bundle_data.len() {
warn!("Frame {} size exceeds bundle data", frame_idx);
break;
}
let frame_data = &bundle_data[offset..offset + frame_size];
let decoded = self
.decode_frame(frame_data)
.with_context(|| format!("Failed to decode frame {}", frame_idx))?;
pcm_samples.extend_from_slice(&decoded);
offset += frame_size;
}
debug!(
"Decoded {} frames to {} PCM samples",
num_frames,
pcm_samples.len()
);
Ok(pcm_samples)
}
pub fn decode_bundle_with_fec(&mut self, bundle_data: &[u8]) -> Result<Vec<i16>> {
if bundle_data.len() < 2 {
return self.decode_bundle(bundle_data);
}
let num_frames = bundle_data[0] as usize;
if num_frames == 0 {
return self.decode_bundle(bundle_data);
}
let first_frame_len = bundle_data[1] as usize;
if 2 + first_frame_len > bundle_data.len() {
return self.decode_bundle(bundle_data);
}
let first_frame = &bundle_data[2..2 + first_frame_len];
let mut fec_pcm = self.decode_frame_with_fec(first_frame)?;
let mut bundle_pcm = self.decode_bundle(bundle_data)?;
fec_pcm.append(&mut bundle_pcm);
Ok(fec_pcm)
}
pub fn frame_size_samples(&self) -> usize {
self.frame_size_samples
}
}
pub struct OpusEncoder {
encoder: Encoder,
sample_rate: u32,
frame_size_samples: usize,
}
impl OpusEncoder {
pub fn new(sample_rate: u32, frame_duration_ms: u32) -> Result<Self> {
if sample_rate != 16000 {
anyhow::bail!("Opus encoder only supports 16kHz");
}
if frame_duration_ms != 20 {
anyhow::bail!("Opus encoder only supports 20ms frames");
}
let frame_size_samples = (sample_rate * frame_duration_ms / 1000) as usize;
let mut encoder = Encoder::new(SampleRate::Hz16000, Channels::Mono, Application::Voip)
.context("Failed to create Opus encoder")?;
encoder
.set_bitrate(audiopus::Bitrate::BitsPerSecond(24000))
.map_err(|e| anyhow::anyhow!("Failed to set bitrate: {:?}", e))?;
encoder
.set_vbr(true)
.map_err(|e| anyhow::anyhow!("Failed to set VBR: {:?}", e))?;
encoder
.set_complexity(5)
.map_err(|e| anyhow::anyhow!("Failed to set complexity: {:?}", e))?;
encoder
.set_signal(audiopus::Signal::Voice)
.map_err(|e| anyhow::anyhow!("Failed to set signal type: {:?}", e))?;
Ok(Self {
encoder,
sample_rate,
frame_size_samples,
})
}
pub fn encode_frame(&mut self, pcm_samples: &[i16]) -> Result<Vec<u8>> {
if pcm_samples.len() != self.frame_size_samples {
anyhow::bail!(
"PCM frame size mismatch: expected {}, got {}",
self.frame_size_samples,
pcm_samples.len()
);
}
let mut opus_frame = vec![0u8; 400];
let encoded_bytes = self
.encoder
.encode(pcm_samples, &mut opus_frame)
.context("Failed to encode Opus frame")?;
opus_frame.truncate(encoded_bytes);
Ok(opus_frame)
}
pub fn encode_buffer(&mut self, pcm_samples: &[i16]) -> Result<Vec<u8>> {
if pcm_samples.is_empty() {
return Ok(Vec::new());
}
let mut opus_data = Vec::new();
let mut offset = 0;
while offset + self.frame_size_samples <= pcm_samples.len() {
let frame = &pcm_samples[offset..offset + self.frame_size_samples];
let encoded_frame = self
.encode_frame(frame)
.with_context(|| format!("Failed to encode frame at offset {}", offset))?;
opus_data.extend_from_slice(&encoded_frame);
offset += self.frame_size_samples;
}
if offset < pcm_samples.len() {
let remaining = pcm_samples.len() - offset;
let mut padded_frame = vec![0i16; self.frame_size_samples];
padded_frame[..remaining].copy_from_slice(&pcm_samples[offset..]);
let encoded_frame = self
.encode_frame(&padded_frame)
.with_context(|| format!("Failed to encode final padded frame"))?;
opus_data.extend_from_slice(&encoded_frame);
}
debug!(
"Encoded {} PCM samples to {} Opus bytes",
pcm_samples.len(),
opus_data.len()
);
Ok(opus_data)
}
pub fn frame_size_samples(&self) -> usize {
self.frame_size_samples
}
}