use unsafe_libopus::{
OPUS_OK, OPUS_RESET_STATE, OPUS_SET_GAIN_REQUEST, OpusMSDecoder, opus_multistream_decode_float,
opus_multistream_decoder_create, opus_multistream_decoder_ctl_impl, opus_multistream_decoder_destroy, varargs,
};
use super::Backend;
use crate::decode::Decoded;
use crate::{Error, Layout, opus};
pub(super) const NAME: &str = "libopus";
const MAX_FRAME_MS: usize = 120;
const MONO: &[u8] = &[0];
const STEREO: &[u8] = &[0, 1];
pub(super) struct Libopus {
inner: *mut OpusMSDecoder,
layout: Layout,
reorder: Option<&'static [usize]>,
streams: u8,
pre_skip: usize,
max_frame_size: usize,
last_frame_size: Option<usize>,
in_dtx: bool,
}
unsafe impl Send for Libopus {}
impl Libopus {
pub(super) fn open(catalog: &hang::catalog::AudioConfig) -> Result<Box<dyn Backend>, Error> {
let head = opus::head(catalog)?;
let channel_count = head.channel_count;
let (streams, coupled, table, layout, reorder) = match &head.mapping {
None => {
let table = match opus::validate_channels(channel_count)? {
1 => MONO,
_ => STEREO,
};
let layout = Layout::from_channels(channel_count)?;
(1, table.len() as i32 - 1, table, layout, None)
}
Some(mapping) if mapping.family() == 1 => {
let (layout, reorder) = vorbis(channel_count)?;
let (streams, coupled) = (mapping.streams() as i32, mapping.coupled() as i32);
(streams, coupled, mapping.table(), layout, reorder)
}
Some(mapping) => {
return Err(Error::Unsupported(format!(
"opus channel mapping family {} declares no speaker positions",
mapping.family()
)));
}
};
let mut err = 0i32;
let inner = unsafe {
opus_multistream_decoder_create(
opus::DECODE_RATE as i32,
table.len() as i32,
streams,
coupled,
table.as_ptr(),
&mut err,
)
};
if err != OPUS_OK || inner.is_null() {
return Err(opus::error(err, "opus_multistream_decoder_create"));
}
let decoder = Self {
inner,
layout,
reorder,
streams: streams as u8,
pre_skip: head.pre_skip as usize,
max_frame_size: (opus::DECODE_RATE as usize * MAX_FRAME_MS) / 1000,
last_frame_size: None,
in_dtx: false,
};
let rc = unsafe {
opus_multistream_decoder_ctl_impl(decoder.inner, OPUS_SET_GAIN_REQUEST, varargs![head.output_gain as i32])
};
if rc != OPUS_OK {
return Err(opus::error(rc, "OPUS_SET_GAIN"));
}
Ok(Box::new(decoder))
}
}
fn vorbis(channels: u32) -> Result<(Layout, Option<&'static [usize]>), Error> {
Ok(match channels {
1 => (Layout::Mono, None),
2 => (Layout::Stereo, None),
3 => (Layout::ThreePointZero, Some(&[0, 2, 1])),
4 => (Layout::Quad, None),
5 => (Layout::FivePointZero, Some(&[0, 2, 1, 3, 4])),
6 => (Layout::FivePointOne, Some(&[0, 2, 1, 5, 3, 4])),
7 => (Layout::SixPointOne, Some(&[0, 2, 1, 6, 5, 3, 4])),
8 => (Layout::SevenPointOne, Some(&[0, 2, 1, 7, 5, 6, 3, 4])),
other => {
return Err(Error::Unsupported(format!(
"opus channel mapping family 1 has no {other}-channel layout"
)));
}
})
}
impl Backend for Libopus {
fn decode(&mut self, packet: &[u8]) -> Result<Decoded, Error> {
let channels = self.layout.channels() as usize;
let frame_size = if packet.is_empty() {
self.last_frame_size.ok_or_else(|| {
Error::Decode("opus packet lost before any packet decoded, so no length to conceal".into())
})?
} else {
self.max_frame_size
};
let mut out = vec![0.0f32; frame_size * channels];
let samples = unsafe {
opus_multistream_decode_float(
self.inner,
packet.as_ptr(),
packet.len() as i32,
out.as_mut_ptr(),
frame_size as i32,
0,
)
};
if samples < 0 {
return Err(opus::decode_error(samples));
}
if !packet.is_empty() {
self.last_frame_size = Some(samples as usize);
}
out.truncate(samples as usize * channels);
if let Some(order) = self.reorder {
let mut vorbis = [0.0f32; 8];
for frame in out.chunks_exact_mut(channels) {
vorbis[..channels].copy_from_slice(frame);
for (sample, &from) in frame.iter_mut().zip(order) {
*sample = vorbis[from];
}
}
}
let activity = opus::multistream_activity(packet, self.streams, self.in_dtx);
self.in_dtx = activity.is_dtx();
Ok(Decoded { samples: out, activity })
}
fn reset(&mut self) -> Result<(), Error> {
let rc = unsafe { opus_multistream_decoder_ctl_impl(self.inner, OPUS_RESET_STATE, varargs![]) };
if rc != OPUS_OK {
return Err(opus::error(rc, "OPUS_RESET_STATE"));
}
self.in_dtx = false;
Ok(())
}
fn sample_rate(&self) -> u32 {
opus::DECODE_RATE
}
fn layout(&self) -> Layout {
self.layout
}
fn delay(&self) -> usize {
self.pre_skip
}
fn name(&self) -> &str {
NAME
}
}
impl Drop for Libopus {
fn drop(&mut self) {
unsafe { opus_multistream_decoder_destroy(self.inner) };
}
}
#[cfg(test)]
mod tests {
use unsafe_libopus::{
OPUS_APPLICATION_AUDIO, opus_multistream_encode_float, opus_multistream_encoder_destroy,
opus_multistream_surround_encoder_create,
};
use crate::Error;
use crate::decode::{Config, Decoder};
use crate::layout::Speaker::{self, *};
const VORBIS: [&[Speaker]; 8] = [
&[FrontCenter],
&[FrontLeft, FrontRight],
&[FrontLeft, FrontCenter, FrontRight],
&[FrontLeft, FrontRight, BackLeft, BackRight],
&[FrontLeft, FrontCenter, FrontRight, SideLeft, SideRight],
&[FrontLeft, FrontCenter, FrontRight, SideLeft, SideRight, Lfe],
&[FrontLeft, FrontCenter, FrontRight, SideLeft, SideRight, BackCenter, Lfe],
&[
FrontLeft,
FrontCenter,
FrontRight,
SideLeft,
SideRight,
BackLeft,
BackRight,
Lfe,
],
];
const RATE: usize = 48_000;
const FRAME: usize = 960;
const PACKETS: usize = 15;
fn tone(speaker: Speaker) -> f32 {
match speaker {
Lfe => 80.0,
other => 400.0 + 300.0 * other as u8 as f32,
}
}
fn surround(channels: usize) -> (bytes::Bytes, Vec<Vec<u8>>) {
let speakers = VORBIS[channels - 1];
let (mut streams, mut coupled, mut mapping) = (0i32, 0i32, [0u8; 8]);
let mut err = 0i32;
let encoder = unsafe {
opus_multistream_surround_encoder_create(
RATE as i32,
channels as i32,
1,
&mut streams,
&mut coupled,
mapping.as_mut_ptr(),
OPUS_APPLICATION_AUDIO,
&mut err,
)
};
assert!(err == 0 && !encoder.is_null(), "encoder create failed: {err}");
let packets = (0..PACKETS)
.map(|packet| {
let mut pcm = Vec::with_capacity(FRAME * channels);
for i in packet * FRAME..(packet + 1) * FRAME {
for &speaker in speakers {
let phase = std::f32::consts::TAU * tone(speaker) * i as f32 / RATE as f32;
pcm.push(phase.sin() * 0.5);
}
}
let mut out = vec![0u8; 4000];
let len = unsafe {
opus_multistream_encode_float(encoder, pcm.as_ptr(), FRAME as i32, out.as_mut_ptr(), 4000)
};
assert!(len > 0, "encode failed: {len}");
out.truncate(len as usize);
out
})
.collect();
unsafe { opus_multistream_encoder_destroy(encoder) };
let mut head = moq_mux::codec::opus::Config::new(RATE as u32, 2)
.encode()
.unwrap()
.to_vec();
head[9] = channels as u8;
head[18] = 1;
head.extend_from_slice(&[streams as u8, coupled as u8]);
head.extend_from_slice(&mapping[..channels]);
(head.into(), packets)
}
fn catalog(head: bytes::Bytes, channels: u32) -> hang::catalog::AudioConfig {
let mut catalog = hang::catalog::AudioConfig::new(hang::catalog::AudioCodec::Opus, 48_000, channels);
catalog.description = Some(head);
catalog
}
fn power(samples: &[f32], freq: f32) -> f32 {
let coeff = 2.0 * (std::f32::consts::TAU * freq / RATE as f32).cos();
let (mut s1, mut s2) = (0.0f32, 0.0f32);
for &x in samples {
let s = x + coeff * s1 - s2;
s2 = s1;
s1 = s;
}
s1 * s1 + s2 * s2 - coeff * s1 * s2
}
#[test]
fn family_one_decodes_in_canonical_order() {
for channels in 1..=8 {
let (head, packets) = surround(channels);
let mut decoder = Decoder::new(&catalog(head, channels as u32), &Config::default()).unwrap();
let layout = decoder.layout();
let mut speakers = VORBIS[channels - 1].to_vec();
speakers.sort_by_key(|&speaker| speaker as u8);
assert_eq!(layout.speakers().unwrap(), speakers, "{channels} channels");
let mut pcm = Vec::new();
for packet in &packets {
pcm.extend(decoder.decode(packet).unwrap().samples);
}
let pcm = &pcm[pcm.len() / 2..];
let tones: Vec<f32> = VORBIS[channels - 1].iter().copied().map(tone).collect();
for (index, &speaker) in layout.speakers().unwrap().iter().enumerate() {
let channel: Vec<f32> = pcm.iter().skip(index).step_by(channels).copied().collect();
let loudest = tones
.iter()
.copied()
.max_by(|a, b| power(&channel, *a).total_cmp(&power(&channel, *b)))
.unwrap();
assert_eq!(loudest, tone(speaker), "{channels} channels, {speaker:?} at {index}");
}
}
}
#[test]
fn surround_silence_is_dtx() {
let (head, packets) = surround(6);
let mut decoder = Decoder::new(&catalog(head.clone(), 6), &Config::default()).unwrap();
let mid = decoder.decode(&packets[PACKETS / 2]).unwrap();
assert!(mid.activity.is_active(), "a coded surround frame must stay active");
let dtx = [0xfc, 0x00, 0xfc, 0x00, 0xf8, 0x00, 0xf8];
let mut decoder = Decoder::new(&catalog(head, 6), &Config::default()).unwrap();
let decoded = decoder.decode(&dtx).expect("empty multistream packet");
assert!(decoded.activity.is_dtx(), "all-DTX surround packet read as active");
assert_eq!(decoded.samples.len() % 6, 0);
}
#[test]
fn malformed_description_is_refused() {
for family in [1u8, 255] {
let mut head = moq_mux::codec::opus::Config::new(48_000, 2).encode().unwrap().to_vec();
head[18] = family;
let err = Decoder::new(&catalog(head.into(), 2), &Config::default())
.err()
.expect("refused");
assert!(
matches!(&err, Error::Unsupported(message) if message.contains("opus description")),
"family {family}: {err}"
);
}
let plain = hang::catalog::AudioConfig::new(hang::catalog::AudioCodec::Opus, 48_000, 2);
assert!(Decoder::new(&plain, &Config::default()).is_ok());
}
#[test]
fn other_families_are_refused() {
for (family, channels, table) in [(255u8, 2u8, &[2, 0, 0, 1][..]), (2, 4, &[4, 0, 0, 1, 2, 3])] {
let mut head = moq_mux::codec::opus::Config::new(48_000, 2).encode().unwrap().to_vec();
head[9] = channels;
head[18] = family;
head.extend_from_slice(table);
let err = Decoder::new(&catalog(head.into(), channels.into()), &Config::default())
.err()
.expect("refused");
assert!(
matches!(&err, Error::Unsupported(message) if message.contains("no speaker positions")),
"family {family}: {err}"
);
}
}
}