skua_voice/input/codecs/
opus.rs

1use crate::constants::*;
2use audiopus::{
3    coder::{Decoder as AudiopusDecoder, GenericCtl},
4    Channels,
5    Error as OpusError,
6    ErrorCode,
7};
8use symphonia_core::{
9    audio::{AsAudioBufferRef, AudioBuffer, AudioBufferRef, Layout, Signal, SignalSpec},
10    codecs::{
11        CodecDescriptor,
12        CodecParameters,
13        Decoder,
14        DecoderOptions,
15        FinalizeResult,
16        CODEC_TYPE_OPUS,
17    },
18    errors::{decode_error, Result as SymphResult},
19    formats::Packet,
20};
21
22/// Opus decoder for symphonia, based on libopus v1.3 (via [`audiopus`]).
23pub struct OpusDecoder {
24    inner: AudiopusDecoder,
25    params: CodecParameters,
26    buf: AudioBuffer<f32>,
27    rawbuf: Vec<f32>,
28}
29
30/// # SAFETY
31/// The underlying Opus decoder (currently) requires only a `&self` parameter
32/// to decode given packets, which is likely a mistaken decision.
33///
34/// This struct makes stronger assumptions and only touches FFI decoder state with a
35/// `&mut self`, preventing data races via `&OpusDecoder` as required by `impl Sync`.
36/// No access to other internal state relies on unsafety or crosses FFI.
37unsafe impl Sync for OpusDecoder {}
38
39impl OpusDecoder {
40    fn decode_inner(&mut self, packet: &Packet) -> SymphResult<()> {
41        let s_ct = loop {
42            let pkt = if packet.buf().is_empty() {
43                None
44            } else if let Ok(checked_pkt) = packet.buf().try_into() {
45                Some(checked_pkt)
46            } else {
47                return decode_error("Opus packet was too large (greater than i32::MAX bytes).");
48            };
49            let out_space = (&mut self.rawbuf[..]).try_into().expect("The following logic expands this buffer safely below i32::MAX, and we throw our own error.");
50
51            match self.inner.decode_float(pkt, out_space, false) {
52                Ok(v) => break v,
53                Err(OpusError::Opus(ErrorCode::BufferTooSmall)) => {
54                    // double the buffer size
55                    // correct behav would be to mirror the decoder logic in the udp_rx set.
56                    let new_size = (self.rawbuf.len() * 2).min(std::i32::MAX as usize);
57                    if new_size == self.rawbuf.len() {
58                        return decode_error("Opus frame too big: cannot expand opus frame decode buffer any further.");
59                    }
60
61                    self.rawbuf.resize(new_size, 0.0);
62                    self.buf = AudioBuffer::new(
63                        self.rawbuf.len() as u64 / 2,
64                        SignalSpec::new_with_layout(SAMPLE_RATE_RAW as u32, Layout::Stereo),
65                    );
66                },
67                Err(e) => {
68                    tracing::error!("Opus decode error: {:?}", e);
69                    return decode_error("Opus decode error: see 'tracing' logs.");
70                },
71            }
72        };
73
74        self.buf.clear();
75        self.buf.render_reserved(Some(s_ct));
76
77        // Forcibly assuming stereo, for now.
78        for ch in 0..2 {
79            let iter = self.rawbuf.chunks_exact(2).map(|chunk| chunk[ch]);
80            for (tgt, src) in self.buf.chan_mut(ch).iter_mut().zip(iter) {
81                *tgt = src;
82            }
83        }
84
85        Ok(())
86    }
87}
88
89impl Decoder for OpusDecoder {
90    fn try_new(params: &CodecParameters, _options: &DecoderOptions) -> SymphResult<Self> {
91        let inner = AudiopusDecoder::new(SAMPLE_RATE, Channels::Stereo).unwrap();
92
93        let mut params = params.clone();
94        params.with_sample_rate(SAMPLE_RATE_RAW as u32);
95
96        Ok(Self {
97            inner,
98            params,
99            buf: AudioBuffer::new(
100                MONO_FRAME_SIZE as u64,
101                SignalSpec::new_with_layout(SAMPLE_RATE_RAW as u32, Layout::Stereo),
102            ),
103            rawbuf: vec![0.0f32; STEREO_FRAME_SIZE],
104        })
105    }
106
107    fn supported_codecs() -> &'static [CodecDescriptor] {
108        &[symphonia_core::support_codec!(
109            CODEC_TYPE_OPUS,
110            "opus",
111            "libopus (1.3+, audiopus)"
112        )]
113    }
114
115    fn codec_params(&self) -> &CodecParameters {
116        &self.params
117    }
118
119    fn decode(&mut self, packet: &Packet) -> SymphResult<AudioBufferRef<'_>> {
120        if let Err(e) = self.decode_inner(packet) {
121            self.buf.clear();
122            Err(e)
123        } else {
124            Ok(self.buf.as_audio_buffer_ref())
125        }
126    }
127
128    fn reset(&mut self) {
129        _ = self.inner.reset_state();
130    }
131
132    fn finalize(&mut self) -> FinalizeResult {
133        FinalizeResult::default()
134    }
135
136    fn last_decoded(&self) -> AudioBufferRef<'_> {
137        self.buf.as_audio_buffer_ref()
138    }
139}
140
141#[cfg(test)]
142mod tests {
143    use crate::{
144        constants::test_data::FILE_WEBM_TARGET,
145        input::{input_tests::*, File},
146    };
147
148    // NOTE: this covers youtube audio in a non-copyright-violating way, since
149    // those depend on an HttpRequest internally anyhow.
150    #[tokio::test]
151    #[ntest::timeout(10_000)]
152    async fn webm_track_plays() {
153        track_plays_passthrough(|| File::new(FILE_WEBM_TARGET)).await;
154    }
155
156    #[tokio::test]
157    #[ntest::timeout(10_000)]
158    async fn webm_forward_seek_correct() {
159        forward_seek_correct(|| File::new(FILE_WEBM_TARGET)).await;
160    }
161
162    #[tokio::test]
163    #[ntest::timeout(10_000)]
164    async fn webm_backward_seek_correct() {
165        backward_seek_correct(|| File::new(FILE_WEBM_TARGET)).await;
166    }
167}