Audio Track in Memory

It is unnecessary to write the audio data to a file. We can keep the audio data in memory and play it directly. To do this, we need to create our own AudioTrack struct implementing the Source trait.

The Source Trait

The Source trait is defined in the rodio crate as follows:

pub trait Source: Iterator
where
    Self::Item: rodio::Sample, 
{
    fn current_frame_len(&self) -> Option<usize>;
    fn channels(&self) -> u16;
    fn sample_rate(&self) -> u32;
    fn total_duration(&self) -> Option<Duration>;
}

To put it simply, the Source trait is an iterator that iterates over the audio samples, with additional information about the audio data. The current_frame_len method returns the number of samples in the current frame. The channels method returns the number of channels. The sample_rate method returns the sample rate. The total_duration method returns the total duration of the audio data.

Wrapping an Iterator

Hence, we can create our own AudioTrack struct by adding some extra fields to existing Iterator structs.

use std::iter::ExactSizeIterator;

pub struct AudioTrack<I: ExactSizeIterator>
where
    I::Item: rodio::Sample,
{
    inner: I,
    config: SupportedStreamConfig,
}

impl<I: ExactSizeIterator> AudioTrack<I>
where
    I::Item: rodio::Sample,
{
    pub fn new(iter: I, config: SupportedStreamConfig) -> Self {
        Self {
            inner: iter,
            config,
        }
    }
}

impl<I: ExactSizeIterator> Iterator for AudioTrack<I>
where
    I::Item: rodio::Sample,
{
    type Item = I::Item;

    fn next(&mut self) -> Option<Self::Item> {
        self.inner.next()
    }
}

The AudioTrack struct has two fields. The inner field is an iterator that iterates over the audio samples. The config field is the configuration of the audio stream. The Iterator trait is implemented for the AudioTrack struct as required by the Source trait.

Next, we need to implement the Source trait for the AudioTrack struct.

impl<I: ExactSizeIterator> Source for AudioTrack<I>
where
    I::Item: rodio::Sample,
{
    fn current_frame_len(&self) -> Option<usize> {
        Some(self.inner.len())
    }

    fn channels(&self) -> u16 {
        self.config.channels()
    }

    fn sample_rate(&self) -> u32 {
        self.config.sample_rate().0
    }

    fn total_duration(&self) -> Option<std::time::Duration> {
        None
    }
}

Recording and Playing

Finally, we can use the AudioTrack struct to record and play audio data without writing to a file.


fn write_input_data<T, U>(data: &[T], writer: &Arc<Mutex<Vec<U>>>)
where
    T: Sample,
    U: Sample + hound::Sample + FromSample<T>,
{
    writer
        .lock()
        .unwrap()
        .extend(data.iter().map(|sample| U::from_sample(*sample)));
}

let writer = Arc::new(Mutex::new(vec![]));
let reader = writer.clone();

... // Create the input stream and record the audio data

let reader = reader.lock().unwrap();
let track = AudioTrack::new(reader.clone().into_iter(), config);

... // Create the output sink

sink.append(track);
sink.sleep_until_end();