Introduce polymorphic stream constructors

This commit is contained in:
Benjamin Saunders 2020-01-21 22:01:49 -08:00
parent 78df791377
commit 076b814a15
5 changed files with 142 additions and 44 deletions

View File

@ -21,14 +21,24 @@ fn main() -> Result<(), anyhow::Error> {
let err_fn = |err| eprintln!("an error occurred on stream: {}", err); let err_fn = |err| eprintln!("an error occurred on stream: {}", err);
let data_fn = move |data: &mut cpal::Data| match data.sample_format() { let stream = match format.data_type {
cpal::SampleFormat::F32 => write_data::<f32>(data, channels, &mut next_value), cpal::SampleFormat::F32 => device.build_output_stream(
cpal::SampleFormat::I16 => write_data::<i16>(data, channels, &mut next_value), &format.shape(),
cpal::SampleFormat::U16 => write_data::<u16>(data, channels, &mut next_value), move |data: &mut [f32]| write_data(data, channels, &mut next_value),
err_fn,
)?,
cpal::SampleFormat::I16 => device.build_output_stream(
&format.shape(),
move |data: &mut [i16]| write_data(data, channels, &mut next_value),
err_fn,
)?,
cpal::SampleFormat::U16 => device.build_output_stream(
&format.shape(),
move |data: &mut [u16]| write_data(data, channels, &mut next_value),
err_fn,
)?,
}; };
let stream = device.build_output_stream_raw(&format, data_fn, err_fn)?;
stream.play()?; stream.play()?;
std::thread::sleep(std::time::Duration::from_millis(1000)); std::thread::sleep(std::time::Duration::from_millis(1000));
@ -36,11 +46,10 @@ fn main() -> Result<(), anyhow::Error> {
Ok(()) Ok(())
} }
fn write_data<T>(output: &mut cpal::Data, channels: usize, next_sample: &mut dyn FnMut() -> f32) fn write_data<T>(output: &mut [T], channels: usize, next_sample: &mut dyn FnMut() -> f32)
where where
T: cpal::Sample, T: cpal::Sample,
{ {
let output = output.as_slice_mut::<T>().expect("unexpected sample type");
for frame in output.chunks_mut(channels) { for frame in output.chunks_mut(channels) {
let value: T = cpal::Sample::from::<f32>(&next_sample()); let value: T = cpal::Sample::from::<f32>(&next_sample());
for sample in frame.iter_mut() { for sample in frame.iter_mut() {

View File

@ -29,12 +29,11 @@ fn main() -> Result<(), anyhow::Error> {
println!("Using default output device: \"{}\"", output_device.name()?); println!("Using default output device: \"{}\"", output_device.name()?);
// We'll try and use the same format between streams to keep it simple // We'll try and use the same format between streams to keep it simple
let mut format = input_device.default_input_format()?; let shape = input_device.default_input_format()?.shape();
format.data_type = cpal::SampleFormat::F32;
// Create a delay in case the input and output devices aren't synced. // Create a delay in case the input and output devices aren't synced.
let latency_frames = (LATENCY_MS / 1_000.0) * format.sample_rate.0 as f32; let latency_frames = (LATENCY_MS / 1_000.0) * shape.sample_rate.0 as f32;
let latency_samples = latency_frames as usize * format.channels as usize; let latency_samples = latency_frames as usize * shape.channels as usize;
// The buffer to share samples // The buffer to share samples
let ring = RingBuffer::new(latency_samples * 2); let ring = RingBuffer::new(latency_samples * 2);
@ -47,9 +46,8 @@ fn main() -> Result<(), anyhow::Error> {
producer.push(0.0).unwrap(); producer.push(0.0).unwrap();
} }
let input_data_fn = move |data: &cpal::Data| { let input_data_fn = move |data: &[f32]| {
let mut output_fell_behind = false; let mut output_fell_behind = false;
let data = data.as_slice::<f32>().expect("unexpected sample type");
for &sample in data { for &sample in data {
if producer.push(sample).is_err() { if producer.push(sample).is_err() {
output_fell_behind = true; output_fell_behind = true;
@ -60,9 +58,8 @@ fn main() -> Result<(), anyhow::Error> {
} }
}; };
let output_data_fn = move |data: &mut cpal::Data| { let output_data_fn = move |data: &mut [f32]| {
let mut input_fell_behind = None; let mut input_fell_behind = None;
let data = data.as_slice_mut::<f32>().expect("unexpected sample type");
for sample in data { for sample in data {
*sample = match consumer.pop() { *sample = match consumer.pop() {
Ok(s) => s, Ok(s) => s,
@ -81,9 +78,12 @@ fn main() -> Result<(), anyhow::Error> {
}; };
// Build streams. // Build streams.
println!("Attempting to build both streams with `{:?}`.", format); println!(
let input_stream = input_device.build_input_stream_raw(&format, input_data_fn, err_fn)?; "Attempting to build both streams with f32 samples and `{:?}`.",
let output_stream = output_device.build_output_stream_raw(&format, output_data_fn, err_fn)?; shape
);
let input_stream = input_device.build_input_stream(&shape, input_data_fn, err_fn)?;
let output_stream = output_device.build_output_stream(&shape, output_data_fn, err_fn)?;
println!("Successfully built streams."); println!("Successfully built streams.");
// Play the streams. // Play the streams.

View File

@ -40,14 +40,24 @@ fn main() -> Result<(), anyhow::Error> {
eprintln!("an error occurred on stream: {}", err); eprintln!("an error occurred on stream: {}", err);
}; };
let data_fn = move |data: &cpal::Data| match data.sample_format() { let stream = match format.data_type {
cpal::SampleFormat::F32 => write_input_data::<f32, f32>(data, &writer_2), cpal::SampleFormat::F32 => device.build_input_stream(
cpal::SampleFormat::I16 => write_input_data::<i16, i16>(data, &writer_2), &format.shape(),
cpal::SampleFormat::U16 => write_input_data::<u16, i16>(data, &writer_2), move |data| write_input_data::<f32, f32>(data, &writer_2),
err_fn,
)?,
cpal::SampleFormat::I16 => device.build_input_stream(
&format.shape(),
move |data| write_input_data::<i16, i16>(data, &writer_2),
err_fn,
)?,
cpal::SampleFormat::U16 => device.build_input_stream(
&format.shape(),
move |data| write_input_data::<u16, i16>(data, &writer_2),
err_fn,
)?,
}; };
let stream = device.build_input_stream_raw(&format, data_fn, err_fn)?;
stream.play()?; stream.play()?;
// Let recording go for roughly three seconds. // Let recording go for roughly three seconds.
@ -77,12 +87,11 @@ fn wav_spec_from_format(format: &cpal::Format) -> hound::WavSpec {
type WavWriterHandle = Arc<Mutex<Option<hound::WavWriter<BufWriter<File>>>>>; type WavWriterHandle = Arc<Mutex<Option<hound::WavWriter<BufWriter<File>>>>>;
fn write_input_data<T, U>(input: &cpal::Data, writer: &WavWriterHandle) fn write_input_data<T, U>(input: &[T], writer: &WavWriterHandle)
where where
T: cpal::Sample, T: cpal::Sample,
U: cpal::Sample + hound::Sample, U: cpal::Sample + hound::Sample,
{ {
let input = input.as_slice::<T>().expect("unexpected sample format");
if let Ok(mut guard) = writer.try_lock() { if let Ok(mut guard) = writer.try_lock() {
if let Some(writer) = guard.as_mut() { if let Some(writer) = guard.as_mut() {
for &sample in input.iter() { for &sample in input.iter() {

View File

@ -59,10 +59,10 @@
//! use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; //! use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
//! # let host = cpal::default_host(); //! # let host = cpal::default_host();
//! # let device = host.default_output_device().unwrap(); //! # let device = host.default_output_device().unwrap();
//! # let format = device.default_output_format().unwrap(); //! # let shape = device.default_output_format().unwrap().shape();
//! let stream = device.build_output_stream_raw( //! let stream = device.build_output_stream(
//! &format, //! &shape,
//! move |data: &mut Data| { //! move |data: &mut [f32]| {
//! // react to stream events and read or write stream data here. //! // react to stream events and read or write stream data here.
//! }, //! },
//! move |err| { //! move |err| {
@ -93,15 +93,14 @@
//! # let device = host.default_output_device().unwrap(); //! # let device = host.default_output_device().unwrap();
//! # let format = device.default_output_format().unwrap(); //! # let format = device.default_output_format().unwrap();
//! let err_fn = |err| eprintln!("an error occurred on the output audio stream: {}", err); //! let err_fn = |err| eprintln!("an error occurred on the output audio stream: {}", err);
//! let data_fn = move |data: &mut Data| match data.sample_format() { //! let shape = format.shape();
//! SampleFormat::F32 => write_silence::<f32>(data), //! let stream = match format.data_type {
//! SampleFormat::I16 => write_silence::<i16>(data), //! SampleFormat::F32 => device.build_output_stream(&shape, write_silence::<f32>, err_fn),
//! SampleFormat::U16 => write_silence::<u16>(data), //! SampleFormat::I16 => device.build_output_stream(&shape, write_silence::<i16>, err_fn),
//! }; //! SampleFormat::U16 => device.build_output_stream(&shape, write_silence::<u16>, err_fn),
//! let stream = device.build_output_stream_raw(&format, data_fn, err_fn).unwrap(); //! }.unwrap();
//! //!
//! fn write_silence<T: Sample>(data: &mut Data) { //! fn write_silence<T: Sample>(data: &mut [T]) {
//! let data = data.as_slice_mut::<T>().unwrap();
//! for sample in data.iter_mut() { //! for sample in data.iter_mut() {
//! *sample = Sample::from(&0.0); //! *sample = Sample::from(&0.0);
//! } //! }
@ -181,6 +180,38 @@ pub struct Format {
pub data_type: SampleFormat, pub data_type: SampleFormat,
} }
impl Format {
/// Construct a format having a particular `shape` and `data_type`.
pub fn with_shape(shape: &Shape, data_type: SampleFormat) -> Self {
Self {
channels: shape.channels,
sample_rate: shape.sample_rate,
data_type,
}
}
/// Extract aspects of the format independent of the data type.
pub fn shape(&self) -> Shape {
self.clone().into()
}
}
/// The properties of an input or output audio stream excluding its data type.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Shape {
pub channels: ChannelCount,
pub sample_rate: SampleRate,
}
impl From<Format> for Shape {
fn from(x: Format) -> Self {
Self {
channels: x.channels,
sample_rate: x.sample_rate,
}
}
}
/// Describes a range of supported stream formats. /// Describes a range of supported stream formats.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct SupportedFormat { pub struct SupportedFormat {
@ -193,9 +224,10 @@ pub struct SupportedFormat {
pub data_type: SampleFormat, pub data_type: SampleFormat,
} }
/// Represents a buffer of audio data, delivered via a user's stream data callback function. /// A buffer of dynamically typed audio data, passed to raw stream callbacks.
/// ///
/// Input stream callbacks receive `&Data`, while output stream callbacks expect `&mut Data`. /// Raw input stream callbacks receive `&Data`, while raw output stream callbacks expect `&mut
/// Data`.
#[derive(Debug)] #[derive(Debug)]
pub struct Data { pub struct Data {
data: *mut (), data: *mut (),

View File

@ -2,8 +2,8 @@
use { use {
BuildStreamError, Data, DefaultFormatError, DeviceNameError, DevicesError, Format, BuildStreamError, Data, DefaultFormatError, DeviceNameError, DevicesError, Format,
InputDevices, OutputDevices, PauseStreamError, PlayStreamError, StreamError, SupportedFormat, InputDevices, OutputDevices, PauseStreamError, PlayStreamError, Sample, Shape, StreamError,
SupportedFormatsError, SupportedFormat, SupportedFormatsError,
}; };
/// A **Host** provides access to the available audio devices on the system. /// A **Host** provides access to the available audio devices on the system.
@ -113,6 +113,54 @@ pub trait DeviceTrait {
fn default_output_format(&self) -> Result<Format, DefaultFormatError>; fn default_output_format(&self) -> Result<Format, DefaultFormatError>;
/// Create an input stream. /// Create an input stream.
fn build_input_stream<T, D, E>(
&self,
shape: &Shape,
mut data_callback: D,
error_callback: E,
) -> Result<Self::Stream, BuildStreamError>
where
T: Sample,
D: FnMut(&[T]) + Send + 'static,
E: FnMut(StreamError) + Send + 'static,
{
self.build_input_stream_raw(
&Format::with_shape(shape, T::FORMAT),
move |data| {
data_callback(
data.as_slice()
.expect("host supplied incorrect sample type"),
)
},
error_callback,
)
}
/// Create an output stream.
fn build_output_stream<T, D, E>(
&self,
shape: &Shape,
mut data_callback: D,
error_callback: E,
) -> Result<Self::Stream, BuildStreamError>
where
T: Sample,
D: FnMut(&mut [T]) + Send + 'static,
E: FnMut(StreamError) + Send + 'static,
{
self.build_output_stream_raw(
&Format::with_shape(shape, T::FORMAT),
move |data| {
data_callback(
data.as_slice_mut()
.expect("host supplied incorrect sample type"),
)
},
error_callback,
)
}
/// Create a dynamically typed input stream.
fn build_input_stream_raw<D, E>( fn build_input_stream_raw<D, E>(
&self, &self,
format: &Format, format: &Format,
@ -123,7 +171,7 @@ pub trait DeviceTrait {
D: FnMut(&Data) + Send + 'static, D: FnMut(&Data) + Send + 'static,
E: FnMut(StreamError) + Send + 'static; E: FnMut(StreamError) + Send + 'static;
/// Create an output stream. /// Create a dynamically typed output stream.
fn build_output_stream_raw<D, E>( fn build_output_stream_raw<D, E>(
&self, &self,
format: &Format, format: &Format,