@@ -12,6 +12,8 @@ use crate::param::AudioParamDescriptor;
1212use crate :: periodic_wave:: { PeriodicWave , PeriodicWaveOptions } ;
1313use crate :: { node, AudioListener } ;
1414
15+ use std:: future:: Future ;
16+
1517/// The interface representing an audio-processing graph built from audio modules linked together,
1618/// each represented by an `AudioNode`.
1719///
@@ -29,11 +31,11 @@ pub trait BaseAudioContext {
2931 ///
3032 /// In addition to the official spec, the input parameter can be any byte stream (not just an
3133 /// array). This means you can decode audio data from a file, network stream, or in memory
32- /// buffer, and any other [`std::io::Read`] implementer. The data if buffered internally so you
34+ /// buffer, and any other [`std::io::Read`] implementer. The data is buffered internally so you
3335 /// should not wrap the source in a `BufReader`.
3436 ///
3537 /// This function operates synchronously, which may be undesirable on the control thread. The
36- /// example shows how to avoid this. An async version is currently not implemented .
38+ /// example shows how to avoid this. See also the async method [`Self::decode_audio_data`] .
3739 ///
3840 /// # Errors
3941 ///
@@ -82,6 +84,50 @@ pub trait BaseAudioContext {
8284 Ok ( buffer)
8385 }
8486
87+ /// Decode an [`AudioBuffer`] from a given input stream.
88+ ///
89+ /// The current implementation can decode FLAC, Opus, PCM, Vorbis, and Wav.
90+ ///
91+ /// In addition to the official spec, the input parameter can be any byte stream (not just an
92+ /// array). This means you can decode audio data from a file, network stream, or in memory
93+ /// buffer, and any other [`std::io::Read`] implementer. The data is buffered internally so you
94+ /// should not wrap the source in a `BufReader`.
95+ ///
96+ /// Warning, the current implementation still uses blocking IO so it's best to use Tokio's
97+ /// `spawn_blocking` to run the decoding on a thread dedicated to blocking operations. See also
98+ /// the async method [`Self::decode_audio_data_sync`].
99+ ///
100+ /// # Errors
101+ ///
102+ /// This method returns an Error in various cases (IO, mime sniffing, decoding).
103+ // Use of `async fn` in public traits is discouraged as auto trait bounds cannot be specified,
104+ // hence we use `-> impl Future + ..` instead.
105+ fn decode_audio_data < R : std:: io:: Read + Send + Sync + ' static > (
106+ & self ,
107+ input : R ,
108+ ) -> impl Future < Output = Result < AudioBuffer , Box < dyn std:: error:: Error + Send + Sync > > >
109+ + Send
110+ + ' static {
111+ let sample_rate = self . sample_rate ( ) ;
112+ async move {
113+ // Set up a media decoder, consume the stream in full and construct a single buffer out of it
114+ let mut buffer = MediaDecoder :: try_new ( input) ?
115+ . collect :: < Result < Vec < _ > , _ > > ( ) ?
116+ . into_iter ( )
117+ . reduce ( |mut accum, item| {
118+ accum. extend ( & item) ;
119+ accum
120+ } )
121+ // if there are no samples decoded, return an empty buffer
122+ . unwrap_or_else ( || AudioBuffer :: from ( vec ! [ vec![ ] ] , sample_rate) ) ;
123+
124+ // resample to desired rate (no-op if already matching)
125+ buffer. resample ( sample_rate) ;
126+
127+ Ok ( buffer)
128+ }
129+ }
130+
85131 /// Create an new "in-memory" `AudioBuffer` with the given number of channels,
86132 /// length (i.e. number of samples per channel) and sample rate.
87133 ///
@@ -339,3 +385,88 @@ pub trait BaseAudioContext {
339385 }
340386 }
341387}
388+
389+ #[ cfg( test) ]
390+ mod tests {
391+ use super :: * ;
392+ use crate :: context:: OfflineAudioContext ;
393+
394+ use float_eq:: assert_float_eq;
395+
396+ fn require_send_sync_static < T : Send + Sync + ' static > ( _: T ) { }
397+
398+ #[ test]
399+ fn test_decode_audio_data_sync ( ) {
400+ let context = OfflineAudioContext :: new ( 1 , 1 , 44100. ) ;
401+ let file = std:: fs:: File :: open ( "samples/sample.wav" ) . unwrap ( ) ;
402+ let audio_buffer = context. decode_audio_data_sync ( file) . unwrap ( ) ;
403+
404+ assert_eq ! ( audio_buffer. sample_rate( ) , 44100. ) ;
405+ assert_eq ! ( audio_buffer. length( ) , 142_187 ) ;
406+ assert_eq ! ( audio_buffer. number_of_channels( ) , 2 ) ;
407+ assert_float_eq ! ( audio_buffer. duration( ) , 3.224 , abs_all <= 0.001 ) ;
408+
409+ let left_start = & audio_buffer. get_channel_data ( 0 ) [ 0 ..100 ] ;
410+ let right_start = & audio_buffer. get_channel_data ( 1 ) [ 0 ..100 ] ;
411+ // assert distinct two channel data
412+ assert ! ( left_start != right_start) ;
413+ }
414+
415+ #[ test]
416+ fn test_decode_audio_data_future_send_static ( ) {
417+ let context = OfflineAudioContext :: new ( 1 , 1 , 44100. ) ;
418+ let file = std:: fs:: File :: open ( "samples/sample.wav" ) . unwrap ( ) ;
419+ let future = context. decode_audio_data ( file) ;
420+ require_send_sync_static ( future) ;
421+ }
422+
423+ #[ test]
424+ fn test_decode_audio_data_async ( ) {
425+ use futures:: executor;
426+ let context = OfflineAudioContext :: new ( 1 , 1 , 44100. ) ;
427+ let file = std:: fs:: File :: open ( "samples/sample.wav" ) . unwrap ( ) ;
428+ let future = context. decode_audio_data ( file) ;
429+ let audio_buffer = executor:: block_on ( future) . unwrap ( ) ;
430+
431+ assert_eq ! ( audio_buffer. sample_rate( ) , 44100. ) ;
432+ assert_eq ! ( audio_buffer. length( ) , 142_187 ) ;
433+ assert_eq ! ( audio_buffer. number_of_channels( ) , 2 ) ;
434+ assert_float_eq ! ( audio_buffer. duration( ) , 3.224 , abs_all <= 0.001 ) ;
435+
436+ let left_start = & audio_buffer. get_channel_data ( 0 ) [ 0 ..100 ] ;
437+ let right_start = & audio_buffer. get_channel_data ( 1 ) [ 0 ..100 ] ;
438+ // assert distinct two channel data
439+ assert ! ( left_start != right_start) ;
440+ }
441+
442+ // #[test]
443+ // disabled: symphonia cannot handle empty WAV-files
444+ #[ allow( dead_code) ]
445+ fn test_decode_audio_data_empty ( ) {
446+ let context = OfflineAudioContext :: new ( 1 , 1 , 44100. ) ;
447+ let file = std:: fs:: File :: open ( "samples/empty_2c.wav" ) . unwrap ( ) ;
448+ let audio_buffer = context. decode_audio_data_sync ( file) . unwrap ( ) ;
449+ assert_eq ! ( audio_buffer. length( ) , 0 ) ;
450+ }
451+
452+ #[ test]
453+ fn test_decode_audio_data_decoding_error ( ) {
454+ let context = OfflineAudioContext :: new ( 1 , 1 , 44100. ) ;
455+ let file = std:: fs:: File :: open ( "samples/corrupt.wav" ) . unwrap ( ) ;
456+ assert ! ( context. decode_audio_data_sync( file) . is_err( ) ) ;
457+ }
458+
459+ #[ test]
460+ fn test_create_buffer ( ) {
461+ let number_of_channels = 3 ;
462+ let length = 2000 ;
463+ let sample_rate = 96_000. ;
464+
465+ let context = OfflineAudioContext :: new ( 1 , 1 , 44100. ) ;
466+ let buffer = context. create_buffer ( number_of_channels, length, sample_rate) ;
467+
468+ assert_eq ! ( buffer. number_of_channels( ) , 3 ) ;
469+ assert_eq ! ( buffer. length( ) , 2000 ) ;
470+ assert_float_eq ! ( buffer. sample_rate( ) , 96000. , abs_all <= 0. ) ;
471+ }
472+ }
0 commit comments