web_audio_api/
decoding.rs1use 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 buffer.resample(target_sample_rate);
52
53 Ok(buffer)
54}
55
56struct 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
94pub(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 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 let input = Box::new(MediaInput::new(input));
115
116 let stream = symphonia::core::io::MediaSourceStream::new(input, Default::default());
118
119 let hint = Hint::new();
122
123 let format_opts: FormatOptions = Default::default();
125 let metadata_opts: MetadataOptions = Default::default();
126 let decoder_opts = AudioDecoderOptions::default().verify(true);
128
129 let format =
131 symphonia::default::get_probe().probe(&hint, stream, format_opts, metadata_opts)?;
132
133 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 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 let track = format.tracks().get(*track_index)?;
204 let track_id = track.id;
205
206 loop {
207 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 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 match decoder.decode(&packet) {
253 Ok(input) => {
254 let output = input.into();
255 return Some(Ok(output));
256 }
257 Err(SymphoniaError::DecodeError(err)) => {
258 log::warn!("Failed to decode packet #{packet_count}: {err}");
260 }
261 Err(SymphoniaError::IoError(err)) => {
262 log::warn!("I/O error while decoding packet #{packet_count}: {err}");
264 }
265 Err(err) => {
266 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()); }
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}