Skip to main content

web_audio_api/
decoding.rs

1use std::error::Error;
2use std::io::{Read, Seek, SeekFrom};
3
4use crate::buffer::{AudioBuffer, ChannelData};
5
6use symphonia::core::audio::GenericAudioBufferRef;
7use symphonia::core::codecs::audio::{
8    AudioCodecId, AudioDecoder, AudioDecoderOptions, FinalizeResult,
9};
10use symphonia::core::errors::Error as SymphoniaError;
11use symphonia::core::formats::probe::Hint;
12use symphonia::core::formats::{FormatOptions, FormatReader, TrackType};
13use symphonia::core::meta::MetadataOptions;
14
15pub(crate) fn decode_media_data<R: std::io::Read + Send + Sync + 'static>(
16    input: R,
17    target_sample_rate: f32,
18) -> Result<AudioBuffer, Box<dyn std::error::Error + Send + Sync>> {
19    let mut sample_rate = None;
20    let mut buffer: Option<AudioBuffer> = None;
21
22    for chunk in MediaDecoder::try_new(input)? {
23        let chunk = chunk?;
24
25        match sample_rate {
26            Some(rate) if rate != chunk.sample_rate() => {
27                return Err(Box::new(std::io::Error::new(
28                    std::io::ErrorKind::InvalidData,
29                    "decoded audio sample rate changed midstream",
30                )));
31            }
32            Some(_) => {}
33            None => sample_rate = Some(chunk.sample_rate()),
34        }
35
36        match buffer {
37            Some(ref mut buffer) if buffer.number_of_channels() != chunk.number_of_channels() => {
38                return Err(Box::new(std::io::Error::new(
39                    std::io::ErrorKind::InvalidData,
40                    "decoded audio channel count changed midstream",
41                )));
42            }
43            Some(ref mut buffer) => buffer.extend(&chunk),
44            None => buffer = Some(chunk),
45        }
46    }
47
48    let mut buffer = buffer.unwrap_or_else(|| AudioBuffer::from(vec![vec![]], target_sample_rate));
49
50    // Resample to desired rate (no-op if already matching).
51    buffer.resample(target_sample_rate);
52
53    Ok(buffer)
54}
55
56/// Wrapper for `Read` implementers to be used in Symphonia decoding
57///
58/// Symphonia requires its input to impl `Seek` - but allows non-seekable sources. Hence we
59/// implement Seek but return false for `is_seekable()`.
60struct MediaInput<R> {
61    input: R,
62}
63
64impl<R: Read> MediaInput<R> {
65    pub fn new(input: R) -> Self {
66        Self { input }
67    }
68}
69
70impl<R: Read> Read for MediaInput<R> {
71    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
72        self.input.read(buf)
73    }
74}
75
76impl<R> Seek for MediaInput<R> {
77    fn seek(&mut self, _pos: SeekFrom) -> std::io::Result<u64> {
78        Err(std::io::Error::new(
79            std::io::ErrorKind::Unsupported,
80            "MediaInput does not support seeking",
81        ))
82    }
83}
84
85impl<R: Read + Send + Sync> symphonia::core::io::MediaSource for MediaInput<R> {
86    fn is_seekable(&self) -> bool {
87        false
88    }
89    fn byte_len(&self) -> Option<u64> {
90        None
91    }
92}
93
94/// Media stream decoder (OGG, WAV, FLAC, ..)
95///
96/// The current implementation supports Symphonia's audio formats and codecs.
97pub(crate) struct MediaDecoder {
98    format: Box<dyn FormatReader>,
99    decoder: Box<dyn AudioDecoder>,
100    track_index: usize,
101    packet_count: usize,
102}
103
104impl MediaDecoder {
105    /// Try to construct a new instance from a `Read` implementer
106    ///
107    /// # Errors
108    ///
109    /// This method returns an Error in various cases (IO, mime sniffing, decoding).
110    pub fn try_new<R: std::io::Read + Send + Sync + 'static>(
111        input: R,
112    ) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
113        // Symphonia lib needs a Box<dyn MediaSource> - use our own MediaInput
114        let input = Box::new(MediaInput::new(input));
115
116        // Create the media source stream using the boxed media source from above.
117        let stream = symphonia::core::io::MediaSourceStream::new(input, Default::default());
118
119        // Create a hint to help the format registry guess what format reader is appropriate. In this
120        // function we'll leave it empty.
121        let hint = Hint::new();
122
123        // TODO: Allow to customize some options.
124        let format_opts: FormatOptions = Default::default();
125        let metadata_opts: MetadataOptions = Default::default();
126        // Opt-in to verify the decoded data against the checksums in the container.
127        let decoder_opts = AudioDecoderOptions::default().verify(true);
128
129        // Probe the media source stream for a format.
130        let format =
131            symphonia::default::get_probe().probe(&hint, stream, format_opts, metadata_opts)?;
132
133        // Get the default audio track.
134        let track = format
135            .default_track(TrackType::Audio)
136            .ok_or(SymphoniaError::Unsupported(
137                "no default media track available",
138            ))?;
139        let track_index = format
140            .tracks()
141            .iter()
142            .position(|t| t.id == track.id)
143            .unwrap();
144
145        let codec_params = track
146            .codec_params
147            .as_ref()
148            .and_then(|params| params.audio())
149            .ok_or(SymphoniaError::Unsupported(
150                "default media track is not an audio track",
151            ))?;
152
153        // Create a (stateful) decoder for the track.
154        let decoder = symphonia::default::get_codecs()
155            .make_audio_decoder(codec_params, &decoder_opts)
156            .map_err(|err| {
157                if matches!(
158                    err,
159                    SymphoniaError::Unsupported("core (codec): unsupported audio codec")
160                ) {
161                    Box::new(UnsupportedAudioCodecError {
162                        codec: codec_params.codec,
163                    }) as Box<dyn std::error::Error + Send + Sync>
164                } else {
165                    Box::new(err) as Box<dyn std::error::Error + Send + Sync>
166                }
167            })?;
168
169        Ok(Self {
170            format,
171            decoder,
172            track_index,
173            packet_count: 0,
174        })
175    }
176}
177
178#[derive(Debug)]
179struct UnsupportedAudioCodecError {
180    codec: AudioCodecId,
181}
182
183impl std::fmt::Display for UnsupportedAudioCodecError {
184    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
185        write!(f, "unsupported audio codec: {}", self.codec)
186    }
187}
188
189impl std::error::Error for UnsupportedAudioCodecError {}
190
191impl Iterator for MediaDecoder {
192    type Item = Result<AudioBuffer, Box<dyn Error + Send + Sync>>;
193
194    fn next(&mut self) -> Option<Self::Item> {
195        let Self {
196            format,
197            decoder,
198            track_index,
199            packet_count,
200        } = self;
201
202        // Get the track.
203        let track = format.tracks().get(*track_index)?;
204        let track_id = track.id;
205
206        loop {
207            // Get the next packet from the format reader.
208            let packet = match format.next_packet() {
209                Ok(None) => {
210                    log::debug!("Decoding finished after {packet_count} packet(s)");
211                    let FinalizeResult { verify_ok } = decoder.finalize();
212                    if verify_ok == Some(false) {
213                        log::warn!("Verification of decoded data failed");
214                    }
215                    return None;
216                }
217                Err(err) => {
218                    if let SymphoniaError::IoError(err) = &err {
219                        if err.kind() == std::io::ErrorKind::UnexpectedEof {
220                            log::debug!(
221                                "Decoding finished after {packet_count} packet(s) at unexpected EOF"
222                            );
223                            let FinalizeResult { verify_ok } = decoder.finalize();
224                            if verify_ok == Some(false) {
225                                log::warn!("Verification of decoded data failed");
226                            }
227                            return None;
228                        }
229                    }
230
231                    log::warn!(
232                        "Failed to fetch next packet following packet #{packet_count}: {err}"
233                    );
234                    return Some(Err(Box::new(err)));
235                }
236                Ok(Some(packet)) => {
237                    *packet_count += 1;
238                    packet
239                }
240            };
241
242            // If the packet does not belong to the selected track, skip it.
243            let packet_track_id = packet.track_id;
244            if packet_track_id != track_id {
245                log::debug!(
246                    "Skipping packet from other track {packet_track_id} while decoding track {track_id}"
247                );
248                continue;
249            }
250
251            // Decode the packet into audio samples.
252            match decoder.decode(&packet) {
253                Ok(input) => {
254                    let output = input.into();
255                    return Some(Ok(output));
256                }
257                Err(SymphoniaError::DecodeError(err)) => {
258                    // Recoverable error, continue with the next packet.
259                    log::warn!("Failed to decode packet #{packet_count}: {err}");
260                }
261                Err(SymphoniaError::IoError(err)) => {
262                    // Recoverable error, continue with the next packet.
263                    log::warn!("I/O error while decoding packet #{packet_count}: {err}");
264                }
265                Err(err) => {
266                    // All other errors are considered fatal and decoding must be aborted.
267                    return Some(Err(Box::new(err)));
268                }
269            };
270        }
271    }
272}
273
274impl From<GenericAudioBufferRef<'_>> for AudioBuffer {
275    fn from(input: GenericAudioBufferRef<'_>) -> Self {
276        let sample_rate = input.spec().rate() as f32;
277
278        let mut data = Vec::new();
279        input.copy_to_vecs_planar::<f32>(&mut data);
280
281        let channels = data.into_iter().map(ChannelData::from).collect();
282        AudioBuffer::from_channels(channels, sample_rate)
283    }
284}
285
286#[cfg(test)]
287mod tests {
288    use super::*;
289    use std::io::Cursor;
290
291    #[test]
292    fn test_media_decoder() {
293        let input = Cursor::new(vec![0; 32]);
294        let media = MediaDecoder::try_new(input);
295
296        assert!(media.is_err()); // the input was not a valid MIME type
297    }
298
299    #[test]
300    fn test_unsupported_audio_codec_error_includes_codec_id() {
301        let input = std::fs::File::open("samples/sample.webm").unwrap();
302        let media = MediaDecoder::try_new(input);
303
304        let err = match media {
305            Ok(_) => panic!("expected unsupported codec error"),
306            Err(err) => err.to_string(),
307        };
308        assert_eq!(err, "unsupported audio codec: 0x1001");
309    }
310}