Recording Sound with CPAL
Here are some concepts cpal exposes:
- A
Host
provides access to the available audio devices on the system. Some platforms have more than one host available, but every platform supported by CPAL has at least onedefault_host
that is guaranteed to be available. - A
Device
is an audio device that may have any number of input and output streams. - A
Stream
is an open flow of audio data. Input streams allow you to receive audio data, output streams allow you to play audio data. You must choose whichDevice
will run your stream before you can create one. Often, a default device can be retrieved via theHost
.
Creating a Stream
To create a stream, you must first create a Host
and a Device
:
use cpal::{
traits::{DeviceTrait, HostTrait},
HostId
};
let host = cpal::host_from_id(HostId::Asio).expect("failed to initialise ASIO host");
let device = host.default_input_device().expect("failed to find input device");
Since we only need one channel of audio, you need to replace the device's default config with one that only has one channel:
use cpal::{SampleRate, SupportedStreamConfig};
let default_config = device.default_input_config().unwrap();
let config = SupportedStreamConfig::new(
1, // mono
SampleRate(48000), // sample rate
default_config.buffer_size().clone(),
default_config.sample_format(),
);
Now you can create a stream from the device and the config:
use cpal::SampleFormat;
let stream = match config.sample_format() {
SampleFormat::I8 => device.build_input_stream(
&config.into(),
move |data: &[i8], _: &_| {
// react to stream events and read or write stream data here.
},
move |err| {
// react to errors here.
},
None,
),
...
}
.unwrap();
While the stream is running, the selected audio device will periodically call the data callback that was passed to the function.
Creating and running a stream will not block the thread. On modern platforms, the given callback is called by a dedicated, high-priority thread responsible for delivering audio data to the system’s audio device in a timely manner.
Starting and Stopping a Stream
Not all platforms automatically start a stream when it is created. To start a stream, call play()
on it:
use cpal::traits::StreamTrait;
stream.play().expect("failed to play stream");
Some devices support pausing the audio stream. This can be done by calling pause()
on the stream:
stream.pause().expect("failed to pause stream");
Writing a WAV File
This example shows how to write a WAV file from a stream. It uses the hound
crate to write the WAV file.
use cpal::{Sample, FromSample};
use hound::{WavSpec, WavWriter};
use std::{
fs::File,
io::BufWriter,
sync::{Arc, Mutex},
thread,
time::Duration,
};
fn write_input_data<T, U>(data: &[T], writer: &Arc<Mutex<WavWriter<BufWriter<File>>>>)
where
T: Sample,
U: Sample + hound::Sample + FromSample<T>,
{
let mut writer = writer.lock().unwrap();
for &sample in data {
writer.write_sample(sample.to_sample::<U>()).ok();
}
}
let spec = wav_spec_from_config(&config);
let writer = Arc::new(Mutex::new(WavWriter::create("output.wav", spec).unwrap()));
let err_fn = |err| eprintln!("an error occurred on stream: {}", err);
let stream = match config.sample_format() {
SampleFormat::I8 => device.build_input_stream(
&config.into(),
move |data: &[i8], _: &_| {
write_input_data::<i8, f32>(data, &writer);
},
err_fn,
None,
),
...
}
.unwrap();
stream.play().expect("failed to play stream");
thread::sleep(Duration::from_secs(5));
stream.pause().expect("failed to pause stream");