use std::collections::VecDeque;
use oxideav_celt::encoder::{CeltEncoder, FRAME_SAMPLES, SAMPLE_RATE};
use oxideav_core::Encoder;
use oxideav_core::{
AudioFrame, CodecId, CodecParameters, Error, Frame, Packet, Result, SampleFormat, TimeBase,
};
const OPUS_CONFIG_CELT_FB_20MS: u8 = 31;
pub fn build_toc_byte(stereo: bool) -> u8 {
let stereo_bit: u8 = if stereo { 1 } else { 0 };
(OPUS_CONFIG_CELT_FB_20MS << 3) | (stereo_bit << 2) }
pub const OPUS_FRAME_SAMPLES: usize = 960;
pub struct OpusEncoder {
out_params: CodecParameters,
input_channels: u16,
celt: CeltEncoder,
output: VecDeque<Packet>,
pts_counter: i64,
}
impl OpusEncoder {
pub fn new(params: &CodecParameters) -> Result<Self> {
let channels = params.channels.unwrap_or(1);
if channels == 0 || channels > 2 {
return Err(Error::unsupported(format!(
"opus encoder: only mono/stereo supported, got {channels}-channel input"
)));
}
let sr = params.sample_rate.unwrap_or(SAMPLE_RATE);
if sr != SAMPLE_RATE {
return Err(Error::unsupported(format!(
"opus encoder: input must be 48 kHz (got {sr}); resample before encoding"
)));
}
let mut celt_params = params.clone();
celt_params.channels = Some(1);
celt_params.sample_rate = Some(SAMPLE_RATE);
celt_params.codec_id = CodecId::new(oxideav_celt::CODEC_ID_STR);
let celt = CeltEncoder::new(&celt_params)?;
let mut out_params = params.clone();
out_params.sample_rate = Some(SAMPLE_RATE);
out_params.channels = Some(channels);
Ok(Self {
out_params,
input_channels: channels,
celt,
output: VecDeque::new(),
pts_counter: 0,
})
}
pub fn new_celt_only_full_band(params: &CodecParameters) -> Result<Self> {
let sr = params.sample_rate.unwrap_or(SAMPLE_RATE);
if sr != SAMPLE_RATE {
return Err(Error::unsupported(format!(
"opus encoder (CELT-only FB): input must be 48 kHz, got {sr}"
)));
}
Self::new(params)
}
fn drain_celt(&mut self) -> Result<()> {
let toc = build_toc_byte(false);
loop {
match self.celt.receive_packet() {
Ok(celt_pkt) => {
let mut data = Vec::with_capacity(1 + celt_pkt.data.len());
data.push(toc);
data.extend_from_slice(&celt_pkt.data);
let tb = TimeBase::new(1, SAMPLE_RATE as i64);
let pts = self.pts_counter;
self.pts_counter += OPUS_FRAME_SAMPLES as i64;
let pkt = Packet::new(0, tb, data)
.with_pts(pts)
.with_duration(OPUS_FRAME_SAMPLES as i64);
self.output.push_back(pkt);
}
Err(Error::NeedMore) => return Ok(()),
Err(e) => return Err(e),
}
}
}
}
impl Encoder for OpusEncoder {
fn codec_id(&self) -> &CodecId {
&self.out_params.codec_id
}
fn output_params(&self) -> &CodecParameters {
&self.out_params
}
fn send_frame(&mut self, frame: &Frame) -> Result<()> {
let audio = match frame {
Frame::Audio(a) => a,
_ => {
return Err(Error::invalid(
"opus encoder: expected audio frame, got video",
))
}
};
if audio.sample_rate != SAMPLE_RATE {
return Err(Error::unsupported(format!(
"opus encoder: input must be 48 kHz (got {}); resample before encoding",
audio.sample_rate
)));
}
if audio.channels != self.input_channels {
return Err(Error::invalid(format!(
"opus encoder: frame channels ({}) differ from configured input channels ({})",
audio.channels, self.input_channels
)));
}
let mono = extract_mono_f32(audio)?;
let mut bytes = Vec::with_capacity(mono.len() * 4);
for &s in &mono {
bytes.extend_from_slice(&s.to_le_bytes());
}
let celt_frame = Frame::Audio(AudioFrame {
format: SampleFormat::F32,
channels: 1,
sample_rate: SAMPLE_RATE,
samples: mono.len() as u32,
pts: audio.pts,
time_base: TimeBase::new(1, SAMPLE_RATE as i64),
data: vec![bytes],
});
self.celt.send_frame(&celt_frame)?;
self.drain_celt()
}
fn receive_packet(&mut self) -> Result<Packet> {
if let Some(p) = self.output.pop_front() {
Ok(p)
} else {
Err(Error::NeedMore)
}
}
fn flush(&mut self) -> Result<()> {
self.celt.flush()?;
self.drain_celt()?;
Ok(())
}
}
fn extract_mono_f32(audio: &AudioFrame) -> Result<Vec<f32>> {
let n = audio.samples as usize;
let ch = audio.channels as usize;
if ch == 0 {
return Err(Error::invalid("opus encoder: 0-channel audio frame"));
}
let mut out = vec![0f32; n];
match audio.format {
SampleFormat::S16 => {
let bytes = &audio.data[0];
let needed = n * ch * 2;
if bytes.len() < needed {
return Err(Error::invalid(
"opus encoder: S16 input shorter than declared sample count",
));
}
for i in 0..n {
let mut acc = 0i32;
for c in 0..ch {
let off = (i * ch + c) * 2;
let s = i16::from_le_bytes([bytes[off], bytes[off + 1]]);
acc += s as i32;
}
out[i] = (acc as f32) / (ch as f32 * 32768.0);
}
}
SampleFormat::S16P => {
if audio.data.len() < ch {
return Err(Error::invalid("opus encoder: S16P input missing planes"));
}
for i in 0..n {
let mut acc = 0i32;
for c in 0..ch {
let plane = &audio.data[c];
if plane.len() < n * 2 {
return Err(Error::invalid(
"opus encoder: S16P plane shorter than declared sample count",
));
}
let off = i * 2;
let s = i16::from_le_bytes([plane[off], plane[off + 1]]);
acc += s as i32;
}
out[i] = (acc as f32) / (ch as f32 * 32768.0);
}
}
SampleFormat::F32 => {
let bytes = &audio.data[0];
let needed = n * ch * 4;
if bytes.len() < needed {
return Err(Error::invalid(
"opus encoder: F32 input shorter than declared sample count",
));
}
for i in 0..n {
let mut acc = 0f32;
for c in 0..ch {
let off = (i * ch + c) * 4;
acc += f32::from_le_bytes([
bytes[off],
bytes[off + 1],
bytes[off + 2],
bytes[off + 3],
]);
}
out[i] = acc / ch as f32;
}
}
SampleFormat::F32P => {
if audio.data.len() < ch {
return Err(Error::invalid("opus encoder: F32P input missing planes"));
}
for i in 0..n {
let mut acc = 0f32;
for c in 0..ch {
let plane = &audio.data[c];
if plane.len() < n * 4 {
return Err(Error::invalid(
"opus encoder: F32P plane shorter than declared sample count",
));
}
let off = i * 4;
acc += f32::from_le_bytes([
plane[off],
plane[off + 1],
plane[off + 2],
plane[off + 3],
]);
}
out[i] = acc / ch as f32;
}
}
other => {
return Err(Error::unsupported(format!(
"opus encoder: sample format {:?} not supported (use S16 / S16P / F32 / F32P)",
other
)));
}
}
let _ = FRAME_SAMPLES;
Ok(out)
}
pub fn make_encoder(params: &CodecParameters) -> Result<Box<dyn Encoder>> {
Ok(Box::new(OpusEncoder::new(params)?))
}
pub const OPUS_CONFIG_SILK_NB_20MS: u8 = 1;
pub const OPUS_CONFIG_SILK_MB_20MS: u8 = 5;
pub const OPUS_CONFIG_SILK_WB_20MS: u8 = 9;
pub const SILK_NB_FRAME_SAMPLES_INTERNAL: usize = 160;
pub const SILK_MB_FRAME_SAMPLES_INTERNAL: usize = 240;
pub const SILK_WB_FRAME_SAMPLES_INTERNAL: usize = 320;
pub const SILK_FRAME_SAMPLES_48K: usize = 960;
pub const SILK_NB_RATE: u32 = 8_000;
pub const SILK_MB_RATE: u32 = 12_000;
pub const SILK_WB_RATE: u32 = 16_000;
pub fn build_silk_nb_20ms_toc(stereo: bool) -> u8 {
let stereo_bit: u8 = if stereo { 1 } else { 0 };
(OPUS_CONFIG_SILK_NB_20MS << 3) | (stereo_bit << 2) }
pub fn build_silk_mb_20ms_toc(stereo: bool) -> u8 {
let stereo_bit: u8 = if stereo { 1 } else { 0 };
(OPUS_CONFIG_SILK_MB_20MS << 3) | (stereo_bit << 2)
}
pub fn build_silk_wb_20ms_toc(stereo: bool) -> u8 {
let stereo_bit: u8 = if stereo { 1 } else { 0 };
(OPUS_CONFIG_SILK_WB_20MS << 3) | (stereo_bit << 2)
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum SilkBw {
Nb,
Mb,
Wb,
}
impl SilkBw {
fn internal_rate(self) -> u32 {
match self {
SilkBw::Nb => SILK_NB_RATE,
SilkBw::Mb => SILK_MB_RATE,
SilkBw::Wb => SILK_WB_RATE,
}
}
fn frame_samples_20ms(self) -> usize {
match self {
SilkBw::Nb => SILK_NB_FRAME_SAMPLES_INTERNAL,
SilkBw::Mb => SILK_MB_FRAME_SAMPLES_INTERNAL,
SilkBw::Wb => SILK_WB_FRAME_SAMPLES_INTERNAL,
}
}
}
#[derive(Copy, Clone, Debug)]
struct SilkMode {
bw: SilkBw,
stereo: bool,
duration_ms: u32,
}
impl SilkMode {
fn new(bw: SilkBw, stereo: bool, duration_ms: u32) -> Self {
debug_assert!(matches!(duration_ms, 10 | 20 | 40 | 60));
Self {
bw,
stereo,
duration_ms,
}
}
fn config(self) -> u8 {
let base = match self.bw {
SilkBw::Nb => 0u8,
SilkBw::Mb => 4,
SilkBw::Wb => 8,
};
let offset = match self.duration_ms {
10 => 0,
20 => 1,
40 => 2,
60 => 3,
_ => unreachable!(),
};
base + offset
}
fn toc_byte(self) -> u8 {
let stereo_bit: u8 = if self.stereo { 1 } else { 0 };
(self.config() << 3) | (stereo_bit << 2)
}
fn internal_rate(self) -> u32 {
self.bw.internal_rate()
}
fn silk_frames_per_packet(self) -> usize {
match self.duration_ms {
10 | 20 => 1,
40 => 2,
60 => 3,
_ => unreachable!(),
}
}
fn subframes_per_silk_frame(self) -> usize {
if self.duration_ms == 10 {
2
} else {
4
}
}
fn frame_samples_internal(self) -> usize {
let per_silk = match self.duration_ms {
10 => self.bw.frame_samples_20ms() / 2,
_ => self.bw.frame_samples_20ms(),
};
per_silk * self.silk_frames_per_packet()
}
fn samples_per_silk_frame(self) -> usize {
self.frame_samples_internal() / self.silk_frames_per_packet()
}
fn frame_samples_48k(self) -> usize {
match self.duration_ms {
10 => 480,
20 => 960,
40 => 1920,
60 => 2880,
_ => unreachable!(),
}
}
fn input_channels(self) -> u16 {
if self.stereo {
2
} else {
1
}
}
fn is_stereo(self) -> bool {
self.stereo
}
fn buffer_bytes(self) -> u32 {
let samples = self.frame_samples_internal();
let base = (samples * 17) / 8 + 128;
let doubled = if self.is_stereo() { base * 2 } else { base };
doubled.next_multiple_of(64).max(384) as u32
}
fn downsample_ratio(self) -> usize {
(SAMPLE_RATE / self.internal_rate()) as usize
}
}
pub struct SilkEncoder {
out_params: CodecParameters,
mode: SilkMode,
silk_mid: crate::silk::encoder::SilkFrameEncoder,
silk_side: Option<crate::silk::encoder::SilkFrameEncoder>,
pending_internal: VecDeque<f32>,
input_sample_rate: u32,
output: VecDeque<Packet>,
pts_counter: i64,
}
impl SilkEncoder {
pub fn new_nb_mono_20ms(params: &CodecParameters) -> Result<Self> {
Self::new_mode(params, SilkMode::new(SilkBw::Nb, false, 20))
}
pub fn new_mb_mono_20ms(params: &CodecParameters) -> Result<Self> {
Self::new_mode(params, SilkMode::new(SilkBw::Mb, false, 20))
}
pub fn new_wb_mono_20ms(params: &CodecParameters) -> Result<Self> {
Self::new_mode(params, SilkMode::new(SilkBw::Wb, false, 20))
}
pub fn new_nb_stereo_20ms(params: &CodecParameters) -> Result<Self> {
Self::new_mode(params, SilkMode::new(SilkBw::Nb, true, 20))
}
pub fn new_mb_stereo_20ms(params: &CodecParameters) -> Result<Self> {
Self::new_mode(params, SilkMode::new(SilkBw::Mb, true, 20))
}
pub fn new_wb_stereo_20ms(params: &CodecParameters) -> Result<Self> {
Self::new_mode(params, SilkMode::new(SilkBw::Wb, true, 20))
}
pub fn new_nb_mono_10ms(params: &CodecParameters) -> Result<Self> {
Self::new_mode(params, SilkMode::new(SilkBw::Nb, false, 10))
}
pub fn new_mb_mono_10ms(params: &CodecParameters) -> Result<Self> {
Self::new_mode(params, SilkMode::new(SilkBw::Mb, false, 10))
}
pub fn new_wb_mono_10ms(params: &CodecParameters) -> Result<Self> {
Self::new_mode(params, SilkMode::new(SilkBw::Wb, false, 10))
}
pub fn new_nb_stereo_10ms(params: &CodecParameters) -> Result<Self> {
Self::new_mode(params, SilkMode::new(SilkBw::Nb, true, 10))
}
pub fn new_mb_stereo_10ms(params: &CodecParameters) -> Result<Self> {
Self::new_mode(params, SilkMode::new(SilkBw::Mb, true, 10))
}
pub fn new_wb_stereo_10ms(params: &CodecParameters) -> Result<Self> {
Self::new_mode(params, SilkMode::new(SilkBw::Wb, true, 10))
}
pub fn new_nb_mono_40ms(params: &CodecParameters) -> Result<Self> {
Self::new_mode(params, SilkMode::new(SilkBw::Nb, false, 40))
}
pub fn new_mb_mono_40ms(params: &CodecParameters) -> Result<Self> {
Self::new_mode(params, SilkMode::new(SilkBw::Mb, false, 40))
}
pub fn new_wb_mono_40ms(params: &CodecParameters) -> Result<Self> {
Self::new_mode(params, SilkMode::new(SilkBw::Wb, false, 40))
}
pub fn new_nb_stereo_40ms(params: &CodecParameters) -> Result<Self> {
Self::new_mode(params, SilkMode::new(SilkBw::Nb, true, 40))
}
pub fn new_mb_stereo_40ms(params: &CodecParameters) -> Result<Self> {
Self::new_mode(params, SilkMode::new(SilkBw::Mb, true, 40))
}
pub fn new_wb_stereo_40ms(params: &CodecParameters) -> Result<Self> {
Self::new_mode(params, SilkMode::new(SilkBw::Wb, true, 40))
}
pub fn new_nb_mono_60ms(params: &CodecParameters) -> Result<Self> {
Self::new_mode(params, SilkMode::new(SilkBw::Nb, false, 60))
}
pub fn new_mb_mono_60ms(params: &CodecParameters) -> Result<Self> {
Self::new_mode(params, SilkMode::new(SilkBw::Mb, false, 60))
}
pub fn new_wb_mono_60ms(params: &CodecParameters) -> Result<Self> {
Self::new_mode(params, SilkMode::new(SilkBw::Wb, false, 60))
}
pub fn new_nb_stereo_60ms(params: &CodecParameters) -> Result<Self> {
Self::new_mode(params, SilkMode::new(SilkBw::Nb, true, 60))
}
pub fn new_mb_stereo_60ms(params: &CodecParameters) -> Result<Self> {
Self::new_mode(params, SilkMode::new(SilkBw::Mb, true, 60))
}
pub fn new_wb_stereo_60ms(params: &CodecParameters) -> Result<Self> {
Self::new_mode(params, SilkMode::new(SilkBw::Wb, true, 60))
}
fn build_frame_encoder(mode: SilkMode) -> crate::silk::encoder::SilkFrameEncoder {
let bw_params = match mode.bw {
SilkBw::Nb => crate::silk::encoder::BandwidthParams::nb(),
SilkBw::Mb => crate::silk::encoder::BandwidthParams::mb(),
SilkBw::Wb => crate::silk::encoder::BandwidthParams::wb(),
};
let subframes = mode.subframes_per_silk_frame();
crate::silk::encoder::SilkFrameEncoder::new_with_subframes(bw_params, subframes)
}
fn new_mode(params: &CodecParameters, mode: SilkMode) -> Result<Self> {
let channels = params.channels.unwrap_or(mode.input_channels());
if channels != mode.input_channels() {
return Err(Error::unsupported(format!(
"SILK encoder: {:?} expects {}-channel input, got {channels} channels",
mode,
mode.input_channels()
)));
}
let sr = params.sample_rate.unwrap_or(SAMPLE_RATE);
let internal = mode.internal_rate();
if sr != internal && sr != SAMPLE_RATE {
return Err(Error::unsupported(format!(
"SILK encoder: {mode:?} expects {internal} Hz or 48 kHz input, got {sr} Hz"
)));
}
let silk_mid = Self::build_frame_encoder(mode);
let silk_side = if mode.is_stereo() {
Some(Self::build_frame_encoder(mode))
} else {
None
};
let mut out_params = params.clone();
out_params.sample_rate = Some(SAMPLE_RATE);
out_params.channels = Some(mode.input_channels());
let per_frame_items =
mode.frame_samples_internal() * (if mode.is_stereo() { 2 } else { 1 });
Ok(Self {
out_params,
mode,
silk_mid,
silk_side,
pending_internal: VecDeque::with_capacity(per_frame_items * 2),
input_sample_rate: sr,
output: VecDeque::new(),
pts_counter: 0,
})
}
fn drain_frames(&mut self) -> Result<()> {
let samples_per_frame = self.mode.frame_samples_internal();
let per_frame_items = samples_per_frame * (if self.mode.is_stereo() { 2 } else { 1 });
while self.pending_internal.len() >= per_frame_items {
if self.mode.is_stereo() {
let mut left = Vec::with_capacity(samples_per_frame);
let mut right = Vec::with_capacity(samples_per_frame);
for _ in 0..samples_per_frame {
left.push(self.pending_internal.pop_front().unwrap_or(0.0));
right.push(self.pending_internal.pop_front().unwrap_or(0.0));
}
let pkt = self.encode_one_stereo_frame(&left, &right)?;
self.output.push_back(pkt);
} else {
let mut frame = Vec::with_capacity(samples_per_frame);
for _ in 0..samples_per_frame {
frame.push(self.pending_internal.pop_front().unwrap_or(0.0));
}
let pkt = self.encode_one_mono_frame(&frame)?;
self.output.push_back(pkt);
}
}
Ok(())
}
fn emit_shared_header(&self, enc: &mut oxideav_celt::range_encoder::RangeEncoder) {
let n_silk_frames = self.mode.silk_frames_per_packet();
let n_channels = if self.mode.is_stereo() { 2 } else { 1 };
for _ch in 0..n_channels {
for _i in 0..n_silk_frames {
enc.encode_bit_logp(true, 1); }
enc.encode_bit_logp(false, 1); }
}
fn encode_one_mono_frame(&mut self, pcm_internal: &[f32]) -> Result<Packet> {
debug_assert_eq!(pcm_internal.len(), self.mode.frame_samples_internal());
let mut re = oxideav_celt::range_encoder::RangeEncoder::new(self.mode.buffer_bytes());
self.emit_shared_header(&mut re);
let per_silk = self.mode.samples_per_silk_frame();
let n = self.mode.silk_frames_per_packet();
for i in 0..n {
let start = i * per_silk;
self.silk_mid
.encode_frame_body(&pcm_internal[start..start + per_silk], &mut re)?;
}
let body = re
.done()
.map_err(|e| Error::other(format!("SILK encoder: {e}")))?;
let body = strip_trailing_zeros(body);
self.finish_packet(body)
}
fn encode_one_stereo_frame(&mut self, left: &[f32], right: &[f32]) -> Result<Packet> {
debug_assert_eq!(left.len(), right.len());
debug_assert_eq!(left.len(), self.mode.frame_samples_internal());
let mut re = oxideav_celt::range_encoder::RangeEncoder::new(self.mode.buffer_bytes());
self.emit_shared_header(&mut re);
let per_silk = self.mode.samples_per_silk_frame();
let n = self.mode.silk_frames_per_packet();
for i in 0..n {
let start = i * per_silk;
let lc = &left[start..start + per_silk];
let rc = &right[start..start + per_silk];
let (mid, side) = crate::silk::encoder::stereo_mid_side(lc, rc);
crate::silk::encoder::encode_stereo_pred_weights(&mut re, [0, 0]);
self.silk_mid.encode_frame_body(&mid, &mut re)?;
let side_enc = self
.silk_side
.as_mut()
.ok_or_else(|| Error::other("SILK stereo encoder: missing side state"))?;
side_enc.encode_frame_body(&side, &mut re)?;
}
let body = re
.done()
.map_err(|e| Error::other(format!("SILK encoder: {e}")))?;
let body = strip_trailing_zeros(body);
self.finish_packet(body)
}
fn finish_packet(&mut self, body: Vec<u8>) -> Result<Packet> {
let toc = self.mode.toc_byte();
let mut data = Vec::with_capacity(1 + body.len());
data.push(toc);
data.extend_from_slice(&body);
let tb = TimeBase::new(1, SAMPLE_RATE as i64);
let pts = self.pts_counter;
let samples_48k = self.mode.frame_samples_48k() as i64;
self.pts_counter += samples_48k;
Ok(Packet::new(0, tb, data)
.with_pts(pts)
.with_duration(samples_48k))
}
}
fn strip_trailing_zeros(mut v: Vec<u8>) -> Vec<u8> {
while v.len() > 1 && v.last() == Some(&0) {
v.pop();
}
v
}
impl Encoder for SilkEncoder {
fn codec_id(&self) -> &CodecId {
&self.out_params.codec_id
}
fn output_params(&self) -> &CodecParameters {
&self.out_params
}
fn send_frame(&mut self, frame: &Frame) -> Result<()> {
let audio = match frame {
Frame::Audio(a) => a,
_ => {
return Err(Error::invalid(
"SILK encoder: expected audio frame, got video",
))
}
};
if audio.channels != self.mode.input_channels() {
return Err(Error::invalid(format!(
"SILK encoder: frame channels ({}) differ from configured input channels ({})",
audio.channels,
self.mode.input_channels()
)));
}
if audio.sample_rate != self.input_sample_rate {
return Err(Error::unsupported(format!(
"SILK encoder: input sample rate ({}) differs from configured rate ({}); reconfigure or resample first",
audio.sample_rate, self.input_sample_rate
)));
}
let internal_items_per_sample = if self.mode.is_stereo() { 2 } else { 1 };
let internal_samples: Vec<f32> = if self.mode.is_stereo() {
let stereo = extract_stereo_f32(audio)?;
if audio.sample_rate == self.mode.internal_rate() {
stereo
} else {
downsample_box_interleaved(&stereo, self.mode.downsample_ratio(), 2)
}
} else {
let mono = extract_mono_f32(audio)?;
if audio.sample_rate == self.mode.internal_rate() {
mono
} else {
downsample_box(&mono, self.mode.downsample_ratio())
}
};
debug_assert_eq!(
internal_items_per_sample * (internal_samples.len() / internal_items_per_sample),
internal_samples.len()
);
self.pending_internal.extend(&internal_samples);
self.drain_frames()
}
fn receive_packet(&mut self) -> Result<Packet> {
if let Some(p) = self.output.pop_front() {
Ok(p)
} else {
Err(Error::NeedMore)
}
}
fn flush(&mut self) -> Result<()> {
if !self.pending_internal.is_empty() {
let per_frame_items =
self.mode.frame_samples_internal() * (if self.mode.is_stereo() { 2 } else { 1 });
while self.pending_internal.len() % per_frame_items != 0 {
self.pending_internal.push_back(0.0);
}
self.drain_frames()?;
}
Ok(())
}
}
fn extract_stereo_f32(audio: &AudioFrame) -> Result<Vec<f32>> {
let n = audio.samples as usize;
let ch = audio.channels as usize;
if ch != 2 {
return Err(Error::invalid(format!(
"SILK stereo encoder: expected 2-channel input, got {ch}"
)));
}
let mut out = vec![0f32; n * 2];
match audio.format {
SampleFormat::S16 => {
let bytes = &audio.data[0];
let needed = n * 2 * 2;
if bytes.len() < needed {
return Err(Error::invalid(
"SILK stereo encoder: S16 input shorter than declared sample count",
));
}
for i in 0..n {
for c in 0..2 {
let off = (i * 2 + c) * 2;
let s = i16::from_le_bytes([bytes[off], bytes[off + 1]]);
out[i * 2 + c] = s as f32 / 32768.0;
}
}
}
SampleFormat::S16P => {
if audio.data.len() < 2 {
return Err(Error::invalid("SILK stereo encoder: S16P missing planes"));
}
for i in 0..n {
for c in 0..2 {
let plane = &audio.data[c];
if plane.len() < n * 2 {
return Err(Error::invalid(
"SILK stereo encoder: S16P plane shorter than declared sample count",
));
}
let off = i * 2;
let s = i16::from_le_bytes([plane[off], plane[off + 1]]);
out[i * 2 + c] = s as f32 / 32768.0;
}
}
}
SampleFormat::F32 => {
let bytes = &audio.data[0];
let needed = n * 2 * 4;
if bytes.len() < needed {
return Err(Error::invalid(
"SILK stereo encoder: F32 input shorter than declared sample count",
));
}
for i in 0..n {
for c in 0..2 {
let off = (i * 2 + c) * 4;
out[i * 2 + c] = f32::from_le_bytes([
bytes[off],
bytes[off + 1],
bytes[off + 2],
bytes[off + 3],
]);
}
}
}
SampleFormat::F32P => {
if audio.data.len() < 2 {
return Err(Error::invalid("SILK stereo encoder: F32P missing planes"));
}
for i in 0..n {
for c in 0..2 {
let plane = &audio.data[c];
if plane.len() < n * 4 {
return Err(Error::invalid(
"SILK stereo encoder: F32P plane shorter than declared sample count",
));
}
let off = i * 4;
out[i * 2 + c] = f32::from_le_bytes([
plane[off],
plane[off + 1],
plane[off + 2],
plane[off + 3],
]);
}
}
}
other => {
return Err(Error::unsupported(format!(
"SILK stereo encoder: sample format {other:?} not supported (use S16 / S16P / F32 / F32P)"
)));
}
}
Ok(out)
}
fn downsample_box_interleaved(input: &[f32], ratio: usize, stride: usize) -> Vec<f32> {
if ratio <= 1 {
return input.to_vec();
}
debug_assert_eq!(input.len() % stride, 0);
let n_out_frames = (input.len() / stride) / ratio;
let mut out = vec![0f32; n_out_frames * stride];
for i in 0..n_out_frames {
for c in 0..stride {
let mut sum = 0f32;
for k in 0..ratio {
sum += input[(i * ratio + k) * stride + c];
}
out[i * stride + c] = sum / ratio as f32;
}
}
out
}
fn downsample_box(input: &[f32], ratio: usize) -> Vec<f32> {
if ratio <= 1 {
return input.to_vec();
}
let n_out = input.len() / ratio;
let mut out = Vec::with_capacity(n_out);
for i in 0..n_out {
let mut sum = 0f32;
for k in 0..ratio {
sum += input[i * ratio + k];
}
out.push(sum / ratio as f32);
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn toc_byte_mono() {
let b = build_toc_byte(false);
assert_eq!(b >> 3, 31, "config should be 31");
assert_eq!((b >> 2) & 1, 0, "stereo bit should be 0");
assert_eq!(b & 0x3, 0, "framing code should be 0");
}
#[test]
fn toc_byte_stereo() {
let b = build_toc_byte(true);
assert_eq!(b >> 3, 31, "config should be 31");
assert_eq!((b >> 2) & 1, 1, "stereo bit should be 1");
assert_eq!(b & 0x3, 0, "framing code should be 0");
}
#[test]
fn rejects_non_48k() {
let mut p = CodecParameters::audio(CodecId::new("opus"));
p.channels = Some(1);
p.sample_rate = Some(44_100);
match OpusEncoder::new(&p) {
Err(Error::Unsupported(_)) => {}
Err(e) => panic!("expected Unsupported, got {e:?}"),
Ok(_) => panic!("expected Unsupported, got Ok"),
}
}
#[test]
fn rejects_more_than_stereo() {
let mut p = CodecParameters::audio(CodecId::new("opus"));
p.channels = Some(6);
p.sample_rate = Some(SAMPLE_RATE);
match OpusEncoder::new(&p) {
Err(Error::Unsupported(_)) => {}
Err(e) => panic!("expected Unsupported, got {e:?}"),
Ok(_) => panic!("expected Unsupported, got Ok"),
}
}
#[test]
fn new_celt_only_fb_accepts_48k_mono() {
let mut p = CodecParameters::audio(CodecId::new("opus"));
p.channels = Some(1);
p.sample_rate = Some(SAMPLE_RATE);
assert!(OpusEncoder::new_celt_only_full_band(&p).is_ok());
}
#[test]
fn new_celt_only_fb_rejects_non_48k() {
let mut p = CodecParameters::audio(CodecId::new("opus"));
p.channels = Some(1);
p.sample_rate = Some(16_000);
match OpusEncoder::new_celt_only_full_band(&p) {
Err(Error::Unsupported(_)) => {}
Err(e) => panic!("expected Unsupported, got {e:?}"),
Ok(_) => panic!("expected Unsupported, got Ok"),
}
}
#[test]
fn mono_encoder_produces_toc_byte() {
let mut p = CodecParameters::audio(CodecId::new("opus"));
p.channels = Some(1);
p.sample_rate = Some(SAMPLE_RATE);
let mut enc = OpusEncoder::new(&p).unwrap();
let bytes = vec![0u8; OPUS_FRAME_SAMPLES * 2];
let frame = Frame::Audio(AudioFrame {
format: SampleFormat::S16,
channels: 1,
sample_rate: SAMPLE_RATE,
samples: OPUS_FRAME_SAMPLES as u32,
pts: None,
time_base: TimeBase::new(1, SAMPLE_RATE as i64),
data: vec![bytes],
});
enc.send_frame(&frame).unwrap();
let pkt = enc.receive_packet().unwrap();
assert!(!pkt.data.is_empty(), "packet must contain TOC + bitstream");
let toc = pkt.data[0];
assert_eq!(toc >> 3, 31, "config should be 31");
assert_eq!((toc >> 2) & 1, 0, "mono → stereo bit = 0");
assert_eq!(toc & 0x3, 0, "single-frame packet → code 0");
}
}