#![allow(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::cast_possible_wrap,
reason = "unsafe-libopus's C-shaped API takes i32 counts/lengths; sample_rate, frame sizes, \
and packet lengths are always small in practice (Opus legal rates top out at \
48 kHz, packets are bytes over the network) — `frame_size_samples` already \
rejects values that would not fit i32 before they reach these casts."
)]
use std::collections::VecDeque;
use std::ptr;
use mediaway_common::{AudioFrame, Bytes, CodecKind, Packet, SampleFormat, StreamInfo};
use unsafe_libopus::{
OpusDecoder as RawOpusDecoder, opus_decode_float, opus_decoder_create, opus_decoder_destroy,
opus_strerror,
};
use crate::opus::config::{OpusDecoderConfig, frame_size_samples};
use crate::opus::error::OpusError;
#[derive(Debug)]
pub struct OpusDecoder {
ptr: ptr::NonNull<RawOpusDecoder>,
stream_info: StreamInfo,
sample_rate: u32,
channels: u16,
max_frame_samples: usize,
pcm_scratch: Vec<f32>,
pending: VecDeque<AudioFrame>,
closed: bool,
}
unsafe impl Send for OpusDecoder {}
impl OpusDecoder {
pub fn open(config: &OpusDecoderConfig) -> Result<Self, OpusError> {
let max_frame_samples = frame_size_samples(config.sample_rate, config.time_base)?;
let channels = i32::from(config.channels);
let mut err: i32 = 0;
let raw = unsafe { opus_decoder_create(config.sample_rate as i32, channels, &raw mut err) };
let Some(ptr) = ptr::NonNull::new(raw) else {
return Err(OpusError::Backend {
code: err,
message: opus_strerror(err),
});
};
Ok(Self {
ptr,
stream_info: StreamInfo::Audio {
id: 0,
codec: CodecKind::Opus,
time_base: config.time_base,
extra_data: Bytes::new(),
sample_rate: config.sample_rate,
channels: config.channels,
},
sample_rate: config.sample_rate,
channels: config.channels,
max_frame_samples,
pcm_scratch: vec![0.0f32; max_frame_samples * usize::from(config.channels)],
pending: VecDeque::new(),
closed: false,
})
}
#[must_use]
pub const fn stream_info(&self) -> &StreamInfo {
&self.stream_info
}
pub fn push_packet(&mut self, packet: &Packet) -> Result<(), OpusError> {
if self.closed {
return Err(OpusError::Closed);
}
let (data_ptr, data_len) = if packet.payload.is_empty() {
(ptr::null(), 0)
} else {
(packet.payload.as_ptr(), packet.payload.len() as i32)
};
let decoded_samples = unsafe {
opus_decode_float(
self.ptr.as_ptr(),
data_ptr,
data_len,
self.pcm_scratch.as_mut_ptr(),
self.max_frame_samples as i32,
0,
)
};
if decoded_samples < 0 {
return Err(OpusError::Backend {
code: decoded_samples,
message: opus_strerror(decoded_samples),
});
}
let sample_count = decoded_samples as usize * usize::from(self.channels);
let mut data = Vec::with_capacity(sample_count * size_of::<f32>());
for sample in &self.pcm_scratch[..sample_count] {
data.extend_from_slice(&sample.to_le_bytes());
}
self.pending.push_back(AudioFrame {
pts: packet.pts,
duration: packet.duration,
sample_rate: self.sample_rate,
channels: self.channels,
format: SampleFormat::F32,
data: Bytes::from(data),
});
Ok(())
}
pub fn poll_frame(&mut self) -> Result<Option<AudioFrame>, OpusError> {
Ok(self.pending.pop_front())
}
pub const fn flush(&mut self) -> Result<(), OpusError> {
self.closed = true;
Ok(())
}
}
impl Drop for OpusDecoder {
fn drop(&mut self) {
unsafe { opus_decoder_destroy(self.ptr.as_ptr()) };
}
}
#[cfg(test)]
#[path = "decoder_tests.rs"]
mod tests;