From 05b62bb1c0d8906aa454070872bd3495c6a42f9c Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Mon, 13 Jan 2020 15:27:41 +0100 Subject: [PATCH 01/14] Remove `UnknownTypeBuffer` in favour of specifying sample type. This is an implementation of the planned changes described in #119. For a quick overview of how the API has changed, check out the updated examples. **TODO:** - [x] Update API. - [x] Update examples. - [ ] Remove `data_type` field from `Format` (see [here](https://github.com/RustAudio/cpal/issues/119#issuecomment-573788380)). - Update backends: - [x] null - [x] ALSA - [ ] ASIO - [ ] WASAPI - [ ] CoreAudio - [ ] Emscripten Closes #119 Closes #260 --- examples/beep.rs | 73 +++--- examples/feedback.rs | 80 +++---- examples/record_wav.rs | 85 +++---- src/host/alsa/mod.rs | 510 ++++++++++++++++++++++++++--------------- src/host/null/mod.rs | 49 ++-- src/lib.rs | 85 ++----- src/platform/mod.rs | 26 ++- src/samples_formats.rs | 27 +-- src/traits.rs | 28 ++- 9 files changed, 543 insertions(+), 420 deletions(-) diff --git a/examples/beep.rs b/examples/beep.rs index 671f1bf..d143fff 100644 --- a/examples/beep.rs +++ b/examples/beep.rs @@ -1,56 +1,61 @@ extern crate anyhow; extern crate cpal; -use cpal::traits::{DeviceTrait, StreamTrait, HostTrait}; +use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; fn main() -> Result<(), anyhow::Error> { let host = cpal::default_host(); - let device = host.default_output_device().expect("failed to find a default output device"); + let device = host + .default_output_device() + .expect("failed to find a default output device"); let format = device.default_output_format()?; let sample_rate = format.sample_rate.0 as f32; - let channels = format.channels; - let mut sample_clock = 0f32; + let channels = format.channels as usize; // Produce a sinusoid of maximum amplitude. + let mut sample_clock = 0f32; let mut next_value = move || { sample_clock = (sample_clock + 1.0) % sample_rate; (sample_clock * 440.0 * 2.0 * 3.141592 / sample_rate).sin() }; - let stream = device.build_output_stream(&format, move |data| { - match data { - cpal::StreamData::Output { buffer: cpal::UnknownTypeOutputBuffer::U16(mut buffer) } => { - for sample in buffer.chunks_mut(channels as usize) { - let value = ((next_value() * 0.5 + 0.5) * std::u16::MAX as f32) as u16; - for out in sample.iter_mut() { - *out = value; - } - } - }, - cpal::StreamData::Output { buffer: cpal::UnknownTypeOutputBuffer::I16(mut buffer) } => { - for sample in buffer.chunks_mut(channels as usize) { - let value = (next_value() * std::i16::MAX as f32) as i16; - for out in sample.iter_mut() { - *out = value; - } - } - }, - cpal::StreamData::Output { buffer: cpal::UnknownTypeOutputBuffer::F32(mut buffer) } => { - for sample in buffer.chunks_mut(channels as usize) { - let value = next_value(); - for out in sample.iter_mut() { - *out = value; - } - } - }, - _ => (), - } - }, move |err| { + let err_fn = |err| { eprintln!("an error occurred on stream: {}", err); - })?; + }; + + let stream = match format.data_type { + cpal::SampleFormat::F32 => device.build_output_stream( + &format, + move |mut data| write_data::(&mut *data, channels, &mut next_value), + err_fn, + ), + cpal::SampleFormat::I16 => device.build_output_stream( + &format, + move |mut data| write_data::(&mut *data, channels, &mut next_value), + err_fn, + ), + cpal::SampleFormat::U16 => device.build_output_stream( + &format, + move |mut data| write_data::(&mut *data, channels, &mut next_value), + err_fn, + ), + }?; + stream.play()?; std::thread::sleep(std::time::Duration::from_millis(1000)); Ok(()) } + +fn write_data(output: &mut [T], channels: usize, next_sample: &mut dyn FnMut() -> f32) +where + T: cpal::Sample, +{ + for frame in output.chunks_mut(channels) { + let value: T = cpal::Sample::from::(&next_sample()); + for sample in frame.iter_mut() { + *sample = value; + } + } +} diff --git a/examples/feedback.rs b/examples/feedback.rs index e6875b2..6697718 100644 --- a/examples/feedback.rs +++ b/examples/feedback.rs @@ -49,50 +49,44 @@ fn main() -> Result<(), anyhow::Error> { // Build streams. println!("Attempting to build both streams with `{:?}`.", format); - let input_stream = input_device.build_input_stream(&format, move |data| { - match data { - cpal::StreamData::Input { - buffer: cpal::UnknownTypeInputBuffer::F32(buffer), - } => { - let mut output_fell_behind = false; - for &sample in buffer.iter() { - if producer.push(sample).is_err() { - output_fell_behind = true; - } + let input_stream = input_device.build_input_stream( + &format, + move |data| { + let mut output_fell_behind = false; + for &sample in data.iter() { + if producer.push(sample).is_err() { + output_fell_behind = true; } - if output_fell_behind { - eprintln!("output stream fell behind: try increasing latency"); - } - }, - _ => panic!("Expected input with f32 data"), - } - }, move |err| { - eprintln!("an error occurred on input stream: {}", err); - })?; - let output_stream = output_device.build_output_stream(&format, move |data| { - match data { - cpal::StreamData::Output { - buffer: cpal::UnknownTypeOutputBuffer::F32(mut buffer), - } => { - let mut input_fell_behind = None; - for sample in buffer.iter_mut() { - *sample = match consumer.pop() { - Ok(s) => s, - Err(err) => { - input_fell_behind = Some(err); - 0.0 - }, - }; - } - if let Some(err) = input_fell_behind { - eprintln!("input stream fell behind: {:?}: try increasing latency", err); - } - }, - _ => panic!("Expected output with f32 data"), - } - }, move |err| { - eprintln!("an error occurred on output stream: {}", err); - })?; + } + if output_fell_behind { + eprintln!("output stream fell behind: try increasing latency"); + } + }, + |err| { + eprintln!("an error occurred on stream: {}", err); + }, + )?; + let output_stream = output_device.build_output_stream( + &format, + move |mut data| { + let mut input_fell_behind = None; + for sample in data.iter_mut() { + *sample = match consumer.pop() { + Ok(s) => s, + Err(err) => { + input_fell_behind = Some(err); + 0.0 + }, + }; + } + if let Some(err) = input_fell_behind { + eprintln!("input stream fell behind: {:?}: try increasing latency", err); + } + }, + move |err| { + eprintln!("an error occurred on output stream: {}", err); + }, + )?; println!("Successfully built streams."); // Play the streams. diff --git a/examples/record_wav.rs b/examples/record_wav.rs index f69c8d0..36e0c86 100644 --- a/examples/record_wav.rs +++ b/examples/record_wav.rs @@ -7,6 +7,9 @@ extern crate cpal; extern crate hound; use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; +use std::sync::{Arc, Mutex}; +use std::fs::File; +use std::io::BufWriter; fn main() -> Result<(), anyhow::Error> { // Use the default host for working with audio devices. @@ -25,55 +28,36 @@ fn main() -> Result<(), anyhow::Error> { const PATH: &'static str = concat!(env!("CARGO_MANIFEST_DIR"), "/recorded.wav"); let spec = wav_spec_from_format(&format); let writer = hound::WavWriter::create(PATH, spec)?; - let writer = std::sync::Arc::new(std::sync::Mutex::new(Some(writer))); + let writer = Arc::new(Mutex::new(Some(writer))); // A flag to indicate that recording is in progress. println!("Begin recording..."); // Run the input stream on a separate thread. let writer_2 = writer.clone(); - let stream = device.build_input_stream(&format, move |data| { - // Otherwise write to the wav writer. - match data { - cpal::StreamData::Input { - buffer: cpal::UnknownTypeInputBuffer::U16(buffer), - } => { - if let Ok(mut guard) = writer_2.try_lock() { - if let Some(writer) = guard.as_mut() { - for sample in buffer.iter() { - let sample = cpal::Sample::to_i16(sample); - writer.write_sample(sample).ok(); - } - } - } - }, - cpal::StreamData::Input { - buffer: cpal::UnknownTypeInputBuffer::I16(buffer), - } => { - if let Ok(mut guard) = writer_2.try_lock() { - if let Some(writer) = guard.as_mut() { - for &sample in buffer.iter() { - writer.write_sample(sample).ok(); - } - } - } - }, - cpal::StreamData::Input { - buffer: cpal::UnknownTypeInputBuffer::F32(buffer), - } => { - if let Ok(mut guard) = writer_2.try_lock() { - if let Some(writer) = guard.as_mut() { - for &sample in buffer.iter() { - writer.write_sample(sample).ok(); - } - } - } - }, - _ => (), - } - }, move |err| { + + let err_fn = move |err| { eprintln!("an error occurred on stream: {}", err); - })?; + }; + + let stream = match format.data_type { + cpal::SampleFormat::F32 => device.build_input_stream( + &format, + move |mut data| write_input_data::(&*data, &writer_2), + err_fn, + ), + cpal::SampleFormat::I16 => device.build_input_stream( + &format, + move |mut data| write_input_data::(&*data, &writer_2), + err_fn, + ), + cpal::SampleFormat::U16 => device.build_input_stream( + &format, + move |mut data| write_input_data::(&*data, &writer_2), + err_fn, + ), + }?; + stream.play()?; // Let recording go for roughly three seconds. @@ -100,3 +84,20 @@ fn wav_spec_from_format(format: &cpal::Format) -> hound::WavSpec { sample_format: sample_format(format.data_type), } } + +type WavWriterHandle = Arc>>>>; + +fn write_input_data(input: &[T], writer: &WavWriterHandle) +where + T: cpal::Sample, + U: cpal::Sample + hound::Sample, +{ + if let Ok(mut guard) = writer.try_lock() { + if let Some(writer) = guard.as_mut() { + for &sample in input.iter() { + let sample: U = cpal::Sample::from(&sample); + writer.write_sample(sample).ok(); + } + } + } +} diff --git a/src/host/alsa/mod.rs b/src/host/alsa/mod.rs index 7068f26..f1724aa 100644 --- a/src/host/alsa/mod.rs +++ b/src/host/alsa/mod.rs @@ -1,29 +1,30 @@ extern crate alsa_sys as alsa; extern crate libc; -use std::{cmp, ffi, io, mem, ptr}; +use crate::{ + BackendSpecificError, + BuildStreamError, + ChannelCount, + DefaultFormatError, + DeviceNameError, + DevicesError, + Format, + InputData, + OutputData, + PauseStreamError, + PlayStreamError, + Sample, + SampleFormat, + SampleRate, + StreamError, + SupportedFormat, + SupportedFormatsError, +}; +use std::{cmp, ffi, io, ptr}; use std::sync::Arc; use std::thread::{self, JoinHandle}; use std::vec::IntoIter as VecIntoIter; - -use BackendSpecificError; -use BuildStreamError; -use ChannelCount; -use DefaultFormatError; -use DeviceNameError; -use DevicesError; -use Format; -use PauseStreamError; -use PlayStreamError; -use SampleFormat; -use SampleRate; -use StreamData; -use StreamError; -use SupportedFormat; -use SupportedFormatsError; use traits::{DeviceTrait, HostTrait, StreamTrait}; -use UnknownTypeInputBuffer; -use UnknownTypeOutputBuffer; pub use self::enumerate::{default_input_device, default_output_device, Devices}; @@ -89,12 +90,40 @@ impl DeviceTrait for Device { Device::default_output_format(self) } - fn build_input_stream(&self, format: &Format, data_callback: D, error_callback: E) -> Result where D: FnMut(StreamData) + Send + 'static, E: FnMut(StreamError) + Send + 'static { - Ok(Stream::new(Arc::new(self.build_stream_inner(format, alsa::SND_PCM_STREAM_CAPTURE)?), data_callback, error_callback)) + fn build_input_stream( + &self, + format: &Format, + data_callback: D, + error_callback: E, + ) -> Result + where + T: Sample, + D: FnMut(InputData) + Send + 'static, + E: FnMut(StreamError) + Send + 'static, + { + // TODO: Consider removing `data_type` field from `Format` and removing this. + assert_eq!(format.data_type, T::FORMAT, "sample format mismatch"); + let stream_inner = self.build_stream_inner(format, alsa::SND_PCM_STREAM_CAPTURE)?; + let stream = Stream::new_input(Arc::new(stream_inner), data_callback, error_callback); + Ok(stream) } - fn build_output_stream(&self, format: &Format, data_callback: D, error_callback: E) -> Result where D: FnMut(StreamData) + Send + 'static, E: FnMut(StreamError) + Send + 'static { - Ok(Stream::new(Arc::new(self.build_stream_inner(format, alsa::SND_PCM_STREAM_PLAYBACK)?), data_callback, error_callback)) + fn build_output_stream( + &self, + format: &Format, + data_callback: D, + error_callback: E, + ) -> Result + where + T: Sample, + D: FnMut(OutputData) + Send + 'static, + E: FnMut(StreamError) + Send + 'static, + { + // TODO: Consider removing `data_type` field from `Format` and removing this. + assert_eq!(format.data_type, T::FORMAT, "sample format mismatch"); + let stream_inner = self.build_stream_inner(format, alsa::SND_PCM_STREAM_PLAYBACK)?; + let stream = Stream::new_output(Arc::new(stream_inner), data_callback, error_callback); + Ok(stream) } } @@ -147,7 +176,11 @@ impl Drop for TriggerReceiver { pub struct Device(String); impl Device { - fn build_stream_inner(&self, format: &Format, stream_type: alsa::snd_pcm_stream_t) -> Result { + fn build_stream_inner( + &self, + format: &Format, + stream_type: alsa::snd_pcm_stream_t, + ) -> Result { let name = ffi::CString::new(self.0.clone()).expect("unable to clone device"); let handle = unsafe { @@ -510,6 +543,7 @@ unsafe impl Send for StreamInner {} unsafe impl Sync for StreamInner {} +#[derive(Debug, Eq, PartialEq)] enum StreamType { Input, Output } pub struct Stream { @@ -524,183 +558,291 @@ pub struct Stream { trigger: TriggerSender, } -/// The inner body of the audio processing thread. Takes the polymorphic -/// callback to avoid generating too much generic code. -fn stream_worker(rx: TriggerReceiver, - stream: &StreamInner, - data_callback: &mut (dyn FnMut(StreamData) + Send + 'static), - error_callback: &mut (dyn FnMut(StreamError) + Send + 'static)) { - let mut descriptors = Vec::new(); - let mut buffer = Vec::new(); - loop { - descriptors.clear(); - // Add the self-pipe for signaling termination. - descriptors.push(libc::pollfd { - fd: rx.0, - events: libc::POLLIN, - revents: 0, - }); +#[derive(Default)] +struct StreamWorkerContext { + descriptors: Vec, + buffer: Vec, +} - // Add ALSA polling fds. - descriptors.reserve(stream.num_descriptors); - let len = descriptors.len(); - let filled = unsafe { - alsa::snd_pcm_poll_descriptors( +fn input_stream_worker( + rx: TriggerReceiver, + stream: &StreamInner, + data_callback: &mut (dyn FnMut(InputData) + Send + 'static), + error_callback: &mut (dyn FnMut(StreamError) + Send + 'static), +) where + T: Sample, +{ + let mut ctxt = StreamWorkerContext::default(); + loop { + match poll_descriptors_and_prepare_buffer(&rx, stream, &mut ctxt, error_callback) { + PollDescriptorsFlow::Continue => continue, + PollDescriptorsFlow::Return => return, + PollDescriptorsFlow::Ready { available_frames, stream_type } => { + assert_eq!( + stream_type, + StreamType::Input, + "expected input stream, but polling descriptors indicated output", + ); + process_input::( + stream, + &mut ctxt.buffer, + available_frames, + data_callback, + error_callback, + ); + } + } + } +} + +fn output_stream_worker( + rx: TriggerReceiver, + stream: &StreamInner, + data_callback: &mut (dyn FnMut(OutputData) + Send + 'static), + error_callback: &mut (dyn FnMut(StreamError) + Send + 'static), +) where + T: Sample, +{ + let mut ctxt = StreamWorkerContext::default(); + loop { + match poll_descriptors_and_prepare_buffer(&rx, stream, &mut ctxt, error_callback) { + PollDescriptorsFlow::Continue => continue, + PollDescriptorsFlow::Return => return, + PollDescriptorsFlow::Ready { available_frames, stream_type } => { + assert_eq!( + stream_type, + StreamType::Output, + "expected output stream, but polling descriptors indicated input", + ); + process_output::( + stream, + &mut ctxt.buffer, + available_frames, + data_callback, + error_callback, + ); + } + } + } +} + +enum PollDescriptorsFlow { + Continue, + Return, + Ready { + stream_type: StreamType, + available_frames: usize, + } +} + +// This block is shared between both input and output stream worker functions. +fn poll_descriptors_and_prepare_buffer( + rx: &TriggerReceiver, + stream: &StreamInner, + ctxt: &mut StreamWorkerContext, + error_callback: &mut (dyn FnMut(StreamError) + Send + 'static), +) -> PollDescriptorsFlow { + let StreamWorkerContext { + ref mut descriptors, + ref mut buffer, + } = *ctxt; + + descriptors.clear(); + + // Add the self-pipe for signaling termination. + descriptors.push(libc::pollfd { + fd: rx.0, + events: libc::POLLIN, + revents: 0, + }); + + // Add ALSA polling fds. + descriptors.reserve(stream.num_descriptors); + let len = descriptors.len(); + let filled = unsafe { + alsa::snd_pcm_poll_descriptors( + stream.channel, + descriptors[len..].as_mut_ptr(), + stream.num_descriptors as libc::c_uint, + ) + }; + debug_assert_eq!(filled, stream.num_descriptors as libc::c_int); + unsafe { + descriptors.set_len(len + stream.num_descriptors); + } + + let res = unsafe { + // Don't timeout, wait forever. + libc::poll(descriptors.as_mut_ptr(), descriptors.len() as libc::nfds_t, -1) + }; + if res < 0 { + let description = format!("`libc::poll()` failed: {}", io::Error::last_os_error()); + error_callback(BackendSpecificError { description }.into()); + return PollDescriptorsFlow::Continue; + } else if res == 0 { + let description = String::from("`libc::poll()` spuriously returned"); + error_callback(BackendSpecificError { description }.into()); + return PollDescriptorsFlow::Continue; + } + + if descriptors[0].revents != 0 { + // The stream has been requested to be destroyed. + rx.clear_pipe(); + return PollDescriptorsFlow::Return; + } + + let stream_type = match check_for_pollout_or_pollin(stream, descriptors[1..].as_mut_ptr()) { + Ok(Some(ty)) => ty, + Ok(None) => { + // Nothing to process, poll again + return PollDescriptorsFlow::Continue; + }, + Err(err) => { + error_callback(err.into()); + return PollDescriptorsFlow::Continue; + } + }; + // Get the number of available samples for reading/writing. + let available_samples = match get_available_samples(stream) { + Ok(n) => n, + Err(err) => { + let description = format!("Failed to query the number of available samples: {}", err); + error_callback(BackendSpecificError { description }.into()); + return PollDescriptorsFlow::Continue; + } + }; + + // Only go on if there is at least `stream.period_len` samples. + if available_samples < stream.period_len { + return PollDescriptorsFlow::Continue; + } + + // Prepare the data buffer. + let buffer_size = stream.sample_format.sample_size() * available_samples; + buffer.resize(buffer_size, 0u8); + let available_frames = available_samples / stream.num_channels as usize; + + PollDescriptorsFlow::Ready { + stream_type, + available_frames, + } +} + +// Read input data from ALSA and deliver it to the user. +fn process_input( + stream: &StreamInner, + buffer: &mut [u8], + available_frames: usize, + data_callback: &mut (dyn FnMut(InputData) + Send + 'static), + error_callback: &mut dyn FnMut(StreamError), +) where + T: Sample, +{ + let result = unsafe { + alsa::snd_pcm_readi( + stream.channel, + buffer.as_mut_ptr() as *mut _, + available_frames as alsa::snd_pcm_uframes_t, + ) + }; + if let Err(err) = check_errors(result as _) { + let description = format!("`snd_pcm_readi` failed: {}", err); + error_callback(BackendSpecificError { description }.into()); + return; + } + let buffer = unsafe { cast_input_buffer::(buffer) }; + let input_data = InputData { buffer }; + data_callback(input_data); +} + +// Request data from the user's function and write it via ALSA. +// +// Returns `true` +fn process_output( + stream: &StreamInner, + buffer: &mut [u8], + available_frames: usize, + data_callback: &mut (dyn FnMut(OutputData) + Send + 'static), + error_callback: &mut dyn FnMut(StreamError), +) where + T: Sample, +{ + { + // We're now sure that we're ready to write data. + let buffer = unsafe { cast_output_buffer::(buffer) }; + let output_data = OutputData { buffer }; + data_callback(output_data); + } + loop { + let result = unsafe { + alsa::snd_pcm_writei( stream.channel, - descriptors[len..].as_mut_ptr(), - stream.num_descriptors as libc::c_uint, + buffer.as_ptr() as *const _, + available_frames as alsa::snd_pcm_uframes_t, ) }; - debug_assert_eq!(filled, stream.num_descriptors as libc::c_int); - unsafe { - descriptors.set_len(len + stream.num_descriptors); - } - - let res = unsafe { - // Don't timeout, wait forever. - libc::poll(descriptors.as_mut_ptr(), descriptors.len() as libc::nfds_t, -1) - }; - if res < 0 { - let description = format!("`libc::poll()` failed: {}", io::Error::last_os_error()); + if result == -libc::EPIPE as i64 { + // buffer underrun + // TODO: Notify the user of this. + unsafe { alsa::snd_pcm_recover(stream.channel, result as i32, 0) }; + } else if let Err(err) = check_errors(result as _) { + let description = format!("`snd_pcm_writei` failed: {}", err); error_callback(BackendSpecificError { description }.into()); continue; - } else if res == 0 { - let description = String::from("`libc::poll()` spuriously returned"); + } else if result as usize != available_frames { + let description = format!( + "unexpected number of frames written: expected {}, \ + result {} (this should never happen)", + available_frames, + result, + ); error_callback(BackendSpecificError { description }.into()); continue; - } - - if descriptors[0].revents != 0 { - // The stream has been requested to be destroyed. - rx.clear_pipe(); - return; - } - - let stream_type = match check_for_pollout_or_pollin(stream, descriptors[1..].as_mut_ptr()) { - Ok(Some(ty)) => ty, - Ok(None) => { - // Nothing to process, poll again - continue; - }, - Err(err) => { - error_callback(err.into()); - continue; - } - }; - // Get the number of available samples for reading/writing. - let available_samples = match get_available_samples(stream) { - Ok(n) => n, - Err(err) => { - let description = format!("Failed to query the number of available samples: {}", err); - error_callback(BackendSpecificError { description }.into()); - continue; - } - }; - - // Only go on if there is at least `stream.period_len` samples. - if available_samples < stream.period_len { - continue; - } - - // Prepare the data buffer. - let buffer_size = stream.sample_format.sample_size() * available_samples; - buffer.resize(buffer_size, 0u8); - let available_frames = available_samples / stream.num_channels as usize; - - match stream_type { - StreamType::Input => { - let result = unsafe { - alsa::snd_pcm_readi( - stream.channel, - buffer.as_mut_ptr() as *mut _, - available_frames as alsa::snd_pcm_uframes_t, - ) - }; - if let Err(err) = check_errors(result as _) { - let description = format!("`snd_pcm_readi` failed: {}", err); - error_callback(BackendSpecificError { description }.into()); - continue; - } - - let input_buffer = match stream.sample_format { - SampleFormat::I16 => UnknownTypeInputBuffer::I16(::InputBuffer { - buffer: unsafe { cast_input_buffer(&mut buffer) }, - }), - SampleFormat::U16 => UnknownTypeInputBuffer::U16(::InputBuffer { - buffer: unsafe { cast_input_buffer(&mut buffer) }, - }), - SampleFormat::F32 => UnknownTypeInputBuffer::F32(::InputBuffer { - buffer: unsafe { cast_input_buffer(&mut buffer) }, - }), - }; - let stream_data = StreamData::Input { - buffer: input_buffer, - }; - data_callback(stream_data); - }, - StreamType::Output => { - { - // We're now sure that we're ready to write data. - let output_buffer = match stream.sample_format { - SampleFormat::I16 => UnknownTypeOutputBuffer::I16(::OutputBuffer { - buffer: unsafe { cast_output_buffer(&mut buffer) }, - }), - SampleFormat::U16 => UnknownTypeOutputBuffer::U16(::OutputBuffer { - buffer: unsafe { cast_output_buffer(&mut buffer) }, - }), - SampleFormat::F32 => UnknownTypeOutputBuffer::F32(::OutputBuffer { - buffer: unsafe { cast_output_buffer(&mut buffer) }, - }), - }; - - let stream_data = StreamData::Output { - buffer: output_buffer, - }; - data_callback(stream_data); - } - loop { - let result = unsafe { - alsa::snd_pcm_writei( - stream.channel, - buffer.as_ptr() as *const _, - available_frames as alsa::snd_pcm_uframes_t, - ) - }; - - if result == -libc::EPIPE as i64 { - // buffer underrun - // TODO: Notify the user of this. - unsafe { alsa::snd_pcm_recover(stream.channel, result as i32, 0) }; - } else if let Err(err) = check_errors(result as _) { - let description = format!("`snd_pcm_writei` failed: {}", err); - error_callback(BackendSpecificError { description }.into()); - continue; - } else if result as usize != available_frames { - let description = format!( - "unexpected number of frames written: expected {}, \ - result {} (this should never happen)", - available_frames, - result, - ); - error_callback(BackendSpecificError { description }.into()); - continue; - } else { - break; - } - } - }, + } else { + break; } } } impl Stream { - fn new(inner: Arc, mut data_callback: D, mut error_callback: E) -> Stream - where D: FnMut(StreamData) + Send + 'static, E: FnMut(StreamError) + Send + 'static { + fn new_input( + inner: Arc, + mut data_callback: D, + mut error_callback: E, + ) -> Stream + where + T: Sample, + D: FnMut(InputData) + Send + 'static, + E: FnMut(StreamError) + Send + 'static, + { let (tx, rx) = trigger(); // Clone the handle for passing into worker thread. let stream = inner.clone(); let thread = thread::spawn(move || { - stream_worker(rx, &*stream, &mut data_callback, &mut error_callback); + input_stream_worker::(rx, &*stream, &mut data_callback, &mut error_callback); + }); + Stream { + thread: Some(thread), + inner, + trigger: tx, + } + } + + fn new_output( + inner: Arc, + mut data_callback: D, + mut error_callback: E, + ) -> Stream + where + T: Sample, + D: FnMut(OutputData) + Send + 'static, + E: FnMut(StreamError) + Send + 'static, + { + let (tx, rx) = trigger(); + // Clone the handle for passing into worker thread. + let stream = inner.clone(); + let thread = thread::spawn(move || { + output_stream_worker::(rx, &*stream, &mut data_callback, &mut error_callback); }); Stream { thread: Some(thread), diff --git a/src/host/null/mod.rs b/src/host/null/mod.rs index 84ce071..09a19c8 100644 --- a/src/host/null/mod.rs +++ b/src/host/null/mod.rs @@ -1,14 +1,17 @@ -use BuildStreamError; -use DefaultFormatError; -use DevicesError; -use DeviceNameError; -use Format; -use PauseStreamError; -use PlayStreamError; -use StreamData; -use StreamError; -use SupportedFormatsError; -use SupportedFormat; +use crate::{ + BuildStreamError, + DefaultFormatError, + DevicesError, + DeviceNameError, + Format, + InputData, + OutputData, + PauseStreamError, + PlayStreamError, + StreamError, + SupportedFormatsError, + SupportedFormat, +}; use traits::{DeviceTrait, HostTrait, StreamTrait}; #[derive(Default)] @@ -68,14 +71,30 @@ impl DeviceTrait for Device { unimplemented!() } - fn build_input_stream(&self, _format: &Format, _data_callback: D, _error_callback: E) -> Result - where D: FnMut(StreamData) + Send + 'static, E: FnMut(StreamError) + Send + 'static { + fn build_input_stream( + &self, + _format: &Format, + _data_callback: D, + _error_callback: E, + ) -> Result + where + D: FnMut(InputData) + Send + 'static, + E: FnMut(StreamError) + Send + 'static, + { unimplemented!() } /// Create an output stream. - fn build_output_stream(&self, _format: &Format, _data_callback: D, _error_callback: E) -> Result - where D: FnMut(StreamData) + Send + 'static, E: FnMut(StreamError) + Send + 'static{ + fn build_output_stream( + &self, + _format: &Format, + _data_callback: D, + _error_callback: E, + ) -> Result + where + D: FnMut(OutputData) + Send + 'static, + E: FnMut(StreamError) + Send + 'static, + { unimplemented!() } } diff --git a/src/lib.rs b/src/lib.rs index 99227f9..e22e3b2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -143,6 +143,7 @@ //! # let format = device.default_output_format().unwrap(); //! # let stream = device.build_output_stream(&format, move |_data| {}, move |_err| {}).unwrap(); //! stream.pause().unwrap(); +//! ``` #![recursion_limit = "512"] @@ -202,25 +203,14 @@ pub struct SupportedFormat { pub data_type: SampleFormat, } -/// Stream data passed to the `EventLoop::run` callback. -#[derive(Debug)] -pub enum StreamData<'a> { - Input { - buffer: UnknownTypeInputBuffer<'a>, - }, - Output { - buffer: UnknownTypeOutputBuffer<'a>, - }, -} - /// Represents a buffer containing audio data that may be read. /// /// This struct implements the `Deref` trait targeting `[T]`. Therefore this buffer can be read the /// same way as reading from a `Vec` or any other kind of Rust array. // TODO: explain audio stuff in general -// TODO: remove the wrapper and just use slices in next major version +// TODO: Consider making this an `enum` with `Interleaved` and `NonInterleaved` variants. #[derive(Debug)] -pub struct InputBuffer<'a, T: 'a> +pub struct InputData<'a, T: 'a> where T: Sample, { @@ -233,42 +223,16 @@ where /// This struct implements the `Deref` and `DerefMut` traits to `[T]`. Therefore writing to this /// buffer is done in the same way as writing to a `Vec` or any other kind of Rust array. // TODO: explain audio stuff in general -// TODO: remove the wrapper and just use slices +// TODO: Consider making this an `enum` with `Interleaved` and `NonInterleaved` variants. #[must_use] #[derive(Debug)] -pub struct OutputBuffer<'a, T: 'a> +pub struct OutputData<'a, T: 'a> where T: Sample, { buffer: &'a mut [T], } -/// This is the struct that is provided to you by cpal when you want to read samples from a buffer. -/// -/// Since the type of data is only known at runtime, you have to read the right buffer. -#[derive(Debug)] -pub enum UnknownTypeInputBuffer<'a> { - /// Samples whose format is `u16`. - U16(InputBuffer<'a, u16>), - /// Samples whose format is `i16`. - I16(InputBuffer<'a, i16>), - /// Samples whose format is `f32`. - F32(InputBuffer<'a, f32>), -} - -/// This is the struct that is provided to you by cpal when you want to write samples to a buffer. -/// -/// Since the type of data is only known at runtime, you have to fill the right buffer. -#[derive(Debug)] -pub enum UnknownTypeOutputBuffer<'a> { - /// Samples whose format is `u16`. - U16(OutputBuffer<'a, u16>), - /// Samples whose format is `i16`. - I16(OutputBuffer<'a, i16>), - /// Samples whose format is `f32`. - F32(OutputBuffer<'a, f32>), -} - impl SupportedFormat { /// Turns this `SupportedFormat` into a `Format` corresponding to the maximum samples rate. #[inline] @@ -352,8 +316,9 @@ impl SupportedFormat { } } -impl<'a, T> Deref for InputBuffer<'a, T> - where T: Sample +impl<'a, T> Deref for InputData<'a, T> +where + T: Sample, { type Target = [T]; @@ -363,8 +328,9 @@ impl<'a, T> Deref for InputBuffer<'a, T> } } -impl<'a, T> Deref for OutputBuffer<'a, T> - where T: Sample +impl<'a, T> Deref for OutputData<'a, T> +where + T: Sample, { type Target = [T]; @@ -374,8 +340,9 @@ impl<'a, T> Deref for OutputBuffer<'a, T> } } -impl<'a, T> DerefMut for OutputBuffer<'a, T> - where T: Sample +impl<'a, T> DerefMut for OutputData<'a, T> +where + T: Sample, { #[inline] fn deref_mut(&mut self) -> &mut [T] { @@ -383,30 +350,6 @@ impl<'a, T> DerefMut for OutputBuffer<'a, T> } } -impl<'a> UnknownTypeInputBuffer<'a> { - /// Returns the length of the buffer in number of samples. - #[inline] - pub fn len(&self) -> usize { - match self { - &UnknownTypeInputBuffer::U16(ref buf) => buf.len(), - &UnknownTypeInputBuffer::I16(ref buf) => buf.len(), - &UnknownTypeInputBuffer::F32(ref buf) => buf.len(), - } - } -} - -impl<'a> UnknownTypeOutputBuffer<'a> { - /// Returns the length of the buffer in number of samples. - #[inline] - pub fn len(&self) -> usize { - match self { - &UnknownTypeOutputBuffer::U16(ref buf) => buf.len(), - &UnknownTypeOutputBuffer::I16(ref buf) => buf.len(), - &UnknownTypeOutputBuffer::F32(ref buf) => buf.len(), - } - } -} - impl From for SupportedFormat { #[inline] fn from(format: Format) -> SupportedFormat { diff --git a/src/platform/mod.rs b/src/platform/mod.rs index f7956d9..dfa2632 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -255,8 +255,17 @@ macro_rules! impl_platform_host { } } - fn build_input_stream(&self, format: &crate::Format, data_callback: D, error_callback: E) -> Result - where D: FnMut(crate::StreamData) + Send + 'static, E: FnMut(crate::StreamError) + Send + 'static { + fn build_input_stream( + &self, + format: &crate::Format, + data_callback: D, + error_callback: E, + ) -> Result + where + T: crate::Sample, + D: FnMut(crate::InputData) + Send + 'static, + E: FnMut(crate::StreamError) + Send + 'static, + { match self.0 { $( DeviceInner::$HostVariant(ref d) => d.build_input_stream(format, data_callback, error_callback) @@ -266,8 +275,17 @@ macro_rules! impl_platform_host { } } - fn build_output_stream(&self, format: &crate::Format, data_callback: D, error_callback: E) -> Result - where D: FnMut(crate::StreamData) + Send + 'static, E: FnMut(crate::StreamError) + Send + 'static { + fn build_output_stream( + &self, + format: &crate::Format, + data_callback: D, + error_callback: E, + ) -> Result + where + T: crate::Sample, + D: FnMut(crate::OutputData) + Send + 'static, + E: FnMut(crate::StreamError) + Send + 'static, + { match self.0 { $( DeviceInner::$HostVariant(ref d) => d.build_output_stream(format, data_callback, error_callback) diff --git a/src/samples_formats.rs b/src/samples_formats.rs index 4c40176..2f5014f 100644 --- a/src/samples_formats.rs +++ b/src/samples_formats.rs @@ -21,20 +21,12 @@ impl SampleFormat { &SampleFormat::F32 => mem::size_of::(), } } - - /// Deprecated. Use `sample_size` instead. - #[inline] - #[deprecated] - pub fn get_sample_size(&self) -> usize { - self.sample_size() - } } /// Trait for containers that contain PCM data. pub unsafe trait Sample: Copy + Clone { - /// Returns the `SampleFormat` corresponding to this data type. - // TODO: rename to `format()`. Requires a breaking change. - fn get_format() -> SampleFormat; + /// The `SampleFormat` corresponding to this data type. + const FORMAT: SampleFormat; /// Turns the sample into its equivalent as a floating-point. fn to_f32(&self) -> f32; @@ -49,10 +41,7 @@ pub unsafe trait Sample: Copy + Clone { } unsafe impl Sample for u16 { - #[inline] - fn get_format() -> SampleFormat { - SampleFormat::U16 - } + const FORMAT: SampleFormat = SampleFormat::U16; #[inline] fn to_f32(&self) -> f32 { @@ -82,10 +71,7 @@ unsafe impl Sample for u16 { } unsafe impl Sample for i16 { - #[inline] - fn get_format() -> SampleFormat { - SampleFormat::I16 - } + const FORMAT: SampleFormat = SampleFormat::I16; #[inline] fn to_f32(&self) -> f32 { @@ -119,10 +105,7 @@ unsafe impl Sample for i16 { } unsafe impl Sample for f32 { - #[inline] - fn get_format() -> SampleFormat { - SampleFormat::F32 - } + const FORMAT: SampleFormat = SampleFormat::F32; #[inline] fn to_f32(&self) -> f32 { diff --git a/src/traits.rs b/src/traits.rs index ed492d1..c00e294 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -6,11 +6,13 @@ use { DeviceNameError, DevicesError, Format, + InputData, InputDevices, + OutputData, OutputDevices, PauseStreamError, PlayStreamError, - StreamData, + Sample, StreamError, SupportedFormat, SupportedFormatsError, @@ -118,12 +120,28 @@ pub trait DeviceTrait { fn default_output_format(&self) -> Result; /// Create an input stream. - fn build_input_stream(&self, format: &Format, data_callback: D, error_callback: E) -> Result - where D: FnMut(StreamData) + Send + 'static, E: FnMut(StreamError) + Send + 'static; + fn build_input_stream( + &self, + format: &Format, + data_callback: D, + error_callback: E, + ) -> Result + where + T: Sample, + D: FnMut(InputData) + Send + 'static, + E: FnMut(StreamError) + Send + 'static; /// Create an output stream. - fn build_output_stream(&self, format: &Format, data_callback: D, error_callback: E) -> Result - where D: FnMut(StreamData) + Send + 'static, E: FnMut(StreamError) + Send + 'static; + fn build_output_stream( + &self, + format: &Format, + data_callback: D, + error_callback: E, + ) -> Result + where + T: Sample, + D: FnMut(OutputData) + Send + 'static, + E: FnMut(StreamError) + Send + 'static; } /// A stream created from `Device`, with methods to control playback. From 6fc2185c99307aa7dbf9e37d1bdbc2b64819826c Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Tue, 14 Jan 2020 21:56:38 +0100 Subject: [PATCH 02/14] Update emscripten backend for removal of `UnknownTypeBuffer` --- src/host/emscripten/mod.rs | 55 ++++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/src/host/emscripten/mod.rs b/src/host/emscripten/mod.rs index eb51b58..176671a 100644 --- a/src/host/emscripten/mod.rs +++ b/src/host/emscripten/mod.rs @@ -7,18 +7,22 @@ use stdweb::unstable::TryInto; use stdweb::web::TypedArray; use stdweb::web::set_timeout; -use BuildStreamError; -use DefaultFormatError; -use DeviceNameError; -use DevicesError; -use Format; -use PauseStreamError; -use PlayStreamError; -use SupportedFormatsError; -use StreamData; -use StreamError; -use SupportedFormat; -use UnknownTypeOutputBuffer; +use crate::{ + BuildStreamError, + DefaultFormatError, + DeviceNameError, + DevicesError, + Format, + InputData, + OutputData, + PauseStreamError, + PlayStreamError, + Sample, + SampleFormat, + StreamError, + SupportedFormat, + SupportedFormatsError, +}; use traits::{DeviceTrait, HostTrait, StreamTrait}; // The emscripten backend currently works by instantiating an `AudioContext` object per `Stream`. @@ -156,29 +160,33 @@ impl DeviceTrait for Device { Device::default_output_format(self) } - fn build_input_stream( + fn build_input_stream( &self, _format: &Format, _data_callback: D, _error_callback: E, ) -> Result where - D: FnMut(StreamData) + Send + 'static, + T: Sample, + D: FnMut(InputData) + Send + 'static, E: FnMut(StreamError) + Send + 'static, { unimplemented!() } - fn build_output_stream( + fn build_output_stream( &self, _format: &Format, data_callback: D, error_callback: E, ) -> Result where - D: FnMut(StreamData) + Send + 'static, + T: Sample, + D: FnMut(OutputData) + Send + 'static, E: FnMut(StreamError) + Send + 'static, { + assert_eq!(T::FORMAT, SampleFormat::F32, "emscripten backend only supports `f32` data"); + // Create the stream. let audio_ctxt_ref = js!(return new AudioContext()).into_reference().unwrap(); let stream = Stream { audio_ctxt_ref }; @@ -193,7 +201,7 @@ impl DeviceTrait for Device { // // See also: The call to `set_timeout` at the end of the `audio_callback_fn` which creates // the loop. - set_timeout(|| audio_callback_fn::(user_data_ptr as *mut c_void), 10); + set_timeout(|| audio_callback_fn::(user_data_ptr as *mut c_void), 10); Ok(stream) } @@ -215,9 +223,10 @@ impl StreamTrait for Stream { // The first argument of the callback function (a `void*`) is a casted pointer to `self` // and to the `callback` parameter that was passed to `run`. -fn audio_callback_fn(user_data_ptr: *mut c_void) +fn audio_callback_fn(user_data_ptr: *mut c_void) where - D: FnMut(StreamData) + Send + 'static, + T: Sample, + D: FnMut(OutputData) + Send + 'static, E: FnMut(StreamError) + Send + 'static, { unsafe { @@ -227,11 +236,11 @@ where let audio_ctxt = &stream.audio_ctxt_ref; // TODO: We should be re-using a buffer. - let mut temporary_buffer = vec![0.0; 44100 * 2 / 3]; + let mut temporary_buffer: Vec<_> = (0..44100 * 2 / 3).map(|_| T::from(&0.0)).collect(); { - let buffer = UnknownTypeOutputBuffer::F32(::OutputBuffer { buffer: &mut temporary_buffer }); - let data = StreamData::Output { buffer: buffer }; + let buffer = &mut temporary_buffer; + let data = OutputData { buffer }; data_cb(data); } @@ -272,7 +281,7 @@ where // TODO: handle latency better ; right now we just use setInterval with the amount of sound // data that is in each buffer ; this is obviously bad, and also the schedule is too tight // and there may be underflows - set_timeout(|| audio_callback_fn::(user_data_ptr), 330); + set_timeout(|| audio_callback_fn::(user_data_ptr), 330); } } From 5a619877f97694569b6bf818c7236a9db157b2e3 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Tue, 14 Jan 2020 22:41:44 +0100 Subject: [PATCH 03/14] Update docs for removal of UnknownTypeBuffer --- examples/record_wav.rs | 6 ++--- src/lib.rs | 58 +++++++++++++++++------------------------- 2 files changed, 27 insertions(+), 37 deletions(-) diff --git a/examples/record_wav.rs b/examples/record_wav.rs index 36e0c86..fb48286 100644 --- a/examples/record_wav.rs +++ b/examples/record_wav.rs @@ -43,17 +43,17 @@ fn main() -> Result<(), anyhow::Error> { let stream = match format.data_type { cpal::SampleFormat::F32 => device.build_input_stream( &format, - move |mut data| write_input_data::(&*data, &writer_2), + move |data| write_input_data::(&*data, &writer_2), err_fn, ), cpal::SampleFormat::I16 => device.build_input_stream( &format, - move |mut data| write_input_data::(&*data, &writer_2), + move |data| write_input_data::(&*data, &writer_2), err_fn, ), cpal::SampleFormat::U16 => device.build_input_stream( &format, - move |mut data| write_input_data::(&*data, &writer_2), + move |data| write_input_data::(&*data, &writer_2), err_fn, ), }?; diff --git a/src/lib.rs b/src/lib.rs index e22e3b2..a80f3a7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -55,13 +55,14 @@ //! Now that we have everything for the stream, we are ready to create it from our selected device: //! //! ```no_run +//! use cpal::OutputData; //! use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; //! # let host = cpal::default_host(); //! # let device = host.default_output_device().unwrap(); //! # let format = device.default_output_format().unwrap(); //! let stream = device.build_output_stream( //! &format, -//! move |data| { +//! move |data: OutputData| { //! // react to stream events and read or write stream data here. //! }, //! move |err| { @@ -71,10 +72,9 @@ //! ``` //! //! While the stream is running, the selected audio device will periodically call the data callback -//! that was passed to the function. The callback is passed an instance of type `StreamData` that -//! represents the data that must be read from or written to. The inner `UnknownTypeOutputBuffer` -//! can be one of `I16`, `U16` or `F32` depending on the format that was passed to -//! `build_output_stream`. +//! that was passed to the function. The callback is passed an instance of either `InputData` or +//! `OutputData` depending on whether the stream is an input stream or output stream +//! respectively. Type `T` represents the desired sample format type. Supported format types //! //! > **Note**: 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 @@ -88,37 +88,23 @@ //! In this example, we simply fill the given output buffer with zeroes. //! //! ```no_run -//! use cpal::{StreamData, UnknownTypeOutputBuffer}; +//! use cpal::{OutputData, Sample, SampleFormat}; //! use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; //! # let host = cpal::default_host(); //! # let device = host.default_output_device().unwrap(); //! # let format = device.default_output_format().unwrap(); -//! let stream = device.build_output_stream( -//! &format, -//! move |data| { -//! match data { -//! StreamData::Output { buffer: UnknownTypeOutputBuffer::U16(mut buffer) } => { -//! for elem in buffer.iter_mut() { -//! *elem = u16::max_value() / 2; -//! } -//! }, -//! StreamData::Output { buffer: UnknownTypeOutputBuffer::I16(mut buffer) } => { -//! for elem in buffer.iter_mut() { -//! *elem = 0; -//! } -//! }, -//! StreamData::Output { buffer: UnknownTypeOutputBuffer::F32(mut buffer) } => { -//! for elem in buffer.iter_mut() { -//! *elem = 0.0; -//! } -//! }, -//! _ => (), -//! } -//! }, -//! move |err| { -//! eprintln!("an error occurred on the output audio stream: {}", err); -//! }, -//! ); +//! let err_fn = move |err| eprintln!("an error occurred on the output audio stream: {}", err); +//! let stream = match format.data_type { +//! SampleFormat::F32 => device.build_output_stream(&format, write_silence::, err_fn), +//! SampleFormat::I16 => device.build_output_stream(&format, write_silence::, err_fn), +//! SampleFormat::U16 => device.build_output_stream(&format, write_silence::, err_fn), +//! }; +//! +//! fn write_silence(mut data: OutputData) { +//! for sample in data.iter_mut() { +//! *sample = Sample::from(&0.0); +//! } +//! } //! ``` //! //! Not all platforms automatically run the stream upon creation. To ensure the stream has started, @@ -129,7 +115,9 @@ //! # let host = cpal::default_host(); //! # let device = host.default_output_device().unwrap(); //! # let format = device.default_output_format().unwrap(); -//! # let stream = device.build_output_stream(&format, move |_data| {}, move |_err| {}).unwrap(); +//! # let data_fn = move |_data: cpal::OutputData| {}; +//! # let err_fn = move |_err| {}; +//! # let stream = device.build_output_stream(&format, data_fn, err_fn).unwrap(); //! stream.play().unwrap(); //! ``` //! @@ -141,7 +129,9 @@ //! # let host = cpal::default_host(); //! # let device = host.default_output_device().unwrap(); //! # let format = device.default_output_format().unwrap(); -//! # let stream = device.build_output_stream(&format, move |_data| {}, move |_err| {}).unwrap(); +//! # let data_fn = move |_data: cpal::OutputData| {}; +//! # let err_fn = move |_err| {}; +//! # let stream = device.build_output_stream(&format, data_fn, err_fn).unwrap(); //! stream.pause().unwrap(); //! ``` From b5bfb8d4223a261fac342d9fc2677b9b07d68c02 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Thu, 16 Jan 2020 01:17:01 +1100 Subject: [PATCH 04/14] Update WASAPI backend for removal of `UnknownTypeBuffer` --- src/host/wasapi/device.rs | 52 ++--- src/host/wasapi/stream.rs | 396 ++++++++++++++++++++++---------------- 2 files changed, 257 insertions(+), 191 deletions(-) diff --git a/src/host/wasapi/device.rs b/src/host/wasapi/device.rs index 50dd239..db5c154 100644 --- a/src/host/wasapi/device.rs +++ b/src/host/wasapi/device.rs @@ -1,3 +1,18 @@ +use crate::{ + BackendSpecificError, + DefaultFormatError, + DeviceNameError, + DevicesError, + Format, + InputData, + OutputData, + Sample, + SampleFormat, + SampleRate, + SupportedFormat, + SupportedFormatsError, + COMMON_SAMPLE_RATES, +}; use std; use std::ffi::OsString; use std::fmt; @@ -9,17 +24,6 @@ use std::ptr; use std::slice; use std::sync::{Arc, Mutex, MutexGuard, atomic::Ordering}; -use BackendSpecificError; -use DefaultFormatError; -use DeviceNameError; -use DevicesError; -use Format; -use SampleFormat; -use SampleRate; -use SupportedFormat; -use SupportedFormatsError; -use COMMON_SAMPLE_RATES; - use super::check_result; use super::check_result_backend_specific; use super::com; @@ -54,7 +58,7 @@ use super::{ stream::{AudioClientFlow, Stream, StreamInner}, winapi::um::synchapi, }; -use crate::{traits::DeviceTrait, BuildStreamError, StreamData, StreamError}; +use crate::{traits::DeviceTrait, BuildStreamError, StreamError}; pub type SupportedInputFormats = std::vec::IntoIter; pub type SupportedOutputFormats = std::vec::IntoIter; @@ -102,38 +106,34 @@ impl DeviceTrait for Device { Device::default_output_format(self) } - fn build_input_stream( + fn build_input_stream( &self, format: &Format, data_callback: D, error_callback: E, ) -> Result where - D: FnMut(StreamData) + Send + 'static, + T: Sample, + D: FnMut(InputData) + Send + 'static, E: FnMut(StreamError) + Send + 'static, { - Ok(Stream::new( - self.build_input_stream_inner(format)?, - data_callback, - error_callback, - )) + let stream_inner = self.build_input_stream_inner(format)?; + Ok(Stream::new_input(stream_inner, data_callback, error_callback)) } - fn build_output_stream( + fn build_output_stream( &self, format: &Format, data_callback: D, error_callback: E, ) -> Result where - D: FnMut(StreamData) + Send + 'static, + T: Sample, + D: FnMut(OutputData) + Send + 'static, E: FnMut(StreamError) + Send + 'static, { - Ok(Stream::new( - self.build_output_stream_inner(format)?, - data_callback, - error_callback, - )) + let stream_inner = self.build_output_stream_inner(format)?; + Ok(Stream::new_output(stream_inner, data_callback, error_callback)) } } diff --git a/src/host/wasapi/stream.rs b/src/host/wasapi/stream.rs index e020846..1c49a13 100644 --- a/src/host/wasapi/stream.rs +++ b/src/host/wasapi/stream.rs @@ -1,3 +1,19 @@ +use crate::{ + BackendSpecificError, + InputData, + OutputData, + PauseStreamError, + PlayStreamError, + Sample, + SampleFormat, + StreamError, +}; +use crate::traits::StreamTrait; +use std::mem; +use std::ptr; +use std::slice; +use std::sync::mpsc::{channel, Receiver, Sender}; +use std::thread::{self, JoinHandle}; use super::check_result; use super::winapi::shared::basetsd::UINT32; use super::winapi::shared::minwindef::{BYTE, FALSE, WORD}; @@ -7,23 +23,6 @@ use super::winapi::um::synchapi; use super::winapi::um::winbase; use super::winapi::um::winnt; -use std::mem; -use std::ptr; -use std::slice; -use std::sync::mpsc::{channel, Receiver, Sender}; - -use crate::traits::StreamTrait; -use std::thread::{self, JoinHandle}; - -use BackendSpecificError; -use PauseStreamError; -use PlayStreamError; -use SampleFormat; -use StreamData; -use StreamError; -use UnknownTypeInputBuffer; -use UnknownTypeOutputBuffer; - pub struct Stream { /// The high-priority audio processing thread calling callbacks. /// Option used for moving out in destructor. @@ -86,13 +85,14 @@ pub struct StreamInner { } impl Stream { - pub(crate) fn new( + pub(crate) fn new_input( stream_inner: StreamInner, mut data_callback: D, mut error_callback: E, ) -> Stream where - D: FnMut(StreamData) + Send + 'static, + T: Sample, + D: FnMut(InputData) + Send + 'static, E: FnMut(StreamError) + Send + 'static, { let pending_scheduled_event = @@ -106,7 +106,37 @@ impl Stream { }; let thread = - thread::spawn(move || run_inner(run_context, &mut data_callback, &mut error_callback)); + thread::spawn(move || run_input(run_context, &mut data_callback, &mut error_callback)); + + Stream { + thread: Some(thread), + commands: tx, + pending_scheduled_event, + } + } + + pub(crate) fn new_output( + stream_inner: StreamInner, + mut data_callback: D, + mut error_callback: E, + ) -> Stream + where + T: Sample, + D: FnMut(OutputData) + Send + 'static, + E: FnMut(StreamError) + Send + 'static, + { + let pending_scheduled_event = + unsafe { synchapi::CreateEventA(ptr::null_mut(), 0, 0, ptr::null()) }; + let (tx, rx) = channel(); + + let run_context = RunContext { + handles: vec![pending_scheduled_event, stream_inner.event], + stream: stream_inner, + commands: rx, + }; + + let thread = + thread::spawn(move || run_output(run_context, &mut data_callback, &mut error_callback)); Stream { thread: Some(thread), @@ -255,160 +285,196 @@ fn stream_error_from_hresult(hresult: winnt::HRESULT) -> Result<(), StreamError> Ok(()) } -fn run_inner( - mut run_context: RunContext, - data_callback: &mut dyn FnMut(StreamData), +fn run_input( + mut run_ctxt: RunContext, + data_callback: &mut dyn FnMut(InputData), error_callback: &mut dyn FnMut(StreamError), -) { +) where + T: Sample, +{ + loop { + match process_commands_and_await_signal(&mut run_ctxt, error_callback) { + Some(ControlFlow::Break) => break, + Some(ControlFlow::Continue) => continue, + None => (), + } + let capture_client = match run_ctxt.stream.client_flow { + AudioClientFlow::Capture { capture_client } => capture_client, + _ => unreachable!(), + }; + match process_input(&mut run_ctxt.stream, capture_client, data_callback, error_callback) { + ControlFlow::Break => break, + ControlFlow::Continue => continue, + } + } +} + +fn run_output( + mut run_ctxt: RunContext, + data_callback: &mut dyn FnMut(OutputData), + error_callback: &mut dyn FnMut(StreamError), +) where + T: Sample, +{ + loop { + match process_commands_and_await_signal(&mut run_ctxt, error_callback) { + Some(ControlFlow::Break) => break, + Some(ControlFlow::Continue) => continue, + None => (), + } + let render_client = match run_ctxt.stream.client_flow { + AudioClientFlow::Render { render_client } => render_client, + _ => unreachable!(), + }; + match process_output(&mut run_ctxt.stream, render_client, data_callback, error_callback) { + ControlFlow::Break => break, + ControlFlow::Continue => continue, + } + } +} + +enum ControlFlow { + Break, + Continue, +} + +fn process_commands_and_await_signal( + run_context: &mut RunContext, + error_callback: &mut dyn FnMut(StreamError), +) -> Option { + // Process queued commands. + match process_commands(run_context) { + Ok(true) => (), + Ok(false) => return Some(ControlFlow::Break), + Err(err) => { + error_callback(err); + return Some(ControlFlow::Break); + } + }; + + // Wait for any of the handles to be signalled. + let handle_idx = match wait_for_handle_signal(&run_context.handles) { + Ok(idx) => idx, + Err(err) => { + error_callback(err.into()); + return Some(ControlFlow::Break); + } + }; + + // If `handle_idx` is 0, then it's `pending_scheduled_event` that was signalled in + // order for us to pick up the pending commands. Otherwise, a stream needs data. + if handle_idx == 0 { + return Some(ControlFlow::Continue); + } + + None +} + +// The loop for processing pending input data. +fn process_input( + stream: &StreamInner, + capture_client: *mut audioclient::IAudioCaptureClient, + data_callback: &mut dyn FnMut(InputData), + error_callback: &mut dyn FnMut(StreamError), +) -> ControlFlow +where + T: Sample, +{ + let mut frames_available = 0; unsafe { - 'stream_loop: loop { - // Process queued commands. - match process_commands(&mut run_context) { - Ok(true) => (), - Ok(false) => break, - Err(err) => { - error_callback(err); - break 'stream_loop; - } - }; + // Get the available data in the shared buffer. + let mut buffer: *mut BYTE = mem::uninitialized(); + let mut flags = mem::uninitialized(); + loop { + let hresult = (*capture_client).GetNextPacketSize(&mut frames_available); + if let Err(err) = stream_error_from_hresult(hresult) { + error_callback(err); + return ControlFlow::Break; + } + if frames_available == 0 { + return ControlFlow::Continue; + } + let hresult = (*capture_client).GetBuffer( + &mut buffer, + &mut frames_available, + &mut flags, + ptr::null_mut(), + ptr::null_mut(), + ); - // Wait for any of the handles to be signalled. - let handle_idx = match wait_for_handle_signal(&run_context.handles) { - Ok(idx) => idx, - Err(err) => { - error_callback(err.into()); - break 'stream_loop; - } - }; - - // If `handle_idx` is 0, then it's `pending_scheduled_event` that was signalled in - // order for us to pick up the pending commands. Otherwise, a stream needs data. - if handle_idx == 0 { + // TODO: Can this happen? + if hresult == AUDCLNT_S_BUFFER_EMPTY { continue; + } else if let Err(err) = stream_error_from_hresult(hresult) { + error_callback(err); + return ControlFlow::Break; } - let stream = &mut run_context.stream; - let sample_size = stream.sample_format.sample_size(); + debug_assert!(!buffer.is_null()); - // Obtaining a pointer to the buffer. - match stream.client_flow { - AudioClientFlow::Capture { capture_client } => { - let mut frames_available = 0; - // Get the available data in the shared buffer. - let mut buffer: *mut BYTE = mem::uninitialized(); - let mut flags = mem::uninitialized(); - loop { - let hresult = (*capture_client).GetNextPacketSize(&mut frames_available); - if let Err(err) = stream_error_from_hresult(hresult) { - error_callback(err); - break 'stream_loop; - } - if frames_available == 0 { - break; - } - let hresult = (*capture_client).GetBuffer( - &mut buffer, - &mut frames_available, - &mut flags, - ptr::null_mut(), - ptr::null_mut(), - ); + let buffer_len = frames_available as usize + * stream.bytes_per_frame as usize + / mem::size_of::(); - // TODO: Can this happen? - if hresult == AUDCLNT_S_BUFFER_EMPTY { - continue; - } else if let Err(err) = stream_error_from_hresult(hresult) { - error_callback(err); - break 'stream_loop; - } - - debug_assert!(!buffer.is_null()); - - let buffer_len = frames_available as usize - * stream.bytes_per_frame as usize - / sample_size; - - // Simplify the capture callback sample format branches. - macro_rules! capture_callback { - ($T:ty, $Variant:ident) => {{ - let buffer_data = buffer as *mut _ as *const $T; - let slice = slice::from_raw_parts(buffer_data, buffer_len); - let unknown_buffer = - UnknownTypeInputBuffer::$Variant(::InputBuffer { - buffer: slice, - }); - let data = StreamData::Input { - buffer: unknown_buffer, - }; - data_callback(data); - // Release the buffer. - let hresult = (*capture_client).ReleaseBuffer(frames_available); - if let Err(err) = stream_error_from_hresult(hresult) { - error_callback(err); - break 'stream_loop; - } - }}; - } - - match stream.sample_format { - SampleFormat::F32 => capture_callback!(f32, F32), - SampleFormat::I16 => capture_callback!(i16, I16), - SampleFormat::U16 => capture_callback!(u16, U16), - } - } - } - - AudioClientFlow::Render { render_client } => { - // The number of frames available for writing. - let frames_available = match get_available_frames(&stream) { - Ok(0) => continue, // TODO: Can this happen? - Ok(n) => n, - Err(err) => { - error_callback(err); - break 'stream_loop; - } - }; - - let mut buffer: *mut BYTE = mem::uninitialized(); - let hresult = - (*render_client).GetBuffer(frames_available, &mut buffer as *mut *mut _); - - if let Err(err) = stream_error_from_hresult(hresult) { - error_callback(err); - break 'stream_loop; - } - - debug_assert!(!buffer.is_null()); - let buffer_len = - frames_available as usize * stream.bytes_per_frame as usize / sample_size; - - // Simplify the render callback sample format branches. - macro_rules! render_callback { - ($T:ty, $Variant:ident) => {{ - let buffer_data = buffer as *mut $T; - let slice = slice::from_raw_parts_mut(buffer_data, buffer_len); - let unknown_buffer = - UnknownTypeOutputBuffer::$Variant(::OutputBuffer { buffer: slice }); - let data = StreamData::Output { - buffer: unknown_buffer, - }; - data_callback(data); - let hresult = - (*render_client).ReleaseBuffer(frames_available as u32, 0); - if let Err(err) = stream_error_from_hresult(hresult) { - error_callback(err); - break 'stream_loop; - } - }}; - } - - match stream.sample_format { - SampleFormat::F32 => render_callback!(f32, F32), - SampleFormat::I16 => render_callback!(i16, I16), - SampleFormat::U16 => render_callback!(u16, U16), - } - } + // Simplify the capture callback sample format branches. + let buffer_data = buffer as *mut _ as *const T; + let slice = slice::from_raw_parts(buffer_data, buffer_len); + let input_data = InputData { buffer: slice }; + data_callback(input_data); + // Release the buffer. + let hresult = (*capture_client).ReleaseBuffer(frames_available); + if let Err(err) = stream_error_from_hresult(hresult) { + error_callback(err); + return ControlFlow::Break; } } } } + +// The loop for writing output data. +fn process_output( + stream: &StreamInner, + render_client: *mut audioclient::IAudioRenderClient, + data_callback: &mut dyn FnMut(OutputData), + error_callback: &mut dyn FnMut(StreamError), +) -> ControlFlow +where + T: Sample, +{ + // The number of frames available for writing. + let frames_available = match get_available_frames(&stream) { + Ok(0) => return ControlFlow::Continue, // TODO: Can this happen? + Ok(n) => n, + Err(err) => { + error_callback(err); + return ControlFlow::Break; + } + }; + + unsafe { + let mut buffer: *mut BYTE = mem::uninitialized(); + let hresult = + (*render_client).GetBuffer(frames_available, &mut buffer as *mut *mut _); + + if let Err(err) = stream_error_from_hresult(hresult) { + error_callback(err); + return ControlFlow::Break; + } + + debug_assert!(!buffer.is_null()); + let buffer_len = + frames_available as usize * stream.bytes_per_frame as usize / mem::size_of::(); + + let buffer_data = buffer as *mut T; + let slice = slice::from_raw_parts_mut(buffer_data, buffer_len); + let output_data = OutputData { buffer: slice }; + data_callback(output_data); + let hresult = (*render_client).ReleaseBuffer(frames_available as u32, 0); + if let Err(err) = stream_error_from_hresult(hresult) { + error_callback(err); + return ControlFlow::Break; + } + } + + ControlFlow::Continue +} From 9e832c6eb378639fa8731e8da6dd6390972c47c8 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Thu, 16 Jan 2020 02:46:01 +1100 Subject: [PATCH 05/14] Update ASIO backend for removal of `UnknownBufferType` --- src/host/asio/mod.rs | 30 ++++++-- src/host/asio/stream.rs | 147 +++++++++++++++++++--------------------- 2 files changed, 94 insertions(+), 83 deletions(-) diff --git a/src/host/asio/mod.rs b/src/host/asio/mod.rs index f4ac830..b667b6d 100644 --- a/src/host/asio/mod.rs +++ b/src/host/asio/mod.rs @@ -1,17 +1,19 @@ extern crate asio_sys as sys; extern crate parking_lot; -use { +use crate::{ BuildStreamError, DefaultFormatError, DeviceNameError, DevicesError, Format, + InputData, + OutputData, PauseStreamError, PlayStreamError, - SupportedFormatsError, - StreamData, + Sample, StreamError, + SupportedFormatsError, }; use traits::{ DeviceTrait, @@ -89,16 +91,30 @@ impl DeviceTrait for Device { Device::default_output_format(self) } - fn build_input_stream(&self, format: &Format, data_callback: D, error_callback: E) -> Result + fn build_input_stream( + &self, + format: &Format, + data_callback: D, + error_callback: E, + ) -> Result where - D: FnMut(StreamData) + Send + 'static, E: FnMut(StreamError) + Send + 'static + T: Sample, + D: FnMut(InputData) + Send + 'static, + E: FnMut(StreamError) + Send + 'static { Device::build_input_stream(self, format, data_callback, error_callback) } - fn build_output_stream(&self, format: &Format, data_callback: D, error_callback: E) -> Result + fn build_output_stream( + &self, + format: &Format, + data_callback: D, + error_callback: E, + ) -> Result where - D: FnMut(StreamData) + Send + 'static, E: FnMut(StreamError) + Send + 'static + T: Sample, + D: FnMut(OutputData) + Send + 'static, + E: FnMut(StreamError) + Send + 'static { Device::build_output_stream(self, format, data_callback, error_callback) } diff --git a/src/host/asio/stream.rs b/src/host/asio/stream.rs index fb18e70..0ad3710 100644 --- a/src/host/asio/stream.rs +++ b/src/host/asio/stream.rs @@ -10,12 +10,12 @@ use super::parking_lot::Mutex; use BackendSpecificError; use BuildStreamError; use Format; +use InputData; +use OutputData; use PauseStreamError; use PlayStreamError; +use Sample; use SampleFormat; -use StreamData; -use UnknownTypeInputBuffer; -use UnknownTypeOutputBuffer; use StreamError; /// Sample types whose constant silent value is known. @@ -23,14 +23,11 @@ trait Silence { const SILENCE: Self; } -/// Constraints on the interleaved sample buffer format required by the CPAL API. -trait InterleavedSample: Clone + Copy + Silence { - fn unknown_type_input_buffer(&[Self]) -> UnknownTypeInputBuffer; - fn unknown_type_output_buffer(&mut [Self]) -> UnknownTypeOutputBuffer; -} - /// Constraints on the ASIO sample types. -trait AsioSample: Clone + Copy + Silence + std::ops::Add {} +trait AsioSample: Clone + Copy + Silence + std::ops::Add { + fn to_cpal_sample(&self) -> T; + fn from_cpal_sample(&T) -> Self; +} // Used to keep track of whether or not the current current asio stream buffer requires // being silencing before summing audio. @@ -61,16 +58,18 @@ impl Stream { } impl Device { - pub fn build_input_stream( + pub fn build_input_stream( &self, format: &Format, mut data_callback: D, _error_callback: E, ) -> Result where - D: FnMut(StreamData) + Send + 'static, - E: FnMut(StreamError) + Send + 'static + T: Sample, + D: FnMut(InputData) + Send + 'static, + E: FnMut(StreamError) + Send + 'static, { + assert_eq!(format.data_type, T::FORMAT, "sample type does not match `format.data_type`"); let stream_type = self.driver.input_data_type().map_err(build_stream_err)?; // Ensure that the desired sample type is supported. @@ -109,20 +108,18 @@ impl Device { /// 1. Write from the ASIO buffer to the interleaved CPAL buffer. /// 2. Deliver the CPAL buffer to the user callback. - unsafe fn process_input_callback( + unsafe fn process_input_callback( callback: &mut D, interleaved: &mut [u8], asio_stream: &sys::AsioStream, buffer_index: usize, from_endianness: F, - to_cpal_sample: G, ) where A: AsioSample, - B: InterleavedSample, - D: FnMut(StreamData) + Send + 'static, + B: Sample, + D: FnMut(InputData) + Send + 'static, F: Fn(A) -> A, - G: Fn(A) -> B, { // 1. Write the ASIO channels to the CPAL buffer. let interleaved: &mut [B] = cast_slice_mut(interleaved); @@ -130,35 +127,32 @@ impl Device { for ch_ix in 0..n_channels { let asio_channel = asio_channel_slice::(asio_stream, buffer_index, ch_ix); for (frame, s_asio) in interleaved.chunks_mut(n_channels).zip(asio_channel) { - frame[ch_ix] = to_cpal_sample(from_endianness(*s_asio)); + frame[ch_ix] = from_endianness(*s_asio).to_cpal_sample(); } } // 2. Deliver the interleaved buffer to the callback. - callback( - StreamData::Input { buffer: B::unknown_type_input_buffer(interleaved) }, - ); + let data = InputData { buffer: interleaved }; + callback(data); } match (&stream_type, data_type) { (&sys::AsioSampleType::ASIOSTInt16LSB, SampleFormat::I16) => { - process_input_callback::( + process_input_callback::( &mut data_callback, &mut interleaved, asio_stream, buffer_index as usize, from_le, - std::convert::identity::, ); } (&sys::AsioSampleType::ASIOSTInt16MSB, SampleFormat::I16) => { - process_input_callback::( + process_input_callback::( &mut data_callback, &mut interleaved, asio_stream, buffer_index as usize, from_be, - std::convert::identity::, ); } @@ -166,13 +160,12 @@ impl Device { // trait for the `to_le` and `to_be` methods, but this does not support floats. (&sys::AsioSampleType::ASIOSTFloat32LSB, SampleFormat::F32) | (&sys::AsioSampleType::ASIOSTFloat32MSB, SampleFormat::F32) => { - process_input_callback::( + process_input_callback::( &mut data_callback, &mut interleaved, asio_stream, buffer_index as usize, std::convert::identity::, - std::convert::identity::, ); } @@ -180,36 +173,33 @@ impl Device { // `process_output_callback` function above by removing the unnecessary sample // conversion function. (&sys::AsioSampleType::ASIOSTInt32LSB, SampleFormat::I16) => { - process_input_callback::( + process_input_callback::( &mut data_callback, &mut interleaved, asio_stream, buffer_index as usize, from_le, - |s| (s >> 16) as i16, ); } (&sys::AsioSampleType::ASIOSTInt32MSB, SampleFormat::I16) => { - process_input_callback::( + process_input_callback::( &mut data_callback, &mut interleaved, asio_stream, buffer_index as usize, from_be, - |s| (s >> 16) as i16, ); } // TODO: Handle endianness conversion for floats? We currently use the `PrimInt` // trait for the `to_le` and `to_be` methods, but this does not support floats. (&sys::AsioSampleType::ASIOSTFloat64LSB, SampleFormat::F32) | (&sys::AsioSampleType::ASIOSTFloat64MSB, SampleFormat::F32) => { - process_input_callback::( + process_input_callback::( &mut data_callback, &mut interleaved, asio_stream, buffer_index as usize, std::convert::identity::, - |s| s as f32, ); } @@ -234,16 +224,18 @@ impl Device { }) } - pub fn build_output_stream( + pub fn build_output_stream( &self, format: &Format, mut data_callback: D, _error_callback: E, ) -> Result where - D: FnMut(StreamData) + Send + 'static, + T: Sample, + D: FnMut(OutputData) + Send + 'static, E: FnMut(StreamError) + Send + 'static, { + assert_eq!(format.data_type, T::FORMAT, "sample type does not match `format.data_type`"); let stream_type = self.driver.output_data_type().map_err(build_stream_err)?; // Ensure that the desired sample type is supported. @@ -305,26 +297,23 @@ impl Device { /// 2. If required, silence the ASIO buffer. /// 3. Finally, write the interleaved data to the non-interleaved ASIO buffer, /// performing endianness conversions as necessary. - unsafe fn process_output_callback( + unsafe fn process_output_callback( callback: &mut D, interleaved: &mut [u8], silence_asio_buffer: bool, asio_stream: &sys::AsioStream, buffer_index: usize, - to_asio_sample: F, - to_endianness: G, + to_endianness: F, ) where - A: InterleavedSample, + A: Sample, B: AsioSample, - D: FnMut(StreamData) + Send + 'static, - F: Fn(A) -> B, - G: Fn(B) -> B, + D: FnMut(OutputData) + Send + 'static, + F: Fn(B) -> B, { // 1. Render interleaved buffer from callback. let interleaved: &mut [A] = cast_slice_mut(interleaved); - let buffer = A::unknown_type_output_buffer(interleaved); - callback(StreamData::Output { buffer }); + callback(OutputData { buffer: interleaved }); // 2. Silence ASIO channels if necessary. let n_channels = interleaved.len() / asio_stream.buffer_size as usize; @@ -341,31 +330,29 @@ impl Device { let asio_channel = asio_channel_slice_mut::(asio_stream, buffer_index, ch_ix); for (frame, s_asio) in interleaved.chunks(n_channels).zip(asio_channel) { - *s_asio = *s_asio + to_endianness(to_asio_sample(frame[ch_ix])); + *s_asio = *s_asio + to_endianness(B::from_cpal_sample(&frame[ch_ix])); } } } match (data_type, &stream_type) { (SampleFormat::I16, &sys::AsioSampleType::ASIOSTInt16LSB) => { - process_output_callback::( + process_output_callback::( &mut data_callback, &mut interleaved, silence, asio_stream, buffer_index as usize, - std::convert::identity::, to_le, ); } (SampleFormat::I16, &sys::AsioSampleType::ASIOSTInt16MSB) => { - process_output_callback::( + process_output_callback::( &mut data_callback, &mut interleaved, silence, asio_stream, buffer_index as usize, - std::convert::identity::, to_be, ); } @@ -374,14 +361,13 @@ impl Device { // trait for the `to_le` and `to_be` methods, but this does not support floats. (SampleFormat::F32, &sys::AsioSampleType::ASIOSTFloat32LSB) | (SampleFormat::F32, &sys::AsioSampleType::ASIOSTFloat32MSB) => { - process_output_callback::( + process_output_callback::( &mut data_callback, &mut interleaved, silence, asio_stream, buffer_index as usize, std::convert::identity::, - std::convert::identity::, ); } @@ -389,24 +375,22 @@ impl Device { // `process_output_callback` function above by removing the unnecessary sample // conversion function. (SampleFormat::I16, &sys::AsioSampleType::ASIOSTInt32LSB) => { - process_output_callback::( + process_output_callback::( &mut data_callback, &mut interleaved, silence, asio_stream, buffer_index as usize, - |s| (s as i32) << 16, to_le, ); } (SampleFormat::I16, &sys::AsioSampleType::ASIOSTInt32MSB) => { - process_output_callback::( + process_output_callback::( &mut data_callback, &mut interleaved, silence, asio_stream, buffer_index as usize, - |s| (s as i32) << 16, to_be, ); } @@ -414,13 +398,12 @@ impl Device { // trait for the `to_le` and `to_be` methods, but this does not support floats. (SampleFormat::F32, &sys::AsioSampleType::ASIOSTFloat64LSB) | (SampleFormat::F32, &sys::AsioSampleType::ASIOSTFloat64MSB) => { - process_output_callback::( + process_output_callback::( &mut data_callback, &mut interleaved, silence, asio_stream, buffer_index as usize, - |s| s as f64, std::convert::identity::, ); } @@ -549,33 +532,45 @@ impl Silence for f64 { const SILENCE: Self = 0.0; } -impl InterleavedSample for i16 { - fn unknown_type_input_buffer(buffer: &[Self]) -> UnknownTypeInputBuffer { - UnknownTypeInputBuffer::I16(::InputBuffer { buffer }) +impl AsioSample for i16 { + fn to_cpal_sample(&self) -> T { + T::from(self) } - - fn unknown_type_output_buffer(buffer: &mut [Self]) -> UnknownTypeOutputBuffer { - UnknownTypeOutputBuffer::I16(::OutputBuffer { buffer }) + fn from_cpal_sample(t: &T) -> Self { + Sample::from(t) } } -impl InterleavedSample for f32 { - fn unknown_type_input_buffer(buffer: &[Self]) -> UnknownTypeInputBuffer { - UnknownTypeInputBuffer::F32(::InputBuffer { buffer }) +impl AsioSample for i32 { + fn to_cpal_sample(&self) -> T { + let s = (*self >> 16) as i16; + s.to_cpal_sample() } - - fn unknown_type_output_buffer(buffer: &mut [Self]) -> UnknownTypeOutputBuffer { - UnknownTypeOutputBuffer::F32(::OutputBuffer { buffer }) + fn from_cpal_sample(t: &T) -> Self { + let s = i16::from_cpal_sample(t); + (s as i32) << 16 } } -impl AsioSample for i16 {} +impl AsioSample for f32 { + fn to_cpal_sample(&self) -> T { + T::from(self) + } + fn from_cpal_sample(t: &T) -> Self { + Sample::from(t) + } +} -impl AsioSample for i32 {} - -impl AsioSample for f32 {} - -impl AsioSample for f64 {} +impl AsioSample for f64 { + fn to_cpal_sample(&self) -> T { + let f = *self as f32; + f.to_cpal_sample() + } + fn from_cpal_sample(t: &T) -> Self { + let f = f32::from_cpal_sample(t); + f as f64 + } +} /// Check whether or not the desired format is supported by the stream. /// From 6fbb701826e833ecf79a7e5e0337f47c6e47dde1 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Wed, 15 Jan 2020 17:21:11 +0100 Subject: [PATCH 06/14] Update CoreAudio backend for removal of `UnknownBufferType` --- asio-sys/Cargo.toml | 2 +- src/host/coreaudio/mod.rs | 156 +++++++++++++++++++++----------------- 2 files changed, 86 insertions(+), 72 deletions(-) diff --git a/asio-sys/Cargo.toml b/asio-sys/Cargo.toml index 7a92552..4677411 100644 --- a/asio-sys/Cargo.toml +++ b/asio-sys/Cargo.toml @@ -10,7 +10,7 @@ keywords = ["audio", "sound", "asio", "steinberg"] build = "build.rs" [target.'cfg(any(target_os = "windows"))'.build-dependencies] -bindgen = "0.42.0" +bindgen = "0.51.0" walkdir = "2" cc = "1.0.25" diff --git a/src/host/coreaudio/mod.rs b/src/host/coreaudio/mod.rs index 17af18b..aeef5af 100644 --- a/src/host/coreaudio/mod.rs +++ b/src/host/coreaudio/mod.rs @@ -1,35 +1,26 @@ extern crate coreaudio; extern crate core_foundation_sys; -use ChannelCount; -use BackendSpecificError; -use BuildStreamError; -use DefaultFormatError; -use DeviceNameError; -use DevicesError; -use Format; -use PauseStreamError; -use PlayStreamError; -use SupportedFormatsError; -use SampleFormat; -use SampleRate; -use StreamData; -use StreamError; -use SupportedFormat; -use UnknownTypeInputBuffer; -use UnknownTypeOutputBuffer; -use traits::{DeviceTrait, HostTrait, StreamTrait}; - -use std::ffi::CStr; -use std::fmt; -use std::mem; -use std::cell::RefCell; -use std::os::raw::c_char; -use std::ptr::null; -use std::slice; -use std::thread; -use std::time::Duration; - +use crate::{ + ChannelCount, + BackendSpecificError, + BuildStreamError, + DefaultFormatError, + DeviceNameError, + DevicesError, + Format, + InputData, + OutputData, + PauseStreamError, + PlayStreamError, + Sample, + SampleFormat, + SampleRate, + StreamError, + SupportedFormat, + SupportedFormatsError, +}; +use crate::traits::{DeviceTrait, HostTrait, StreamTrait}; use self::coreaudio::audio_unit::{AudioUnit, Scope, Element}; use self::coreaudio::audio_unit::render_callback::{self, data}; use self::coreaudio::sys::{ @@ -69,6 +60,15 @@ use self::core_foundation_sys::string::{ CFStringRef, CFStringGetCStringPtr, }; +use std::ffi::CStr; +use std::fmt; +use std::mem; +use std::cell::RefCell; +use std::os::raw::c_char; +use std::ptr::null; +use std::slice; +use std::thread; +use std::time::Duration; mod enumerate; @@ -131,11 +131,33 @@ impl DeviceTrait for Device { Device::default_output_format(self) } - fn build_input_stream(&self, format: &Format, data_callback: D, error_callback: E) -> Result where D: FnMut(StreamData) + Send + 'static, E: FnMut(StreamError) + Send + 'static { + fn build_input_stream( + &self, + format: &Format, + data_callback: D, + error_callback: E, + ) -> Result + where + T: Sample, + D: FnMut(InputData) + Send + 'static, + E: FnMut(StreamError) + Send + 'static, + { + assert_eq!(T::FORMAT, format.data_type); Device::build_input_stream(self, format, data_callback, error_callback) } - fn build_output_stream(&self, format: &Format, data_callback: D, error_callback: E) -> Result where D: FnMut(StreamData) + Send + 'static, E: FnMut(StreamError) + Send + 'static { + fn build_output_stream( + &self, + format: &Format, + data_callback: D, + error_callback: E, + ) -> Result + where + T: Sample, + D: FnMut(OutputData) + Send + 'static, + E: FnMut(StreamError) + Send + 'static, + { + assert_eq!(T::FORMAT, format.data_type); Device::build_output_stream(self, format, data_callback, error_callback) } } @@ -478,7 +500,17 @@ fn audio_unit_from_device(device: &Device, input: bool) -> Result(&self, format: &Format, mut data_callback: D, _error_callback: E) -> Result where D: FnMut(StreamData) + Send + 'static, E: FnMut(StreamError) + Send + 'static { + fn build_input_stream( + &self, + format: &Format, + mut data_callback: D, + _error_callback: E, + ) -> Result + where + T: Sample, + D: FnMut(InputData) + Send + 'static, + E: FnMut(StreamError) + Send + 'static, + { // The scope and element for working with a device's input stream. let scope = Scope::Output; let element = Element::Input; @@ -625,8 +657,7 @@ impl Device { // Register the callback that is being called by coreaudio whenever it needs data to be // fed to the audio buffer. - let sample_format = format.data_type; - let bytes_per_channel = format.data_type.sample_size(); + let bytes_per_channel = std::mem::size_of::(); type Args = render_callback::Args; audio_unit.set_input_callback(move |args: Args| unsafe { let ptr = (*args.data.data).mBuffers.as_ptr() as *const AudioBuffer; @@ -640,23 +671,10 @@ impl Device { mData: data } = buffers[0]; - // A small macro to simplify handling the callback for different sample types. - macro_rules! try_callback { - ($SampleFormat:ident, $SampleType:ty) => {{ - let data_len = (data_byte_size as usize / bytes_per_channel) as usize; - let data_slice = slice::from_raw_parts(data as *const $SampleType, data_len); - let unknown_type_buffer = UnknownTypeInputBuffer::$SampleFormat(::InputBuffer { buffer: data_slice }); - let stream_data = StreamData::Input { buffer: unknown_type_buffer }; - data_callback(stream_data); - }}; - } - - match sample_format { - SampleFormat::F32 => try_callback!(F32, f32), - SampleFormat::I16 => try_callback!(I16, i16), - SampleFormat::U16 => try_callback!(U16, u16), - } - + let data_len = (data_byte_size as usize / bytes_per_channel) as usize; + let data_slice = slice::from_raw_parts(data as *const T, data_len); + let input_data = InputData { buffer: data_slice }; + data_callback(input_data); Ok(()) })?; @@ -669,7 +687,17 @@ impl Device { })) } - fn build_output_stream(&self, format: &Format, mut data_callback: D, _error_callback: E) -> Result where D: FnMut(StreamData) + Send + 'static, E: FnMut(StreamError) + Send + 'static { + fn build_output_stream( + &self, + format: &Format, + mut data_callback: D, + _error_callback: E, + ) -> Result + where + T: Sample, + D: FnMut(OutputData) + Send + 'static, + E: FnMut(StreamError) + Send + 'static, + { let mut audio_unit = audio_unit_from_device(self, false)?; // The scope and element for working with a device's output stream. @@ -682,8 +710,7 @@ impl Device { // Register the callback that is being called by coreaudio whenever it needs data to be // fed to the audio buffer. - let sample_format = format.data_type; - let bytes_per_channel = format.data_type.sample_size(); + let bytes_per_channel = std::mem::size_of::(); type Args = render_callback::Args; audio_unit.set_render_callback(move |args: Args| unsafe { // If `run()` is currently running, then a callback will be available from this list. @@ -695,23 +722,10 @@ impl Device { mData: data } = (*args.data.data).mBuffers[0]; - // A small macro to simplify handling the callback for different sample types. - macro_rules! try_callback { - ($SampleFormat:ident, $SampleType:ty, $equilibrium:expr) => {{ - let data_len = (data_byte_size as usize / bytes_per_channel) as usize; - let data_slice = slice::from_raw_parts_mut(data as *mut $SampleType, data_len); - let unknown_type_buffer = UnknownTypeOutputBuffer::$SampleFormat(::OutputBuffer { buffer: data_slice }); - let stream_data = StreamData::Output { buffer: unknown_type_buffer }; - data_callback(stream_data); - }}; - } - - match sample_format { - SampleFormat::F32 => try_callback!(F32, f32, 0.0), - SampleFormat::I16 => try_callback!(I16, i16, 0), - SampleFormat::U16 => try_callback!(U16, u16, ::std::u16::MAX / 2), - } - + let data_len = (data_byte_size as usize / bytes_per_channel) as usize; + let data_slice = slice::from_raw_parts_mut(data as *mut T, data_len); + let output_data = OutputData { buffer: data_slice }; + data_callback(output_data); Ok(()) })?; From 01425b4b80b224ede40eaf63a28e5e0a2e5c91b7 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Sun, 19 Jan 2020 03:39:15 +1100 Subject: [PATCH 07/14] Patch for bindgen update in ASIO backend --- asio-sys/src/bindings/mod.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/asio-sys/src/bindings/mod.rs b/asio-sys/src/bindings/mod.rs index 0d38801..a1c7b1a 100644 --- a/asio-sys/src/bindings/mod.rs +++ b/asio-sys/src/bindings/mod.rs @@ -319,9 +319,7 @@ impl Asio { // Make owned CString to send to load driver let driver_name_cstring = CString::new(driver_name) .expect("failed to create `CString` from driver name"); - let mut driver_info = ai::ASIODriverInfo { - _bindgen_opaque_blob: [0u32; 43], - }; + let mut driver_info = std::mem::MaybeUninit::::uninit(); unsafe { // TODO: Check that a driver of the same name does not already exist? @@ -329,7 +327,8 @@ impl Asio { false => Err(LoadDriverError::LoadDriverFailed), true => { // Initialize ASIO. - asio_result!(ai::ASIOInit(&mut driver_info))?; + asio_result!(ai::ASIOInit(driver_info.as_mut_ptr()))?; + let _driver_info = driver_info.assume_init(); let state = Mutex::new(DriverState::Initialized); let name = driver_name.to_string(); let destroyed = false; From 58356f49b46300410f2a5209c65a05f31f712210 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Sun, 19 Jan 2020 15:06:19 +0100 Subject: [PATCH 08/14] An alternative approach to removing `UnknownBufferType`. This is a potential alternative to #359. This PR is based on #359. This approach opts for a dynamically checked sample type approach with the aim of minimising compile time and binary size. You can read more discussion on this [here](https://github.com/RustAudio/cpal/pull/359#issuecomment-575931461) Implemented backends: - [x] null - [x] ALSA - [ ] CoreAudio - [ ] WASAPI - [ ] ASIO - [ ] Emscripten --- examples/beep.rs | 29 +++------ examples/feedback.rs | 74 +++++++++++----------- examples/record_wav.rs | 29 ++++----- src/host/alsa/mod.rs | 102 ++++++++++++------------------ src/host/null/mod.rs | 11 ++-- src/lib.rs | 139 ++++++++++++++++++++++++----------------- src/platform/mod.rs | 10 ++- src/traits.rs | 14 ++--- 8 files changed, 189 insertions(+), 219 deletions(-) diff --git a/examples/beep.rs b/examples/beep.rs index d143fff..35718d2 100644 --- a/examples/beep.rs +++ b/examples/beep.rs @@ -19,27 +19,15 @@ fn main() -> Result<(), anyhow::Error> { (sample_clock * 440.0 * 2.0 * 3.141592 / sample_rate).sin() }; - 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() { + cpal::SampleFormat::F32 => write_data::(data, channels, &mut next_value), + cpal::SampleFormat::I16 => write_data::(data, channels, &mut next_value), + cpal::SampleFormat::U16 => write_data::(data, channels, &mut next_value), }; - let stream = match format.data_type { - cpal::SampleFormat::F32 => device.build_output_stream( - &format, - move |mut data| write_data::(&mut *data, channels, &mut next_value), - err_fn, - ), - cpal::SampleFormat::I16 => device.build_output_stream( - &format, - move |mut data| write_data::(&mut *data, channels, &mut next_value), - err_fn, - ), - cpal::SampleFormat::U16 => device.build_output_stream( - &format, - move |mut data| write_data::(&mut *data, channels, &mut next_value), - err_fn, - ), - }?; + let stream = device.build_output_stream(&format, data_fn, err_fn)?; stream.play()?; @@ -48,10 +36,11 @@ fn main() -> Result<(), anyhow::Error> { Ok(()) } -fn write_data(output: &mut [T], channels: usize, next_sample: &mut dyn FnMut() -> f32) +fn write_data(output: &mut cpal::Data, channels: usize, next_sample: &mut dyn FnMut() -> f32) where T: cpal::Sample, { + let output = output.as_slice_mut::().expect("unexpected sample type"); for frame in output.chunks_mut(channels) { let value: T = cpal::Sample::from::(&next_sample()); for sample in frame.iter_mut() { diff --git a/examples/feedback.rs b/examples/feedback.rs index 6697718..6e8c48e 100644 --- a/examples/feedback.rs +++ b/examples/feedback.rs @@ -47,46 +47,40 @@ fn main() -> Result<(), anyhow::Error> { producer.push(0.0).unwrap(); } + let input_data_fn = move |data: &cpal::Data| { + let mut output_fell_behind = false; + let data = data.as_slice::().expect("unexpected sample type"); + for &sample in data { + if producer.push(sample).is_err() { + output_fell_behind = true; + } + } + if output_fell_behind { + eprintln!("output stream fell behind: try increasing latency"); + } + }; + + let output_data_fn = move |data: &mut cpal::Data| { + let mut input_fell_behind = None; + let data = data.as_slice_mut::().expect("unexpected sample type"); + for sample in data { + *sample = match consumer.pop() { + Ok(s) => s, + Err(err) => { + input_fell_behind = Some(err); + 0.0 + }, + }; + } + if let Some(err) = input_fell_behind { + eprintln!("input stream fell behind: {:?}: try increasing latency", err); + } + }; + // Build streams. println!("Attempting to build both streams with `{:?}`.", format); - let input_stream = input_device.build_input_stream( - &format, - move |data| { - let mut output_fell_behind = false; - for &sample in data.iter() { - if producer.push(sample).is_err() { - output_fell_behind = true; - } - } - if output_fell_behind { - eprintln!("output stream fell behind: try increasing latency"); - } - }, - |err| { - eprintln!("an error occurred on stream: {}", err); - }, - )?; - let output_stream = output_device.build_output_stream( - &format, - move |mut data| { - let mut input_fell_behind = None; - for sample in data.iter_mut() { - *sample = match consumer.pop() { - Ok(s) => s, - Err(err) => { - input_fell_behind = Some(err); - 0.0 - }, - }; - } - if let Some(err) = input_fell_behind { - eprintln!("input stream fell behind: {:?}: try increasing latency", err); - } - }, - move |err| { - eprintln!("an error occurred on output stream: {}", err); - }, - )?; + let input_stream = input_device.build_input_stream(&format, input_data_fn, err_fn)?; + let output_stream = output_device.build_output_stream(&format, output_data_fn, err_fn)?; println!("Successfully built streams."); // Play the streams. @@ -105,3 +99,7 @@ fn main() -> Result<(), anyhow::Error> { println!("Done!"); Ok(()) } + +fn err_fn(err: cpal::StreamError) { + eprintln!("an error occurred on stream: {}", err); +} diff --git a/examples/record_wav.rs b/examples/record_wav.rs index fb48286..5d00b8e 100644 --- a/examples/record_wav.rs +++ b/examples/record_wav.rs @@ -40,23 +40,15 @@ fn main() -> Result<(), anyhow::Error> { eprintln!("an error occurred on stream: {}", err); }; - let stream = match format.data_type { - cpal::SampleFormat::F32 => device.build_input_stream( - &format, - move |data| write_input_data::(&*data, &writer_2), - err_fn, - ), - cpal::SampleFormat::I16 => device.build_input_stream( - &format, - move |data| write_input_data::(&*data, &writer_2), - err_fn, - ), - cpal::SampleFormat::U16 => device.build_input_stream( - &format, - move |data| write_input_data::(&*data, &writer_2), - err_fn, - ), - }?; + let data_fn = move |data: &cpal::Data| { + match data.sample_format() { + cpal::SampleFormat::F32 => write_input_data::(data, &writer_2), + cpal::SampleFormat::I16 => write_input_data::(data, &writer_2), + cpal::SampleFormat::U16 => write_input_data::(data, &writer_2), + } + }; + + let stream = device.build_input_stream(&format, data_fn, err_fn)?; stream.play()?; @@ -87,11 +79,12 @@ fn wav_spec_from_format(format: &cpal::Format) -> hound::WavSpec { type WavWriterHandle = Arc>>>>; -fn write_input_data(input: &[T], writer: &WavWriterHandle) +fn write_input_data(input: &cpal::Data, writer: &WavWriterHandle) where T: cpal::Sample, U: cpal::Sample + hound::Sample, { + let input = input.as_slice::().expect("unexpected sample format"); if let Ok(mut guard) = writer.try_lock() { if let Some(writer) = guard.as_mut() { for &sample in input.iter() { diff --git a/src/host/alsa/mod.rs b/src/host/alsa/mod.rs index f1724aa..512ed0c 100644 --- a/src/host/alsa/mod.rs +++ b/src/host/alsa/mod.rs @@ -5,15 +5,13 @@ use crate::{ BackendSpecificError, BuildStreamError, ChannelCount, + Data, DefaultFormatError, DeviceNameError, DevicesError, Format, - InputData, - OutputData, PauseStreamError, PlayStreamError, - Sample, SampleFormat, SampleRate, StreamError, @@ -90,37 +88,31 @@ impl DeviceTrait for Device { Device::default_output_format(self) } - fn build_input_stream( + fn build_input_stream( &self, format: &Format, data_callback: D, error_callback: E, ) -> Result where - T: Sample, - D: FnMut(InputData) + Send + 'static, + D: FnMut(&Data) + Send + 'static, E: FnMut(StreamError) + Send + 'static, { - // TODO: Consider removing `data_type` field from `Format` and removing this. - assert_eq!(format.data_type, T::FORMAT, "sample format mismatch"); let stream_inner = self.build_stream_inner(format, alsa::SND_PCM_STREAM_CAPTURE)?; let stream = Stream::new_input(Arc::new(stream_inner), data_callback, error_callback); Ok(stream) } - fn build_output_stream( + fn build_output_stream( &self, format: &Format, data_callback: D, error_callback: E, ) -> Result where - T: Sample, - D: FnMut(OutputData) + Send + 'static, + D: FnMut(&mut Data) + Send + 'static, E: FnMut(StreamError) + Send + 'static, { - // TODO: Consider removing `data_type` field from `Format` and removing this. - assert_eq!(format.data_type, T::FORMAT, "sample format mismatch"); let stream_inner = self.build_stream_inner(format, alsa::SND_PCM_STREAM_PLAYBACK)?; let stream = Stream::new_output(Arc::new(stream_inner), data_callback, error_callback); Ok(stream) @@ -564,14 +556,12 @@ struct StreamWorkerContext { buffer: Vec, } -fn input_stream_worker( +fn input_stream_worker( rx: TriggerReceiver, stream: &StreamInner, - data_callback: &mut (dyn FnMut(InputData) + Send + 'static), + data_callback: &mut (dyn FnMut(&Data) + Send + 'static), error_callback: &mut (dyn FnMut(StreamError) + Send + 'static), -) where - T: Sample, -{ +) { let mut ctxt = StreamWorkerContext::default(); loop { match poll_descriptors_and_prepare_buffer(&rx, stream, &mut ctxt, error_callback) { @@ -583,7 +573,7 @@ fn input_stream_worker( StreamType::Input, "expected input stream, but polling descriptors indicated output", ); - process_input::( + process_input( stream, &mut ctxt.buffer, available_frames, @@ -595,14 +585,12 @@ fn input_stream_worker( } } -fn output_stream_worker( +fn output_stream_worker( rx: TriggerReceiver, stream: &StreamInner, - data_callback: &mut (dyn FnMut(OutputData) + Send + 'static), + data_callback: &mut (dyn FnMut(&mut Data) + Send + 'static), error_callback: &mut (dyn FnMut(StreamError) + Send + 'static), -) where - T: Sample, -{ +) { let mut ctxt = StreamWorkerContext::default(); loop { match poll_descriptors_and_prepare_buffer(&rx, stream, &mut ctxt, error_callback) { @@ -614,7 +602,7 @@ fn output_stream_worker( StreamType::Output, "expected output stream, but polling descriptors indicated input", ); - process_output::( + process_output( stream, &mut ctxt.buffer, available_frames, @@ -729,15 +717,13 @@ fn poll_descriptors_and_prepare_buffer( } // Read input data from ALSA and deliver it to the user. -fn process_input( +fn process_input( stream: &StreamInner, buffer: &mut [u8], available_frames: usize, - data_callback: &mut (dyn FnMut(InputData) + Send + 'static), + data_callback: &mut (dyn FnMut(&Data) + Send + 'static), error_callback: &mut dyn FnMut(StreamError), -) where - T: Sample, -{ +) { let result = unsafe { alsa::snd_pcm_readi( stream.channel, @@ -750,28 +736,34 @@ fn process_input( error_callback(BackendSpecificError { description }.into()); return; } - let buffer = unsafe { cast_input_buffer::(buffer) }; - let input_data = InputData { buffer }; - data_callback(input_data); + let sample_format = stream.sample_format; + let data = buffer.as_mut_ptr() as *mut (); + let len = buffer.len() / sample_format.sample_size(); + let data = unsafe { + Data::from_parts(data, len, sample_format) + }; + data_callback(&data); } // Request data from the user's function and write it via ALSA. // // Returns `true` -fn process_output( +fn process_output( stream: &StreamInner, buffer: &mut [u8], available_frames: usize, - data_callback: &mut (dyn FnMut(OutputData) + Send + 'static), + data_callback: &mut (dyn FnMut(&mut Data) + Send + 'static), error_callback: &mut dyn FnMut(StreamError), -) where - T: Sample, -{ +) { { // We're now sure that we're ready to write data. - let buffer = unsafe { cast_output_buffer::(buffer) }; - let output_data = OutputData { buffer }; - data_callback(output_data); + let sample_format = stream.sample_format; + let data = buffer.as_mut_ptr() as *mut (); + let len = buffer.len() / sample_format.sample_size(); + let mut data = unsafe { + Data::from_parts(data, len, sample_format) + }; + data_callback(&mut data); } loop { let result = unsafe { @@ -805,21 +797,20 @@ fn process_output( } impl Stream { - fn new_input( + fn new_input( inner: Arc, mut data_callback: D, mut error_callback: E, ) -> Stream where - T: Sample, - D: FnMut(InputData) + Send + 'static, + D: FnMut(&Data) + Send + 'static, E: FnMut(StreamError) + Send + 'static, { let (tx, rx) = trigger(); // Clone the handle for passing into worker thread. let stream = inner.clone(); let thread = thread::spawn(move || { - input_stream_worker::(rx, &*stream, &mut data_callback, &mut error_callback); + input_stream_worker(rx, &*stream, &mut data_callback, &mut error_callback); }); Stream { thread: Some(thread), @@ -828,21 +819,20 @@ impl Stream { } } - fn new_output( + fn new_output( inner: Arc, mut data_callback: D, mut error_callback: E, ) -> Stream where - T: Sample, - D: FnMut(OutputData) + Send + 'static, + D: FnMut(&mut Data) + Send + 'static, E: FnMut(StreamError) + Send + 'static, { let (tx, rx) = trigger(); // Clone the handle for passing into worker thread. let stream = inner.clone(); let thread = thread::spawn(move || { - output_stream_worker::(rx, &*stream, &mut data_callback, &mut error_callback); + output_stream_worker(rx, &*stream, &mut data_callback, &mut error_callback); }); Stream { thread: Some(thread), @@ -1080,17 +1070,3 @@ fn check_errors(err: libc::c_int) -> Result<(), String> { Ok(()) } - -/// Cast a byte slice into a (immutable) slice of desired type. -/// Safety: it's up to the caller to ensure that the input slice has valid bit representations. -unsafe fn cast_input_buffer(v: &[u8]) -> &[T] { - debug_assert!(v.len() % std::mem::size_of::() == 0); - std::slice::from_raw_parts(v.as_ptr() as *const T, v.len() / std::mem::size_of::()) -} - -/// Cast a byte slice into a mutable slice of desired type. -/// Safety: it's up to the caller to ensure that the input slice has valid bit representations. -unsafe fn cast_output_buffer(v: &mut [u8]) -> &mut [T] { - debug_assert!(v.len() % std::mem::size_of::() == 0); - std::slice::from_raw_parts_mut(v.as_mut_ptr() as *mut T, v.len() / std::mem::size_of::()) -} diff --git a/src/host/null/mod.rs b/src/host/null/mod.rs index 09a19c8..def00c9 100644 --- a/src/host/null/mod.rs +++ b/src/host/null/mod.rs @@ -1,11 +1,10 @@ use crate::{ BuildStreamError, + Data, DefaultFormatError, DevicesError, DeviceNameError, Format, - InputData, - OutputData, PauseStreamError, PlayStreamError, StreamError, @@ -71,28 +70,28 @@ impl DeviceTrait for Device { unimplemented!() } - fn build_input_stream( + fn build_input_stream( &self, _format: &Format, _data_callback: D, _error_callback: E, ) -> Result where - D: FnMut(InputData) + Send + 'static, + D: FnMut(&Data) + Send + 'static, E: FnMut(StreamError) + Send + 'static, { unimplemented!() } /// Create an output stream. - fn build_output_stream( + fn build_output_stream( &self, _format: &Format, _data_callback: D, _error_callback: E, ) -> Result where - D: FnMut(OutputData) + Send + 'static, + D: FnMut(&mut Data) + Send + 'static, E: FnMut(StreamError) + Send + 'static, { unimplemented!() diff --git a/src/lib.rs b/src/lib.rs index a80f3a7..07f06ab 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -152,7 +152,6 @@ pub use platform::{ HostId, Stream, SupportedInputFormats, SupportedOutputFormats, }; pub use samples_formats::{Sample, SampleFormat}; -use std::ops::{Deref, DerefMut}; mod error; mod host; @@ -193,34 +192,90 @@ pub struct SupportedFormat { pub data_type: SampleFormat, } -/// Represents a buffer containing audio data that may be read. +/// Represents a buffer of audio data, delivered via a user's stream data callback function. /// -/// This struct implements the `Deref` trait targeting `[T]`. Therefore this buffer can be read the -/// same way as reading from a `Vec` or any other kind of Rust array. -// TODO: explain audio stuff in general -// TODO: Consider making this an `enum` with `Interleaved` and `NonInterleaved` variants. +/// Input stream callbacks receive `&Data`, while output stream callbacks expect `&mut Data`. #[derive(Debug)] -pub struct InputData<'a, T: 'a> -where - T: Sample, -{ - buffer: &'a [T], +pub struct Data { + data: *mut (), + len: usize, + sample_format: SampleFormat, } -/// Represents a buffer that must be filled with audio data. The buffer in unfilled state may -/// contain garbage values. -/// -/// This struct implements the `Deref` and `DerefMut` traits to `[T]`. Therefore writing to this -/// buffer is done in the same way as writing to a `Vec` or any other kind of Rust array. -// TODO: explain audio stuff in general -// TODO: Consider making this an `enum` with `Interleaved` and `NonInterleaved` variants. -#[must_use] -#[derive(Debug)] -pub struct OutputData<'a, T: 'a> -where - T: Sample, -{ - buffer: &'a mut [T], +impl Data { + // Internal constructor for host implementations to use. + pub(crate) unsafe fn from_parts( + data: *mut (), + len: usize, + sample_format: SampleFormat, + ) -> Self { + Data { data, len, sample_format } + } + + /// The sample format of the internal audio data. + pub fn sample_format(&self) -> SampleFormat { + self.sample_format + } + + /// The full length of the buffer in samples. + /// + /// The returned length is the same length as the slice of type `T` that would be returned via + /// `as_slice` given a sample type that matches the inner sample format. + pub fn len(&self) -> usize { + self.len + } + + /// The raw slice of memory representing the underlying audio data as a slice of bytes. + /// + /// It is up to the user to interprate the slice of memory based on `Data::sample_format`. + pub fn bytes(&self) -> &[u8] { + let len = self.len * self.sample_format.sample_size(); + unsafe { + std::slice::from_raw_parts(self.data as *const u8, len) + } + } + + /// The raw slice of memory representing the underlying audio data as a slice of bytes. + /// + /// It is up to the user to interprate the slice of memory based on `Data::sample_format`. + pub fn bytes_mut(&mut self) -> &mut [u8] { + let len = self.len * self.sample_format.sample_size(); + unsafe { + std::slice::from_raw_parts_mut(self.data as *mut u8, len) + } + } + + /// Access the data as a slice of sample type `T`. + /// + /// Returns `None` if the sample type does not match the expected sample format. + pub fn as_slice(&self) -> Option<&[T]> + where + T: Sample, + { + if T::FORMAT == self.sample_format { + unsafe { + Some(std::slice::from_raw_parts(self.data as *const T, self.len)) + } + } else { + None + } + } + + /// Access the data as a slice of sample type `T`. + /// + /// Returns `None` if the sample type does not match the expected sample format. + pub fn as_slice_mut(&mut self) -> Option<&mut [T]> + where + T: Sample, + { + if T::FORMAT == self.sample_format { + unsafe { + Some(std::slice::from_raw_parts_mut(self.data as *mut T, self.len)) + } + } else { + None + } + } } impl SupportedFormat { @@ -306,40 +361,6 @@ impl SupportedFormat { } } -impl<'a, T> Deref for InputData<'a, T> -where - T: Sample, -{ - type Target = [T]; - - #[inline] - fn deref(&self) -> &[T] { - self.buffer - } -} - -impl<'a, T> Deref for OutputData<'a, T> -where - T: Sample, -{ - type Target = [T]; - - #[inline] - fn deref(&self) -> &[T] { - self.buffer - } -} - -impl<'a, T> DerefMut for OutputData<'a, T> -where - T: Sample, -{ - #[inline] - fn deref_mut(&mut self) -> &mut [T] { - self.buffer - } -} - impl From for SupportedFormat { #[inline] fn from(format: Format) -> SupportedFormat { diff --git a/src/platform/mod.rs b/src/platform/mod.rs index dfa2632..8b936a9 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -255,15 +255,14 @@ macro_rules! impl_platform_host { } } - fn build_input_stream( + fn build_input_stream( &self, format: &crate::Format, data_callback: D, error_callback: E, ) -> Result where - T: crate::Sample, - D: FnMut(crate::InputData) + Send + 'static, + D: FnMut(&crate::Data) + Send + 'static, E: FnMut(crate::StreamError) + Send + 'static, { match self.0 { @@ -275,15 +274,14 @@ macro_rules! impl_platform_host { } } - fn build_output_stream( + fn build_output_stream( &self, format: &crate::Format, data_callback: D, error_callback: E, ) -> Result where - T: crate::Sample, - D: FnMut(crate::OutputData) + Send + 'static, + D: FnMut(&mut crate::Data) + Send + 'static, E: FnMut(crate::StreamError) + Send + 'static, { match self.0 { diff --git a/src/traits.rs b/src/traits.rs index c00e294..4aeb8e6 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -2,17 +2,15 @@ use { BuildStreamError, + Data, DefaultFormatError, DeviceNameError, DevicesError, Format, - InputData, InputDevices, - OutputData, OutputDevices, PauseStreamError, PlayStreamError, - Sample, StreamError, SupportedFormat, SupportedFormatsError, @@ -120,27 +118,25 @@ pub trait DeviceTrait { fn default_output_format(&self) -> Result; /// Create an input stream. - fn build_input_stream( + fn build_input_stream( &self, format: &Format, data_callback: D, error_callback: E, ) -> Result where - T: Sample, - D: FnMut(InputData) + Send + 'static, + D: FnMut(&Data) + Send + 'static, E: FnMut(StreamError) + Send + 'static; /// Create an output stream. - fn build_output_stream( + fn build_output_stream( &self, format: &Format, data_callback: D, error_callback: E, ) -> Result where - T: Sample, - D: FnMut(OutputData) + Send + 'static, + D: FnMut(&mut Data) + Send + 'static, E: FnMut(StreamError) + Send + 'static; } From 7f27b897bb54d08dc0fd1304456742ca1cebe40b Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Sun, 19 Jan 2020 16:04:06 +0100 Subject: [PATCH 09/14] Update emscripten backend for new stream Data type --- src/host/emscripten/mod.rs | 41 +++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/src/host/emscripten/mod.rs b/src/host/emscripten/mod.rs index 176671a..0eb951f 100644 --- a/src/host/emscripten/mod.rs +++ b/src/host/emscripten/mod.rs @@ -9,15 +9,13 @@ use stdweb::web::set_timeout; use crate::{ BuildStreamError, + Data, DefaultFormatError, DeviceNameError, DevicesError, Format, - InputData, - OutputData, PauseStreamError, PlayStreamError, - Sample, SampleFormat, StreamError, SupportedFormat, @@ -160,32 +158,34 @@ impl DeviceTrait for Device { Device::default_output_format(self) } - fn build_input_stream( + fn build_input_stream( &self, _format: &Format, _data_callback: D, _error_callback: E, ) -> Result where - T: Sample, - D: FnMut(InputData) + Send + 'static, + D: FnMut(&Data) + Send + 'static, E: FnMut(StreamError) + Send + 'static, { unimplemented!() } - fn build_output_stream( + fn build_output_stream( &self, - _format: &Format, + format: &Format, data_callback: D, error_callback: E, ) -> Result where - T: Sample, - D: FnMut(OutputData) + Send + 'static, + D: FnMut(&mut Data) + Send + 'static, E: FnMut(StreamError) + Send + 'static, { - assert_eq!(T::FORMAT, SampleFormat::F32, "emscripten backend only supports `f32` data"); + assert_eq!( + format.data_type, + SampleFormat::F32, + "emscripten backend currently only supports `f32` data", + ); // Create the stream. let audio_ctxt_ref = js!(return new AudioContext()).into_reference().unwrap(); @@ -201,7 +201,7 @@ impl DeviceTrait for Device { // // See also: The call to `set_timeout` at the end of the `audio_callback_fn` which creates // the loop. - set_timeout(|| audio_callback_fn::(user_data_ptr as *mut c_void), 10); + set_timeout(|| audio_callback_fn::(user_data_ptr as *mut c_void), 10); Ok(stream) } @@ -223,10 +223,9 @@ impl StreamTrait for Stream { // The first argument of the callback function (a `void*`) is a casted pointer to `self` // and to the `callback` parameter that was passed to `run`. -fn audio_callback_fn(user_data_ptr: *mut c_void) +fn audio_callback_fn(user_data_ptr: *mut c_void) where - T: Sample, - D: FnMut(OutputData) + Send + 'static, + D: FnMut(&mut Data) + Send + 'static, E: FnMut(StreamError) + Send + 'static, { unsafe { @@ -236,12 +235,14 @@ where let audio_ctxt = &stream.audio_ctxt_ref; // TODO: We should be re-using a buffer. - let mut temporary_buffer: Vec<_> = (0..44100 * 2 / 3).map(|_| T::from(&0.0)).collect(); + let mut temporary_buffer = vec![0.0; 44100 * 2 / 3]; { - let buffer = &mut temporary_buffer; - let data = OutputData { buffer }; - data_cb(data); + let len = temporary_buffer.len(); + let data = temporary_buffer.as_mut_ptr() as *mut (); + let sample_format = SampleFormat::F32; + let mut data = Data::from_parts(data, len, sample_format); + data_cb(&mut data); } // TODO: directly use a TypedArray once this is supported by stdweb @@ -281,7 +282,7 @@ where // TODO: handle latency better ; right now we just use setInterval with the amount of sound // data that is in each buffer ; this is obviously bad, and also the schedule is too tight // and there may be underflows - set_timeout(|| audio_callback_fn::(user_data_ptr), 330); + set_timeout(|| audio_callback_fn::(user_data_ptr), 330); } } From 3fdf18984885323bd55f58f22f64721b0296b91b Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Sun, 19 Jan 2020 16:16:09 +0100 Subject: [PATCH 10/14] Update docs for addition of the new stream Data type --- src/lib.rs | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 07f06ab..b701141 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -55,14 +55,14 @@ //! Now that we have everything for the stream, we are ready to create it from our selected device: //! //! ```no_run -//! use cpal::OutputData; +//! use cpal::Data; //! use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; //! # let host = cpal::default_host(); //! # let device = host.default_output_device().unwrap(); //! # let format = device.default_output_format().unwrap(); //! let stream = device.build_output_stream( //! &format, -//! move |data: OutputData| { +//! move |data: &mut Data| { //! // react to stream events and read or write stream data here. //! }, //! move |err| { @@ -72,9 +72,8 @@ //! ``` //! //! While the stream is running, the selected audio device will periodically call the data callback -//! that was passed to the function. The callback is passed an instance of either `InputData` or -//! `OutputData` depending on whether the stream is an input stream or output stream -//! respectively. Type `T` represents the desired sample format type. Supported format types +//! that was passed to the function. The callback is passed an instance of either `&Data` or +//! `&mut Data` depending on whether the stream is an input stream or output stream respectively. //! //! > **Note**: 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 @@ -85,22 +84,24 @@ //! > please share your issue and use-case with the CPAL team on the github issue tracker for //! > consideration.* //! -//! In this example, we simply fill the given output buffer with zeroes. +//! In this example, we simply fill the given output buffer with silence. //! //! ```no_run -//! use cpal::{OutputData, Sample, SampleFormat}; +//! use cpal::{Data, Sample, SampleFormat}; //! use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; //! # let host = cpal::default_host(); //! # let device = host.default_output_device().unwrap(); //! # let format = device.default_output_format().unwrap(); -//! let err_fn = move |err| eprintln!("an error occurred on the output audio stream: {}", err); -//! let stream = match format.data_type { -//! SampleFormat::F32 => device.build_output_stream(&format, write_silence::, err_fn), -//! SampleFormat::I16 => device.build_output_stream(&format, write_silence::, err_fn), -//! SampleFormat::U16 => device.build_output_stream(&format, write_silence::, err_fn), +//! 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() { +//! SampleFormat::F32 => write_silence::(data), +//! SampleFormat::I16 => write_silence::(data), +//! SampleFormat::U16 => write_silence::(data), //! }; +//! let stream = device.build_output_stream(&format, data_fn, err_fn).unwrap(); //! -//! fn write_silence(mut data: OutputData) { +//! fn write_silence(data: &mut Data) { +//! let data = data.as_slice_mut::().unwrap(); //! for sample in data.iter_mut() { //! *sample = Sample::from(&0.0); //! } @@ -115,7 +116,7 @@ //! # let host = cpal::default_host(); //! # let device = host.default_output_device().unwrap(); //! # let format = device.default_output_format().unwrap(); -//! # let data_fn = move |_data: cpal::OutputData| {}; +//! # let data_fn = move |_data: &mut cpal::Data| {}; //! # let err_fn = move |_err| {}; //! # let stream = device.build_output_stream(&format, data_fn, err_fn).unwrap(); //! stream.play().unwrap(); @@ -129,7 +130,7 @@ //! # let host = cpal::default_host(); //! # let device = host.default_output_device().unwrap(); //! # let format = device.default_output_format().unwrap(); -//! # let data_fn = move |_data: cpal::OutputData| {}; +//! # let data_fn = move |_data: &mut cpal::Data| {}; //! # let err_fn = move |_err| {}; //! # let stream = device.build_output_stream(&format, data_fn, err_fn).unwrap(); //! stream.pause().unwrap(); From dbb1cc414066b75681028ff85a8e4f770718f6d0 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Sun, 19 Jan 2020 19:05:17 +0100 Subject: [PATCH 11/14] Add some notes for devs about the safety requirements of `Data` --- src/lib.rs | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index b701141..b77d454 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -205,6 +205,14 @@ pub struct Data { impl Data { // Internal constructor for host implementations to use. + // + // The following requirements must be met in order for the safety of `Data`'s public API. + // + // - The `data` pointer must point to the first sample in the slice containing all samples. + // - The `len` must describe the length of the buffer as a number of samples in the expected + // format specified via the `sample_format` argument. + // - The `sample_format` must correctly represent the underlying sample data delivered/expected + // by the stream. pub(crate) unsafe fn from_parts( data: *mut (), len: usize, @@ -228,9 +236,11 @@ impl Data { /// The raw slice of memory representing the underlying audio data as a slice of bytes. /// - /// It is up to the user to interprate the slice of memory based on `Data::sample_format`. + /// It is up to the user to interpret the slice of memory based on `Data::sample_format`. pub fn bytes(&self) -> &[u8] { let len = self.len * self.sample_format.sample_size(); + // The safety of this block relies on correct construction of the `Data` instance. See + // the unsafe `from_parts` constructor for these requirements. unsafe { std::slice::from_raw_parts(self.data as *const u8, len) } @@ -238,9 +248,11 @@ impl Data { /// The raw slice of memory representing the underlying audio data as a slice of bytes. /// - /// It is up to the user to interprate the slice of memory based on `Data::sample_format`. + /// It is up to the user to interpret the slice of memory based on `Data::sample_format`. pub fn bytes_mut(&mut self) -> &mut [u8] { let len = self.len * self.sample_format.sample_size(); + // The safety of this block relies on correct construction of the `Data` instance. See + // the unsafe `from_parts` constructor for these requirements. unsafe { std::slice::from_raw_parts_mut(self.data as *mut u8, len) } @@ -254,6 +266,8 @@ impl Data { T: Sample, { if T::FORMAT == self.sample_format { + // The safety of this block relies on correct construction of the `Data` instance. See + // the unsafe `from_parts` constructor for these requirements. unsafe { Some(std::slice::from_raw_parts(self.data as *const T, self.len)) } @@ -270,6 +284,8 @@ impl Data { T: Sample, { if T::FORMAT == self.sample_format { + // The safety of this block relies on correct construction of the `Data` instance. See + // the unsafe `from_parts` constructor for these requirements. unsafe { Some(std::slice::from_raw_parts_mut(self.data as *mut T, self.len)) } From 1b5cf579cb22ecfd3482457f5301aecf0f37aa11 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Sun, 19 Jan 2020 19:17:40 +0100 Subject: [PATCH 12/14] Update WASAPI backend for addition of new stream `Data` type --- src/host/wasapi/device.rs | 14 +++----- src/host/wasapi/stream.rs | 74 +++++++++++++++------------------------ 2 files changed, 34 insertions(+), 54 deletions(-) diff --git a/src/host/wasapi/device.rs b/src/host/wasapi/device.rs index db5c154..de8aa3e 100644 --- a/src/host/wasapi/device.rs +++ b/src/host/wasapi/device.rs @@ -1,12 +1,10 @@ use crate::{ BackendSpecificError, + Data, DefaultFormatError, DeviceNameError, DevicesError, Format, - InputData, - OutputData, - Sample, SampleFormat, SampleRate, SupportedFormat, @@ -106,30 +104,28 @@ impl DeviceTrait for Device { Device::default_output_format(self) } - fn build_input_stream( + fn build_input_stream( &self, format: &Format, data_callback: D, error_callback: E, ) -> Result where - T: Sample, - D: FnMut(InputData) + Send + 'static, + D: FnMut(&Data) + Send + 'static, E: FnMut(StreamError) + Send + 'static, { let stream_inner = self.build_input_stream_inner(format)?; Ok(Stream::new_input(stream_inner, data_callback, error_callback)) } - fn build_output_stream( + fn build_output_stream( &self, format: &Format, data_callback: D, error_callback: E, ) -> Result where - T: Sample, - D: FnMut(OutputData) + Send + 'static, + D: FnMut(&mut Data) + Send + 'static, E: FnMut(StreamError) + Send + 'static, { let stream_inner = self.build_output_stream_inner(format)?; diff --git a/src/host/wasapi/stream.rs b/src/host/wasapi/stream.rs index 1c49a13..cfeb71f 100644 --- a/src/host/wasapi/stream.rs +++ b/src/host/wasapi/stream.rs @@ -1,17 +1,14 @@ use crate::{ BackendSpecificError, - InputData, - OutputData, + Data, PauseStreamError, PlayStreamError, - Sample, SampleFormat, StreamError, }; use crate::traits::StreamTrait; use std::mem; use std::ptr; -use std::slice; use std::sync::mpsc::{channel, Receiver, Sender}; use std::thread::{self, JoinHandle}; use super::check_result; @@ -85,14 +82,13 @@ pub struct StreamInner { } impl Stream { - pub(crate) fn new_input( + pub(crate) fn new_input( stream_inner: StreamInner, mut data_callback: D, mut error_callback: E, ) -> Stream where - T: Sample, - D: FnMut(InputData) + Send + 'static, + D: FnMut(&Data) + Send + 'static, E: FnMut(StreamError) + Send + 'static, { let pending_scheduled_event = @@ -115,14 +111,13 @@ impl Stream { } } - pub(crate) fn new_output( + pub(crate) fn new_output( stream_inner: StreamInner, mut data_callback: D, mut error_callback: E, ) -> Stream where - T: Sample, - D: FnMut(OutputData) + Send + 'static, + D: FnMut(&mut Data) + Send + 'static, E: FnMut(StreamError) + Send + 'static, { let pending_scheduled_event = @@ -285,13 +280,11 @@ fn stream_error_from_hresult(hresult: winnt::HRESULT) -> Result<(), StreamError> Ok(()) } -fn run_input( +fn run_input( mut run_ctxt: RunContext, - data_callback: &mut dyn FnMut(InputData), + data_callback: &mut dyn FnMut(&Data), error_callback: &mut dyn FnMut(StreamError), -) where - T: Sample, -{ +) { loop { match process_commands_and_await_signal(&mut run_ctxt, error_callback) { Some(ControlFlow::Break) => break, @@ -309,13 +302,11 @@ fn run_input( } } -fn run_output( +fn run_output( mut run_ctxt: RunContext, - data_callback: &mut dyn FnMut(OutputData), + data_callback: &mut dyn FnMut(&mut Data), error_callback: &mut dyn FnMut(StreamError), -) where - T: Sample, -{ +) { loop { match process_commands_and_await_signal(&mut run_ctxt, error_callback) { Some(ControlFlow::Break) => break, @@ -371,15 +362,12 @@ fn process_commands_and_await_signal( } // The loop for processing pending input data. -fn process_input( +fn process_input( stream: &StreamInner, capture_client: *mut audioclient::IAudioCaptureClient, - data_callback: &mut dyn FnMut(InputData), + data_callback: &mut dyn FnMut(&Data), error_callback: &mut dyn FnMut(StreamError), -) -> ControlFlow -where - T: Sample, -{ +) -> ControlFlow { let mut frames_available = 0; unsafe { // Get the available data in the shared buffer. @@ -412,15 +400,13 @@ where debug_assert!(!buffer.is_null()); - let buffer_len = frames_available as usize + let data = buffer as *mut (); + let len = frames_available as usize * stream.bytes_per_frame as usize - / mem::size_of::(); + / stream.sample_format.sample_size(); + let data = Data::from_parts(data, len, stream.sample_format); + data_callback(&data); - // Simplify the capture callback sample format branches. - let buffer_data = buffer as *mut _ as *const T; - let slice = slice::from_raw_parts(buffer_data, buffer_len); - let input_data = InputData { buffer: slice }; - data_callback(input_data); // Release the buffer. let hresult = (*capture_client).ReleaseBuffer(frames_available); if let Err(err) = stream_error_from_hresult(hresult) { @@ -432,15 +418,12 @@ where } // The loop for writing output data. -fn process_output( +fn process_output( stream: &StreamInner, render_client: *mut audioclient::IAudioRenderClient, - data_callback: &mut dyn FnMut(OutputData), + data_callback: &mut dyn FnMut(&mut Data), error_callback: &mut dyn FnMut(StreamError), -) -> ControlFlow -where - T: Sample, -{ +) -> ControlFlow { // The number of frames available for writing. let frames_available = match get_available_frames(&stream) { Ok(0) => return ControlFlow::Continue, // TODO: Can this happen? @@ -462,13 +445,14 @@ where } debug_assert!(!buffer.is_null()); - let buffer_len = - frames_available as usize * stream.bytes_per_frame as usize / mem::size_of::(); - let buffer_data = buffer as *mut T; - let slice = slice::from_raw_parts_mut(buffer_data, buffer_len); - let output_data = OutputData { buffer: slice }; - data_callback(output_data); + let data = buffer as *mut (); + let len = frames_available as usize + * stream.bytes_per_frame as usize + / stream.sample_format.sample_size(); + let mut data = Data::from_parts(data, len, stream.sample_format); + data_callback(&mut data); + let hresult = (*render_client).ReleaseBuffer(frames_available as u32, 0); if let Err(err) = stream_error_from_hresult(hresult) { error_callback(err); From c0a28b5198bf9fe942072294badb4fd6b4a060ec Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Sun, 19 Jan 2020 19:26:23 +0100 Subject: [PATCH 13/14] Update ASIO host for addition of new stream `Data` type. --- src/host/asio/mod.rs | 14 ++++------- src/host/asio/stream.rs | 54 ++++++++++++++++++++--------------------- 2 files changed, 32 insertions(+), 36 deletions(-) diff --git a/src/host/asio/mod.rs b/src/host/asio/mod.rs index b667b6d..3295470 100644 --- a/src/host/asio/mod.rs +++ b/src/host/asio/mod.rs @@ -3,15 +3,13 @@ extern crate parking_lot; use crate::{ BuildStreamError, + Data, DefaultFormatError, DeviceNameError, DevicesError, Format, - InputData, - OutputData, PauseStreamError, PlayStreamError, - Sample, StreamError, SupportedFormatsError, }; @@ -91,29 +89,27 @@ impl DeviceTrait for Device { Device::default_output_format(self) } - fn build_input_stream( + fn build_input_stream( &self, format: &Format, data_callback: D, error_callback: E, ) -> Result where - T: Sample, - D: FnMut(InputData) + Send + 'static, + D: FnMut(&Data) + Send + 'static, E: FnMut(StreamError) + Send + 'static { Device::build_input_stream(self, format, data_callback, error_callback) } - fn build_output_stream( + fn build_output_stream( &self, format: &Format, data_callback: D, error_callback: E, ) -> Result where - T: Sample, - D: FnMut(OutputData) + Send + 'static, + D: FnMut(&mut Data) + Send + 'static, E: FnMut(StreamError) + Send + 'static { Device::build_output_stream(self, format, data_callback, error_callback) diff --git a/src/host/asio/stream.rs b/src/host/asio/stream.rs index 0ad3710..696491c 100644 --- a/src/host/asio/stream.rs +++ b/src/host/asio/stream.rs @@ -9,9 +9,8 @@ use std::sync::Arc; use super::parking_lot::Mutex; use BackendSpecificError; use BuildStreamError; +use Data; use Format; -use InputData; -use OutputData; use PauseStreamError; use PlayStreamError; use Sample; @@ -58,18 +57,16 @@ impl Stream { } impl Device { - pub fn build_input_stream( + pub fn build_input_stream( &self, format: &Format, mut data_callback: D, _error_callback: E, ) -> Result where - T: Sample, - D: FnMut(InputData) + Send + 'static, + D: FnMut(&Data) + Send + 'static, E: FnMut(StreamError) + Send + 'static, { - assert_eq!(format.data_type, T::FORMAT, "sample type does not match `format.data_type`"); let stream_type = self.driver.input_data_type().map_err(build_stream_err)?; // Ensure that the desired sample type is supported. @@ -118,7 +115,7 @@ impl Device { where A: AsioSample, B: Sample, - D: FnMut(InputData) + Send + 'static, + D: FnMut(&Data) + Send + 'static, F: Fn(A) -> A, { // 1. Write the ASIO channels to the CPAL buffer. @@ -132,13 +129,15 @@ impl Device { } // 2. Deliver the interleaved buffer to the callback. - let data = InputData { buffer: interleaved }; - callback(data); + let data = interleaved.as_mut_ptr() as *mut (); + let len = interleaved.len(); + let data = Data::from_parts(data, len, B::FORMAT); + callback(&data); } match (&stream_type, data_type) { (&sys::AsioSampleType::ASIOSTInt16LSB, SampleFormat::I16) => { - process_input_callback::( + process_input_callback::( &mut data_callback, &mut interleaved, asio_stream, @@ -147,7 +146,7 @@ impl Device { ); } (&sys::AsioSampleType::ASIOSTInt16MSB, SampleFormat::I16) => { - process_input_callback::( + process_input_callback::( &mut data_callback, &mut interleaved, asio_stream, @@ -160,7 +159,7 @@ impl Device { // trait for the `to_le` and `to_be` methods, but this does not support floats. (&sys::AsioSampleType::ASIOSTFloat32LSB, SampleFormat::F32) | (&sys::AsioSampleType::ASIOSTFloat32MSB, SampleFormat::F32) => { - process_input_callback::( + process_input_callback::( &mut data_callback, &mut interleaved, asio_stream, @@ -173,7 +172,7 @@ impl Device { // `process_output_callback` function above by removing the unnecessary sample // conversion function. (&sys::AsioSampleType::ASIOSTInt32LSB, SampleFormat::I16) => { - process_input_callback::( + process_input_callback::( &mut data_callback, &mut interleaved, asio_stream, @@ -182,7 +181,7 @@ impl Device { ); } (&sys::AsioSampleType::ASIOSTInt32MSB, SampleFormat::I16) => { - process_input_callback::( + process_input_callback::( &mut data_callback, &mut interleaved, asio_stream, @@ -194,7 +193,7 @@ impl Device { // trait for the `to_le` and `to_be` methods, but this does not support floats. (&sys::AsioSampleType::ASIOSTFloat64LSB, SampleFormat::F32) | (&sys::AsioSampleType::ASIOSTFloat64MSB, SampleFormat::F32) => { - process_input_callback::( + process_input_callback::( &mut data_callback, &mut interleaved, asio_stream, @@ -224,18 +223,16 @@ impl Device { }) } - pub fn build_output_stream( + pub fn build_output_stream( &self, format: &Format, mut data_callback: D, _error_callback: E, ) -> Result where - T: Sample, - D: FnMut(OutputData) + Send + 'static, + D: FnMut(&mut Data) + Send + 'static, E: FnMut(StreamError) + Send + 'static, { - assert_eq!(format.data_type, T::FORMAT, "sample type does not match `format.data_type`"); let stream_type = self.driver.output_data_type().map_err(build_stream_err)?; // Ensure that the desired sample type is supported. @@ -308,12 +305,15 @@ impl Device { where A: Sample, B: AsioSample, - D: FnMut(OutputData) + Send + 'static, + D: FnMut(&mut Data) + Send + 'static, F: Fn(B) -> B, { // 1. Render interleaved buffer from callback. let interleaved: &mut [A] = cast_slice_mut(interleaved); - callback(OutputData { buffer: interleaved }); + let data = interleaved.as_mut_ptr() as *mut (); + let len = interleaved.len(); + let mut data = Data::from_parts(data, len, A::FORMAT); + callback(&mut data); // 2. Silence ASIO channels if necessary. let n_channels = interleaved.len() / asio_stream.buffer_size as usize; @@ -337,7 +337,7 @@ impl Device { match (data_type, &stream_type) { (SampleFormat::I16, &sys::AsioSampleType::ASIOSTInt16LSB) => { - process_output_callback::( + process_output_callback::( &mut data_callback, &mut interleaved, silence, @@ -347,7 +347,7 @@ impl Device { ); } (SampleFormat::I16, &sys::AsioSampleType::ASIOSTInt16MSB) => { - process_output_callback::( + process_output_callback::( &mut data_callback, &mut interleaved, silence, @@ -361,7 +361,7 @@ impl Device { // trait for the `to_le` and `to_be` methods, but this does not support floats. (SampleFormat::F32, &sys::AsioSampleType::ASIOSTFloat32LSB) | (SampleFormat::F32, &sys::AsioSampleType::ASIOSTFloat32MSB) => { - process_output_callback::( + process_output_callback::( &mut data_callback, &mut interleaved, silence, @@ -375,7 +375,7 @@ impl Device { // `process_output_callback` function above by removing the unnecessary sample // conversion function. (SampleFormat::I16, &sys::AsioSampleType::ASIOSTInt32LSB) => { - process_output_callback::( + process_output_callback::( &mut data_callback, &mut interleaved, silence, @@ -385,7 +385,7 @@ impl Device { ); } (SampleFormat::I16, &sys::AsioSampleType::ASIOSTInt32MSB) => { - process_output_callback::( + process_output_callback::( &mut data_callback, &mut interleaved, silence, @@ -398,7 +398,7 @@ impl Device { // trait for the `to_le` and `to_be` methods, but this does not support floats. (SampleFormat::F32, &sys::AsioSampleType::ASIOSTFloat64LSB) | (SampleFormat::F32, &sys::AsioSampleType::ASIOSTFloat64MSB) => { - process_output_callback::( + process_output_callback::( &mut data_callback, &mut interleaved, silence, From 64f8fd12cc587fc23f2183871fa8c89ea35a0bda Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Sun, 19 Jan 2020 19:42:43 +0100 Subject: [PATCH 14/14] Update CoreAudio host for the addition of the new stream `Data` type --- src/host/coreaudio/mod.rs | 48 +++++++++++++++++---------------------- 1 file changed, 21 insertions(+), 27 deletions(-) diff --git a/src/host/coreaudio/mod.rs b/src/host/coreaudio/mod.rs index aeef5af..e56ab61 100644 --- a/src/host/coreaudio/mod.rs +++ b/src/host/coreaudio/mod.rs @@ -5,15 +5,13 @@ use crate::{ ChannelCount, BackendSpecificError, BuildStreamError, + Data, DefaultFormatError, DeviceNameError, DevicesError, Format, - InputData, - OutputData, PauseStreamError, PlayStreamError, - Sample, SampleFormat, SampleRate, StreamError, @@ -131,33 +129,29 @@ impl DeviceTrait for Device { Device::default_output_format(self) } - fn build_input_stream( + fn build_input_stream( &self, format: &Format, data_callback: D, error_callback: E, ) -> Result where - T: Sample, - D: FnMut(InputData) + Send + 'static, + D: FnMut(&Data) + Send + 'static, E: FnMut(StreamError) + Send + 'static, { - assert_eq!(T::FORMAT, format.data_type); Device::build_input_stream(self, format, data_callback, error_callback) } - fn build_output_stream( + fn build_output_stream( &self, format: &Format, data_callback: D, error_callback: E, ) -> Result where - T: Sample, - D: FnMut(OutputData) + Send + 'static, + D: FnMut(&mut Data) + Send + 'static, E: FnMut(StreamError) + Send + 'static, { - assert_eq!(T::FORMAT, format.data_type); Device::build_output_stream(self, format, data_callback, error_callback) } } @@ -500,15 +494,14 @@ fn audio_unit_from_device(device: &Device, input: bool) -> Result( + fn build_input_stream( &self, format: &Format, mut data_callback: D, _error_callback: E, ) -> Result where - T: Sample, - D: FnMut(InputData) + Send + 'static, + D: FnMut(&Data) + Send + 'static, E: FnMut(StreamError) + Send + 'static, { // The scope and element for working with a device's input stream. @@ -657,7 +650,8 @@ impl Device { // Register the callback that is being called by coreaudio whenever it needs data to be // fed to the audio buffer. - let bytes_per_channel = std::mem::size_of::(); + let sample_format = format.data_type; + let bytes_per_channel = sample_format.sample_size(); type Args = render_callback::Args; audio_unit.set_input_callback(move |args: Args| unsafe { let ptr = (*args.data.data).mBuffers.as_ptr() as *const AudioBuffer; @@ -671,10 +665,10 @@ impl Device { mData: data } = buffers[0]; - let data_len = (data_byte_size as usize / bytes_per_channel) as usize; - let data_slice = slice::from_raw_parts(data as *const T, data_len); - let input_data = InputData { buffer: data_slice }; - data_callback(input_data); + let data = data as *mut (); + let len = (data_byte_size as usize / bytes_per_channel) as usize; + let data = Data::from_parts(data, len, sample_format); + data_callback(&data); Ok(()) })?; @@ -687,15 +681,14 @@ impl Device { })) } - fn build_output_stream( + fn build_output_stream( &self, format: &Format, mut data_callback: D, _error_callback: E, ) -> Result where - T: Sample, - D: FnMut(OutputData) + Send + 'static, + D: FnMut(&mut Data) + Send + 'static, E: FnMut(StreamError) + Send + 'static, { let mut audio_unit = audio_unit_from_device(self, false)?; @@ -710,7 +703,8 @@ impl Device { // Register the callback that is being called by coreaudio whenever it needs data to be // fed to the audio buffer. - let bytes_per_channel = std::mem::size_of::(); + let sample_format = format.data_type; + let bytes_per_channel = sample_format.sample_size(); type Args = render_callback::Args; audio_unit.set_render_callback(move |args: Args| unsafe { // If `run()` is currently running, then a callback will be available from this list. @@ -722,10 +716,10 @@ impl Device { mData: data } = (*args.data.data).mBuffers[0]; - let data_len = (data_byte_size as usize / bytes_per_channel) as usize; - let data_slice = slice::from_raw_parts_mut(data as *mut T, data_len); - let output_data = OutputData { buffer: data_slice }; - data_callback(output_data); + let data = data as *mut (); + let len = (data_byte_size as usize / bytes_per_channel) as usize; + let mut data = Data::from_parts(data, len, sample_format); + data_callback(&mut data); Ok(()) })?;