use audiopus::Application;
use audiopus::Bitrate;
use audiopus::Channels as OpusChannels;
use audiopus::SampleRate;
use audiopus::coder::Encoder as OpusEncoderInner;
use crate::audio::resample::AudioResampler;
use crate::audio::{
AudioCodec, AudioEncoder, AudioEncoderConfig, AudioError, AudioFrame, EncodedAudioPacket,
};
mod dops;
mod multistream;
#[cfg(test)]
mod tests;
use dops::build_dops;
use multistream::{MultistreamEncoder, surround_mapping_family_1};
const OPUS_FRAME_SAMPLES_48K: usize = 960;
const OPUS_INTERNAL_RATE: u32 = 48_000;
const OPUS_MAX_PACKET_BYTES: usize = 4000;
const OPUS_MAX_MS_PACKET_BYTES: usize = 16_384;
const DEFAULT_BITRATE_MONO: u32 = 64_000;
const DEFAULT_BITRATE_STEREO: u32 = 96_000;
enum OpusInner {
Regular(OpusEncoderInner),
Multistream(MultistreamEncoder),
}
pub struct OpusEncoder {
inner: OpusInner,
in_rate: u32,
channels: u8,
resampler: Option<AudioResampler>,
sample_carry: Vec<f32>,
pre_skip_48k: u16,
extra_data: Vec<u8>,
next_pts_us: Option<i64>,
frame_duration_us: i64,
encode_out: Vec<u8>,
}
impl OpusEncoder {
pub fn new(config: AudioEncoderConfig) -> Result<Self, AudioError> {
if config.codec != AudioCodec::Opus {
return Err(AudioError::Encode(format!(
"OpusEncoder constructed with codec {:?}",
config.codec
)));
}
if config.channels == 0 {
return Err(AudioError::Unsupported(
"Opus channel count must be >= 1".to_string(),
));
}
if config.channels > 8 {
return Err(AudioError::Unsupported(format!(
"Opus supports up to 8 channels (channel-mapping family 1, RFC 7845 §5.1.1.2); \
got {} channels",
config.channels
)));
}
if config.sample_rate == 0 {
return Err(AudioError::Encode("input sample_rate is 0".to_string()));
}
let channels = config.channels;
let (inner, ms_meta, max_packet_bytes) = if channels <= 2 {
let opus_channels = match channels {
1 => OpusChannels::Mono,
2 => OpusChannels::Stereo,
_ => unreachable!("channel-count guarded above"),
};
let mut enc =
OpusEncoderInner::new(SampleRate::Hz48000, opus_channels, Application::Audio)
.map_err(|e| AudioError::Encode(format!("opus encoder create: {e}")))?;
let bitrate_bps = if config.bitrate == 0 {
if channels == 1 {
DEFAULT_BITRATE_MONO
} else {
DEFAULT_BITRATE_STEREO
}
} else {
config.bitrate
};
enc.set_bitrate(Bitrate::BitsPerSecond(bitrate_bps as i32))
.map_err(|e| AudioError::Encode(format!("opus set_bitrate: {e}")))?;
enc.set_vbr(true)
.map_err(|e| AudioError::Encode(format!("opus set_vbr: {e}")))?;
(OpusInner::Regular(enc), None, OPUS_MAX_PACKET_BYTES)
} else {
let (streams, coupled, mapping) = surround_mapping_family_1(channels)?;
let mut ms = MultistreamEncoder::new(
OPUS_INTERNAL_RATE,
channels,
streams,
coupled,
mapping,
Application::Audio,
)?;
let bitrate_bps = if config.bitrate == 0 {
let coupled_u = coupled as u32;
let mono_u = streams as u32 - coupled_u;
coupled_u * DEFAULT_BITRATE_STEREO + mono_u * DEFAULT_BITRATE_MONO
} else {
config.bitrate
};
ms.set_bitrate(bitrate_bps as i32)?;
ms.set_vbr(true)?;
(
OpusInner::Multistream(ms),
Some((streams, coupled, mapping)),
OPUS_MAX_MS_PACKET_BYTES,
)
};
let pre_skip_48k_u32 = match &inner {
OpusInner::Regular(enc) => enc
.lookahead()
.map_err(|e| AudioError::Encode(format!("opus lookahead: {e}")))?,
OpusInner::Multistream(ms) => ms.lookahead()?,
};
let pre_skip_48k: u16 = pre_skip_48k_u32.try_into().unwrap_or(u16::MAX);
let resampler = if config.sample_rate == OPUS_INTERNAL_RATE {
None
} else {
let chunk = ((config.sample_rate as usize) * 20) / 1000;
let chunk = chunk.max(1);
Some(AudioResampler::new(
config.sample_rate,
OPUS_INTERNAL_RATE,
channels,
chunk,
)?)
};
let extra_data = build_dops(channels, pre_skip_48k, config.sample_rate, ms_meta);
let frame_duration_us =
(OPUS_FRAME_SAMPLES_48K as i64 * 1_000_000) / OPUS_INTERNAL_RATE as i64;
Ok(Self {
inner,
in_rate: config.sample_rate,
channels,
resampler,
sample_carry: Vec::with_capacity(OPUS_FRAME_SAMPLES_48K * channels as usize * 4),
pre_skip_48k,
extra_data,
next_pts_us: None,
frame_duration_us,
encode_out: vec![0u8; max_packet_bytes],
})
}
fn drain_packets(&mut self) -> Result<Vec<EncodedAudioPacket>, AudioError> {
let mut out = Vec::new();
let chans = self.channels as usize;
let frame_interleaved_len = OPUS_FRAME_SAMPLES_48K * chans;
while self.sample_carry.len() >= frame_interleaved_len {
let frame_slice = &self.sample_carry[..frame_interleaved_len];
let n = match &mut self.inner {
OpusInner::Regular(enc) => enc
.encode_float(frame_slice, &mut self.encode_out)
.map_err(|e| AudioError::Encode(format!("opus encode_float: {e}")))?,
OpusInner::Multistream(ms) => {
ms.encode_float(frame_slice, OPUS_FRAME_SAMPLES_48K, &mut self.encode_out)?
}
};
if n > 0 {
let pts = self.next_pts_us.unwrap_or(0);
self.next_pts_us = Some(pts + self.frame_duration_us);
out.push(EncodedAudioPacket {
data: self.encode_out[..n].to_vec(),
pts,
duration: OPUS_FRAME_SAMPLES_48K as i64, });
}
self.sample_carry.drain(..frame_interleaved_len);
}
Ok(out)
}
}
impl AudioEncoder for OpusEncoder {
fn encode(&mut self, frame: &AudioFrame) -> Result<Vec<EncodedAudioPacket>, AudioError> {
if frame.channels == 0 || frame.channels > 8 {
return Err(AudioError::Unsupported(format!(
"Opus AudioFrame channel count must be 1..=8; got {}",
frame.channels
)));
}
if frame.channels != self.channels {
return Err(AudioError::Encode(format!(
"channel count mismatch: encoder configured for {}, frame has {}",
self.channels, frame.channels
)));
}
if frame.sample_rate != self.in_rate {
return Err(AudioError::Encode(format!(
"sample rate mismatch: encoder configured for {}, frame has {}",
self.in_rate, frame.sample_rate
)));
}
if self.next_pts_us.is_none() {
self.next_pts_us = Some(frame.pts);
}
if let Some(r) = self.resampler.as_mut() {
r.process(frame, &mut self.sample_carry)?;
} else {
self.sample_carry.extend_from_slice(&frame.samples);
}
self.drain_packets()
}
fn flush(&mut self) -> Result<Vec<EncodedAudioPacket>, AudioError> {
if let Some(r) = self.resampler.as_mut() {
r.flush(&mut self.sample_carry)?;
}
let chans = self.channels as usize;
let frame_interleaved_len = OPUS_FRAME_SAMPLES_48K * chans;
if !self.sample_carry.is_empty() && self.sample_carry.len() < frame_interleaved_len {
self.sample_carry.resize(frame_interleaved_len, 0.0);
}
self.drain_packets()
}
fn pre_skip(&self) -> u16 {
self.pre_skip_48k
}
fn extra_data(&self) -> Vec<u8> {
self.extra_data.clone()
}
}