Skip to content

Commit 5b03c5d

Browse files
committed
Implement async method decode_audio_data (however still blocking IO)
Fixes #423
1 parent 4c2e9ef commit 5b03c5d

File tree

1 file changed

+75
-2
lines changed

1 file changed

+75
-2
lines changed

src/context/base.rs

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ use crate::param::AudioParamDescriptor;
1212
use crate::periodic_wave::{PeriodicWave, PeriodicWaveOptions};
1313
use 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,48 @@ 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+
fn decode_audio_data<R: std::io::Read + Send + Sync + 'static>(
104+
&self,
105+
input: R,
106+
) -> impl Future<Output = Result<AudioBuffer, Box<dyn std::error::Error + Send + Sync>>>
107+
+ Send
108+
+ 'static {
109+
let sample_rate = self.sample_rate();
110+
async move {
111+
// Set up a media decoder, consume the stream in full and construct a single buffer out of it
112+
let mut buffer = MediaDecoder::try_new(input)?
113+
.collect::<Result<Vec<_>, _>>()?
114+
.into_iter()
115+
.reduce(|mut accum, item| {
116+
accum.extend(&item);
117+
accum
118+
})
119+
// if there are no samples decoded, return an empty buffer
120+
.unwrap_or_else(|| AudioBuffer::from(vec![vec![]], sample_rate));
121+
122+
// resample to desired rate (no-op if already matching)
123+
buffer.resample(sample_rate);
124+
125+
Ok(buffer)
126+
}
127+
}
128+
85129
/// Create an new "in-memory" `AudioBuffer` with the given number of channels,
86130
/// length (i.e. number of samples per channel) and sample rate.
87131
///
@@ -347,6 +391,8 @@ mod tests {
347391

348392
use float_eq::assert_float_eq;
349393

394+
fn require_send_sync_static<T: Send + Sync + 'static>(_: T) {}
395+
350396
#[test]
351397
fn test_decode_audio_data_sync() {
352398
let context = OfflineAudioContext::new(1, 1, 44100.);
@@ -364,6 +410,33 @@ mod tests {
364410
assert!(left_start != right_start);
365411
}
366412

413+
#[test]
414+
fn test_decode_audio_data_future_send_static() {
415+
let context = OfflineAudioContext::new(1, 1, 44100.);
416+
let file = std::fs::File::open("samples/sample.wav").unwrap();
417+
let future = context.decode_audio_data(file);
418+
require_send_sync_static(future);
419+
}
420+
421+
#[test]
422+
fn test_decode_audio_data_async() {
423+
use futures::executor;
424+
let context = OfflineAudioContext::new(1, 1, 44100.);
425+
let file = std::fs::File::open("samples/sample.wav").unwrap();
426+
let future = context.decode_audio_data(file);
427+
let audio_buffer = executor::block_on(future).unwrap();
428+
429+
assert_eq!(audio_buffer.sample_rate(), 44100.);
430+
assert_eq!(audio_buffer.length(), 142_187);
431+
assert_eq!(audio_buffer.number_of_channels(), 2);
432+
assert_float_eq!(audio_buffer.duration(), 3.224, abs_all <= 0.001);
433+
434+
let left_start = &audio_buffer.get_channel_data(0)[0..100];
435+
let right_start = &audio_buffer.get_channel_data(1)[0..100];
436+
// assert distinct two channel data
437+
assert!(left_start != right_start);
438+
}
439+
367440
// #[test]
368441
// disabled: symphonia cannot handle empty WAV-files
369442
#[allow(dead_code)]

0 commit comments

Comments
 (0)