From c97d1dd3fa1b9e162cc186cbdc9de43de832dea1 Mon Sep 17 00:00:00 2001 From: Tatsuyuki Ishi Date: Tue, 9 Jul 2019 15:47:33 +0900 Subject: [PATCH 01/41] Remove EventLoop and port the ALSA backend --- examples/beep.rs | 26 +- examples/feedback.rs | 128 +++--- examples/record_wav.rs | 101 +++-- src/host/alsa/mod.rs | 897 ++++++++++++++--------------------------- src/host/null/mod.rs | 58 +-- src/lib.rs | 5 +- src/platform/mod.rs | 160 ++------ src/traits.rs | 95 +---- 8 files changed, 519 insertions(+), 951 deletions(-) diff --git a/examples/beep.rs b/examples/beep.rs index 17c2bf9..d2c570c 100644 --- a/examples/beep.rs +++ b/examples/beep.rs @@ -1,37 +1,34 @@ extern crate anyhow; extern crate cpal; -use cpal::traits::{DeviceTrait, EventLoopTrait, HostTrait}; +use cpal::traits::{DeviceTrait, StreamTrait, HostTrait}; 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 format = device.default_output_format()?; - let event_loop = host.event_loop(); - let stream_id = event_loop.build_output_stream(&device, &format)?; - event_loop.play_stream(stream_id.clone())?; - let sample_rate = format.sample_rate.0 as f32; + let channels = format.channels; let mut sample_clock = 0f32; // Produce a sinusoid of maximum amplitude. - let mut next_value = || { + let mut next_value = move || { sample_clock = (sample_clock + 1.0) % sample_rate; (sample_clock * 440.0 * 2.0 * 3.141592 / sample_rate).sin() }; - event_loop.run(move |id, result| { + let stream = device.build_output_stream(&format, move |result| { let data = match result { Ok(data) => data, Err(err) => { - eprintln!("an error occurred on stream {:?}: {}", id, err); + eprintln!("an error occurred on stream: {}", err); return; } }; match data { cpal::StreamData::Output { buffer: cpal::UnknownTypeOutputBuffer::U16(mut buffer) } => { - for sample in buffer.chunks_mut(format.channels as usize) { + 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; @@ -39,7 +36,7 @@ fn main() -> Result<(), anyhow::Error> { } }, cpal::StreamData::Output { buffer: cpal::UnknownTypeOutputBuffer::I16(mut buffer) } => { - for sample in buffer.chunks_mut(format.channels as usize) { + 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; @@ -47,7 +44,7 @@ fn main() -> Result<(), anyhow::Error> { } }, cpal::StreamData::Output { buffer: cpal::UnknownTypeOutputBuffer::F32(mut buffer) } => { - for sample in buffer.chunks_mut(format.channels as usize) { + for sample in buffer.chunks_mut(channels as usize) { let value = next_value(); for out in sample.iter_mut() { *out = value; @@ -56,5 +53,10 @@ fn main() -> Result<(), anyhow::Error> { }, _ => (), } - }); + })?; + stream.play()?; + + std::thread::sleep(std::time::Duration::from_millis(1000)); + + Ok(()) } diff --git a/examples/feedback.rs b/examples/feedback.rs index 682bf37..11c1331 100644 --- a/examples/feedback.rs +++ b/examples/feedback.rs @@ -10,18 +10,21 @@ extern crate anyhow; extern crate cpal; extern crate ringbuf; -use cpal::traits::{DeviceTrait, EventLoopTrait, HostTrait}; +use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; use ringbuf::RingBuffer; const LATENCY_MS: f32 = 150.0; fn main() -> Result<(), anyhow::Error> { let host = cpal::default_host(); - let event_loop = host.event_loop(); // Default devices. - let input_device = host.default_input_device().expect("failed to get default input device"); - let output_device = host.default_output_device().expect("failed to get default output device"); + let input_device = host + .default_input_device() + .expect("failed to get default input device"); + let output_device = host + .default_output_device() + .expect("failed to get default output device"); println!("Using default input device: \"{}\"", input_device.name()?); println!("Using default output device: \"{}\"", output_device.name()?); @@ -29,12 +32,6 @@ fn main() -> Result<(), anyhow::Error> { let mut format = input_device.default_input_format()?; format.data_type = cpal::SampleFormat::F32; - // Build streams. - println!("Attempting to build both streams with `{:?}`.", format); - let input_stream_id = event_loop.build_input_stream(&input_device, &format)?; - let output_stream_id = event_loop.build_output_stream(&output_device, &format)?; - println!("Successfully built streams."); - // Create a delay in case the input and output devices aren't synced. let latency_frames = (LATENCY_MS / 1_000.0) * format.sample_rate.0 as f32; let latency_samples = latency_frames as usize * format.channels as usize; @@ -50,59 +47,78 @@ fn main() -> Result<(), anyhow::Error> { producer.push(0.0).unwrap(); } - // Play the streams. - println!("Starting the input and output streams with `{}` milliseconds of latency.", LATENCY_MS); - event_loop.play_stream(input_stream_id.clone())?; - event_loop.play_stream(output_stream_id.clone())?; + // Build streams. + println!("Attempting to build both streams with `{:?}`.", format); + let input_stream = input_device.build_input_stream(&format, move |result| { + let data = match result { + Ok(data) => data, + Err(err) => { + eprintln!("an error occurred on input stream: {}", err); + return; + }, + }; - // Run the event loop on a separate thread. - std::thread::spawn(move || { - event_loop.run(move |id, result| { - let data = match result { - Ok(data) => data, - Err(err) => { - eprintln!("an error occurred on stream {:?}: {}", id, err); - return; + 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; + } } - }; + if output_fell_behind { + eprintln!("output stream fell behind: try increasing latency"); + } + }, + _ => panic!("Expected input with f32 data"), + } + })?; + let output_stream = output_device.build_output_stream(&format, move |result| { + let data = match result { + Ok(data) => data, + Err(err) => { + eprintln!("an error occurred on output stream: {}", err); + return; + }, + }; + 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"), + } + })?; + println!("Successfully built streams."); - match data { - cpal::StreamData::Input { buffer: cpal::UnknownTypeInputBuffer::F32(buffer) } => { - assert_eq!(id, input_stream_id); - let mut output_fell_behind = false; - for &sample in buffer.iter() { - if producer.push(sample).is_err() { - output_fell_behind = true; - } - } - if output_fell_behind { - eprintln!("output stream fell behind: try increasing latency"); - } - }, - cpal::StreamData::Output { buffer: cpal::UnknownTypeOutputBuffer::F32(mut buffer) } => { - assert_eq!(id, output_stream_id); - 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(_) = input_fell_behind { - eprintln!("input stream fell behind: try increasing latency"); - } - }, - _ => panic!("we're expecting f32 data"), - } - }); - }); + // Play the streams. + println!( + "Starting the input and output streams with `{}` milliseconds of latency.", + LATENCY_MS + ); + input_stream.play()?; + output_stream.play()?; // Run for 3 seconds before closing. println!("Playing for 3 seconds... "); std::thread::sleep(std::time::Duration::from_secs(3)); + drop(input_stream); + drop(output_stream); println!("Done!"); Ok(()) } diff --git a/examples/record_wav.rs b/examples/record_wav.rs index 111ede8..d53f9b3 100644 --- a/examples/record_wav.rs +++ b/examples/record_wav.rs @@ -6,21 +6,21 @@ extern crate anyhow; extern crate cpal; extern crate hound; -use cpal::traits::{DeviceTrait, EventLoopTrait, HostTrait}; +use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; fn main() -> Result<(), anyhow::Error> { // Use the default host for working with audio devices. let host = cpal::default_host(); // Setup the default input device and stream with the default input format. - let device = host.default_input_device().expect("Failed to get default input device"); + let device = host + .default_input_device() + .expect("Failed to get default input device"); println!("Default input device: {}", device.name()?); - let format = device.default_input_format().expect("Failed to get default input format"); + let format = device + .default_input_format() + .expect("Failed to get default input format"); println!("Default input format: {:?}", format); - let event_loop = host.event_loop(); - let stream_id = event_loop.build_input_stream(&device, &format)?; - event_loop.play_stream(stream_id)?; - // The WAV file we're recording to. const PATH: &'static str = concat!(env!("CARGO_MANIFEST_DIR"), "/recorded.wav"); let spec = wav_spec_from_format(&format); @@ -29,63 +29,62 @@ fn main() -> Result<(), anyhow::Error> { // A flag to indicate that recording is in progress. println!("Begin recording..."); - let recording = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(true)); // Run the input stream on a separate thread. let writer_2 = writer.clone(); - let recording_2 = recording.clone(); - std::thread::spawn(move || { - event_loop.run(move |id, event| { - let data = match event { - Ok(data) => data, - Err(err) => { - eprintln!("an error occurred on stream {:?}: {}", id, err); - return; - } - }; - - // If we're done recording, return early. - if !recording_2.load(std::sync::atomic::Ordering::Relaxed) { + let stream = device.build_input_stream(&format, move |event| { + let data = match event { + Ok(data) => data, + Err(err) => { + eprintln!("an error occurred on stream: {}", err); return; - } - // 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(); - } + }, + }; + + // 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::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(); - } + } + }, + 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(); } } - }, - _ => (), - } - }); - }); + } + }, + _ => (), + } + })?; + stream.play()?; // Let recording go for roughly three seconds. std::thread::sleep(std::time::Duration::from_secs(3)); - recording.store(false, std::sync::atomic::Ordering::Relaxed); + drop(stream); writer.lock().unwrap().take().unwrap().finalize()?; println!("Recording {} complete!", PATH); Ok(()) diff --git a/src/host/alsa/mod.rs b/src/host/alsa/mod.rs index afc32c6..72c3afe 100644 --- a/src/host/alsa/mod.rs +++ b/src/host/alsa/mod.rs @@ -1,11 +1,14 @@ extern crate alsa_sys as alsa; extern crate libc; -pub use self::enumerate::{Devices, default_input_device, default_output_device}; +use std::{cmp, ffi, io, mem, ptr}; +use std::sync::Arc; +use std::thread::{self, JoinHandle}; +use std::vec::IntoIter as VecIntoIter; -use ChannelCount; use BackendSpecificError; use BuildStreamError; +use ChannelCount; use DefaultFormatError; use DeviceNameError; use DevicesError; @@ -14,20 +17,15 @@ use PauseStreamError; use PlayStreamError; use SampleFormat; use SampleRate; -use SupportedFormatsError; use StreamData; use StreamDataResult; -use StreamError; use SupportedFormat; +use SupportedFormatsError; +use traits::{DeviceTrait, HostTrait, StreamTrait}; use UnknownTypeInputBuffer; use UnknownTypeOutputBuffer; -use traits::{DeviceTrait, EventLoopTrait, HostTrait, StreamIdTrait}; -use std::{cmp, ffi, ptr}; -use std::sync::Mutex; -use std::sync::mpsc::{channel, Sender, Receiver}; -use std::sync::atomic::{AtomicUsize, Ordering}; -use std::vec::IntoIter as VecIntoIter; +pub use self::enumerate::{default_input_device, default_output_device, Devices}; pub type SupportedInputFormats = VecIntoIter; pub type SupportedOutputFormats = VecIntoIter; @@ -47,7 +45,6 @@ impl Host { impl HostTrait for Host { type Devices = Devices; type Device = Device; - type EventLoop = EventLoop; fn is_available() -> bool { // Assume ALSA is always available on linux/freebsd. @@ -65,15 +62,12 @@ impl HostTrait for Host { fn default_output_device(&self) -> Option { default_output_device() } - - fn event_loop(&self) -> Self::EventLoop { - EventLoop::new() - } } impl DeviceTrait for Device { type SupportedInputFormats = SupportedInputFormats; type SupportedOutputFormats = SupportedOutputFormats; + type Stream = Stream; fn name(&self) -> Result { Device::name(self) @@ -94,95 +88,132 @@ impl DeviceTrait for Device { fn default_output_format(&self) -> Result { Device::default_output_format(self) } -} -impl EventLoopTrait for EventLoop { - type Device = Device; - type StreamId = StreamId; - - fn build_input_stream( - &self, - device: &Self::Device, - format: &Format, - ) -> Result { - EventLoop::build_input_stream(self, device, format) + fn build_input_stream(&self, format: &Format, callback: F) -> Result where F: FnMut(StreamDataResult) + Send + 'static { + Ok(Stream::new(Arc::new(self.build_stream_inner(format, alsa::SND_PCM_STREAM_CAPTURE)?), callback)) } - fn build_output_stream( - &self, - device: &Self::Device, - format: &Format, - ) -> Result { - EventLoop::build_output_stream(self, device, format) - } - - fn play_stream(&self, stream: Self::StreamId) -> Result<(), PlayStreamError> { - EventLoop::play_stream(self, stream) - } - - fn pause_stream(&self, stream: Self::StreamId) -> Result<(), PauseStreamError> { - EventLoop::pause_stream(self, stream) - } - - fn destroy_stream(&self, stream: Self::StreamId) { - EventLoop::destroy_stream(self, stream) - } - - fn run(&self, callback: F) -> ! - where - F: FnMut(Self::StreamId, StreamDataResult) + Send, - { - EventLoop::run(self, callback) + fn build_output_stream(&self, format: &Format, callback: F) -> Result where F: FnMut(StreamDataResult) + Send + 'static { + Ok(Stream::new(Arc::new(self.build_stream_inner(format, alsa::SND_PCM_STREAM_PLAYBACK)?), callback)) } } -impl StreamIdTrait for StreamId {} -struct Trigger { - // [read fd, write fd] - fds: [libc::c_int; 2], -} +struct TriggerSender(libc::c_int); -impl Trigger { - fn new() -> Self { - let mut fds = [0, 0]; - match unsafe { libc::pipe(fds.as_mut_ptr()) } { - 0 => Trigger { fds: fds }, - _ => panic!("Could not create pipe"), - } - } - fn read_fd(&self) -> libc::c_int { - self.fds[0] - } - fn write_fd(&self) -> libc::c_int { - self.fds[1] - } +struct TriggerReceiver(libc::c_int); + +impl TriggerSender { fn wakeup(&self) { let buf = 1u64; - let ret = unsafe { libc::write(self.write_fd(), &buf as *const u64 as *const _, 8) }; + let ret = unsafe { libc::write(self.0, &buf as *const u64 as *const _, 8) }; assert!(ret == 8); } +} + +impl TriggerReceiver { fn clear_pipe(&self) { let mut out = 0u64; - let ret = unsafe { libc::read(self.read_fd(), &mut out as *mut u64 as *mut _, 8) }; + let ret = unsafe { libc::read(self.0, &mut out as *mut u64 as *mut _, 8) }; assert_eq!(ret, 8); } } -impl Drop for Trigger { +fn trigger() -> (TriggerSender, TriggerReceiver) { + let mut fds = [0, 0]; + match unsafe { libc::pipe(fds.as_mut_ptr()) } { + 0 => (TriggerSender(fds[1]), TriggerReceiver(fds[0])), + _ => panic!("Could not create pipe"), + } +} + +impl Drop for TriggerSender { fn drop(&mut self) { unsafe { - libc::close(self.fds[0]); - libc::close(self.fds[1]); + libc::close(self.0); } } } +impl Drop for TriggerReceiver { + fn drop(&mut self) { + unsafe { + libc::close(self.0); + } + } +} #[derive(Clone, Debug, PartialEq, Eq)] pub struct Device(String); impl Device { + 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 { + let mut handle = ptr::null_mut(); + match alsa::snd_pcm_open( + &mut handle, + name.as_ptr(), + stream_type, + alsa::SND_PCM_NONBLOCK, + ) { + -16 /* determined empirically */ => return Err(BuildStreamError::DeviceNotAvailable), + -22 => return Err(BuildStreamError::InvalidArgument), + e => if let Err(description) = check_errors(e) { + let err = BackendSpecificError { description }; + return Err(err.into()); + } + } + handle + }; + let can_pause = unsafe { + let hw_params = HwParams::alloc(); + set_hw_params_from_format(handle, &hw_params, format) + .map_err(|description| BackendSpecificError { description })?; + + alsa::snd_pcm_hw_params_can_pause(hw_params.0) == 1 + }; + let (buffer_len, period_len) = unsafe { + set_sw_params_from_format(handle, format) + .map_err(|description| BackendSpecificError { description })? + }; + + if let Err(desc) = check_errors(unsafe { alsa::snd_pcm_prepare(handle) }) { + let description = format!("could not get handle: {}", desc); + let err = BackendSpecificError { description }; + return Err(err.into()); + } + + let num_descriptors = { + let num_descriptors = unsafe { alsa::snd_pcm_poll_descriptors_count(handle) }; + if num_descriptors == 0 { + let description = "poll descriptor count for stream was 0".to_string(); + let err = BackendSpecificError { description }; + return Err(err.into()); + } + num_descriptors as usize + }; + + let stream_inner = StreamInner { + channel: handle, + sample_format: format.data_type, + num_descriptors, + num_channels: format.channels as u16, + buffer_len, + period_len, + can_pause, + }; + + if let Err(desc) = check_errors(unsafe { alsa::snd_pcm_start(handle) }) { + let description = format!("could not start stream: {}", desc); + let err = BackendSpecificError { description }; + return Err(err.into()); + } + + Ok(stream_inner) + } + #[inline] fn name(&self) -> Result { Ok(self.0.clone()) @@ -450,48 +481,7 @@ impl Device { } } -pub struct EventLoop { - // Each newly-created stream gets a new ID from this counter. The counter is then incremented. - next_stream_id: AtomicUsize, // TODO: use AtomicU64 when stable? - - // A trigger that uses a `pipe()` as backend. Signalled whenever a new command is ready, so - // that `poll()` can wake up and pick the changes. - pending_command_trigger: Trigger, - - // This field is locked by the `run()` method. - // The mutex also ensures that only one thread at a time has `run()` running. - run_context: Mutex, - - // Commands processed by the `run()` method that is currently running. - commands: Sender, -} - -unsafe impl Send for EventLoop { -} - -unsafe impl Sync for EventLoop { -} - -enum Command { - NewStream(StreamInner), - PlayStream(StreamId), - PauseStream(StreamId), - DestroyStream(StreamId), -} - -struct RunContext { - // Descriptors to wait for. Always contains `pending_command_trigger.read_fd()` as first element. - descriptors: Vec, - // List of streams that are written in `descriptors`. - streams: Vec, - - commands: Receiver, -} - struct StreamInner { - // The id of the stream. - id: StreamId, - // The ALSA channel. channel: *mut alsa::snd_pcm_t, @@ -513,501 +503,230 @@ struct StreamInner { // Whether or not the hardware supports pausing the stream. can_pause: bool, - - // Whether or not the sample stream is currently paused. - is_paused: bool, - - // A file descriptor opened with `eventfd`. - // It is used to wait for resume signal. - resume_trigger: Trigger, - - // Lazily allocated buffer that is reused inside the loop. - // Zero-allocate a new buffer (the fastest way to have zeroed memory) at the first time this is - // used. - buffer: Vec, } -#[derive(Copy, Debug, Clone, PartialEq, Eq, Hash)] -pub struct StreamId(usize); +// Assume that the ALSA library is built with thread safe option. +unsafe impl Send for StreamInner {} + +unsafe impl Sync for StreamInner {} enum StreamType { Input, Output } +pub struct Stream { + /// The high-priority audio processing thread calling callbacks. + /// Option used for moving out in destructor. + thread: Option>, -impl EventLoop { - #[inline] - fn new() -> EventLoop { - let pending_command_trigger = Trigger::new(); + /// Handle to the underlying stream for playback controls. + inner: Arc, - let mut initial_descriptors = vec![]; - reset_descriptors_with_pending_command_trigger( - &mut initial_descriptors, - &pending_command_trigger, - ); - - let (tx, rx) = channel(); - - let run_context = Mutex::new(RunContext { - descriptors: initial_descriptors, - streams: Vec::new(), - commands: rx, - }); - - EventLoop { - next_stream_id: AtomicUsize::new(0), - pending_command_trigger: pending_command_trigger, - run_context, - commands: tx, - } - } - - #[inline] - fn run(&self, mut callback: F) -> ! - where F: FnMut(StreamId, StreamDataResult) - { - self.run_inner(&mut callback) - } - - fn run_inner(&self, callback: &mut dyn FnMut(StreamId, StreamDataResult)) -> ! { - unsafe { - let mut run_context = self.run_context.lock().unwrap(); - let run_context = &mut *run_context; - - 'stream_loop: loop { - process_commands(run_context); - - reset_descriptors_with_pending_command_trigger( - &mut run_context.descriptors, - &self.pending_command_trigger, - ); - append_stream_poll_descriptors(run_context); - - // At this point, this should include the command `pending_commands_trigger` along - // with the poll descriptors for each stream. - match poll_all_descriptors(&mut run_context.descriptors) { - Ok(true) => (), - Ok(false) => continue, - Err(err) => { - for stream in run_context.streams.iter() { - let result = Err(err.clone().into()); - callback(stream.id, result); - } - run_context.streams.clear(); - break 'stream_loop; - } - } - - // If the `pending_command_trigger` was signaled, we need to process the comands. - if run_context.descriptors[0].revents != 0 { - run_context.descriptors[0].revents = 0; - self.pending_command_trigger.clear_pipe(); - } - - // The set of streams that error within the following loop and should be removed. - let mut streams_to_remove: Vec<(StreamId, StreamError)> = vec![]; - - // Iterate over each individual stream/descriptor. - let mut i_stream = 0; - let mut i_descriptor = 1; - while (i_descriptor as usize) < run_context.descriptors.len() { - let stream = &mut run_context.streams[i_stream]; - let stream_descriptor_ptr = run_context.descriptors.as_mut_ptr().offset(i_descriptor); - - // Only go on if this event was a pollout or pollin event. - let stream_type = match check_for_pollout_or_pollin(stream, stream_descriptor_ptr) { - Ok(Some(ty)) => ty, - Ok(None) => { - i_descriptor += stream.num_descriptors as isize; - i_stream += 1; - continue; - }, - Err(err) => { - streams_to_remove.push((stream.id, err.into())); - i_descriptor += stream.num_descriptors as isize; - i_stream += 1; - continue; - } - }; - - // Get the number of available samples for reading/writing. - let available_samples = match get_available_samples(stream) { - Ok(n) => n, - Err(err) => { - streams_to_remove.push((stream.id, err.into())); - i_descriptor += stream.num_descriptors as isize; - i_stream += 1; - continue; - } - }; - - // Only go on if there is at least `stream.period_len` samples. - if available_samples < stream.period_len { - i_descriptor += stream.num_descriptors as isize; - i_stream += 1; - continue; - } - - // Prepare the data buffer. - let buffer_size = stream.sample_format.sample_size() * available_samples; - stream.buffer.resize(buffer_size, 0u8); - let available_frames = available_samples / stream.num_channels as usize; - - match stream_type { - StreamType::Input => { - let result = alsa::snd_pcm_readi( - stream.channel, - stream.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); - let err = BackendSpecificError { description }; - streams_to_remove.push((stream.id, err.into())); - continue; - } - - let input_buffer = match stream.sample_format { - SampleFormat::I16 => UnknownTypeInputBuffer::I16(::InputBuffer { - buffer: cast_input_buffer(&mut stream.buffer), - }), - SampleFormat::U16 => UnknownTypeInputBuffer::U16(::InputBuffer { - buffer: cast_input_buffer(&mut stream.buffer), - }), - SampleFormat::F32 => UnknownTypeInputBuffer::F32(::InputBuffer { - buffer: cast_input_buffer(&mut stream.buffer), - }), - }; - let stream_data = StreamData::Input { - buffer: input_buffer, - }; - callback(stream.id, Ok(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: cast_output_buffer(&mut stream.buffer), - }), - SampleFormat::U16 => UnknownTypeOutputBuffer::U16(::OutputBuffer { - buffer: cast_output_buffer(&mut stream.buffer), - }), - SampleFormat::F32 => UnknownTypeOutputBuffer::F32(::OutputBuffer { - buffer: cast_output_buffer(&mut stream.buffer), - }), - }; - - let stream_data = StreamData::Output { - buffer: output_buffer, - }; - callback(stream.id, Ok(stream_data)); - } - loop { - let result = alsa::snd_pcm_writei( - stream.channel, - stream.buffer.as_ptr() as *const _, - available_frames as alsa::snd_pcm_uframes_t, - ); - - if result as i32 == -libc::EPIPE { - // buffer underrun - // TODO: Notify the user of this. - 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); - let err = BackendSpecificError { description }; - streams_to_remove.push((stream.id, err.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, - ); - let err = BackendSpecificError { description }; - streams_to_remove.push((stream.id, err.into())); - continue; - } else { - break; - } - } - }, - } - } - - // Remove any streams that have errored and notify the user. - for (stream_id, err) in streams_to_remove { - run_context.streams.retain(|s| s.id != stream_id); - callback(stream_id, Err(err.into())); - } - } - } - - panic!("`cpal::EventLoop::run` API currently disallows returning"); - } - - fn build_input_stream( - &self, - device: &Device, - format: &Format, - ) -> Result - { - unsafe { - let name = ffi::CString::new(device.0.clone()).expect("unable to clone device"); - - let mut capture_handle = ptr::null_mut(); - match alsa::snd_pcm_open( - &mut capture_handle, - name.as_ptr(), - alsa::SND_PCM_STREAM_CAPTURE, - alsa::SND_PCM_NONBLOCK, - ) { - -16 /* determined empirically */ => return Err(BuildStreamError::DeviceNotAvailable), - -22 => return Err(BuildStreamError::InvalidArgument), - e => if let Err(description) = check_errors(e) { - let err = BackendSpecificError { description }; - return Err(err.into()); - } - } - let hw_params = HwParams::alloc(); - - set_hw_params_from_format(capture_handle, &hw_params, format) - .map_err(|description| BackendSpecificError { description })?; - - let can_pause = alsa::snd_pcm_hw_params_can_pause(hw_params.0) == 1; - - let (buffer_len, period_len) = set_sw_params_from_format(capture_handle, format) - .map_err(|description| BackendSpecificError { description })?; - - if let Err(desc) = check_errors(alsa::snd_pcm_prepare(capture_handle)) { - let description = format!("could not get capture handle: {}", desc); - let err = BackendSpecificError { description }; - return Err(err.into()); - } - - let num_descriptors = { - let num_descriptors = alsa::snd_pcm_poll_descriptors_count(capture_handle); - if num_descriptors == 0 { - let description = "poll descriptor count for capture stream was 0".to_string(); - let err = BackendSpecificError { description }; - return Err(err.into()); - } - num_descriptors as usize - }; - - let new_stream_id = StreamId(self.next_stream_id.fetch_add(1, Ordering::Relaxed)); - if new_stream_id.0 == usize::max_value() { - panic!("number of streams used has overflowed usize"); - } - - let stream_inner = StreamInner { - id: new_stream_id.clone(), - channel: capture_handle, - sample_format: format.data_type, - num_descriptors: num_descriptors, - num_channels: format.channels as u16, - buffer_len: buffer_len, - period_len: period_len, - can_pause: can_pause, - is_paused: false, - resume_trigger: Trigger::new(), - buffer: vec![], - }; - - if let Err(desc) = check_errors(alsa::snd_pcm_start(capture_handle)) { - let description = format!("could not start capture stream: {}", desc); - let err = BackendSpecificError { description }; - return Err(err.into()); - } - - self.push_command(Command::NewStream(stream_inner)); - Ok(new_stream_id) - } - } - - fn build_output_stream( - &self, - device: &Device, - format: &Format, - ) -> Result - { - unsafe { - let name = ffi::CString::new(device.0.clone()).expect("unable to clone device"); - - let mut playback_handle = ptr::null_mut(); - match alsa::snd_pcm_open( - &mut playback_handle, - name.as_ptr(), - alsa::SND_PCM_STREAM_PLAYBACK, - alsa::SND_PCM_NONBLOCK, - ) { - -16 /* determined empirically */ => return Err(BuildStreamError::DeviceNotAvailable), - -22 => return Err(BuildStreamError::InvalidArgument), - e => if let Err(description) = check_errors(e) { - let err = BackendSpecificError { description }; - return Err(err.into()) - } - } - let hw_params = HwParams::alloc(); - - set_hw_params_from_format(playback_handle, &hw_params, format) - .map_err(|description| BackendSpecificError { description })?; - - let can_pause = alsa::snd_pcm_hw_params_can_pause(hw_params.0) == 1; - - let (buffer_len, period_len) = set_sw_params_from_format(playback_handle, format) - .map_err(|description| BackendSpecificError { description })?; - - if let Err(desc) = check_errors(alsa::snd_pcm_prepare(playback_handle)) { - let description = format!("could not get playback handle: {}", desc); - let err = BackendSpecificError { description }; - return Err(err.into()); - } - - let num_descriptors = { - let num_descriptors = alsa::snd_pcm_poll_descriptors_count(playback_handle); - if num_descriptors == 0 { - let description = "poll descriptor count for playback stream was 0".to_string(); - let err = BackendSpecificError { description }; - return Err(err.into()); - } - num_descriptors as usize - }; - - let new_stream_id = StreamId(self.next_stream_id.fetch_add(1, Ordering::Relaxed)); - if new_stream_id.0 == usize::max_value() { - return Err(BuildStreamError::StreamIdOverflow); - } - - let stream_inner = StreamInner { - id: new_stream_id.clone(), - channel: playback_handle, - sample_format: format.data_type, - num_descriptors: num_descriptors, - num_channels: format.channels as u16, - buffer_len: buffer_len, - period_len: period_len, - can_pause: can_pause, - is_paused: false, - resume_trigger: Trigger::new(), - buffer: vec![], - }; - - self.push_command(Command::NewStream(stream_inner)); - Ok(new_stream_id) - } - } - - #[inline] - fn push_command(&self, command: Command) { - // Safe to unwrap: sender outlives receiver. - self.commands.send(command).unwrap(); - self.pending_command_trigger.wakeup(); - } - - #[inline] - fn destroy_stream(&self, stream_id: StreamId) { - self.push_command(Command::DestroyStream(stream_id)); - } - - #[inline] - fn play_stream(&self, stream_id: StreamId) -> Result<(), PlayStreamError> { - self.push_command(Command::PlayStream(stream_id)); - Ok(()) - } - - #[inline] - fn pause_stream(&self, stream_id: StreamId) -> Result<(), PauseStreamError> { - self.push_command(Command::PauseStream(stream_id)); - Ok(()) - } + /// Used to signal to stop processing. + trigger: TriggerSender, } -// Process any pending `Command`s within the `RunContext`'s queue. -fn process_commands(run_context: &mut RunContext) { - for command in run_context.commands.try_iter() { - match command { - Command::DestroyStream(stream_id) => { - run_context.streams.retain(|s| s.id != stream_id); - }, - Command::PlayStream(stream_id) => { - if let Some(stream) = run_context.streams.iter_mut() - .find(|stream| stream.can_pause && stream.id == stream_id) - { - unsafe { - alsa::snd_pcm_pause(stream.channel, 0); - } - stream.is_paused = false; - } - }, - Command::PauseStream(stream_id) => { - if let Some(stream) = run_context.streams.iter_mut() - .find(|stream| stream.can_pause && stream.id == stream_id) - { - unsafe { - alsa::snd_pcm_pause(stream.channel, 1); - } - stream.is_paused = true; - } - }, - Command::NewStream(stream_inner) => { - run_context.streams.push(stream_inner); - }, - } - } -} +/// 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, callback: &mut (dyn FnMut(StreamDataResult) + 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, + }); -// Resets the descriptors so that only `pending_command_trigger.read_fd()` is contained. -fn reset_descriptors_with_pending_command_trigger( - descriptors: &mut Vec, - pending_command_trigger: &Trigger, -) { - descriptors.clear(); - descriptors.push(libc::pollfd { - fd: pending_command_trigger.read_fd(), - events: libc::POLLIN, - revents: 0, - }); -} - -// Appends the `poll` descriptors for each stream onto the `RunContext`'s descriptor slice, ready -// for a call to `libc::poll`. -fn append_stream_poll_descriptors(run_context: &mut RunContext) { - for stream in run_context.streams.iter() { - run_context.descriptors.reserve(stream.num_descriptors); - let len = run_context.descriptors.len(); + // Add ALSA polling fds. + descriptors.reserve(stream.num_descriptors); + let len = descriptors.len(); let filled = unsafe { alsa::snd_pcm_poll_descriptors( stream.channel, - run_context.descriptors.as_mut_ptr().offset(len as isize), + descriptors[len..].as_mut_ptr(), stream.num_descriptors as libc::c_uint, ) }; debug_assert_eq!(filled, stream.num_descriptors as libc::c_int); unsafe { - run_context.descriptors.set_len(len + stream.num_descriptors); + 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()); + callback(Err(BackendSpecificError { description }.into())); + continue; + } else if res == 0 { + let description = String::from("`libc::poll()` spuriously returned"); + callback(Err(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) => { + // TODO: signal errors + 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); + callback(Err(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); + callback(Err(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, + }; + callback(Ok(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, + }; + callback(Ok(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); + callback(Err(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, + ); + callback(Err(BackendSpecificError { description }.into())); + continue; + } else { + break; + } + } + }, } } } -// Poll all descriptors within the given set. -// -// Returns `Ok(true)` if some event has occurred or `Ok(false)` if no events have -// occurred. -// -// Returns an `Err` if `libc::poll` returns a negative value for some reason. -fn poll_all_descriptors(descriptors: &mut [libc::pollfd]) -> Result { - 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: {}", res); - Err(BackendSpecificError { description }) - } else if res == 0 { - Ok(false) - } else { - Ok(true) +impl Stream { + fn new(inner: Arc, mut callback: F) -> Stream where F: FnMut(StreamDataResult) + 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 callback); + }); + Stream { + thread: Some(thread), + inner, + trigger: tx, + } + } +} + +impl Drop for Stream { + fn drop(&mut self) { + self.trigger.wakeup(); + self.thread.take().unwrap().join().unwrap(); + } +} + +impl StreamTrait for Stream { + fn play(&self) -> Result<(), PlayStreamError> { + unsafe { + alsa::snd_pcm_pause(self.inner.channel, 0); + } + // TODO: error handling + Ok(()) + } + fn pause(&self)-> Result<(), PauseStreamError> { + unsafe { + alsa::snd_pcm_pause(self.inner.channel, 1); + } + // TODO: error handling + Ok(()) } } diff --git a/src/host/null/mod.rs b/src/host/null/mod.rs index 2126207..032f797 100644 --- a/src/host/null/mod.rs +++ b/src/host/null/mod.rs @@ -10,7 +10,7 @@ use PlayStreamError; use StreamDataResult; use SupportedFormatsError; use SupportedFormat; -use traits::{DeviceTrait, EventLoopTrait, HostTrait, StreamIdTrait}; +use traits::{DeviceTrait, HostTrait, StreamTrait}; #[derive(Default)] pub struct Devices; @@ -23,7 +23,7 @@ pub struct EventLoop; pub struct Host; #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct StreamId; +pub struct Stream; pub struct SupportedInputFormats; pub struct SupportedOutputFormats; @@ -49,6 +49,7 @@ impl EventLoop { impl DeviceTrait for Device { type SupportedInputFormats = SupportedInputFormats; type SupportedOutputFormats = SupportedOutputFormats; + type Stream = Stream; #[inline] fn name(&self) -> Result { @@ -74,49 +75,22 @@ impl DeviceTrait for Device { fn default_output_format(&self) -> Result { unimplemented!() } -} -impl EventLoopTrait for EventLoop { - type Device = Device; - type StreamId = StreamId; - - #[inline] - fn run(&self, _callback: F) -> ! - where F: FnMut(StreamId, StreamDataResult) - { - loop { /* TODO: don't spin */ } - } - - #[inline] - fn build_input_stream(&self, _: &Device, _: &Format) -> Result { - Err(BuildStreamError::DeviceNotAvailable) - } - - #[inline] - fn build_output_stream(&self, _: &Device, _: &Format) -> Result { - Err(BuildStreamError::DeviceNotAvailable) - } - - #[inline] - fn destroy_stream(&self, _: StreamId) { + fn build_input_stream(&self, format: &Format, callback: F) -> Result + where F: FnMut(StreamDataResult) + Send + 'static { unimplemented!() } - #[inline] - fn play_stream(&self, _: StreamId) -> Result<(), PlayStreamError> { - panic!() - } - - #[inline] - fn pause_stream(&self, _: StreamId) -> Result<(), PauseStreamError> { - panic!() + /// Create an output stream. + fn build_output_stream(&self, format: &Format, callback: F) -> Result + where F: FnMut(StreamDataResult) + Send + 'static{ + unimplemented!() } } impl HostTrait for Host { type Device = Device; type Devices = Devices; - type EventLoop = EventLoop; fn is_available() -> bool { false @@ -133,13 +107,17 @@ impl HostTrait for Host { fn default_output_device(&self) -> Option { None } - - fn event_loop(&self) -> Self::EventLoop { - EventLoop::new() - } } -impl StreamIdTrait for StreamId {} +impl StreamTrait for Stream { + fn play(&self) -> Result<(), PlayStreamError> { + unimplemented!() + } + + fn pause(&self) -> Result<(), PauseStreamError> { + unimplemented!() + } +} impl Iterator for Devices { type Item = Device; diff --git a/src/lib.rs b/src/lib.rs index 86d4ea0..61815e3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -151,11 +151,10 @@ extern crate thiserror; pub use error::*; pub use platform::{ - ALL_HOSTS, Device, Devices, EventLoop, Host, HostId, SupportedInputFormats, - SupportedOutputFormats, StreamId, available_hosts, default_host, host_from_id, + ALL_HOSTS, available_hosts, default_host, Device, Devices, Host, host_from_id, + HostId, Stream, SupportedInputFormats, SupportedOutputFormats, }; pub use samples_formats::{Sample, SampleFormat}; - use std::ops::{Deref, DerefMut}; mod error; diff --git a/src/platform/mod.rs b/src/platform/mod.rs index 6e9ee28..c85a8e6 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -58,14 +58,9 @@ macro_rules! impl_platform_host { /// type. pub struct Devices(DevicesInner); - /// The **EventLoop** implementation associated with the platform's dynamically dispatched + /// The **Stream** implementation associated with the platform's dynamically dispatched /// **Host** type. - pub struct EventLoop(EventLoopInner); - - /// The **StreamId** implementation associated with the platform's dynamically dispatched - /// **Host** type. - #[derive(Clone, Debug, Eq, Hash, PartialEq)] - pub struct StreamId(StreamIdInner); + pub struct Stream(StreamInner); /// The **SupportedInputFormats** iterator associated with the platform's dynamically /// dispatched **Host** type. @@ -95,22 +90,15 @@ macro_rules! impl_platform_host { )* } - enum EventLoopInner { - $( - $HostVariant(crate::host::$host_mod::EventLoop), - )* - } - enum HostInner { $( $HostVariant(crate::host::$host_mod::Host), )* } - #[derive(Clone, Debug, Eq, Hash, PartialEq)] - enum StreamIdInner { + enum StreamInner { $( - $HostVariant(crate::host::$host_mod::StreamId), + $HostVariant(crate::host::$host_mod::Stream), )* } @@ -212,6 +200,7 @@ macro_rules! impl_platform_host { impl crate::traits::DeviceTrait for Device { type SupportedInputFormats = SupportedInputFormats; type SupportedOutputFormats = SupportedOutputFormats; + type Stream = Stream; fn name(&self) -> Result { match self.0 { @@ -260,96 +249,25 @@ macro_rules! impl_platform_host { )* } } - } - impl crate::traits::EventLoopTrait for EventLoop { - type StreamId = StreamId; - type Device = Device; - - #[allow(unreachable_patterns)] - fn build_input_stream( - &self, - device: &Self::Device, - format: &crate::Format, - ) -> Result { - match (&self.0, &device.0) { - $( - (&EventLoopInner::$HostVariant(ref e), &DeviceInner::$HostVariant(ref d)) => { - e.build_input_stream(d, format) - .map(StreamIdInner::$HostVariant) - .map(StreamId) - } - )* - _ => panic!("tried to build a stream with a device from another host"), - } - } - - #[allow(unreachable_patterns)] - fn build_output_stream( - &self, - device: &Self::Device, - format: &crate::Format, - ) -> Result { - match (&self.0, &device.0) { - $( - (&EventLoopInner::$HostVariant(ref e), &DeviceInner::$HostVariant(ref d)) => { - e.build_output_stream(d, format) - .map(StreamIdInner::$HostVariant) - .map(StreamId) - } - )* - _ => panic!("tried to build a stream with a device from another host"), - } - } - - #[allow(unreachable_patterns)] - fn play_stream(&self, stream: Self::StreamId) -> Result<(), crate::PlayStreamError> { - match (&self.0, stream.0) { - $( - (&EventLoopInner::$HostVariant(ref e), StreamIdInner::$HostVariant(ref s)) => { - e.play_stream(s.clone()) - } - )* - _ => panic!("tried to play a stream with an ID associated with another host"), - } - } - - #[allow(unreachable_patterns)] - fn pause_stream(&self, stream: Self::StreamId) -> Result<(), crate::PauseStreamError> { - match (&self.0, stream.0) { - $( - (&EventLoopInner::$HostVariant(ref e), StreamIdInner::$HostVariant(ref s)) => { - e.pause_stream(s.clone()) - } - )* - _ => panic!("tried to pause a stream with an ID associated with another host"), - } - } - - #[allow(unreachable_patterns)] - fn destroy_stream(&self, stream: Self::StreamId) { - match (&self.0, stream.0) { - $( - (&EventLoopInner::$HostVariant(ref e), StreamIdInner::$HostVariant(ref s)) => { - e.destroy_stream(s.clone()) - } - )* - _ => panic!("tried to destroy a stream with an ID associated with another host"), - } - } - - fn run(&self, mut callback: F) -> ! - where - F: FnMut(Self::StreamId, crate::StreamDataResult) + Send - { + fn build_input_stream(&self, format: &crate::Format, callback: F) -> Result + where F: FnMut(crate::StreamDataResult) + Send + 'static { match self.0 { $( - EventLoopInner::$HostVariant(ref e) => { - e.run(|id, result| { - let result = result; - callback(StreamId(StreamIdInner::$HostVariant(id)), result); - }); - }, + DeviceInner::$HostVariant(ref d) => d.build_input_stream(format, callback) + .map(StreamInner::$HostVariant) + .map(Stream), + )* + } + } + + fn build_output_stream(&self, format: &crate::Format, callback: F) -> Result + where F: FnMut(crate::StreamDataResult) + Send + 'static { + match self.0 { + $( + DeviceInner::$HostVariant(ref d) => d.build_output_stream(format, callback) + .map(StreamInner::$HostVariant) + .map(Stream), )* } } @@ -358,7 +276,6 @@ macro_rules! impl_platform_host { impl crate::traits::HostTrait for Host { type Devices = Devices; type Device = Device; - type EventLoop = EventLoop; fn is_available() -> bool { $( crate::host::$host_mod::Host::is_available() ||)* false @@ -393,20 +310,30 @@ macro_rules! impl_platform_host { )* } } + } - fn event_loop(&self) -> Self::EventLoop { + impl crate::traits::StreamTrait for Stream { + fn play(&self) -> Result<(), crate::PlayStreamError> { match self.0 { $( - HostInner::$HostVariant(ref h) => { - EventLoop(EventLoopInner::$HostVariant(h.event_loop())) + StreamInner::$HostVariant(ref s) => { + s.play() + } + )* + } + } + + fn pause(&self) -> Result<(), crate::PauseStreamError> { + match self.0 { + $( + StreamInner::$HostVariant(ref s) => { + s.pause() } )* } } } - impl crate::traits::StreamIdTrait for StreamId {} - $( impl From for Device { fn from(h: crate::host::$host_mod::Device) -> Self { @@ -420,21 +347,15 @@ macro_rules! impl_platform_host { } } - impl From for EventLoop { - fn from(h: crate::host::$host_mod::EventLoop) -> Self { - EventLoop(EventLoopInner::$HostVariant(h)) - } - } - impl From for Host { fn from(h: crate::host::$host_mod::Host) -> Self { Host(HostInner::$HostVariant(h)) } } - impl From for StreamId { - fn from(h: crate::host::$host_mod::StreamId) -> Self { - StreamId(StreamIdInner::$HostVariant(h)) + impl From for Stream { + fn from(h: crate::host::$host_mod::Stream) -> Self { + Stream(StreamInner::$HostVariant(h)) } } )* @@ -471,9 +392,8 @@ mod platform_impl { pub use crate::host::alsa::{ Device as AlsaDevice, Devices as AlsaDevices, - EventLoop as AlsaEventLoop, Host as AlsaHost, - StreamId as AlsaStreamId, + Stream as AlsaStream, SupportedInputFormats as AlsaSupportedInputFormats, SupportedOutputFormats as AlsaSupportedOutputFormats, }; diff --git a/src/traits.rs b/src/traits.rs index f50764e..f50bedc 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -39,8 +39,6 @@ pub trait HostTrait { type Devices: Iterator; /// The `Device` type yielded by the host. type Device: DeviceTrait; - /// The event loop type used by the `Host` - type EventLoop: EventLoopTrait; /// Whether or not the host is available on the system. fn is_available() -> bool; @@ -60,9 +58,6 @@ pub trait HostTrait { /// Returns `None` if no output device is available. fn default_output_device(&self) -> Option; - /// Initialise the event loop, ready for managing audio streams. - fn event_loop(&self) -> Self::EventLoop; - /// An iterator yielding all `Device`s currently available to the system that support one or more /// input stream formats. /// @@ -99,6 +94,8 @@ pub trait DeviceTrait { type SupportedInputFormats: Iterator; /// The iterator type yielding supported output stream formats. type SupportedOutputFormats: Iterator; + /// The stream type created by `build_input_stream` and `build_output_stream`. + type Stream: StreamTrait; /// The human-readable name of the device. fn name(&self) -> Result; @@ -118,81 +115,19 @@ pub trait DeviceTrait { /// The default output stream format for the device. fn default_output_format(&self) -> Result; + + /// Create an input stream. + fn build_input_stream(&self, format: &Format, callback: F) -> Result + where F: FnMut(StreamDataResult) + Send + 'static; + + /// Create an output stream. + fn build_output_stream(&self, format: &Format, callback: F) -> Result + where F: FnMut(StreamDataResult) + Send + 'static; } -/// Collection of streams managed together. -/// -/// Created with the `Host::event_loop` method. -pub trait EventLoopTrait { - /// The `Device` type yielded by the host. - type Device: DeviceTrait; - /// The type used to uniquely distinguish between streams. - type StreamId: StreamIdTrait; +/// A stream created from `Device`, with methods to control playback. +pub trait StreamTrait { + fn play(&self) -> Result<(), PlayStreamError>; - /// Creates a new input stream that will run from the given device and with the given format. - /// - /// On success, returns an identifier for the stream. - /// - /// Can return an error if the device is no longer valid, or if the input stream format is not - /// supported by the device. - fn build_input_stream( - &self, - device: &Self::Device, - format: &Format, - ) -> Result; - - /// Creates a new output stream that will play on the given device and with the given format. - /// - /// On success, returns an identifier for the stream. - /// - /// Can return an error if the device is no longer valid, or if the output stream format is not - /// supported by the device. - fn build_output_stream( - &self, - device: &Self::Device, - format: &Format, - ) -> Result; - - /// Instructs the audio device that it should start playing the stream with the given ID. - /// - /// Has no effect is the stream was already playing. - /// - /// Only call this after you have submitted some data, otherwise you may hear some glitches. - /// - /// # Panic - /// - /// If the stream does not exist, this function can either panic or be a no-op. - fn play_stream(&self, stream: Self::StreamId) -> Result<(), PlayStreamError>; - - /// Instructs the audio device that it should stop playing the stream with the given ID. - /// - /// Has no effect is the stream was already paused. - /// - /// If you call `play` afterwards, the playback will resume where it was. - /// - /// # Panic - /// - /// If the stream does not exist, this function can either panic or be a no-op. - fn pause_stream(&self, stream: Self::StreamId) -> Result<(), PauseStreamError>; - - /// Destroys an existing stream. - /// - /// # Panic - /// - /// If the stream does not exist, this function can either panic or be a no-op. - fn destroy_stream(&self, stream: Self::StreamId); - - /// Takes control of the current thread and begins the stream processing. - /// - /// > **Note**: Since it takes control of the thread, this method is best called on a separate - /// > thread. - /// - /// Whenever a stream needs to be fed some data, the closure passed as parameter is called. - /// You can call the other methods of `EventLoop` without getting a deadlock. - fn run(&self, callback: F) -> ! - where - F: FnMut(Self::StreamId, StreamDataResult) + Send; -} - -/// The set of required bounds for host `StreamId` types. -pub trait StreamIdTrait: Clone + std::fmt::Debug + std::hash::Hash + PartialEq + Eq {} + fn pause(&self) -> Result<(), PauseStreamError>; +} \ No newline at end of file From 3cce3e43d9b7125b5ec7f8e0e4b638b79a292a36 Mon Sep 17 00:00:00 2001 From: Tatsuyuki Ishi Date: Mon, 15 Jul 2019 22:37:03 +0900 Subject: [PATCH 02/41] Change callback interface so that it takes a dedicated error callback --- examples/beep.rs | 12 +++--------- examples/feedback.rs | 23 ++++++----------------- examples/record_wav.rs | 12 +++--------- src/host/alsa/mod.rs | 36 ++++++++++++++++++++---------------- src/host/null/mod.rs | 11 ++++++----- src/lib.rs | 4 ---- src/platform/mod.rs | 12 ++++++------ src/traits.rs | 11 ++++++----- 8 files changed, 50 insertions(+), 71 deletions(-) diff --git a/examples/beep.rs b/examples/beep.rs index d2c570c..686230c 100644 --- a/examples/beep.rs +++ b/examples/beep.rs @@ -17,15 +17,7 @@ fn main() -> Result<(), anyhow::Error> { (sample_clock * 440.0 * 2.0 * 3.141592 / sample_rate).sin() }; - let stream = device.build_output_stream(&format, move |result| { - let data = match result { - Ok(data) => data, - Err(err) => { - eprintln!("an error occurred on stream: {}", err); - return; - } - }; - + 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) { @@ -53,6 +45,8 @@ fn main() -> Result<(), anyhow::Error> { }, _ => (), } + }, move |err| { + eprintln!("an error occurred on stream: {}", err); })?; stream.play()?; diff --git a/examples/feedback.rs b/examples/feedback.rs index 11c1331..e6875b2 100644 --- a/examples/feedback.rs +++ b/examples/feedback.rs @@ -49,15 +49,7 @@ 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 |result| { - let data = match result { - Ok(data) => data, - Err(err) => { - eprintln!("an error occurred on input stream: {}", err); - return; - }, - }; - + let input_stream = input_device.build_input_stream(&format, move |data| { match data { cpal::StreamData::Input { buffer: cpal::UnknownTypeInputBuffer::F32(buffer), @@ -74,15 +66,10 @@ fn main() -> Result<(), anyhow::Error> { }, _ => 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 |result| { - let data = match result { - Ok(data) => data, - Err(err) => { - eprintln!("an error occurred on output stream: {}", err); - return; - }, - }; + let output_stream = output_device.build_output_stream(&format, move |data| { match data { cpal::StreamData::Output { buffer: cpal::UnknownTypeOutputBuffer::F32(mut buffer), @@ -103,6 +90,8 @@ fn main() -> Result<(), anyhow::Error> { }, _ => panic!("Expected output with f32 data"), } + }, move |err| { + eprintln!("an error occurred on output stream: {}", err); })?; println!("Successfully built streams."); diff --git a/examples/record_wav.rs b/examples/record_wav.rs index d53f9b3..f69c8d0 100644 --- a/examples/record_wav.rs +++ b/examples/record_wav.rs @@ -32,15 +32,7 @@ fn main() -> Result<(), anyhow::Error> { // Run the input stream on a separate thread. let writer_2 = writer.clone(); - let stream = device.build_input_stream(&format, move |event| { - let data = match event { - Ok(data) => data, - Err(err) => { - eprintln!("an error occurred on stream: {}", err); - return; - }, - }; - + let stream = device.build_input_stream(&format, move |data| { // Otherwise write to the wav writer. match data { cpal::StreamData::Input { @@ -79,6 +71,8 @@ fn main() -> Result<(), anyhow::Error> { }, _ => (), } + }, move |err| { + eprintln!("an error occurred on stream: {}", err); })?; stream.play()?; diff --git a/src/host/alsa/mod.rs b/src/host/alsa/mod.rs index 72c3afe..4401ea9 100644 --- a/src/host/alsa/mod.rs +++ b/src/host/alsa/mod.rs @@ -18,7 +18,7 @@ use PlayStreamError; use SampleFormat; use SampleRate; use StreamData; -use StreamDataResult; +use StreamError; use SupportedFormat; use SupportedFormatsError; use traits::{DeviceTrait, HostTrait, StreamTrait}; @@ -89,12 +89,12 @@ impl DeviceTrait for Device { Device::default_output_format(self) } - fn build_input_stream(&self, format: &Format, callback: F) -> Result where F: FnMut(StreamDataResult) + Send + 'static { - Ok(Stream::new(Arc::new(self.build_stream_inner(format, alsa::SND_PCM_STREAM_CAPTURE)?), callback)) + 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_output_stream(&self, format: &Format, callback: F) -> Result where F: FnMut(StreamDataResult) + Send + 'static { - Ok(Stream::new(Arc::new(self.build_stream_inner(format, alsa::SND_PCM_STREAM_PLAYBACK)?), 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 { + Ok(Stream::new(Arc::new(self.build_stream_inner(format, alsa::SND_PCM_STREAM_PLAYBACK)?), data_callback, error_callback)) } } @@ -526,7 +526,10 @@ pub struct Stream { /// 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, callback: &mut (dyn FnMut(StreamDataResult) + Send + 'static)) { +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 { @@ -559,11 +562,11 @@ fn stream_worker(rx: TriggerReceiver, stream: &StreamInner, callback: &mut (dyn }; if res < 0 { let description = format!("`libc::poll()` failed: {}", io::Error::last_os_error()); - callback(Err(BackendSpecificError { description }.into())); + error_callback(BackendSpecificError { description }.into()); continue; } else if res == 0 { let description = String::from("`libc::poll()` spuriously returned"); - callback(Err(BackendSpecificError { description }.into())); + error_callback(BackendSpecificError { description }.into()); continue; } @@ -589,7 +592,7 @@ fn stream_worker(rx: TriggerReceiver, stream: &StreamInner, callback: &mut (dyn Ok(n) => n, Err(err) => { let description = format!("Failed to query the number of available samples: {}", err); - callback(Err(BackendSpecificError { description }.into())); + error_callback(BackendSpecificError { description }.into()); continue; } }; @@ -615,7 +618,7 @@ fn stream_worker(rx: TriggerReceiver, stream: &StreamInner, callback: &mut (dyn }; if let Err(err) = check_errors(result as _) { let description = format!("`snd_pcm_readi` failed: {}", err); - callback(Err(BackendSpecificError { description }.into())); + error_callback(BackendSpecificError { description }.into()); continue; } @@ -633,7 +636,7 @@ fn stream_worker(rx: TriggerReceiver, stream: &StreamInner, callback: &mut (dyn let stream_data = StreamData::Input { buffer: input_buffer, }; - callback(Ok(stream_data)); + data_callback(stream_data); }, StreamType::Output => { { @@ -653,7 +656,7 @@ fn stream_worker(rx: TriggerReceiver, stream: &StreamInner, callback: &mut (dyn let stream_data = StreamData::Output { buffer: output_buffer, }; - callback(Ok(stream_data)); + data_callback(stream_data); } loop { let result = unsafe { @@ -670,7 +673,7 @@ fn stream_worker(rx: TriggerReceiver, stream: &StreamInner, callback: &mut (dyn 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); - callback(Err(BackendSpecificError { description }.into())); + error_callback(BackendSpecificError { description }.into()); continue; } else if result as usize != available_frames { let description = format!( @@ -679,7 +682,7 @@ fn stream_worker(rx: TriggerReceiver, stream: &StreamInner, callback: &mut (dyn available_frames, result, ); - callback(Err(BackendSpecificError { description }.into())); + error_callback(BackendSpecificError { description }.into()); continue; } else { break; @@ -691,12 +694,13 @@ fn stream_worker(rx: TriggerReceiver, stream: &StreamInner, callback: &mut (dyn } impl Stream { - fn new(inner: Arc, mut callback: F) -> Stream where F: FnMut(StreamDataResult) + Send + 'static { + fn new(inner: Arc, mut data_callback: D, mut error_callback: E) -> Stream + where D: FnMut(StreamData) + 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 callback); + 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 032f797..1683946 100644 --- a/src/host/null/mod.rs +++ b/src/host/null/mod.rs @@ -7,7 +7,8 @@ use DeviceNameError; use Format; use PauseStreamError; use PlayStreamError; -use StreamDataResult; +use StreamData; +use StreamError; use SupportedFormatsError; use SupportedFormat; use traits::{DeviceTrait, HostTrait, StreamTrait}; @@ -76,14 +77,14 @@ impl DeviceTrait for Device { unimplemented!() } - fn build_input_stream(&self, format: &Format, callback: F) -> Result - where F: FnMut(StreamDataResult) + Send + 'static { + 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 { unimplemented!() } /// Create an output stream. - fn build_output_stream(&self, format: &Format, callback: F) -> Result - where F: FnMut(StreamDataResult) + Send + 'static{ + 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{ unimplemented!() } } diff --git a/src/lib.rs b/src/lib.rs index 61815e3..36f1ac8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -206,10 +206,6 @@ pub enum StreamData<'a> { }, } -/// Stream data passed to the `EventLoop::run` callback, or an error in the case that the device -/// was invalidated or some backend-specific error occurred. -pub type StreamDataResult<'a> = Result, StreamError>; - /// 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 diff --git a/src/platform/mod.rs b/src/platform/mod.rs index c85a8e6..c16c9dc 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -250,22 +250,22 @@ macro_rules! impl_platform_host { } } - fn build_input_stream(&self, format: &crate::Format, callback: F) -> Result - where F: FnMut(crate::StreamDataResult) + Send + 'static { + 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 { match self.0 { $( - DeviceInner::$HostVariant(ref d) => d.build_input_stream(format, callback) + DeviceInner::$HostVariant(ref d) => d.build_input_stream(format, data_callback, error_callback) .map(StreamInner::$HostVariant) .map(Stream), )* } } - fn build_output_stream(&self, format: &crate::Format, callback: F) -> Result - where F: FnMut(crate::StreamDataResult) + Send + 'static { + 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 { match self.0 { $( - DeviceInner::$HostVariant(ref d) => d.build_output_stream(format, callback) + DeviceInner::$HostVariant(ref d) => d.build_output_stream(format, data_callback, error_callback) .map(StreamInner::$HostVariant) .map(Stream), )* diff --git a/src/traits.rs b/src/traits.rs index f50bedc..d90d782 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -10,7 +10,8 @@ use { OutputDevices, PauseStreamError, PlayStreamError, - StreamDataResult, + StreamData, + StreamError, SupportedFormat, SupportedFormatsError, }; @@ -117,12 +118,12 @@ pub trait DeviceTrait { fn default_output_format(&self) -> Result; /// Create an input stream. - fn build_input_stream(&self, format: &Format, callback: F) -> Result - where F: FnMut(StreamDataResult) + Send + 'static; + 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; /// Create an output stream. - fn build_output_stream(&self, format: &Format, callback: F) -> Result - where F: FnMut(StreamDataResult) + Send + 'static; + 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; } /// A stream created from `Device`, with methods to control playback. From 508e7d8ccf347836101ea28718de42c12be6dcd0 Mon Sep 17 00:00:00 2001 From: Tatsuyuki Ishi Date: Mon, 15 Jul 2019 22:39:04 +0900 Subject: [PATCH 03/41] Add one missing error handling --- src/host/alsa/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/host/alsa/mod.rs b/src/host/alsa/mod.rs index 4401ea9..6c885a7 100644 --- a/src/host/alsa/mod.rs +++ b/src/host/alsa/mod.rs @@ -583,7 +583,7 @@ fn stream_worker(rx: TriggerReceiver, continue; }, Err(err) => { - // TODO: signal errors + error_callback(err.into()); continue; } }; From e782ca1ac829115c6edb7ac31fb5ca9a851955e2 Mon Sep 17 00:00:00 2001 From: Tatsuyuki Ishi Date: Mon, 15 Jul 2019 22:40:34 +0900 Subject: [PATCH 04/41] Suppress warnings in null backend --- src/host/null/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/host/null/mod.rs b/src/host/null/mod.rs index 1683946..054008d 100644 --- a/src/host/null/mod.rs +++ b/src/host/null/mod.rs @@ -77,13 +77,13 @@ impl DeviceTrait for Device { unimplemented!() } - 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 { unimplemented!() } /// Create an output stream. - 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{ unimplemented!() } From f57e7c74e467560df1657ef13fbec004c9ab59b7 Mon Sep 17 00:00:00 2001 From: Viktor Lazarev Date: Wed, 28 Aug 2019 07:25:24 +0200 Subject: [PATCH 05/41] Ignore RLS log files --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 6d067b3..0289afe 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ /Cargo.lock .cargo/ .DS_Store -recorded.wav \ No newline at end of file +recorded.wav +rls*.log From 37e7b5b63bcefecf05dcc95d9a63af41debef3ec Mon Sep 17 00:00:00 2001 From: Viktor Lazarev Date: Wed, 28 Aug 2019 15:14:18 +0200 Subject: [PATCH 06/41] Move DeviceTrait implementation to wasapi::device --- src/host/wasapi/device.rs | 27 +++++++++++++++++++++++++++ src/host/wasapi/mod.rs | 27 +-------------------------- 2 files changed, 28 insertions(+), 26 deletions(-) diff --git a/src/host/wasapi/device.rs b/src/host/wasapi/device.rs index 3093b10..a067e47 100644 --- a/src/host/wasapi/device.rs +++ b/src/host/wasapi/device.rs @@ -68,6 +68,8 @@ use super::winapi::um::mmdeviceapi::{ IMMEndpoint, }; +use crate::traits::DeviceTrait; + pub type SupportedInputFormats = std::vec::IntoIter; pub type SupportedOutputFormats = std::vec::IntoIter; @@ -87,6 +89,31 @@ pub struct Device { future_audio_client: Arc>>, // TODO: add NonZero around the ptr } +impl DeviceTrait for Device { + type SupportedInputFormats = SupportedInputFormats; + type SupportedOutputFormats = SupportedOutputFormats; + + fn name(&self) -> Result { + Device::name(self) + } + + fn supported_input_formats(&self) -> Result { + Device::supported_input_formats(self) + } + + fn supported_output_formats(&self) -> Result { + Device::supported_output_formats(self) + } + + fn default_input_format(&self) -> Result { + Device::default_input_format(self) + } + + fn default_output_format(&self) -> Result { + Device::default_output_format(self) + } +} + struct Endpoint { endpoint: *mut IMMEndpoint, } diff --git a/src/host/wasapi/mod.rs b/src/host/wasapi/mod.rs index 239adbf..d833ac4 100644 --- a/src/host/wasapi/mod.rs +++ b/src/host/wasapi/mod.rs @@ -12,7 +12,7 @@ use StreamDataResult; use SupportedFormatsError; use self::winapi::um::winnt::HRESULT; use std::io::Error as IoError; -use traits::{DeviceTrait, EventLoopTrait, HostTrait, StreamIdTrait}; +use traits::{EventLoopTrait, HostTrait, StreamIdTrait}; pub use self::device::{Device, Devices, SupportedInputFormats, SupportedOutputFormats, default_input_device, default_output_device}; pub use self::stream::{EventLoop, StreamId}; @@ -61,31 +61,6 @@ impl HostTrait for Host { } } -impl DeviceTrait for Device { - type SupportedInputFormats = SupportedInputFormats; - type SupportedOutputFormats = SupportedOutputFormats; - - fn name(&self) -> Result { - Device::name(self) - } - - fn supported_input_formats(&self) -> Result { - Device::supported_input_formats(self) - } - - fn supported_output_formats(&self) -> Result { - Device::supported_output_formats(self) - } - - fn default_input_format(&self) -> Result { - Device::default_input_format(self) - } - - fn default_output_format(&self) -> Result { - Device::default_output_format(self) - } -} - impl EventLoopTrait for EventLoop { type Device = Device; type StreamId = StreamId; From cffd2da582dd147fe7c6fb287bea5d7a62145db2 Mon Sep 17 00:00:00 2001 From: Viktor Lazarev Date: Wed, 28 Aug 2019 15:20:41 +0200 Subject: [PATCH 07/41] Rename "StreamDataResult" to "StreamData" --- src/host/wasapi/stream.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/host/wasapi/stream.rs b/src/host/wasapi/stream.rs index bd20ba8..617e678 100644 --- a/src/host/wasapi/stream.rs +++ b/src/host/wasapi/stream.rs @@ -28,7 +28,6 @@ use PauseStreamError; use PlayStreamError; use SampleFormat; use StreamData; -use StreamDataResult; use StreamError; use UnknownTypeOutputBuffer; use UnknownTypeInputBuffer; @@ -452,12 +451,12 @@ impl EventLoop { #[inline] pub(crate) fn run(&self, mut callback: F) -> ! - where F: FnMut(StreamId, StreamDataResult) + where F: FnMut(StreamId, StreamData) { self.run_inner(&mut callback); } - fn run_inner(&self, callback: &mut dyn FnMut(StreamId, StreamDataResult)) -> ! { + fn run_inner(&self, callback: &mut dyn FnMut(StreamId, StreamData)) -> ! { unsafe { // We keep `run_context` locked forever, which guarantees that two invocations of // `run()` cannot run simultaneously. @@ -753,7 +752,7 @@ fn format_to_waveformatextensible(format: &Format) -> Option Date: Wed, 28 Aug 2019 15:22:53 +0200 Subject: [PATCH 08/41] Keep a single stream only --- src/host/wasapi/stream.rs | 42 ++++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/src/host/wasapi/stream.rs b/src/host/wasapi/stream.rs index 617e678..2599fa8 100644 --- a/src/host/wasapi/stream.rs +++ b/src/host/wasapi/stream.rs @@ -21,6 +21,8 @@ use std::sync::mpsc::{channel, Sender, Receiver}; use std::sync::atomic::AtomicUsize; use std::sync::atomic::Ordering; +use std::sync::{Arc}; + use BackendSpecificError; use BuildStreamError; use Format; @@ -55,7 +57,7 @@ pub struct EventLoop { struct RunContext { // Streams that have been created in this event loop. - streams: Vec, + stream: Arc, // Handles corresponding to the `event` field of each element of `voices`. Must always be in // sync with `voices`, except that the first element is always `pending_scheduled_event`. @@ -106,7 +108,7 @@ impl EventLoop { EventLoop { pending_scheduled_event: pending_scheduled_event, run_context: Mutex::new(RunContext { - streams: Vec::new(), + stream: Arc::new(), handles: vec![pending_scheduled_event], commands: rx, }), @@ -473,11 +475,11 @@ impl EventLoop { 'stream_loop: loop { // Remove any failed streams. for (stream_id, err) in streams_to_remove.drain(..) { - match run_context.streams.iter().position(|s| s.id == stream_id) { + match run_context.stream.iter().position(|s| s.id == stream_id) { None => continue, Some(p) => { run_context.handles.remove(p + 1); - run_context.streams.remove(p); + run_context.stream.remove(p); callback(stream_id, Err(err.into())); }, } @@ -490,10 +492,10 @@ impl EventLoop { let handle_idx = match wait_for_handle_signal(&run_context.handles) { Ok(idx) => idx, Err(err) => { - for stream in &run_context.streams { + for stream in &run_context.stream { callback(stream.id.clone(), Err(err.clone().into())); } - run_context.streams.clear(); + run_context.stream.clear(); run_context.handles.truncate(1); break 'stream_loop; } @@ -506,7 +508,7 @@ impl EventLoop { } let stream_idx = handle_idx - 1; - let stream = &mut run_context.streams[stream_idx]; + let stream = &mut run_context.stream[stream_idx]; let sample_size = stream.sample_format.sample_size(); @@ -759,34 +761,34 @@ fn process_commands( match command { Command::NewStream(stream_inner) => { let event = stream_inner.event; - run_context.streams.push(stream_inner); + run_context.stream.push(stream_inner); run_context.handles.push(event); }, Command::DestroyStream(stream_id) => { - match run_context.streams.iter().position(|s| s.id == stream_id) { + match run_context.stream.iter().position(|s| s.id == stream_id) { None => continue, Some(p) => { run_context.handles.remove(p + 1); - run_context.streams.remove(p); + run_context.stream.remove(p); }, } }, Command::PlayStream(stream_id) => { - match run_context.streams.iter().position(|s| s.id == stream_id) { + match run_context.stream.iter().position(|s| s.id == stream_id) { None => continue, Some(p) => { - if !run_context.streams[p].playing { + if !run_context.stream[p].playing { let hresult = unsafe { - (*run_context.streams[p].audio_client).Start() + (*run_context.stream[p].audio_client).Start() }; match stream_error_from_hresult(hresult) { Ok(()) => { - run_context.streams[p].playing = true; + run_context.stream[p].playing = true; } Err(err) => { callback(stream_id, Err(err.into())); run_context.handles.remove(p + 1); - run_context.streams.remove(p); + run_context.stream.remove(p); } } } @@ -794,21 +796,21 @@ fn process_commands( } }, Command::PauseStream(stream_id) => { - match run_context.streams.iter().position(|s| s.id == stream_id) { + match run_context.stream.iter().position(|s| s.id == stream_id) { None => continue, Some(p) => { - if run_context.streams[p].playing { + if run_context.stream[p].playing { let hresult = unsafe { - (*run_context.streams[p].audio_client).Stop() + (*run_context.stream[p].audio_client).Stop() }; match stream_error_from_hresult(hresult) { Ok(()) => { - run_context.streams[p].playing = false; + run_context.stream[p].playing = false; } Err(err) => { callback(stream_id, Err(err.into())); run_context.handles.remove(p + 1); - run_context.streams.remove(p); + run_context.stream.remove(p); } } } From b00630bdcc9c675a2fd403dbe69b8da7bbed63f4 Mon Sep 17 00:00:00 2001 From: Viktor Lazarev Date: Wed, 28 Aug 2019 15:39:50 +0200 Subject: [PATCH 09/41] Extract "run_inner" as a standalone function --- src/host/wasapi/stream.rs | 354 +++++++++++++++++++------------------- 1 file changed, 177 insertions(+), 177 deletions(-) diff --git a/src/host/wasapi/stream.rs b/src/host/wasapi/stream.rs index 2599fa8..a4cc166 100644 --- a/src/host/wasapi/stream.rs +++ b/src/host/wasapi/stream.rs @@ -458,183 +458,6 @@ impl EventLoop { self.run_inner(&mut callback); } - fn run_inner(&self, callback: &mut dyn FnMut(StreamId, StreamData)) -> ! { - unsafe { - // We keep `run_context` locked forever, which guarantees that two invocations of - // `run()` cannot run simultaneously. - let mut run_context = self.run_context.lock().unwrap(); - // Force a deref so that borrow checker can operate on each field independently. - // Shadow the name because we don't use (or drop) it otherwise. - let run_context = &mut *run_context; - - // Keep track of the set of streams that should be removed due to some error occurring. - // - // Checked at the start of each loop. - let mut streams_to_remove: Vec<(StreamId, StreamError)> = vec![]; - - 'stream_loop: loop { - // Remove any failed streams. - for (stream_id, err) in streams_to_remove.drain(..) { - match run_context.stream.iter().position(|s| s.id == stream_id) { - None => continue, - Some(p) => { - run_context.handles.remove(p + 1); - run_context.stream.remove(p); - callback(stream_id, Err(err.into())); - }, - } - } - - // Process queued commands. - process_commands(run_context, callback); - - // 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) => { - for stream in &run_context.stream { - callback(stream.id.clone(), Err(err.clone().into())); - } - run_context.stream.clear(); - run_context.handles.truncate(1); - 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 { - continue; - } - - let stream_idx = handle_idx - 1; - let stream = &mut run_context.stream[stream_idx]; - - let sample_size = stream.sample_format.sample_size(); - - // 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) { - streams_to_remove.push((stream.id.clone(), err)); - break; // Identical to continuing the outer loop - } - if frames_available == 0 { - break; - } - let hresult = (*capture_client).GetBuffer( - &mut buffer, - &mut frames_available, - &mut flags, - ptr::null_mut(), - ptr::null_mut(), - ); - - // TODO: Can this happen? - if hresult == AUDCLNT_S_BUFFER_EMPTY { - continue; - } else if let Err(err) = stream_error_from_hresult(hresult) { - streams_to_remove.push((stream.id.clone(), err)); - break; // Identical to continuing the outer 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 }; - callback(stream.id.clone(), Ok(data)); - // Release the buffer. - let hresult = (*capture_client).ReleaseBuffer(frames_available); - if let Err(err) = stream_error_from_hresult(hresult) { - streams_to_remove.push((stream.id.clone(), err)); - continue; - } - }}; - } - - 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) => { - streams_to_remove.push((stream.id.clone(), err)); - continue; - } - }; - - 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) { - streams_to_remove.push((stream.id.clone(), err)); - continue; - } - - 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 }; - callback(stream.id.clone(), Ok(data)); - let hresult = (*render_client) - .ReleaseBuffer(frames_available as u32, 0); - if let Err(err) = stream_error_from_hresult(hresult) { - streams_to_remove.push((stream.id.clone(), err)); - continue; - } - }} - } - - match stream.sample_format { - SampleFormat::F32 => render_callback!(f32, F32), - SampleFormat::I16 => render_callback!(i16, I16), - SampleFormat::U16 => render_callback!(u16, U16), - } - }, - } - } - } - - panic!("`cpal::EventLoop::run` API currently disallows returning"); - } - #[inline] pub(crate) fn play_stream(&self, stream: StreamId) -> Result<(), PlayStreamError> { self.push_command(Command::PlayStream(stream)); @@ -876,3 +699,180 @@ fn stream_error_from_hresult(hresult: winnt::HRESULT) -> Result<(), StreamError> } Ok(()) } + +fn run_inner(run_context: RunContext, data_callback: &mut dyn FnMut(StreamData), error_callback: &mut dyn FnMut(StreamError)) -> () { + unsafe { + // We keep `run_context` locked forever, which guarantees that two invocations of + // `run()` cannot run simultaneously. + let mut run_context = self.run_context.lock().unwrap(); + // Force a deref so that borrow checker can operate on each field independently. + // Shadow the name because we don't use (or drop) it otherwise. + let run_context = &mut *run_context; + + // Keep track of the set of streams that should be removed due to some error occurring. + // + // Checked at the start of each loop. + let mut streams_to_remove: Vec<(StreamId, StreamError)> = vec![]; + + 'stream_loop: loop { + // Remove any failed streams. + for (stream_id, err) in streams_to_remove.drain(..) { + match run_context.stream.iter().position(|s| s.id == stream_id) { + None => continue, + Some(p) => { + run_context.handles.remove(p + 1); + run_context.stream.remove(p); + callback(stream_id, Err(err.into())); + }, + } + } + + // Process queued commands. + process_commands(run_context, callback); + + // 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) => { + for stream in &run_context.stream { + callback(stream.id.clone(), Err(err.clone().into())); + } + run_context.stream.clear(); + run_context.handles.truncate(1); + 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 { + continue; + } + + let stream_idx = handle_idx - 1; + let stream = &mut run_context.stream[stream_idx]; + + let sample_size = stream.sample_format.sample_size(); + + // 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) { + streams_to_remove.push((stream.id.clone(), err)); + break; // Identical to continuing the outer loop + } + if frames_available == 0 { + break; + } + let hresult = (*capture_client).GetBuffer( + &mut buffer, + &mut frames_available, + &mut flags, + ptr::null_mut(), + ptr::null_mut(), + ); + + // TODO: Can this happen? + if hresult == AUDCLNT_S_BUFFER_EMPTY { + continue; + } else if let Err(err) = stream_error_from_hresult(hresult) { + streams_to_remove.push((stream.id.clone(), err)); + break; // Identical to continuing the outer 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 }; + callback(stream.id.clone(), Ok(data)); + // Release the buffer. + let hresult = (*capture_client).ReleaseBuffer(frames_available); + if let Err(err) = stream_error_from_hresult(hresult) { + streams_to_remove.push((stream.id.clone(), err)); + continue; + } + }}; + } + + 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) => { + streams_to_remove.push((stream.id.clone(), err)); + continue; + } + }; + + 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) { + streams_to_remove.push((stream.id.clone(), err)); + continue; + } + + 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 }; + callback(stream.id.clone(), Ok(data)); + let hresult = (*render_client) + .ReleaseBuffer(frames_available as u32, 0); + if let Err(err) = stream_error_from_hresult(hresult) { + streams_to_remove.push((stream.id.clone(), err)); + continue; + } + }} + } + + match stream.sample_format { + SampleFormat::F32 => render_callback!(f32, F32), + SampleFormat::I16 => render_callback!(i16, I16), + SampleFormat::U16 => render_callback!(u16, U16), + } + }, + } + } + } + + panic!("`cpal::EventLoop::run` API currently disallows returning"); +} From 28cf3fee43748826791712f7947c7abd0ee33c54 Mon Sep 17 00:00:00 2001 From: Viktor Lazarev Date: Wed, 28 Aug 2019 15:48:19 +0200 Subject: [PATCH 10/41] Fix "run_inner" fn --- src/host/wasapi/stream.rs | 72 +++++++++++++-------------------------- 1 file changed, 23 insertions(+), 49 deletions(-) diff --git a/src/host/wasapi/stream.rs b/src/host/wasapi/stream.rs index a4cc166..fc8902b 100644 --- a/src/host/wasapi/stream.rs +++ b/src/host/wasapi/stream.rs @@ -702,43 +702,21 @@ fn stream_error_from_hresult(hresult: winnt::HRESULT) -> Result<(), StreamError> fn run_inner(run_context: RunContext, data_callback: &mut dyn FnMut(StreamData), error_callback: &mut dyn FnMut(StreamError)) -> () { unsafe { - // We keep `run_context` locked forever, which guarantees that two invocations of - // `run()` cannot run simultaneously. - let mut run_context = self.run_context.lock().unwrap(); - // Force a deref so that borrow checker can operate on each field independently. - // Shadow the name because we don't use (or drop) it otherwise. - let run_context = &mut *run_context; - - // Keep track of the set of streams that should be removed due to some error occurring. - // - // Checked at the start of each loop. - let mut streams_to_remove: Vec<(StreamId, StreamError)> = vec![]; - 'stream_loop: loop { - // Remove any failed streams. - for (stream_id, err) in streams_to_remove.drain(..) { - match run_context.stream.iter().position(|s| s.id == stream_id) { - None => continue, - Some(p) => { - run_context.handles.remove(p + 1); - run_context.stream.remove(p); - callback(stream_id, Err(err.into())); - }, - } - } - // Process queued commands. - process_commands(run_context, callback); + match process_commands(run_context, error_callback) { + Ok(()) => (), + Err(err) => { + error_callback(err); + break 'stream_loop; + } + }; // 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) => { - for stream in &run_context.stream { - callback(stream.id.clone(), Err(err.clone().into())); - } - run_context.stream.clear(); - run_context.handles.truncate(1); + error_callback(err); break 'stream_loop; } }; @@ -749,9 +727,7 @@ fn run_inner(run_context: RunContext, data_callback: &mut dyn FnMut(StreamData), continue; } - let stream_idx = handle_idx - 1; - let stream = &mut run_context.stream[stream_idx]; - + let stream = run_context.stream; let sample_size = stream.sample_format.sample_size(); // Obtaining a pointer to the buffer. @@ -765,8 +741,8 @@ fn run_inner(run_context: RunContext, data_callback: &mut dyn FnMut(StreamData), loop { let hresult = (*capture_client).GetNextPacketSize(&mut frames_available); if let Err(err) = stream_error_from_hresult(hresult) { - streams_to_remove.push((stream.id.clone(), err)); - break; // Identical to continuing the outer loop + error_callback(err); + break 'stream_loop; } if frames_available == 0 { break; @@ -783,8 +759,8 @@ fn run_inner(run_context: RunContext, data_callback: &mut dyn FnMut(StreamData), if hresult == AUDCLNT_S_BUFFER_EMPTY { continue; } else if let Err(err) = stream_error_from_hresult(hresult) { - streams_to_remove.push((stream.id.clone(), err)); - break; // Identical to continuing the outer loop + error_callback(err); + break 'stream_loop; } debug_assert!(!buffer.is_null()); @@ -801,12 +777,12 @@ fn run_inner(run_context: RunContext, data_callback: &mut dyn FnMut(StreamData), buffer: slice, }); let data = StreamData::Input { buffer: unknown_buffer }; - callback(stream.id.clone(), Ok(data)); + data_callback(stream.id.clone(), Ok(data)); // Release the buffer. let hresult = (*capture_client).ReleaseBuffer(frames_available); if let Err(err) = stream_error_from_hresult(hresult) { - streams_to_remove.push((stream.id.clone(), err)); - continue; + error_callback(err); + break 'stream_loop; } }}; } @@ -825,8 +801,8 @@ fn run_inner(run_context: RunContext, data_callback: &mut dyn FnMut(StreamData), Ok(0) => continue, // TODO: Can this happen? Ok(n) => n, Err(err) => { - streams_to_remove.push((stream.id.clone(), err)); - continue; + error_callback(err); + break 'stream_loop; } }; @@ -837,8 +813,8 @@ fn run_inner(run_context: RunContext, data_callback: &mut dyn FnMut(StreamData), ); if let Err(err) = stream_error_from_hresult(hresult) { - streams_to_remove.push((stream.id.clone(), err)); - continue; + error_callback(err); + break 'stream_loop; } debug_assert!(!buffer.is_null()); @@ -854,12 +830,12 @@ fn run_inner(run_context: RunContext, data_callback: &mut dyn FnMut(StreamData), buffer: slice }); let data = StreamData::Output { buffer: unknown_buffer }; - callback(stream.id.clone(), Ok(data)); + data_callback(stream.id.clone(), Ok(data)); let hresult = (*render_client) .ReleaseBuffer(frames_available as u32, 0); if let Err(err) = stream_error_from_hresult(hresult) { - streams_to_remove.push((stream.id.clone(), err)); - continue; + error_callback(err); + break 'stream_loop; } }} } @@ -873,6 +849,4 @@ fn run_inner(run_context: RunContext, data_callback: &mut dyn FnMut(StreamData), } } } - - panic!("`cpal::EventLoop::run` API currently disallows returning"); } From dd418c08ae9a5f507f4bc715e8ab69707e086368 Mon Sep 17 00:00:00 2001 From: Viktor Lazarev Date: Wed, 28 Aug 2019 19:14:36 +0200 Subject: [PATCH 11/41] Adjust "process_commands" and "Command" --- src/host/wasapi/stream.rs | 85 +++++++++++---------------------------- 1 file changed, 23 insertions(+), 62 deletions(-) diff --git a/src/host/wasapi/stream.rs b/src/host/wasapi/stream.rs index fc8902b..8af16cd 100644 --- a/src/host/wasapi/stream.rs +++ b/src/host/wasapi/stream.rs @@ -67,10 +67,8 @@ struct RunContext { } enum Command { - NewStream(StreamInner), - DestroyStream(StreamId), - PlayStream(StreamId), - PauseStream(StreamId), + PlayStream, + PauseStream, } enum AudioClientFlow { @@ -575,75 +573,38 @@ fn format_to_waveformatextensible(format: &Format) -> Option Result<(), StreamError> { // Process the pending commands. for command in run_context.commands.try_iter() { match command { - Command::NewStream(stream_inner) => { - let event = stream_inner.event; - run_context.stream.push(stream_inner); - run_context.handles.push(event); - }, - Command::DestroyStream(stream_id) => { - match run_context.stream.iter().position(|s| s.id == stream_id) { - None => continue, - Some(p) => { - run_context.handles.remove(p + 1); - run_context.stream.remove(p); - }, - } - }, - Command::PlayStream(stream_id) => { - match run_context.stream.iter().position(|s| s.id == stream_id) { - None => continue, - Some(p) => { - if !run_context.stream[p].playing { - let hresult = unsafe { - (*run_context.stream[p].audio_client).Start() - }; - match stream_error_from_hresult(hresult) { - Ok(()) => { - run_context.stream[p].playing = true; - } - Err(err) => { - callback(stream_id, Err(err.into())); - run_context.handles.remove(p + 1); - run_context.stream.remove(p); - } - } - } + Command::PlayStream => { + if !run_context.stream.playing { + let hresult = unsafe { + (*run_context.stream.audio_client).Start() + }; + + if let Err(err) = stream_error_from_hresult(hresult) { + return Err(err); } + run_context.stream.playing = true; } }, - Command::PauseStream(stream_id) => { - match run_context.stream.iter().position(|s| s.id == stream_id) { - None => continue, - Some(p) => { - if run_context.stream[p].playing { - let hresult = unsafe { - (*run_context.stream[p].audio_client).Stop() - }; - match stream_error_from_hresult(hresult) { - Ok(()) => { - run_context.stream[p].playing = false; - } - Err(err) => { - callback(stream_id, Err(err.into())); - run_context.handles.remove(p + 1); - run_context.stream.remove(p); - } - } - } - }, + Command::PauseStream => { + if run_context.stream.playing { + let hresult = unsafe { + (*run_context.stream.audio_client).Stop() + }; + if let Err(err) = stream_error_from_hresult(hresult) { + return Err(err); + } + run_context.stream.playing = false; } }, } } -} + Ok(()) +} // Wait for any of the given handles to be signalled. // // Returns the index of the `handle` that was signalled, or an `Err` if From e25c44a4a13cc7fa97ca758e522369c87371d815 Mon Sep 17 00:00:00 2001 From: Viktor Lazarev Date: Wed, 28 Aug 2019 20:37:08 +0200 Subject: [PATCH 12/41] Move code from stream to device --- src/host/wasapi/device.rs | 402 +++++++++++++++++++++++++++++++++++++- src/host/wasapi/stream.rs | 392 +------------------------------------ 2 files changed, 403 insertions(+), 391 deletions(-) diff --git a/src/host/wasapi/device.rs b/src/host/wasapi/device.rs index a067e47..929c604 100644 --- a/src/host/wasapi/device.rs +++ b/src/host/wasapi/device.rs @@ -7,7 +7,7 @@ use std::ops::{Deref, DerefMut}; use std::os::windows::ffi::OsStringExt; use std::ptr; use std::slice; -use std::sync::{Arc, Mutex, MutexGuard}; +use std::sync::{Arc, Mutex, MutexGuard, atomic::Ordering}; use BackendSpecificError; use DefaultFormatError; @@ -33,6 +33,7 @@ use super::winapi::shared::guiddef::{ use super::winapi::shared::winerror; use super::winapi::shared::minwindef::{ DWORD, + WORD, }; use super::winapi::shared::mmreg; use super::winapi::shared::wtypes; @@ -41,12 +42,14 @@ use super::winapi::um::winnt::LPWSTR; use super::winapi::um::winnt::WCHAR; use super::winapi::um::coml2api; use super::winapi::um::audioclient::{ + self, IAudioClient, IID_IAudioClient, AUDCLNT_E_DEVICE_INVALIDATED, }; use super::winapi::um::audiosessiontypes::{ AUDCLNT_SHAREMODE_SHARED, + AUDCLNT_STREAMFLAGS_EVENTCALLBACK, }; use super::winapi::um::combaseapi::{ CoCreateInstance, @@ -68,7 +71,8 @@ use super::winapi::um::mmdeviceapi::{ IMMEndpoint, }; -use crate::traits::DeviceTrait; +use crate::{traits::DeviceTrait, BuildStreamError, StreamData, StreamError}; +use super::{stream::{Stream, AudioClientFlow, StreamInner, Command}, winapi::um::synchapi}; pub type SupportedInputFormats = std::vec::IntoIter; pub type SupportedOutputFormats = std::vec::IntoIter; @@ -112,6 +116,24 @@ impl DeviceTrait for Device { fn default_output_format(&self) -> Result { 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_input_stream_inner(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 { + Ok(Stream::new( + Arc::new(self.build_output_stream_inner(format)?), + data_callback, + error_callback, + )) + } } struct Endpoint { @@ -600,6 +622,327 @@ impl Device { Err(DefaultFormatError::StreamTypeNotSupported) } } + + pub(crate) fn build_input_stream_inner( + &self, + format: &Format, + ) -> Result + { + unsafe { + // Making sure that COM is initialized. + // It's not actually sure that this is required, but when in doubt do it. + com::com_initialized(); + + // Obtaining a `IAudioClient`. + let audio_client = match self.build_audioclient() { + Ok(client) => client, + Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => + return Err(BuildStreamError::DeviceNotAvailable), + Err(e) => { + let description = format!("{}", e); + let err = BackendSpecificError { description }; + return Err(err.into()); + } + }; + + // Computing the format and initializing the device. + let waveformatex = { + let format_attempt = format_to_waveformatextensible(format) + .ok_or(BuildStreamError::FormatNotSupported)?; + let share_mode = AUDCLNT_SHAREMODE_SHARED; + + // Ensure the format is supported. + match super::device::is_format_supported(audio_client, &format_attempt.Format) { + Ok(false) => return Err(BuildStreamError::FormatNotSupported), + Err(_) => return Err(BuildStreamError::DeviceNotAvailable), + _ => (), + } + + // finally initializing the audio client + let hresult = (*audio_client).Initialize( + share_mode, + AUDCLNT_STREAMFLAGS_EVENTCALLBACK, + 0, + 0, + &format_attempt.Format, + ptr::null(), + ); + match check_result(hresult) { + Err(ref e) + if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => { + (*audio_client).Release(); + return Err(BuildStreamError::DeviceNotAvailable); + }, + Err(e) => { + (*audio_client).Release(); + let description = format!("{}", e); + let err = BackendSpecificError { description }; + return Err(err.into()); + }, + Ok(()) => (), + }; + + format_attempt.Format + }; + + // obtaining the size of the samples buffer in number of frames + let max_frames_in_buffer = { + let mut max_frames_in_buffer = mem::uninitialized(); + let hresult = (*audio_client).GetBufferSize(&mut max_frames_in_buffer); + + match check_result(hresult) { + Err(ref e) + if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => { + (*audio_client).Release(); + return Err(BuildStreamError::DeviceNotAvailable); + }, + Err(e) => { + (*audio_client).Release(); + let description = format!("{}", e); + let err = BackendSpecificError { description }; + return Err(err.into()); + }, + Ok(()) => (), + }; + + max_frames_in_buffer + }; + + // Creating the event that will be signalled whenever we need to submit some samples. + let event = { + let event = synchapi::CreateEventA(ptr::null_mut(), 0, 0, ptr::null()); + if event == ptr::null_mut() { + (*audio_client).Release(); + let description = format!("failed to create event"); + let err = BackendSpecificError { description }; + return Err(err.into()); + } + + if let Err(e) = check_result((*audio_client).SetEventHandle(event)) { + (*audio_client).Release(); + let description = format!("failed to call SetEventHandle: {}", e); + let err = BackendSpecificError { description }; + return Err(err.into()); + } + + event + }; + + // Building a `IAudioCaptureClient` that will be used to read captured samples. + let capture_client = { + let mut capture_client: *mut audioclient::IAudioCaptureClient = mem::uninitialized(); + let hresult = (*audio_client).GetService( + &audioclient::IID_IAudioCaptureClient, + &mut capture_client as *mut *mut audioclient::IAudioCaptureClient as *mut _, + ); + + match check_result(hresult) { + Err(ref e) + if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => { + (*audio_client).Release(); + return Err(BuildStreamError::DeviceNotAvailable); + }, + Err(e) => { + (*audio_client).Release(); + let description = format!("failed to build capture client: {}", e); + let err = BackendSpecificError { description }; + return Err(err.into()); + }, + Ok(()) => (), + }; + + &mut *capture_client + }; + + let new_stream_id = StreamId(self.next_stream_id.fetch_add(1, Ordering::Relaxed)); + if new_stream_id.0 == usize::max_value() { + return Err(BuildStreamError::StreamIdOverflow); + } + + // Once we built the `StreamInner`, we add a command that will be picked up by the + // `run()` method and added to the `RunContext`. + { + let client_flow = AudioClientFlow::Capture { + capture_client: capture_client, + }; + let inner = StreamInner { + id: new_stream_id.clone(), + audio_client: audio_client, + client_flow: client_flow, + event: event, + playing: false, + max_frames_in_buffer: max_frames_in_buffer, + bytes_per_frame: waveformatex.nBlockAlign, + sample_format: format.data_type, + }; + + self.push_command(Command::NewStream(inner)); + }; + + Ok(new_stream_id) + } + } + + pub(crate) fn build_output_stream_inner( + &self, + format: &Format, + ) -> Result + { + unsafe { + // Making sure that COM is initialized. + // It's not actually sure that this is required, but when in doubt do it. + com::com_initialized(); + + // Obtaining a `IAudioClient`. + let audio_client = match self.build_audioclient() { + Ok(client) => client, + Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => + return Err(BuildStreamError::DeviceNotAvailable), + Err(e) => { + let description = format!("{}", e); + let err = BackendSpecificError { description }; + return Err(err.into()); + } + }; + + // Computing the format and initializing the device. + let waveformatex = { + let format_attempt = format_to_waveformatextensible(format) + .ok_or(BuildStreamError::FormatNotSupported)?; + let share_mode = AUDCLNT_SHAREMODE_SHARED; + + // Ensure the format is supported. + match super::device::is_format_supported(audio_client, &format_attempt.Format) { + Ok(false) => return Err(BuildStreamError::FormatNotSupported), + Err(_) => return Err(BuildStreamError::DeviceNotAvailable), + _ => (), + } + + // finally initializing the audio client + let hresult = (*audio_client).Initialize(share_mode, + AUDCLNT_STREAMFLAGS_EVENTCALLBACK, + 0, + 0, + &format_attempt.Format, + ptr::null()); + match check_result(hresult) { + Err(ref e) + if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => { + (*audio_client).Release(); + return Err(BuildStreamError::DeviceNotAvailable); + }, + Err(e) => { + (*audio_client).Release(); + let description = format!("{}", e); + let err = BackendSpecificError { description }; + return Err(err.into()); + }, + Ok(()) => (), + }; + + format_attempt.Format + }; + + // Creating the event that will be signalled whenever we need to submit some samples. + let event = { + let event = synchapi::CreateEventA(ptr::null_mut(), 0, 0, ptr::null()); + if event == ptr::null_mut() { + (*audio_client).Release(); + let description = format!("failed to create event"); + let err = BackendSpecificError { description }; + return Err(err.into()); + } + + match check_result((*audio_client).SetEventHandle(event)) { + Err(e) => { + (*audio_client).Release(); + let description = format!("failed to call SetEventHandle: {}", e); + let err = BackendSpecificError { description }; + return Err(err.into()); + }, + Ok(_) => (), + }; + + event + }; + + // obtaining the size of the samples buffer in number of frames + let max_frames_in_buffer = { + let mut max_frames_in_buffer = mem::uninitialized(); + let hresult = (*audio_client).GetBufferSize(&mut max_frames_in_buffer); + + match check_result(hresult) { + Err(ref e) + if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => { + (*audio_client).Release(); + return Err(BuildStreamError::DeviceNotAvailable); + }, + Err(e) => { + (*audio_client).Release(); + let description = format!("failed to obtain buffer size: {}", e); + let err = BackendSpecificError { description }; + return Err(err.into()); + }, + Ok(()) => (), + }; + + max_frames_in_buffer + }; + + // Building a `IAudioRenderClient` that will be used to fill the samples buffer. + let render_client = { + let mut render_client: *mut audioclient::IAudioRenderClient = mem::uninitialized(); + let hresult = (*audio_client).GetService(&audioclient::IID_IAudioRenderClient, + &mut render_client as + *mut *mut audioclient::IAudioRenderClient as + *mut _); + + match check_result(hresult) { + Err(ref e) + if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => { + (*audio_client).Release(); + return Err(BuildStreamError::DeviceNotAvailable); + }, + Err(e) => { + (*audio_client).Release(); + let description = format!("failed to build render client: {}", e); + let err = BackendSpecificError { description }; + return Err(err.into()); + }, + Ok(()) => (), + }; + + &mut *render_client + }; + + let new_stream_id = StreamId(self.next_stream_id.fetch_add(1, Ordering::Relaxed)); + if new_stream_id.0 == usize::max_value() { + return Err(BuildStreamError::StreamIdOverflow); + } + + // Once we built the `StreamInner`, we add a command that will be picked up by the + // `run()` method and added to the `RunContext`. + { + let client_flow = AudioClientFlow::Render { + render_client: render_client, + }; + let inner = StreamInner { + id: new_stream_id.clone(), + audio_client: audio_client, + client_flow: client_flow, + event: event, + playing: false, + max_frames_in_buffer: max_frames_in_buffer, + bytes_per_frame: waveformatex.nBlockAlign, + sample_format: format.data_type, + }; + + self.push_command(Command::NewStream(inner)); + }; + + Ok(new_stream_id) + } + } } impl PartialEq for Device { @@ -842,3 +1185,58 @@ pub fn default_input_device() -> Option { pub fn default_output_device() -> Option { default_device(eRender) } + + +// Turns a `Format` into a `WAVEFORMATEXTENSIBLE`. +// +// Returns `None` if the WAVEFORMATEXTENSIBLE does not support the given format. +fn format_to_waveformatextensible(format: &Format) -> Option { + let format_tag = match format.data_type { + SampleFormat::I16 => mmreg::WAVE_FORMAT_PCM, + SampleFormat::F32 => mmreg::WAVE_FORMAT_EXTENSIBLE, + SampleFormat::U16 => return None, + }; + let channels = format.channels as WORD; + let sample_rate = format.sample_rate.0 as DWORD; + let sample_bytes = format.data_type.sample_size() as WORD; + let avg_bytes_per_sec = channels as DWORD * sample_rate * sample_bytes as DWORD; + let block_align = channels * sample_bytes; + let bits_per_sample = 8 * sample_bytes; + let cb_size = match format.data_type { + SampleFormat::I16 => 0, + SampleFormat::F32 => { + let extensible_size = mem::size_of::(); + let ex_size = mem::size_of::(); + (extensible_size - ex_size) as WORD + }, + SampleFormat::U16 => return None, + }; + let waveformatex = mmreg::WAVEFORMATEX { + wFormatTag: format_tag, + nChannels: channels, + nSamplesPerSec: sample_rate, + nAvgBytesPerSec: avg_bytes_per_sec, + nBlockAlign: block_align, + wBitsPerSample: bits_per_sample, + cbSize: cb_size, + }; + + // CPAL does not care about speaker positions, so pass audio straight through. + // TODO: This constant should be defined in winapi but is missing. + const KSAUDIO_SPEAKER_DIRECTOUT: DWORD = 0; + let channel_mask = KSAUDIO_SPEAKER_DIRECTOUT; + + let sub_format = match format.data_type { + SampleFormat::I16 => ksmedia::KSDATAFORMAT_SUBTYPE_PCM, + SampleFormat::F32 => ksmedia::KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, + SampleFormat::U16 => return None, + }; + let waveformatextensible = mmreg::WAVEFORMATEXTENSIBLE { + Format: waveformatex, + Samples: bits_per_sample as WORD, + dwChannelMask: channel_mask, + SubFormat: sub_format, + }; + + Some(waveformatextensible) +} diff --git a/src/host/wasapi/stream.rs b/src/host/wasapi/stream.rs index 8af16cd..de499f5 100644 --- a/src/host/wasapi/stream.rs +++ b/src/host/wasapi/stream.rs @@ -1,4 +1,3 @@ -use super::Device; use super::check_result; use super::com; use super::winapi::shared::basetsd::UINT32; @@ -6,8 +5,6 @@ use super::winapi::shared::ksmedia; use super::winapi::shared::minwindef::{BYTE, DWORD, FALSE, WORD}; use super::winapi::shared::mmreg; use super::winapi::um::audioclient::{self, AUDCLNT_E_DEVICE_INVALIDATED, AUDCLNT_S_BUFFER_EMPTY}; -use super::winapi::um::audiosessiontypes::{AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, AUDCLNT_STREAMFLAGS_LOOPBACK}; -use super::winapi::um::mmdeviceapi::eRender; use super::winapi::um::handleapi; use super::winapi::um::synchapi; use super::winapi::um::winbase; @@ -66,12 +63,12 @@ struct RunContext { commands: Receiver, } -enum Command { +pub (crate) enum Command { PlayStream, PauseStream, } -enum AudioClientFlow { +pub (crate) enum AudioClientFlow { Render { render_client: *mut audioclient::IAudioRenderClient, }, @@ -80,7 +77,7 @@ enum AudioClientFlow { }, } -struct StreamInner { +pub (crate) struct StreamInner { id: StreamId, audio_client: *mut audioclient::IAudioClient, client_flow: AudioClientFlow, @@ -115,335 +112,6 @@ impl EventLoop { } } - pub(crate) fn build_input_stream( - &self, - device: &Device, - format: &Format, - ) -> Result - { - unsafe { - // Making sure that COM is initialized. - // It's not actually sure that this is required, but when in doubt do it. - com::com_initialized(); - - // Obtaining a `IAudioClient`. - let audio_client = match device.build_audioclient() { - Ok(client) => client, - Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => - return Err(BuildStreamError::DeviceNotAvailable), - Err(e) => { - let description = format!("{}", e); - let err = BackendSpecificError { description }; - return Err(err.into()); - } - }; - - // Computing the format and initializing the device. - let waveformatex = { - let format_attempt = format_to_waveformatextensible(format) - .ok_or(BuildStreamError::FormatNotSupported)?; - let share_mode = AUDCLNT_SHAREMODE_SHARED; - - // Ensure the format is supported. - match super::device::is_format_supported(audio_client, &format_attempt.Format) { - Ok(false) => return Err(BuildStreamError::FormatNotSupported), - Err(_) => return Err(BuildStreamError::DeviceNotAvailable), - _ => (), - } - - // Support capturing output devices. - let mut stream_flags: DWORD = AUDCLNT_STREAMFLAGS_EVENTCALLBACK; - if device.data_flow() == eRender { - stream_flags |= AUDCLNT_STREAMFLAGS_LOOPBACK; - } - - // finally initializing the audio client - let hresult = (*audio_client).Initialize( - share_mode, - stream_flags, - 0, - 0, - &format_attempt.Format, - ptr::null(), - ); - match check_result(hresult) { - Err(ref e) - if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => { - (*audio_client).Release(); - return Err(BuildStreamError::DeviceNotAvailable); - }, - Err(e) => { - (*audio_client).Release(); - let description = format!("{}", e); - let err = BackendSpecificError { description }; - return Err(err.into()); - }, - Ok(()) => (), - }; - - format_attempt.Format - }; - - // obtaining the size of the samples buffer in number of frames - let max_frames_in_buffer = { - let mut max_frames_in_buffer = mem::uninitialized(); - let hresult = (*audio_client).GetBufferSize(&mut max_frames_in_buffer); - - match check_result(hresult) { - Err(ref e) - if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => { - (*audio_client).Release(); - return Err(BuildStreamError::DeviceNotAvailable); - }, - Err(e) => { - (*audio_client).Release(); - let description = format!("{}", e); - let err = BackendSpecificError { description }; - return Err(err.into()); - }, - Ok(()) => (), - }; - - max_frames_in_buffer - }; - - // Creating the event that will be signalled whenever we need to submit some samples. - let event = { - let event = synchapi::CreateEventA(ptr::null_mut(), 0, 0, ptr::null()); - if event == ptr::null_mut() { - (*audio_client).Release(); - let description = format!("failed to create event"); - let err = BackendSpecificError { description }; - return Err(err.into()); - } - - if let Err(e) = check_result((*audio_client).SetEventHandle(event)) { - (*audio_client).Release(); - let description = format!("failed to call SetEventHandle: {}", e); - let err = BackendSpecificError { description }; - return Err(err.into()); - } - - event - }; - - // Building a `IAudioCaptureClient` that will be used to read captured samples. - let capture_client = { - let mut capture_client: *mut audioclient::IAudioCaptureClient = mem::uninitialized(); - let hresult = (*audio_client).GetService( - &audioclient::IID_IAudioCaptureClient, - &mut capture_client as *mut *mut audioclient::IAudioCaptureClient as *mut _, - ); - - match check_result(hresult) { - Err(ref e) - if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => { - (*audio_client).Release(); - return Err(BuildStreamError::DeviceNotAvailable); - }, - Err(e) => { - (*audio_client).Release(); - let description = format!("failed to build capture client: {}", e); - let err = BackendSpecificError { description }; - return Err(err.into()); - }, - Ok(()) => (), - }; - - &mut *capture_client - }; - - let new_stream_id = StreamId(self.next_stream_id.fetch_add(1, Ordering::Relaxed)); - if new_stream_id.0 == usize::max_value() { - return Err(BuildStreamError::StreamIdOverflow); - } - - // Once we built the `StreamInner`, we add a command that will be picked up by the - // `run()` method and added to the `RunContext`. - { - let client_flow = AudioClientFlow::Capture { - capture_client: capture_client, - }; - let inner = StreamInner { - id: new_stream_id.clone(), - audio_client: audio_client, - client_flow: client_flow, - event: event, - playing: false, - max_frames_in_buffer: max_frames_in_buffer, - bytes_per_frame: waveformatex.nBlockAlign, - sample_format: format.data_type, - }; - - self.push_command(Command::NewStream(inner)); - }; - - Ok(new_stream_id) - } - } - - pub(crate) fn build_output_stream( - &self, - device: &Device, - format: &Format, - ) -> Result - { - unsafe { - // Making sure that COM is initialized. - // It's not actually sure that this is required, but when in doubt do it. - com::com_initialized(); - - // Obtaining a `IAudioClient`. - let audio_client = match device.build_audioclient() { - Ok(client) => client, - Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => - return Err(BuildStreamError::DeviceNotAvailable), - Err(e) => { - let description = format!("{}", e); - let err = BackendSpecificError { description }; - return Err(err.into()); - } - }; - - // Computing the format and initializing the device. - let waveformatex = { - let format_attempt = format_to_waveformatextensible(format) - .ok_or(BuildStreamError::FormatNotSupported)?; - let share_mode = AUDCLNT_SHAREMODE_SHARED; - - // Ensure the format is supported. - match super::device::is_format_supported(audio_client, &format_attempt.Format) { - Ok(false) => return Err(BuildStreamError::FormatNotSupported), - Err(_) => return Err(BuildStreamError::DeviceNotAvailable), - _ => (), - } - - // finally initializing the audio client - let hresult = (*audio_client).Initialize(share_mode, - AUDCLNT_STREAMFLAGS_EVENTCALLBACK, - 0, - 0, - &format_attempt.Format, - ptr::null()); - match check_result(hresult) { - Err(ref e) - if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => { - (*audio_client).Release(); - return Err(BuildStreamError::DeviceNotAvailable); - }, - Err(e) => { - (*audio_client).Release(); - let description = format!("{}", e); - let err = BackendSpecificError { description }; - return Err(err.into()); - }, - Ok(()) => (), - }; - - format_attempt.Format - }; - - // Creating the event that will be signalled whenever we need to submit some samples. - let event = { - let event = synchapi::CreateEventA(ptr::null_mut(), 0, 0, ptr::null()); - if event == ptr::null_mut() { - (*audio_client).Release(); - let description = format!("failed to create event"); - let err = BackendSpecificError { description }; - return Err(err.into()); - } - - match check_result((*audio_client).SetEventHandle(event)) { - Err(e) => { - (*audio_client).Release(); - let description = format!("failed to call SetEventHandle: {}", e); - let err = BackendSpecificError { description }; - return Err(err.into()); - }, - Ok(_) => (), - }; - - event - }; - - // obtaining the size of the samples buffer in number of frames - let max_frames_in_buffer = { - let mut max_frames_in_buffer = mem::uninitialized(); - let hresult = (*audio_client).GetBufferSize(&mut max_frames_in_buffer); - - match check_result(hresult) { - Err(ref e) - if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => { - (*audio_client).Release(); - return Err(BuildStreamError::DeviceNotAvailable); - }, - Err(e) => { - (*audio_client).Release(); - let description = format!("failed to obtain buffer size: {}", e); - let err = BackendSpecificError { description }; - return Err(err.into()); - }, - Ok(()) => (), - }; - - max_frames_in_buffer - }; - - // Building a `IAudioRenderClient` that will be used to fill the samples buffer. - let render_client = { - let mut render_client: *mut audioclient::IAudioRenderClient = mem::uninitialized(); - let hresult = (*audio_client).GetService(&audioclient::IID_IAudioRenderClient, - &mut render_client as - *mut *mut audioclient::IAudioRenderClient as - *mut _); - - match check_result(hresult) { - Err(ref e) - if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => { - (*audio_client).Release(); - return Err(BuildStreamError::DeviceNotAvailable); - }, - Err(e) => { - (*audio_client).Release(); - let description = format!("failed to build render client: {}", e); - let err = BackendSpecificError { description }; - return Err(err.into()); - }, - Ok(()) => (), - }; - - &mut *render_client - }; - - let new_stream_id = StreamId(self.next_stream_id.fetch_add(1, Ordering::Relaxed)); - if new_stream_id.0 == usize::max_value() { - return Err(BuildStreamError::StreamIdOverflow); - } - - // Once we built the `StreamInner`, we add a command that will be picked up by the - // `run()` method and added to the `RunContext`. - { - let client_flow = AudioClientFlow::Render { - render_client: render_client, - }; - let inner = StreamInner { - id: new_stream_id.clone(), - audio_client: audio_client, - client_flow: client_flow, - event: event, - playing: false, - max_frames_in_buffer: max_frames_in_buffer, - bytes_per_frame: waveformatex.nBlockAlign, - sample_format: format.data_type, - }; - - self.push_command(Command::NewStream(inner)); - }; - - Ok(new_stream_id) - } - } - #[inline] pub(crate) fn destroy_stream(&self, stream_id: StreamId) { self.push_command(Command::DestroyStream(stream_id)); @@ -518,60 +186,6 @@ impl Drop for StreamInner { } } -// Turns a `Format` into a `WAVEFORMATEXTENSIBLE`. -// -// Returns `None` if the WAVEFORMATEXTENSIBLE does not support the given format. -fn format_to_waveformatextensible(format: &Format) -> Option { - let format_tag = match format.data_type { - SampleFormat::I16 => mmreg::WAVE_FORMAT_PCM, - SampleFormat::F32 => mmreg::WAVE_FORMAT_EXTENSIBLE, - SampleFormat::U16 => return None, - }; - let channels = format.channels as WORD; - let sample_rate = format.sample_rate.0 as DWORD; - let sample_bytes = format.data_type.sample_size() as WORD; - let avg_bytes_per_sec = channels as DWORD * sample_rate * sample_bytes as DWORD; - let block_align = channels * sample_bytes; - let bits_per_sample = 8 * sample_bytes; - let cb_size = match format.data_type { - SampleFormat::I16 => 0, - SampleFormat::F32 => { - let extensible_size = mem::size_of::(); - let ex_size = mem::size_of::(); - (extensible_size - ex_size) as WORD - }, - SampleFormat::U16 => return None, - }; - let waveformatex = mmreg::WAVEFORMATEX { - wFormatTag: format_tag, - nChannels: channels, - nSamplesPerSec: sample_rate, - nAvgBytesPerSec: avg_bytes_per_sec, - nBlockAlign: block_align, - wBitsPerSample: bits_per_sample, - cbSize: cb_size, - }; - - // CPAL does not care about speaker positions, so pass audio straight through. - // TODO: This constant should be defined in winapi but is missing. - const KSAUDIO_SPEAKER_DIRECTOUT: DWORD = 0; - let channel_mask = KSAUDIO_SPEAKER_DIRECTOUT; - - let sub_format = match format.data_type { - SampleFormat::I16 => ksmedia::KSDATAFORMAT_SUBTYPE_PCM, - SampleFormat::F32 => ksmedia::KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, - SampleFormat::U16 => return None, - }; - let waveformatextensible = mmreg::WAVEFORMATEXTENSIBLE { - Format: waveformatex, - Samples: bits_per_sample as WORD, - dwChannelMask: channel_mask, - SubFormat: sub_format, - }; - - Some(waveformatextensible) -} - // Process any pending commands that are queued within the `RunContext`. fn process_commands(run_context: &mut RunContext) -> Result<(), StreamError> { // Process the pending commands. From 0541bf8667bb0e0937e91edc48c9b67078a3158f Mon Sep 17 00:00:00 2001 From: Viktor Lazarev Date: Thu, 29 Aug 2019 08:02:48 +0200 Subject: [PATCH 13/41] Add Stream struct --- src/host/wasapi/stream.rs | 47 ++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/src/host/wasapi/stream.rs b/src/host/wasapi/stream.rs index de499f5..e0c82db 100644 --- a/src/host/wasapi/stream.rs +++ b/src/host/wasapi/stream.rs @@ -16,9 +16,9 @@ use std::slice; use std::sync::Mutex; use std::sync::mpsc::{channel, Sender, Receiver}; use std::sync::atomic::AtomicUsize; -use std::sync::atomic::Ordering; -use std::sync::{Arc}; +use std::{sync::{Arc}, +thread::{self, JoinHandle}}; use BackendSpecificError; use BuildStreamError; @@ -31,16 +31,10 @@ use StreamError; use UnknownTypeOutputBuffer; use UnknownTypeInputBuffer; -pub struct EventLoop { - // Data used by the `run()` function implementation. The mutex is kept lock permanently by - // `run()`. This ensures that two `run()` invocations can't run at the same time, and also - // means that we shouldn't try to lock this field from anywhere else but `run()`. - run_context: Mutex, - - // Identifier of the next stream to create. Each new stream increases this counter. If the - // counter overflows, there's a panic. - // TODO: use AtomicU64 instead - next_stream_id: AtomicUsize, +pub struct Stream { + /// The high-priority audio processing thread calling callbacks. + /// Option used for moving out in destructor. + thread: Option>, // Commands processed by the `run()` method that is currently running. // `pending_scheduled_event` must be signalled whenever a command is added here, so that it @@ -93,25 +87,32 @@ pub (crate) struct StreamInner { sample_format: SampleFormat, } -impl EventLoop { - pub fn new() -> EventLoop { +impl Stream { + fn new(stream_inner: Arc, mut data_callback: D, mut error_callback: E) -> Stream + where D: FnMut(StreamData) + 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(); - EventLoop { - pending_scheduled_event: pending_scheduled_event, - run_context: Mutex::new(RunContext { - stream: Arc::new(), - handles: vec![pending_scheduled_event], - commands: rx, - }), - next_stream_id: AtomicUsize::new(0), + let run_context = RunContext { + stream: Arc::new(stream_inner), + handles: vec![pending_scheduled_event], + commands: rx, + }; + + let thread = thread::spawn(move || { + run_inner(run_context, &mut data_callback, &mut error_callback) + }); + + Stream { + thread: Some(thread), commands: tx, + pending_scheduled_event, } } +} +impl EventLoop { #[inline] pub(crate) fn destroy_stream(&self, stream_id: StreamId) { self.push_command(Command::DestroyStream(stream_id)); From 7765e941ed99b6be6648b55b9d4442319c289cbd Mon Sep 17 00:00:00 2001 From: Viktor Lazarev Date: Thu, 29 Aug 2019 08:10:31 +0200 Subject: [PATCH 14/41] Implement Drop for Stream --- src/host/wasapi/stream.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/host/wasapi/stream.rs b/src/host/wasapi/stream.rs index e0c82db..3d57d92 100644 --- a/src/host/wasapi/stream.rs +++ b/src/host/wasapi/stream.rs @@ -148,12 +148,17 @@ impl EventLoop { } } -impl Drop for EventLoop { +impl Drop for Stream { #[inline] fn drop(&mut self) { unsafe { handleapi::CloseHandle(self.pending_scheduled_event); } + unsafe { + let result = synchapi::SetEvent(self.pending_scheduled_event); + assert!(result != 0); + } + self.thread.take().unwrap().join().unwrap(); } } From d12d9f479beb8a1bc2f56628b7e3da7640a3be31 Mon Sep 17 00:00:00 2001 From: Viktor Lazarev Date: Thu, 29 Aug 2019 08:14:05 +0200 Subject: [PATCH 15/41] Remove redundant field names --- src/host/wasapi/device.rs | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/host/wasapi/device.rs b/src/host/wasapi/device.rs index 929c604..d7e162f 100644 --- a/src/host/wasapi/device.rs +++ b/src/host/wasapi/device.rs @@ -336,7 +336,7 @@ unsafe fn format_from_waveformatex_ptr( let format = Format { channels: (*waveformatex_ptr).nChannels as _, sample_rate: SampleRate((*waveformatex_ptr).nSamplesPerSec), - data_type: data_type, + data_type, }; Some(format) } @@ -400,7 +400,7 @@ impl Device { #[inline] fn from_immdevice(device: *mut IMMDevice) -> Self { Device { - device: device, + device, future_audio_client: Arc::new(Mutex::new(None)), } } @@ -763,15 +763,15 @@ impl Device { // `run()` method and added to the `RunContext`. { let client_flow = AudioClientFlow::Capture { - capture_client: capture_client, + capture_client, }; let inner = StreamInner { id: new_stream_id.clone(), - audio_client: audio_client, - client_flow: client_flow, - event: event, + audio_client, + client_flow, + event, playing: false, - max_frames_in_buffer: max_frames_in_buffer, + max_frames_in_buffer, bytes_per_frame: waveformatex.nBlockAlign, sample_format: format.data_type, }; @@ -924,15 +924,15 @@ impl Device { // `run()` method and added to the `RunContext`. { let client_flow = AudioClientFlow::Render { - render_client: render_client, + render_client, }; let inner = StreamInner { id: new_stream_id.clone(), - audio_client: audio_client, - client_flow: client_flow, - event: event, + audio_client, + client_flow, + event, playing: false, - max_frames_in_buffer: max_frames_in_buffer, + max_frames_in_buffer, bytes_per_frame: waveformatex.nBlockAlign, sample_format: format.data_type, }; @@ -1039,7 +1039,7 @@ impl From<*const IMMDevice> for Endpoint { fn from(device: *const IMMDevice) -> Self { unsafe { let endpoint = immendpoint_from_immdevice(device); - Endpoint { endpoint: endpoint } + Endpoint { endpoint } } } } @@ -1118,7 +1118,7 @@ impl Devices { check_result_backend_specific((*collection).GetCount(&mut count))?; Ok(Devices { - collection: collection, + collection, total_count: count, next_item: 0, }) From 125bebd733bcfd989f28072bc2b618b0d0b28163 Mon Sep 17 00:00:00 2001 From: Viktor Lazarev Date: Thu, 29 Aug 2019 08:17:46 +0200 Subject: [PATCH 16/41] Clean up wasdapi mod --- src/host/wasapi/mod.rs | 59 ++---------------------------------------- 1 file changed, 2 insertions(+), 57 deletions(-) diff --git a/src/host/wasapi/mod.rs b/src/host/wasapi/mod.rs index d833ac4..cf1e997 100644 --- a/src/host/wasapi/mod.rs +++ b/src/host/wasapi/mod.rs @@ -1,20 +1,12 @@ extern crate winapi; use BackendSpecificError; -use BuildStreamError; -use DefaultFormatError; -use DeviceNameError; use DevicesError; -use Format; -use PlayStreamError; -use PauseStreamError; -use StreamDataResult; -use SupportedFormatsError; use self::winapi::um::winnt::HRESULT; use std::io::Error as IoError; -use traits::{EventLoopTrait, HostTrait, StreamIdTrait}; +use traits::{HostTrait}; pub use self::device::{Device, Devices, SupportedInputFormats, SupportedOutputFormats, default_input_device, default_output_device}; -pub use self::stream::{EventLoop, StreamId}; +pub use self::stream::{StreamId}; mod com; mod device; @@ -37,7 +29,6 @@ impl Host { impl HostTrait for Host { type Devices = Devices; type Device = Device; - type EventLoop = EventLoop; fn is_available() -> bool { // Assume WASAPI is always available on windows. @@ -55,54 +46,8 @@ impl HostTrait for Host { fn default_output_device(&self) -> Option { default_output_device() } - - fn event_loop(&self) -> Self::EventLoop { - EventLoop::new() - } } -impl EventLoopTrait for EventLoop { - type Device = Device; - type StreamId = StreamId; - - fn build_input_stream( - &self, - device: &Self::Device, - format: &Format, - ) -> Result { - EventLoop::build_input_stream(self, device, format) - } - - fn build_output_stream( - &self, - device: &Self::Device, - format: &Format, - ) -> Result { - EventLoop::build_output_stream(self, device, format) - } - - fn play_stream(&self, stream: Self::StreamId) -> Result<(), PlayStreamError> { - EventLoop::play_stream(self, stream) - } - - fn pause_stream(&self, stream: Self::StreamId) -> Result<(), PauseStreamError> { - EventLoop::pause_stream(self, stream) - } - - fn destroy_stream(&self, stream: Self::StreamId) { - EventLoop::destroy_stream(self, stream) - } - - fn run(&self, callback: F) -> ! - where - F: FnMut(Self::StreamId, StreamDataResult) + Send, - { - EventLoop::run(self, callback) - } -} - -impl StreamIdTrait for StreamId {} - #[inline] fn check_result(result: HRESULT) -> Result<(), IoError> { if result < 0 { From f65d0e65bcbbb516d3ef5ea20db57b343423b528 Mon Sep 17 00:00:00 2001 From: Viktor Lazarev Date: Thu, 29 Aug 2019 08:20:53 +0200 Subject: [PATCH 17/41] Expose Stream instead of StreamId --- src/host/wasapi/mod.rs | 2 +- src/platform/mod.rs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/host/wasapi/mod.rs b/src/host/wasapi/mod.rs index cf1e997..ecf7d7a 100644 --- a/src/host/wasapi/mod.rs +++ b/src/host/wasapi/mod.rs @@ -6,7 +6,7 @@ use self::winapi::um::winnt::HRESULT; use std::io::Error as IoError; use traits::{HostTrait}; pub use self::device::{Device, Devices, SupportedInputFormats, SupportedOutputFormats, default_input_device, default_output_device}; -pub use self::stream::{StreamId}; +pub use self::stream::Stream; mod com; mod device; diff --git a/src/platform/mod.rs b/src/platform/mod.rs index c16c9dc..45d91ed 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -468,9 +468,8 @@ mod platform_impl { pub use crate::host::wasapi::{ Device as WasapiDevice, Devices as WasapiDevices, - EventLoop as WasapiEventLoop, + Stream as WasapiStream, Host as WasapiHost, - StreamId as WasapiStreamId, SupportedInputFormats as WasapiSupportedInputFormats, SupportedOutputFormats as WasapiSupportedOutputFormats, }; From 1021141d16a6afc0e6018c436c5340356a0bf1f0 Mon Sep 17 00:00:00 2001 From: Viktor Lazarev Date: Thu, 29 Aug 2019 08:29:48 +0200 Subject: [PATCH 18/41] Fix "build_input/output_stream_inner" methods --- src/host/wasapi/device.rs | 70 ++++++++++++++------------------------- 1 file changed, 24 insertions(+), 46 deletions(-) diff --git a/src/host/wasapi/device.rs b/src/host/wasapi/device.rs index d7e162f..1848bc9 100644 --- a/src/host/wasapi/device.rs +++ b/src/host/wasapi/device.rs @@ -626,7 +626,7 @@ impl Device { pub(crate) fn build_input_stream_inner( &self, format: &Format, - ) -> Result + ) -> Result { unsafe { // Making sure that COM is initialized. @@ -754,39 +754,28 @@ impl Device { &mut *capture_client }; - let new_stream_id = StreamId(self.next_stream_id.fetch_add(1, Ordering::Relaxed)); - if new_stream_id.0 == usize::max_value() { - return Err(BuildStreamError::StreamIdOverflow); - } - // Once we built the `StreamInner`, we add a command that will be picked up by the // `run()` method and added to the `RunContext`. - { - let client_flow = AudioClientFlow::Capture { - capture_client, - }; - let inner = StreamInner { - id: new_stream_id.clone(), - audio_client, - client_flow, - event, - playing: false, - max_frames_in_buffer, - bytes_per_frame: waveformatex.nBlockAlign, - sample_format: format.data_type, - }; - - self.push_command(Command::NewStream(inner)); + let client_flow = AudioClientFlow::Capture { + capture_client, }; - Ok(new_stream_id) + Ok( StreamInner { + audio_client, + client_flow, + event, + playing: false, + max_frames_in_buffer, + bytes_per_frame: waveformatex.nBlockAlign, + sample_format: format.data_type, + }) } } pub(crate) fn build_output_stream_inner( &self, format: &Format, - ) -> Result + ) -> Result { unsafe { // Making sure that COM is initialized. @@ -915,32 +904,21 @@ impl Device { &mut *render_client }; - let new_stream_id = StreamId(self.next_stream_id.fetch_add(1, Ordering::Relaxed)); - if new_stream_id.0 == usize::max_value() { - return Err(BuildStreamError::StreamIdOverflow); - } - // Once we built the `StreamInner`, we add a command that will be picked up by the // `run()` method and added to the `RunContext`. - { - let client_flow = AudioClientFlow::Render { - render_client, - }; - let inner = StreamInner { - id: new_stream_id.clone(), - audio_client, - client_flow, - event, - playing: false, - max_frames_in_buffer, - bytes_per_frame: waveformatex.nBlockAlign, - sample_format: format.data_type, - }; - - self.push_command(Command::NewStream(inner)); + let client_flow = AudioClientFlow::Render { + render_client, }; - Ok(new_stream_id) + Ok(StreamInner { + audio_client, + client_flow, + event, + playing: false, + max_frames_in_buffer, + bytes_per_frame: waveformatex.nBlockAlign, + sample_format: format.data_type, + }) } } } From e7f708701c1349c2715021ab2d16b8bb2f95c53e Mon Sep 17 00:00:00 2001 From: Viktor Lazarev Date: Thu, 29 Aug 2019 08:30:56 +0200 Subject: [PATCH 19/41] Remove old code --- src/host/wasapi/stream.rs | 46 --------------------------------------- 1 file changed, 46 deletions(-) diff --git a/src/host/wasapi/stream.rs b/src/host/wasapi/stream.rs index 3d57d92..f29a02b 100644 --- a/src/host/wasapi/stream.rs +++ b/src/host/wasapi/stream.rs @@ -72,7 +72,6 @@ pub (crate) enum AudioClientFlow { } pub (crate) struct StreamInner { - id: StreamId, audio_client: *mut audioclient::IAudioClient, client_flow: AudioClientFlow, // Event that is signalled by WASAPI whenever audio data must be written. @@ -112,42 +111,6 @@ impl Stream { } } -impl EventLoop { - #[inline] - pub(crate) fn destroy_stream(&self, stream_id: StreamId) { - self.push_command(Command::DestroyStream(stream_id)); - } - - #[inline] - pub(crate) fn run(&self, mut callback: F) -> ! - where F: FnMut(StreamId, StreamData) - { - self.run_inner(&mut callback); - } - - #[inline] - pub(crate) fn play_stream(&self, stream: StreamId) -> Result<(), PlayStreamError> { - self.push_command(Command::PlayStream(stream)); - Ok(()) - } - - #[inline] - pub(crate) fn pause_stream(&self, stream: StreamId) -> Result<(), PauseStreamError> { - self.push_command(Command::PauseStream(stream)); - Ok(()) - } - - #[inline] - fn push_command(&self, command: Command) { - // Safe to unwrap: sender outlives receiver. - self.commands.send(command).unwrap(); - unsafe { - let result = synchapi::SetEvent(self.pending_scheduled_event); - assert!(result != 0); - } - } -} - impl Drop for Stream { #[inline] fn drop(&mut self) { @@ -162,15 +125,6 @@ impl Drop for Stream { } } -unsafe impl Send for EventLoop { -} -unsafe impl Sync for EventLoop { -} - -// The content of a stream ID is a number that was fetched from `next_stream_id`. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct StreamId(usize); - impl Drop for AudioClientFlow { fn drop(&mut self) { unsafe { From a412b00336cdefc1d86fc85569bc1daf5ce58acb Mon Sep 17 00:00:00 2001 From: Viktor Lazarev Date: Thu, 29 Aug 2019 08:31:18 +0200 Subject: [PATCH 20/41] Fix the "Stream::new' method --- src/host/wasapi/stream.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/host/wasapi/stream.rs b/src/host/wasapi/stream.rs index f29a02b..c42421d 100644 --- a/src/host/wasapi/stream.rs +++ b/src/host/wasapi/stream.rs @@ -87,14 +87,14 @@ pub (crate) struct StreamInner { } impl Stream { - fn new(stream_inner: Arc, mut data_callback: D, mut error_callback: E) -> Stream + pub (crate) fn new(stream_inner: Arc, mut data_callback: D, mut error_callback: E) -> Stream where D: FnMut(StreamData) + 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 { - stream: Arc::new(stream_inner), + stream: stream_inner, handles: vec![pending_scheduled_event], commands: rx, }; From a218dc90e6471c48b58d73b4d2a7007488a2d450 Mon Sep 17 00:00:00 2001 From: Viktor Lazarev Date: Thu, 29 Aug 2019 08:34:41 +0200 Subject: [PATCH 21/41] Fixing compile errors --- src/host/wasapi/device.rs | 1 + src/host/wasapi/stream.rs | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/host/wasapi/device.rs b/src/host/wasapi/device.rs index 1848bc9..fd9840a 100644 --- a/src/host/wasapi/device.rs +++ b/src/host/wasapi/device.rs @@ -96,6 +96,7 @@ pub struct Device { impl DeviceTrait for Device { type SupportedInputFormats = SupportedInputFormats; type SupportedOutputFormats = SupportedOutputFormats; + type Stream = Stream; fn name(&self) -> Result { Device::name(self) diff --git a/src/host/wasapi/stream.rs b/src/host/wasapi/stream.rs index c42421d..5ed67e7 100644 --- a/src/host/wasapi/stream.rs +++ b/src/host/wasapi/stream.rs @@ -239,7 +239,7 @@ fn run_inner(run_context: RunContext, data_callback: &mut dyn FnMut(StreamData), unsafe { 'stream_loop: loop { // Process queued commands. - match process_commands(run_context, error_callback) { + match process_commands(&mut run_context) { Ok(()) => (), Err(err) => { error_callback(err); @@ -251,7 +251,7 @@ fn run_inner(run_context: RunContext, data_callback: &mut dyn FnMut(StreamData), let handle_idx = match wait_for_handle_signal(&run_context.handles) { Ok(idx) => idx, Err(err) => { - error_callback(err); + error_callback(err.into()); break 'stream_loop; } }; @@ -312,7 +312,7 @@ fn run_inner(run_context: RunContext, data_callback: &mut dyn FnMut(StreamData), buffer: slice, }); let data = StreamData::Input { buffer: unknown_buffer }; - data_callback(stream.id.clone(), Ok(data)); + data_callback(data); // Release the buffer. let hresult = (*capture_client).ReleaseBuffer(frames_available); if let Err(err) = stream_error_from_hresult(hresult) { @@ -332,7 +332,7 @@ fn run_inner(run_context: RunContext, data_callback: &mut dyn FnMut(StreamData), AudioClientFlow::Render { render_client } => { // The number of frames available for writing. - let frames_available = match get_available_frames(stream) { + let frames_available = match get_available_frames(&stream) { Ok(0) => continue, // TODO: Can this happen? Ok(n) => n, Err(err) => { @@ -365,7 +365,7 @@ fn run_inner(run_context: RunContext, data_callback: &mut dyn FnMut(StreamData), buffer: slice }); let data = StreamData::Output { buffer: unknown_buffer }; - data_callback(stream.id.clone(), Ok(data)); + data_callback(data); let hresult = (*render_client) .ReleaseBuffer(frames_available as u32, 0); if let Err(err) = stream_error_from_hresult(hresult) { From 10dc7799437e0fd9529a128fa50f8243d0312765 Mon Sep 17 00:00:00 2001 From: Viktor Lazarev Date: Thu, 29 Aug 2019 08:51:10 +0200 Subject: [PATCH 22/41] Return back missed play/pause implementation --- src/host/wasapi/stream.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/host/wasapi/stream.rs b/src/host/wasapi/stream.rs index 5ed67e7..decada6 100644 --- a/src/host/wasapi/stream.rs +++ b/src/host/wasapi/stream.rs @@ -19,6 +19,7 @@ use std::sync::atomic::AtomicUsize; use std::{sync::{Arc}, thread::{self, JoinHandle}}; +use crate::traits::StreamTrait; use BackendSpecificError; use BuildStreamError; @@ -109,6 +110,16 @@ impl Stream { pending_scheduled_event, } } + + #[inline] + fn push_command(&self, command: Command) { + // Safe to unwrap: sender outlives receiver. + self.commands.send(command).unwrap(); + unsafe { + let result = synchapi::SetEvent(self.pending_scheduled_event); + assert!(result != 0); + } + } } impl Drop for Stream { @@ -125,6 +136,17 @@ impl Drop for Stream { } } +impl StreamTrait for Stream { + fn play(&self) -> Result<(), PlayStreamError> { + self.push_command(Command::PlayStream); + Ok(()) + } + fn pause(&self)-> Result<(), PauseStreamError> { + self.push_command(Command::PauseStream); + Ok(()) + } +} + impl Drop for AudioClientFlow { fn drop(&mut self) { unsafe { From c62cb48e19297cf458271a55931e8e09c09cdfca Mon Sep 17 00:00:00 2001 From: Viktor Lazarev Date: Thu, 29 Aug 2019 08:58:27 +0200 Subject: [PATCH 23/41] Apply suggestions proposed by Clippy --- src/host/wasapi/device.rs | 10 +++++----- src/host/wasapi/mod.rs | 5 +++-- src/host/wasapi/stream.rs | 11 ++--------- 3 files changed, 10 insertions(+), 16 deletions(-) diff --git a/src/host/wasapi/device.rs b/src/host/wasapi/device.rs index fd9840a..e9e53e6 100644 --- a/src/host/wasapi/device.rs +++ b/src/host/wasapi/device.rs @@ -7,7 +7,7 @@ use std::ops::{Deref, DerefMut}; use std::os::windows::ffi::OsStringExt; use std::ptr; use std::slice; -use std::sync::{Arc, Mutex, MutexGuard, atomic::Ordering}; +use std::sync::{Arc, Mutex, MutexGuard}; use BackendSpecificError; use DefaultFormatError; @@ -72,7 +72,7 @@ use super::winapi::um::mmdeviceapi::{ }; use crate::{traits::DeviceTrait, BuildStreamError, StreamData, StreamError}; -use super::{stream::{Stream, AudioClientFlow, StreamInner, Command}, winapi::um::synchapi}; +use super::{stream::{Stream, AudioClientFlow, StreamInner}, winapi::um::synchapi}; pub type SupportedInputFormats = std::vec::IntoIter; pub type SupportedOutputFormats = std::vec::IntoIter; @@ -267,7 +267,7 @@ pub unsafe fn is_format_supported( // has been found, but not an exact match) so we also treat this as unsupported. match (result, check_result(result)) { (_, Err(ref e)) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => { - return Err(SupportedFormatsError::DeviceNotAvailable); + Err(SupportedFormatsError::DeviceNotAvailable) }, (_, Err(_)) => { Ok(false) @@ -486,7 +486,7 @@ impl Device { }; // If the default format can't succeed we have no hope of finding other formats. - assert_eq!(try!(is_format_supported(client, default_waveformatex_ptr.0)), true); + assert_eq!(is_format_supported(client, default_waveformatex_ptr.0)?, true); // Copy the format to use as a test format (as to avoid mutating the original format). let mut test_format = { @@ -508,7 +508,7 @@ impl Device { test_format.nSamplesPerSec = rate; test_format.nAvgBytesPerSec = rate * (*default_waveformatex_ptr.0).nBlockAlign as DWORD; - if try!(is_format_supported(client, test_format.as_ptr())) { + if is_format_supported(client, test_format.as_ptr())? { supported_sample_rates.push(rate); } } diff --git a/src/host/wasapi/mod.rs b/src/host/wasapi/mod.rs index ecf7d7a..c7fc241 100644 --- a/src/host/wasapi/mod.rs +++ b/src/host/wasapi/mod.rs @@ -61,8 +61,9 @@ fn check_result_backend_specific(result: HRESULT) -> Result<(), BackendSpecificE match check_result(result) { Ok(()) => Ok(()), Err(err) => { - let description = format!("{}", err); - return Err(BackendSpecificError { description }); + Err(BackendSpecificError { + description: format!("{}", err), + }) } } } diff --git a/src/host/wasapi/stream.rs b/src/host/wasapi/stream.rs index decada6..72a863f 100644 --- a/src/host/wasapi/stream.rs +++ b/src/host/wasapi/stream.rs @@ -1,9 +1,6 @@ use super::check_result; -use super::com; use super::winapi::shared::basetsd::UINT32; -use super::winapi::shared::ksmedia; -use super::winapi::shared::minwindef::{BYTE, DWORD, FALSE, WORD}; -use super::winapi::shared::mmreg; +use super::winapi::shared::minwindef::{BYTE, FALSE, WORD}; use super::winapi::um::audioclient::{self, AUDCLNT_E_DEVICE_INVALIDATED, AUDCLNT_S_BUFFER_EMPTY}; use super::winapi::um::handleapi; use super::winapi::um::synchapi; @@ -13,17 +10,13 @@ use super::winapi::um::winnt; use std::mem; use std::ptr; use std::slice; -use std::sync::Mutex; use std::sync::mpsc::{channel, Sender, Receiver}; -use std::sync::atomic::AtomicUsize; use std::{sync::{Arc}, thread::{self, JoinHandle}}; use crate::traits::StreamTrait; use BackendSpecificError; -use BuildStreamError; -use Format; use PauseStreamError; use PlayStreamError; use SampleFormat; @@ -257,7 +250,7 @@ fn stream_error_from_hresult(hresult: winnt::HRESULT) -> Result<(), StreamError> Ok(()) } -fn run_inner(run_context: RunContext, data_callback: &mut dyn FnMut(StreamData), error_callback: &mut dyn FnMut(StreamError)) -> () { +fn run_inner(run_context: RunContext, data_callback: &mut dyn FnMut(StreamData), error_callback: &mut dyn FnMut(StreamError)) { unsafe { 'stream_loop: loop { // Process queued commands. From d4965d36735b07b5068e8668c455276e4a2e26a7 Mon Sep 17 00:00:00 2001 From: Viktor Lazarev Date: Thu, 29 Aug 2019 09:00:21 +0200 Subject: [PATCH 24/41] `cargo fmt` --- src/host/wasapi/com.rs | 2 +- src/host/wasapi/device.rs | 358 ++++++++++++++++++-------------------- src/host/wasapi/mod.rs | 21 +-- src/host/wasapi/stream.rs | 110 ++++++------ 4 files changed, 241 insertions(+), 250 deletions(-) diff --git a/src/host/wasapi/com.rs b/src/host/wasapi/com.rs index bc322d1..1b1d109 100644 --- a/src/host/wasapi/com.rs +++ b/src/host/wasapi/com.rs @@ -3,8 +3,8 @@ use super::check_result; use std::ptr; -use super::winapi::um::objbase::{COINIT_MULTITHREADED}; use super::winapi::um::combaseapi::{CoInitializeEx, CoUninitialize}; +use super::winapi::um::objbase::COINIT_MULTITHREADED; thread_local!(static COM_INITIALIZED: ComInitialized = { unsafe { diff --git a/src/host/wasapi/device.rs b/src/host/wasapi/device.rs index e9e53e6..eee8e24 100644 --- a/src/host/wasapi/device.rs +++ b/src/host/wasapi/device.rs @@ -14,65 +14,47 @@ use DefaultFormatError; use DeviceNameError; use DevicesError; use Format; -use SupportedFormatsError; 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; -use super::winapi::Interface; use super::winapi::ctypes::c_void; use super::winapi::shared::devpkey; +use super::winapi::shared::guiddef::GUID; use super::winapi::shared::ksmedia; -use super::winapi::shared::guiddef::{ - GUID, -}; -use super::winapi::shared::winerror; -use super::winapi::shared::minwindef::{ - DWORD, - WORD, -}; +use super::winapi::shared::minwindef::{DWORD, WORD}; use super::winapi::shared::mmreg; +use super::winapi::shared::winerror; use super::winapi::shared::wtypes; +use super::winapi::Interface; // https://msdn.microsoft.com/en-us/library/cc230355.aspx -use super::winapi::um::winnt::LPWSTR; -use super::winapi::um::winnt::WCHAR; -use super::winapi::um::coml2api; use super::winapi::um::audioclient::{ - self, - IAudioClient, - IID_IAudioClient, - AUDCLNT_E_DEVICE_INVALIDATED, + self, IAudioClient, IID_IAudioClient, AUDCLNT_E_DEVICE_INVALIDATED, }; use super::winapi::um::audiosessiontypes::{ - AUDCLNT_SHAREMODE_SHARED, - AUDCLNT_STREAMFLAGS_EVENTCALLBACK, + AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, }; use super::winapi::um::combaseapi::{ - CoCreateInstance, - CoTaskMemFree, - CLSCTX_ALL, - PropVariantClear, + CoCreateInstance, CoTaskMemFree, PropVariantClear, CLSCTX_ALL, }; +use super::winapi::um::coml2api; use super::winapi::um::mmdeviceapi::{ - eAll, - eCapture, - eConsole, - eRender, - CLSID_MMDeviceEnumerator, - DEVICE_STATE_ACTIVE, - EDataFlow, - IMMDevice, - IMMDeviceCollection, - IMMDeviceEnumerator, - IMMEndpoint, + eAll, eCapture, eConsole, eRender, CLSID_MMDeviceEnumerator, EDataFlow, IMMDevice, + IMMDeviceCollection, IMMDeviceEnumerator, IMMEndpoint, DEVICE_STATE_ACTIVE, }; +use super::winapi::um::winnt::LPWSTR; +use super::winapi::um::winnt::WCHAR; +use super::{ + stream::{AudioClientFlow, Stream, StreamInner}, + winapi::um::synchapi, +}; use crate::{traits::DeviceTrait, BuildStreamError, StreamData, StreamError}; -use super::{stream::{Stream, AudioClientFlow, StreamInner}, winapi::um::synchapi}; pub type SupportedInputFormats = std::vec::IntoIter; pub type SupportedOutputFormats = std::vec::IntoIter; @@ -80,10 +62,8 @@ pub type SupportedOutputFormats = std::vec::IntoIter; /// Wrapper because of that stupid decision to remove `Send` and `Sync` from raw pointers. #[derive(Copy, Clone)] struct IAudioClientWrapper(*mut IAudioClient); -unsafe impl Send for IAudioClientWrapper { -} -unsafe impl Sync for IAudioClientWrapper { -} +unsafe impl Send for IAudioClientWrapper {} +unsafe impl Sync for IAudioClientWrapper {} /// An opaque type that identifies an end point. pub struct Device { @@ -102,11 +82,15 @@ impl DeviceTrait for Device { Device::name(self) } - fn supported_input_formats(&self) -> Result { + fn supported_input_formats( + &self, + ) -> Result { Device::supported_input_formats(self) } - fn supported_output_formats(&self) -> Result { + fn supported_output_formats( + &self, + ) -> Result { Device::supported_output_formats(self) } @@ -118,8 +102,16 @@ 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 + D: FnMut(StreamData) + Send + 'static, + E: FnMut(StreamError) + Send + 'static, + { Ok(Stream::new( Arc::new(self.build_input_stream_inner(format)?), data_callback, @@ -127,8 +119,16 @@ impl DeviceTrait for Device { )) } - 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(StreamData) + Send + 'static, + E: FnMut(StreamError) + Send + 'static, + { Ok(Stream::new( Arc::new(self.build_output_stream_inner(format)?), data_callback, @@ -157,7 +157,6 @@ impl Drop for WaveFormatExPtr { } } - impl WaveFormat { // Given a pointer to some format, returns a valid copy of the format. pub fn copy_from_waveformatex_ptr(ptr: *const mmreg::WAVEFORMATEX) -> Option { @@ -165,11 +164,11 @@ impl WaveFormat { match (*ptr).wFormatTag { mmreg::WAVE_FORMAT_PCM | mmreg::WAVE_FORMAT_IEEE_FLOAT => { Some(WaveFormat::Ex(*ptr)) - }, + } mmreg::WAVE_FORMAT_EXTENSIBLE => { let extensible_ptr = ptr as *const mmreg::WAVEFORMATEXTENSIBLE; Some(WaveFormat::Extensible(*extensible_ptr)) - }, + } _ => None, } } @@ -200,11 +199,12 @@ impl DerefMut for WaveFormat { } } - unsafe fn immendpoint_from_immdevice(device: *const IMMDevice) -> *mut IMMEndpoint { let mut endpoint: *mut IMMEndpoint = mem::uninitialized(); - check_result((*device).QueryInterface(&IMMEndpoint::uuidof(), &mut endpoint as *mut _ as *mut _)) - .expect("could not query IMMDevice interface for IMMEndpoint"); + check_result( + (*device).QueryInterface(&IMMEndpoint::uuidof(), &mut endpoint as *mut _ as *mut _), + ) + .expect("could not query IMMDevice interface for IMMEndpoint"); endpoint } @@ -219,10 +219,7 @@ unsafe fn data_flow_from_immendpoint(endpoint: *const IMMEndpoint) -> EDataFlow pub unsafe fn is_format_supported( client: *const IAudioClient, waveformatex_ptr: *const mmreg::WAVEFORMATEX, -) -> Result -{ - - +) -> Result { /* // `IsFormatSupported` checks whether the format is supported and fills // a `WAVEFORMATEX` @@ -255,7 +252,6 @@ pub unsafe fn is_format_supported( }; */ - // Check if the given format is supported. let is_supported = |waveformatex_ptr, mut closest_waveformatex_ptr| { let result = (*client).IsFormatSupported( @@ -268,16 +264,10 @@ pub unsafe fn is_format_supported( match (result, check_result(result)) { (_, Err(ref e)) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => { Err(SupportedFormatsError::DeviceNotAvailable) - }, - (_, Err(_)) => { - Ok(false) - }, - (winerror::S_FALSE, _) => { - Ok(false) - }, - (_, Ok(())) => { - Ok(true) - }, + } + (_, Err(_)) => Ok(false), + (winerror::S_FALSE, _) => Ok(false), + (_, Ok(())) => Ok(true), } }; @@ -290,34 +280,30 @@ pub unsafe fn is_format_supported( let mut closest_waveformatex = *waveformatex_ptr; let closest_waveformatex_ptr = &mut closest_waveformatex as *mut _; is_supported(waveformatex_ptr, closest_waveformatex_ptr) - }, + } mmreg::WAVE_FORMAT_EXTENSIBLE => { - let waveformatextensible_ptr = - waveformatex_ptr as *const mmreg::WAVEFORMATEXTENSIBLE; + let waveformatextensible_ptr = waveformatex_ptr as *const mmreg::WAVEFORMATEXTENSIBLE; let mut closest_waveformatextensible = *waveformatextensible_ptr; - let closest_waveformatextensible_ptr = - &mut closest_waveformatextensible as *mut _; + let closest_waveformatextensible_ptr = &mut closest_waveformatextensible as *mut _; let closest_waveformatex_ptr = closest_waveformatextensible_ptr as *mut mmreg::WAVEFORMATEX; is_supported(waveformatex_ptr, closest_waveformatex_ptr) - }, + } _ => Ok(false), } } - // Get a cpal Format from a WAVEFORMATEX. unsafe fn format_from_waveformatex_ptr( waveformatex_ptr: *const mmreg::WAVEFORMATEX, -) -> Option -{ +) -> Option { fn cmp_guid(a: &GUID, b: &GUID) -> bool { - a.Data1 == b.Data1 - && a.Data2 == b.Data2 - && a.Data3 == b.Data3 - && a.Data4 == b.Data4 + a.Data1 == b.Data1 && a.Data2 == b.Data2 && a.Data3 == b.Data3 && a.Data4 == b.Data4 } - let data_type = match ((*waveformatex_ptr).wBitsPerSample, (*waveformatex_ptr).wFormatTag) { + let data_type = match ( + (*waveformatex_ptr).wBitsPerSample, + (*waveformatex_ptr).wFormatTag, + ) { (16, mmreg::WAVE_FORMAT_PCM) => SampleFormat::I16, (32, mmreg::WAVE_FORMAT_IEEE_FLOAT) => SampleFormat::F32, (n_bits, mmreg::WAVE_FORMAT_EXTENSIBLE) => { @@ -330,7 +316,7 @@ unsafe fn format_from_waveformatex_ptr( } else { return None; } - }, + } // Unknown data format returned by GetMixFormat. _ => return None, }; @@ -342,10 +328,8 @@ unsafe fn format_from_waveformatex_ptr( Some(format) } -unsafe impl Send for Device { -} -unsafe impl Sync for Device { -} +unsafe impl Send for Device {} +unsafe impl Sync for Device {} impl Device { pub fn name(&self) -> Result { @@ -356,12 +340,10 @@ impl Device { // Get the endpoint's friendly-name property. let mut property_value = mem::zeroed(); - if let Err(err) = check_result( - (*property_store).GetValue( - &devpkey::DEVPKEY_Device_FriendlyName as *const _ as *const _, - &mut property_value - ) - ) { + if let Err(err) = check_result((*property_store).GetValue( + &devpkey::DEVPKEY_Device_FriendlyName as *const _ as *const _, + &mut property_value, + )) { let description = format!("failed to retrieve name from property store: {}", err); let err = BackendSpecificError { description }; return Err(err.into()); @@ -369,8 +351,10 @@ impl Device { // Read the friendly-name from the union data field, expecting a *const u16. if property_value.vt != wtypes::VT_LPWSTR as _ { - let description = - format!("property store produced invalid data: {:?}", property_value.vt); + let description = format!( + "property store produced invalid data: {:?}", + property_value.vt + ); let err = BackendSpecificError { description }; return Err(err.into()); } @@ -407,8 +391,9 @@ impl Device { } /// Ensures that `future_audio_client` contains a `Some` and returns a locked mutex to it. - fn ensure_future_audio_client(&self) - -> Result>, IoError> { + fn ensure_future_audio_client( + &self, + ) -> Result>, IoError> { let mut lock = self.future_audio_client.lock().unwrap(); if lock.is_some() { return Ok(lock); @@ -416,10 +401,12 @@ impl Device { let audio_client: *mut IAudioClient = unsafe { let mut audio_client = mem::uninitialized(); - let hresult = (*self.device).Activate(&IID_IAudioClient, - CLSCTX_ALL, - ptr::null_mut(), - &mut audio_client); + let hresult = (*self.device).Activate( + &IID_IAudioClient, + CLSCTX_ALL, + ptr::null_mut(), + &mut audio_client, + ); // can fail if the device has been disconnected since we enumerated it, or if // the device doesn't support playback for some reason @@ -466,7 +453,7 @@ impl Device { let description = format!("{}", e); let err = BackendSpecificError { description }; return Err(err.into()); - }, + } }; let client = lock.unwrap().0; @@ -477,16 +464,19 @@ impl Device { Ok(()) => (), Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => { return Err(SupportedFormatsError::DeviceNotAvailable); - }, + } Err(e) => { let description = format!("{}", e); let err = BackendSpecificError { description }; return Err(err.into()); - }, + } }; // If the default format can't succeed we have no hope of finding other formats. - assert_eq!(is_format_supported(client, default_waveformatex_ptr.0)?, true); + assert_eq!( + is_format_supported(client, default_waveformatex_ptr.0)?, + true + ); // Copy the format to use as a test format (as to avoid mutating the original format). let mut test_format = { @@ -553,7 +543,9 @@ impl Device { } } - pub fn supported_output_formats(&self) -> Result { + pub fn supported_output_formats( + &self, + ) -> Result { if self.data_flow() == eRender { self.supported_formats() // If it's an input device, assume no output formats. @@ -588,12 +580,12 @@ impl Device { match check_result((*client).GetMixFormat(&mut format_ptr.0)) { Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => { return Err(DefaultFormatError::DeviceNotAvailable); - }, + } Err(e) => { let description = format!("{}", e); let err = BackendSpecificError { description }; return Err(err.into()); - }, + } Ok(()) => (), }; @@ -623,12 +615,11 @@ impl Device { Err(DefaultFormatError::StreamTypeNotSupported) } } - + pub(crate) fn build_input_stream_inner( &self, format: &Format, - ) -> Result - { + ) -> Result { unsafe { // Making sure that COM is initialized. // It's not actually sure that this is required, but when in doubt do it. @@ -637,8 +628,9 @@ impl Device { // Obtaining a `IAudioClient`. let audio_client = match self.build_audioclient() { Ok(client) => client, - Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => - return Err(BuildStreamError::DeviceNotAvailable), + Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => { + return Err(BuildStreamError::DeviceNotAvailable) + } Err(e) => { let description = format!("{}", e); let err = BackendSpecificError { description }; @@ -669,17 +661,16 @@ impl Device { ptr::null(), ); match check_result(hresult) { - Err(ref e) - if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => { + Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => { (*audio_client).Release(); return Err(BuildStreamError::DeviceNotAvailable); - }, + } Err(e) => { (*audio_client).Release(); let description = format!("{}", e); let err = BackendSpecificError { description }; return Err(err.into()); - }, + } Ok(()) => (), }; @@ -692,17 +683,16 @@ impl Device { let hresult = (*audio_client).GetBufferSize(&mut max_frames_in_buffer); match check_result(hresult) { - Err(ref e) - if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => { + Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => { (*audio_client).Release(); return Err(BuildStreamError::DeviceNotAvailable); - }, + } Err(e) => { (*audio_client).Release(); let description = format!("{}", e); let err = BackendSpecificError { description }; return Err(err.into()); - }, + } Ok(()) => (), }; @@ -731,24 +721,24 @@ impl Device { // Building a `IAudioCaptureClient` that will be used to read captured samples. let capture_client = { - let mut capture_client: *mut audioclient::IAudioCaptureClient = mem::uninitialized(); + let mut capture_client: *mut audioclient::IAudioCaptureClient = + mem::uninitialized(); let hresult = (*audio_client).GetService( &audioclient::IID_IAudioCaptureClient, &mut capture_client as *mut *mut audioclient::IAudioCaptureClient as *mut _, ); match check_result(hresult) { - Err(ref e) - if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => { + Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => { (*audio_client).Release(); return Err(BuildStreamError::DeviceNotAvailable); - }, + } Err(e) => { (*audio_client).Release(); let description = format!("failed to build capture client: {}", e); let err = BackendSpecificError { description }; return Err(err.into()); - }, + } Ok(()) => (), }; @@ -757,11 +747,9 @@ impl Device { // Once we built the `StreamInner`, we add a command that will be picked up by the // `run()` method and added to the `RunContext`. - let client_flow = AudioClientFlow::Capture { - capture_client, - }; + let client_flow = AudioClientFlow::Capture { capture_client }; - Ok( StreamInner { + Ok(StreamInner { audio_client, client_flow, event, @@ -776,8 +764,7 @@ impl Device { pub(crate) fn build_output_stream_inner( &self, format: &Format, - ) -> Result - { + ) -> Result { unsafe { // Making sure that COM is initialized. // It's not actually sure that this is required, but when in doubt do it. @@ -786,8 +773,9 @@ impl Device { // Obtaining a `IAudioClient`. let audio_client = match self.build_audioclient() { Ok(client) => client, - Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => - return Err(BuildStreamError::DeviceNotAvailable), + Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => { + return Err(BuildStreamError::DeviceNotAvailable) + } Err(e) => { let description = format!("{}", e); let err = BackendSpecificError { description }; @@ -809,24 +797,25 @@ impl Device { } // finally initializing the audio client - let hresult = (*audio_client).Initialize(share_mode, - AUDCLNT_STREAMFLAGS_EVENTCALLBACK, - 0, - 0, - &format_attempt.Format, - ptr::null()); + let hresult = (*audio_client).Initialize( + share_mode, + AUDCLNT_STREAMFLAGS_EVENTCALLBACK, + 0, + 0, + &format_attempt.Format, + ptr::null(), + ); match check_result(hresult) { - Err(ref e) - if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => { + Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => { (*audio_client).Release(); return Err(BuildStreamError::DeviceNotAvailable); - }, + } Err(e) => { (*audio_client).Release(); let description = format!("{}", e); let err = BackendSpecificError { description }; return Err(err.into()); - }, + } Ok(()) => (), }; @@ -849,7 +838,7 @@ impl Device { let description = format!("failed to call SetEventHandle: {}", e); let err = BackendSpecificError { description }; return Err(err.into()); - }, + } Ok(_) => (), }; @@ -862,17 +851,16 @@ impl Device { let hresult = (*audio_client).GetBufferSize(&mut max_frames_in_buffer); match check_result(hresult) { - Err(ref e) - if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => { + Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => { (*audio_client).Release(); return Err(BuildStreamError::DeviceNotAvailable); - }, + } Err(e) => { (*audio_client).Release(); let description = format!("failed to obtain buffer size: {}", e); let err = BackendSpecificError { description }; return Err(err.into()); - }, + } Ok(()) => (), }; @@ -882,23 +870,22 @@ impl Device { // Building a `IAudioRenderClient` that will be used to fill the samples buffer. let render_client = { let mut render_client: *mut audioclient::IAudioRenderClient = mem::uninitialized(); - let hresult = (*audio_client).GetService(&audioclient::IID_IAudioRenderClient, - &mut render_client as - *mut *mut audioclient::IAudioRenderClient as - *mut _); + let hresult = (*audio_client).GetService( + &audioclient::IID_IAudioRenderClient, + &mut render_client as *mut *mut audioclient::IAudioRenderClient as *mut _, + ); match check_result(hresult) { - Err(ref e) - if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => { + Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => { (*audio_client).Release(); return Err(BuildStreamError::DeviceNotAvailable); - }, + } Err(e) => { (*audio_client).Release(); let description = format!("failed to build render client: {}", e); let err = BackendSpecificError { description }; return Err(err.into()); - }, + } Ok(()) => (), }; @@ -907,9 +894,7 @@ impl Device { // Once we built the `StreamInner`, we add a command that will be picked up by the // `run()` method and added to the `RunContext`. - let client_flow = AudioClientFlow::Render { - render_client, - }; + let client_flow = AudioClientFlow::Render { render_client }; Ok(StreamInner { audio_client, @@ -935,38 +920,45 @@ impl PartialEq for Device { // In this code section we're trying to use the GetId method for the device comparison, cf. // https://docs.microsoft.com/en-us/windows/desktop/api/mmdeviceapi/nf-mmdeviceapi-immdevice-getid unsafe { - struct IdRAII (LPWSTR); + struct IdRAII(LPWSTR); /// RAII for device IDs. impl Drop for IdRAII { fn drop(&mut self) { - unsafe {CoTaskMemFree(self.0 as *mut c_void)} + unsafe { CoTaskMemFree(self.0 as *mut c_void) } } } let mut id1: LPWSTR = ptr::null_mut(); let rc1 = (*self.device).GetId(&mut id1); // GetId only fails with E_OUTOFMEMORY and if it does, we're probably dead already. // Plus it won't do to change the device comparison logic unexpectedly. - if rc1 != winerror::S_OK {panic! ("cpal: GetId failure: {}", rc1)} + if rc1 != winerror::S_OK { + panic!("cpal: GetId failure: {}", rc1) + } let id1 = IdRAII(id1); let mut id2: LPWSTR = ptr::null_mut(); let rc2 = (*other.device).GetId(&mut id2); - if rc2 != winerror::S_OK {panic! ("cpal: GetId failure: {}", rc1)} + if rc2 != winerror::S_OK { + panic!("cpal: GetId failure: {}", rc1) + } let id2 = IdRAII(id2); // 16-bit null-terminated comparison. let mut offset = 0; loop { let w1: WCHAR = *id1.0.offset(offset); let w2: WCHAR = *id2.0.offset(offset); - if w1 == 0 && w2 == 0 {return true} - if w1 != w2 {return false} + if w1 == 0 && w2 == 0 { + return true; + } + if w1 != w2 { + return false; + } offset += 1; } } } } -impl Eq for Device { -} +impl Eq for Device {} impl Clone for Device { #[inline] @@ -1025,9 +1017,7 @@ impl From<*const IMMDevice> for Endpoint { impl Endpoint { fn data_flow(&self) -> EDataFlow { - unsafe { - data_flow_from_immendpoint(self.endpoint) - } + unsafe { data_flow_from_immendpoint(self.endpoint) } } } @@ -1058,10 +1048,8 @@ lazy_static! { /// RAII object around `IMMDeviceEnumerator`. struct Enumerator(*mut IMMDeviceEnumerator); -unsafe impl Send for Enumerator { -} -unsafe impl Sync for Enumerator { -} +unsafe impl Send for Enumerator {} +unsafe impl Sync for Enumerator {} impl Drop for Enumerator { #[inline] @@ -1084,13 +1072,11 @@ impl Devices { unsafe { let mut collection: *mut IMMDeviceCollection = mem::uninitialized(); // can fail because of wrong parameters (should never happen) or out of memory - check_result_backend_specific( - (*ENUMERATOR.0).EnumAudioEndpoints( - eAll, - DEVICE_STATE_ACTIVE, - &mut collection, - ) - )?; + check_result_backend_specific((*ENUMERATOR.0).EnumAudioEndpoints( + eAll, + DEVICE_STATE_ACTIVE, + &mut collection, + ))?; let mut count = mem::uninitialized(); // can fail if the parameter is null, which should never happen @@ -1105,10 +1091,8 @@ impl Devices { } } -unsafe impl Send for Devices { -} -unsafe impl Sync for Devices { -} +unsafe impl Send for Devices {} +unsafe impl Sync for Devices {} impl Drop for Devices { #[inline] @@ -1148,8 +1132,7 @@ impl Iterator for Devices { fn default_device(data_flow: EDataFlow) -> Option { unsafe { let mut device = mem::uninitialized(); - let hres = (*ENUMERATOR.0) - .GetDefaultAudioEndpoint(data_flow, eConsole, &mut device); + let hres = (*ENUMERATOR.0).GetDefaultAudioEndpoint(data_flow, eConsole, &mut device); if let Err(_err) = check_result(hres) { return None; // TODO: check specifically for `E_NOTFOUND`, and panic otherwise } @@ -1165,7 +1148,6 @@ pub fn default_output_device() -> Option { default_device(eRender) } - // Turns a `Format` into a `WAVEFORMATEXTENSIBLE`. // // Returns `None` if the WAVEFORMATEXTENSIBLE does not support the given format. @@ -1187,7 +1169,7 @@ fn format_to_waveformatextensible(format: &Format) -> Option(); let ex_size = mem::size_of::(); (extensible_size - ex_size) as WORD - }, + } SampleFormat::U16 => return None, }; let waveformatex = mmreg::WAVEFORMATEX { diff --git a/src/host/wasapi/mod.rs b/src/host/wasapi/mod.rs index c7fc241..7fb6038 100644 --- a/src/host/wasapi/mod.rs +++ b/src/host/wasapi/mod.rs @@ -1,12 +1,15 @@ extern crate winapi; -use BackendSpecificError; -use DevicesError; +pub use self::device::{ + default_input_device, default_output_device, Device, Devices, SupportedInputFormats, + SupportedOutputFormats, +}; +pub use self::stream::Stream; use self::winapi::um::winnt::HRESULT; use std::io::Error as IoError; -use traits::{HostTrait}; -pub use self::device::{Device, Devices, SupportedInputFormats, SupportedOutputFormats, default_input_device, default_output_device}; -pub use self::stream::Stream; +use traits::HostTrait; +use BackendSpecificError; +use DevicesError; mod com; mod device; @@ -60,10 +63,8 @@ fn check_result(result: HRESULT) -> Result<(), IoError> { fn check_result_backend_specific(result: HRESULT) -> Result<(), BackendSpecificError> { match check_result(result) { Ok(()) => Ok(()), - Err(err) => { - Err(BackendSpecificError { - description: format!("{}", err), - }) - } + Err(err) => Err(BackendSpecificError { + description: format!("{}", err), + }), } } diff --git a/src/host/wasapi/stream.rs b/src/host/wasapi/stream.rs index 72a863f..8d7689b 100644 --- a/src/host/wasapi/stream.rs +++ b/src/host/wasapi/stream.rs @@ -10,11 +10,13 @@ use super::winapi::um::winnt; use std::mem; use std::ptr; use std::slice; -use std::sync::mpsc::{channel, Sender, Receiver}; +use std::sync::mpsc::{channel, Receiver, Sender}; -use std::{sync::{Arc}, -thread::{self, JoinHandle}}; use crate::traits::StreamTrait; +use std::{ + sync::Arc, + thread::{self, JoinHandle}, +}; use BackendSpecificError; use PauseStreamError; @@ -22,8 +24,8 @@ use PlayStreamError; use SampleFormat; use StreamData; use StreamError; -use UnknownTypeOutputBuffer; use UnknownTypeInputBuffer; +use UnknownTypeOutputBuffer; pub struct Stream { /// The high-priority audio processing thread calling callbacks. @@ -51,12 +53,12 @@ struct RunContext { commands: Receiver, } -pub (crate) enum Command { +pub(crate) enum Command { PlayStream, PauseStream, } -pub (crate) enum AudioClientFlow { +pub(crate) enum AudioClientFlow { Render { render_client: *mut audioclient::IAudioRenderClient, }, @@ -65,7 +67,7 @@ pub (crate) enum AudioClientFlow { }, } -pub (crate) struct StreamInner { +pub(crate) struct StreamInner { audio_client: *mut audioclient::IAudioClient, client_flow: AudioClientFlow, // Event that is signalled by WASAPI whenever audio data must be written. @@ -81,8 +83,15 @@ pub (crate) struct StreamInner { } impl Stream { - pub (crate) fn new(stream_inner: Arc, mut data_callback: D, mut error_callback: E) -> Stream - where D: FnMut(StreamData) + Send + 'static, E: FnMut(StreamError) + Send + 'static { + pub(crate) fn new( + stream_inner: Arc, + mut data_callback: D, + mut error_callback: E, + ) -> Stream + where + D: FnMut(StreamData) + 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(); @@ -93,9 +102,8 @@ impl Stream { commands: rx, }; - let thread = thread::spawn(move || { - run_inner(run_context, &mut data_callback, &mut error_callback) - }); + let thread = + thread::spawn(move || run_inner(run_context, &mut data_callback, &mut error_callback)); Stream { thread: Some(thread), @@ -121,9 +129,9 @@ impl Drop for Stream { unsafe { handleapi::CloseHandle(self.pending_scheduled_event); } - unsafe { - let result = synchapi::SetEvent(self.pending_scheduled_event); - assert!(result != 0); + unsafe { + let result = synchapi::SetEvent(self.pending_scheduled_event); + assert!(result != 0); } self.thread.take().unwrap().join().unwrap(); } @@ -134,7 +142,7 @@ impl StreamTrait for Stream { self.push_command(Command::PlayStream); Ok(()) } - fn pause(&self)-> Result<(), PauseStreamError> { + fn pause(&self) -> Result<(), PauseStreamError> { self.push_command(Command::PauseStream); Ok(()) } @@ -168,27 +176,23 @@ fn process_commands(run_context: &mut RunContext) -> Result<(), StreamError> { match command { Command::PlayStream => { if !run_context.stream.playing { - let hresult = unsafe { - (*run_context.stream.audio_client).Start() - }; + let hresult = unsafe { (*run_context.stream.audio_client).Start() }; if let Err(err) = stream_error_from_hresult(hresult) { return Err(err); } run_context.stream.playing = true; } - }, + } Command::PauseStream => { if run_context.stream.playing { - let hresult = unsafe { - (*run_context.stream.audio_client).Stop() - }; + let hresult = unsafe { (*run_context.stream.audio_client).Stop() }; if let Err(err) = stream_error_from_hresult(hresult) { return Err(err); } run_context.stream.playing = false; } - }, + } } } @@ -208,15 +212,13 @@ fn wait_for_handle_signal(handles: &[winnt::HANDLE]) -> Result Result<(), StreamError> Ok(()) } -fn run_inner(run_context: RunContext, data_callback: &mut dyn FnMut(StreamData), error_callback: &mut dyn FnMut(StreamError)) { +fn run_inner( + run_context: RunContext, + data_callback: &mut dyn FnMut(StreamData), + error_callback: &mut dyn FnMut(StreamError), +) { unsafe { 'stream_loop: loop { // Process queued commands. @@ -282,7 +288,6 @@ fn run_inner(run_context: RunContext, data_callback: &mut dyn FnMut(StreamData), // 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. @@ -316,17 +321,21 @@ fn run_inner(run_context: RunContext, data_callback: &mut dyn FnMut(StreamData), debug_assert!(!buffer.is_null()); let buffer_len = frames_available as usize - * stream.bytes_per_frame as usize / sample_size; + * 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 }; + 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); @@ -343,7 +352,7 @@ fn run_inner(run_context: RunContext, data_callback: &mut dyn FnMut(StreamData), SampleFormat::U16 => capture_callback!(u16, U16), } } - }, + } AudioClientFlow::Render { render_client } => { // The number of frames available for writing. @@ -357,10 +366,8 @@ fn run_inner(run_context: RunContext, data_callback: &mut dyn FnMut(StreamData), }; let mut buffer: *mut BYTE = mem::uninitialized(); - let hresult = (*render_client).GetBuffer( - frames_available, - &mut buffer as *mut *mut _, - ); + let hresult = + (*render_client).GetBuffer(frames_available, &mut buffer as *mut *mut _); if let Err(err) = stream_error_from_hresult(hresult) { error_callback(err); @@ -368,26 +375,27 @@ fn run_inner(run_context: RunContext, data_callback: &mut dyn FnMut(StreamData), } debug_assert!(!buffer.is_null()); - let buffer_len = frames_available as usize - * stream.bytes_per_frame as usize / sample_size; + 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 }; + 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); + 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 { @@ -395,7 +403,7 @@ fn run_inner(run_context: RunContext, data_callback: &mut dyn FnMut(StreamData), SampleFormat::I16 => render_callback!(i16, I16), SampleFormat::U16 => render_callback!(u16, U16), } - }, + } } } } From 49a38c2641b1746a0678342f671f8053d9ac802f Mon Sep 17 00:00:00 2001 From: Tatsuyuki Ishi Date: Thu, 29 Aug 2019 16:22:01 +0900 Subject: [PATCH 25/41] Fix up WASAPI host --- src/host/wasapi/device.rs | 4 ++-- src/host/wasapi/stream.rs | 36 ++++++++++++++++++------------------ 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/host/wasapi/device.rs b/src/host/wasapi/device.rs index eee8e24..88aee26 100644 --- a/src/host/wasapi/device.rs +++ b/src/host/wasapi/device.rs @@ -113,7 +113,7 @@ impl DeviceTrait for Device { E: FnMut(StreamError) + Send + 'static, { Ok(Stream::new( - Arc::new(self.build_input_stream_inner(format)?), + self.build_input_stream_inner(format)?, data_callback, error_callback, )) @@ -130,7 +130,7 @@ impl DeviceTrait for Device { E: FnMut(StreamError) + Send + 'static, { Ok(Stream::new( - Arc::new(self.build_output_stream_inner(format)?), + self.build_output_stream_inner(format)?, data_callback, error_callback, )) diff --git a/src/host/wasapi/stream.rs b/src/host/wasapi/stream.rs index 8d7689b..495a59f 100644 --- a/src/host/wasapi/stream.rs +++ b/src/host/wasapi/stream.rs @@ -13,10 +13,7 @@ use std::slice; use std::sync::mpsc::{channel, Receiver, Sender}; use crate::traits::StreamTrait; -use std::{ - sync::Arc, - thread::{self, JoinHandle}, -}; +use std::thread::{self, JoinHandle}; use BackendSpecificError; use PauseStreamError; @@ -44,7 +41,7 @@ pub struct Stream { struct RunContext { // Streams that have been created in this event loop. - stream: Arc, + stream: StreamInner, // Handles corresponding to the `event` field of each element of `voices`. Must always be in // sync with `voices`, except that the first element is always `pending_scheduled_event`. @@ -53,12 +50,15 @@ struct RunContext { commands: Receiver, } -pub(crate) enum Command { +// Once we start running the eventloop, the RunContext will not be moved. +unsafe impl Send for RunContext {} + +pub enum Command { PlayStream, PauseStream, } -pub(crate) enum AudioClientFlow { +pub enum AudioClientFlow { Render { render_client: *mut audioclient::IAudioRenderClient, }, @@ -67,24 +67,24 @@ pub(crate) enum AudioClientFlow { }, } -pub(crate) struct StreamInner { - audio_client: *mut audioclient::IAudioClient, - client_flow: AudioClientFlow, +pub struct StreamInner { + pub audio_client: *mut audioclient::IAudioClient, + pub client_flow: AudioClientFlow, // Event that is signalled by WASAPI whenever audio data must be written. - event: winnt::HANDLE, + pub event: winnt::HANDLE, // True if the stream is currently playing. False if paused. - playing: bool, + pub playing: bool, // Number of frames of audio data in the underlying buffer allocated by WASAPI. - max_frames_in_buffer: UINT32, + pub max_frames_in_buffer: UINT32, // Number of bytes that each frame occupies. - bytes_per_frame: WORD, + pub bytes_per_frame: WORD, // The sample format with which the stream was created. - sample_format: SampleFormat, + pub sample_format: SampleFormat, } impl Stream { pub(crate) fn new( - stream_inner: Arc, + stream_inner: StreamInner, mut data_callback: D, mut error_callback: E, ) -> Stream @@ -253,7 +253,7 @@ fn stream_error_from_hresult(hresult: winnt::HRESULT) -> Result<(), StreamError> } fn run_inner( - run_context: RunContext, + mut run_context: RunContext, data_callback: &mut dyn FnMut(StreamData), error_callback: &mut dyn FnMut(StreamError), ) { @@ -283,7 +283,7 @@ fn run_inner( continue; } - let stream = run_context.stream; + let stream = &mut run_context.stream; let sample_size = stream.sample_format.sample_size(); // Obtaining a pointer to the buffer. From d46415a6e775bf37cf37c6aecf850c2caa71d1f5 Mon Sep 17 00:00:00 2001 From: Viktor Lazarev Date: Thu, 29 Aug 2019 18:53:03 +0200 Subject: [PATCH 26/41] `cargo clippy` --- src/host/wasapi/device.rs | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/src/host/wasapi/device.rs b/src/host/wasapi/device.rs index 88aee26..20e594b 100644 --- a/src/host/wasapi/device.rs +++ b/src/host/wasapi/device.rs @@ -497,7 +497,7 @@ impl Device { let rate = rate.0 as DWORD; test_format.nSamplesPerSec = rate; test_format.nAvgBytesPerSec = - rate * (*default_waveformatex_ptr.0).nBlockAlign as DWORD; + rate * u32::from((*default_waveformatex_ptr.0).nBlockAlign); if is_format_supported(client, test_format.as_ptr())? { supported_sample_rates.push(rate); } @@ -702,9 +702,9 @@ impl Device { // Creating the event that will be signalled whenever we need to submit some samples. let event = { let event = synchapi::CreateEventA(ptr::null_mut(), 0, 0, ptr::null()); - if event == ptr::null_mut() { + if event.is_null() { (*audio_client).Release(); - let description = format!("failed to create event"); + let description = "failed to create event".to_string(); let err = BackendSpecificError { description }; return Err(err.into()); } @@ -825,21 +825,18 @@ impl Device { // Creating the event that will be signalled whenever we need to submit some samples. let event = { let event = synchapi::CreateEventA(ptr::null_mut(), 0, 0, ptr::null()); - if event == ptr::null_mut() { + if event.is_null() { (*audio_client).Release(); - let description = format!("failed to create event"); + let description = "failed to create event".to_string(); let err = BackendSpecificError { description }; return Err(err.into()); } - match check_result((*audio_client).SetEventHandle(event)) { - Err(e) => { - (*audio_client).Release(); - let description = format!("failed to call SetEventHandle: {}", e); - let err = BackendSpecificError { description }; - return Err(err.into()); - } - Ok(_) => (), + if let Err(e) = check_result((*audio_client).SetEventHandle(event)) { + (*audio_client).Release(); + let description = format!("failed to call SetEventHandle: {}", e); + let err = BackendSpecificError { description }; + return Err(err.into()); }; event @@ -1078,9 +1075,9 @@ impl Devices { &mut collection, ))?; - let mut count = mem::uninitialized(); + let count = mem::uninitialized(); // can fail if the parameter is null, which should never happen - check_result_backend_specific((*collection).GetCount(&mut count))?; + check_result_backend_specific((*collection).GetCount(&count))?; Ok(Devices { collection, @@ -1160,7 +1157,7 @@ fn format_to_waveformatextensible(format: &Format) -> Option Date: Fri, 30 Aug 2019 09:33:21 +0900 Subject: [PATCH 27/41] Additional clippy fixes --- src/host/wasapi/device.rs | 3 +-- src/host/wasapi/stream.rs | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/host/wasapi/device.rs b/src/host/wasapi/device.rs index 20e594b..a510f4e 100644 --- a/src/host/wasapi/device.rs +++ b/src/host/wasapi/device.rs @@ -358,8 +358,7 @@ impl Device { let err = BackendSpecificError { description }; return Err(err.into()); } - let ptr_usize: usize = *(&property_value.data as *const _ as *const usize); - let ptr_utf16 = ptr_usize as *const u16; + let ptr_utf16 = *(&property_value.data as *const _ as *const (*const u16)); // Find the length of the friendly name. let mut len = 0; diff --git a/src/host/wasapi/stream.rs b/src/host/wasapi/stream.rs index 495a59f..6d1e4ee 100644 --- a/src/host/wasapi/stream.rs +++ b/src/host/wasapi/stream.rs @@ -224,7 +224,6 @@ fn wait_for_handle_signal(handles: &[winnt::HANDLE]) -> Result= winbase::WAIT_OBJECT_0); let handle_idx = (result - winbase::WAIT_OBJECT_0) as usize; Ok(handle_idx) } From c09a58ac02cbb5f68cdfe884db2e4c261aa0b8ab Mon Sep 17 00:00:00 2001 From: ishitatsuyuki Date: Sat, 31 Aug 2019 18:13:40 +0900 Subject: [PATCH 28/41] Fix the remainder of WASAPI backend --- src/host/wasapi/stream.rs | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/host/wasapi/stream.rs b/src/host/wasapi/stream.rs index 6d1e4ee..b072292 100644 --- a/src/host/wasapi/stream.rs +++ b/src/host/wasapi/stream.rs @@ -56,6 +56,7 @@ unsafe impl Send for RunContext {} pub enum Command { PlayStream, PauseStream, + Terminate, } pub enum AudioClientFlow { @@ -97,8 +98,8 @@ impl Stream { let (tx, rx) = channel(); let run_context = RunContext { + handles: vec![pending_scheduled_event, stream_inner.event], stream: stream_inner, - handles: vec![pending_scheduled_event], commands: rx, }; @@ -118,7 +119,7 @@ impl Stream { self.commands.send(command).unwrap(); unsafe { let result = synchapi::SetEvent(self.pending_scheduled_event); - assert!(result != 0); + assert_ne!(result, 0); } } } @@ -126,14 +127,11 @@ impl Stream { impl Drop for Stream { #[inline] fn drop(&mut self) { + self.push_command(Command::Terminate); + self.thread.take().unwrap().join().unwrap(); unsafe { handleapi::CloseHandle(self.pending_scheduled_event); } - unsafe { - let result = synchapi::SetEvent(self.pending_scheduled_event); - assert!(result != 0); - } - self.thread.take().unwrap().join().unwrap(); } } @@ -170,7 +168,8 @@ impl Drop for StreamInner { } // Process any pending commands that are queued within the `RunContext`. -fn process_commands(run_context: &mut RunContext) -> Result<(), StreamError> { +// Returns `true` if the loop should continue running, `false` if it should terminate. +fn process_commands(run_context: &mut RunContext) -> Result { // Process the pending commands. for command in run_context.commands.try_iter() { match command { @@ -193,10 +192,13 @@ fn process_commands(run_context: &mut RunContext) -> Result<(), StreamError> { run_context.stream.playing = false; } } + Command::Terminate => { + return Ok(false); + } } } - Ok(()) + Ok(true) } // Wait for any of the given handles to be signalled. // @@ -260,7 +262,8 @@ fn run_inner( 'stream_loop: loop { // Process queued commands. match process_commands(&mut run_context) { - Ok(()) => (), + Ok(true) => (), + Ok(false) => break, Err(err) => { error_callback(err); break 'stream_loop; From 70dcf2390abdff5afbccb50db97fcb67be267e99 Mon Sep 17 00:00:00 2001 From: Tatsuyuki Ishi Date: Thu, 5 Sep 2019 17:34:07 +0900 Subject: [PATCH 29/41] Port CoreAudio backend --- src/host/coreaudio/mod.rs | 265 +++++++------------------------------- src/platform/mod.rs | 3 +- 2 files changed, 47 insertions(+), 221 deletions(-) diff --git a/src/host/coreaudio/mod.rs b/src/host/coreaudio/mod.rs index d527a8f..9314169 100644 --- a/src/host/coreaudio/mod.rs +++ b/src/host/coreaudio/mod.rs @@ -14,20 +14,18 @@ use SupportedFormatsError; use SampleFormat; use SampleRate; use StreamData; -use StreamDataResult; +use StreamError; use SupportedFormat; use UnknownTypeInputBuffer; use UnknownTypeOutputBuffer; -use traits::{DeviceTrait, EventLoopTrait, HostTrait, StreamIdTrait}; +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::sync::{Arc, Condvar, Mutex}; -use std::thread; -use std::time::Duration; use std::slice; use self::coreaudio::audio_unit::{AudioUnit, Scope, Element}; @@ -87,7 +85,6 @@ impl Host { impl HostTrait for Host { type Devices = Devices; type Device = Device; - type EventLoop = EventLoop; fn is_available() -> bool { // Assume coreaudio is always available on macOS and iOS. @@ -105,15 +102,12 @@ impl HostTrait for Host { fn default_output_device(&self) -> Option { default_output_device() } - - fn event_loop(&self) -> Self::EventLoop { - EventLoop::new() - } } impl DeviceTrait for Device { type SupportedInputFormats = SupportedInputFormats; type SupportedOutputFormats = SupportedOutputFormats; + type Stream = Stream; fn name(&self) -> Result { Device::name(self) @@ -134,50 +128,16 @@ impl DeviceTrait for Device { fn default_output_format(&self) -> Result { Device::default_output_format(self) } -} -impl EventLoopTrait for EventLoop { - type Device = Device; - type StreamId = StreamId; - - fn build_input_stream( - &self, - device: &Self::Device, - format: &Format, - ) -> Result { - EventLoop::build_input_stream(self, device, format) + 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 { + Device::build_input_stream(self, format, data_callback, error_callback) } - fn build_output_stream( - &self, - device: &Self::Device, - format: &Format, - ) -> Result { - EventLoop::build_output_stream(self, device, format) - } - - fn play_stream(&self, stream: Self::StreamId) -> Result<(), PlayStreamError> { - EventLoop::play_stream(self, stream) - } - - fn pause_stream(&self, stream: Self::StreamId) -> Result<(), PauseStreamError> { - EventLoop::pause_stream(self, stream) - } - - fn destroy_stream(&self, stream: Self::StreamId) { - EventLoop::destroy_stream(self, stream) - } - - fn run(&self, callback: F) -> ! - where - F: FnMut(Self::StreamId, StreamDataResult) + Send, - { - EventLoop::run(self, 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 { + Device::build_output_stream(self, format, data_callback, error_callback) } } -impl StreamIdTrait for StreamId {} - #[derive(Clone, PartialEq, Eq)] pub struct Device { audio_device_id: AudioDeviceID, @@ -420,31 +380,6 @@ impl fmt::Debug for Device { } } -// The ID of a stream is its index within the `streams` array of the events loop. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct StreamId(usize); - -pub struct EventLoop { - // This `Arc` is shared with all the callbacks of coreaudio. - // - // TODO: Eventually, CPAL's API should be changed to allow for submitting a unique callback per - // stream to avoid streams blocking one another. - user_callback: Arc>, - streams: Mutex>>, - loop_cond: Arc<(Mutex, Condvar)>, -} - -enum UserCallback { - // When `run` is called with a callback, that callback will be stored here. - // - // It is essential for the safety of the program that this callback is removed before `run` - // returns (not possible with the current CPAL API). - Active(&'static mut (dyn FnMut(StreamId, StreamDataResult) + Send)), - // A queue of events that have occurred but that have not yet been emitted to the user as we - // don't yet have a callback to do so. - Inactive, -} - struct StreamInner { playing: bool, audio_unit: AudioUnit, @@ -540,75 +475,8 @@ fn audio_unit_from_device(device: &Device, input: bool) -> Result EventLoop { - EventLoop { - user_callback: Arc::new(Mutex::new(UserCallback::Inactive)), - streams: Mutex::new(Vec::new()), - loop_cond: Arc::new((Mutex::new(false), Condvar::new())), - } - } - - #[inline] - fn run(&self, mut callback: F) -> ! - where F: FnMut(StreamId, StreamDataResult) + Send - { - { - let mut guard = self.user_callback.lock().unwrap(); - if let UserCallback::Active(_) = *guard { - panic!("`EventLoop::run` was called when the event loop was already running"); - } - let callback: &mut (dyn FnMut(StreamId, StreamDataResult) + Send) = &mut callback; - *guard = UserCallback::Active(unsafe { mem::transmute(callback) }); - } - - // Wait on a condvar to notify, which should never happen. - let &(ref lock, ref cvar) = &*self.loop_cond; - let mut running = lock.lock().unwrap(); - *running = true; - while *running { - running = cvar.wait(running).unwrap(); - } - - unreachable!("current `EventLoop` API requires that `run` may not return"); - - // It is critical that we remove the callback before returning (currently not possible). - // *self.user_callback.lock().unwrap() = UserCallback::Inactive; - } - - fn next_stream_id(&self) -> usize { - let streams_lock = self.streams.lock().unwrap(); - let stream_id = streams_lock - .iter() - .position(|n| n.is_none()) - .unwrap_or(streams_lock.len()); - stream_id - } - - // Add the stream to the list of streams within `self`. - fn add_stream(&self, stream_id: usize, au: AudioUnit, device_id: AudioDeviceID) { - let inner = StreamInner { - playing: true, - audio_unit: au, - device_id: device_id, - }; - - let mut streams_lock = self.streams.lock().unwrap(); - if stream_id == streams_lock.len() { - streams_lock.push(Some(inner)); - } else { - streams_lock[stream_id] = Some(inner); - } - } - - #[inline] - fn build_input_stream( - &self, - device: &Device, - format: &Format, - ) -> Result - { +impl Device { + 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 { // The scope and element for working with a device's input stream. let scope = Scope::Output; let element = Element::Input; @@ -624,7 +492,7 @@ impl EventLoop { let sample_rate: f64 = 0.0; let data_size = mem::size_of::() as u32; let status = AudioObjectGetPropertyData( - device.audio_device_id, + self.audio_device_id, &property_address as *const _, 0, null(), @@ -635,26 +503,11 @@ impl EventLoop { // If the requested sample rate is different to the device sample rate, update the device. if sample_rate as u32 != format.sample_rate.0 { - - // In order to avoid breaking existing input streams we return an error if there is - // already an active input stream for this device with the actual sample rate. - for stream in &*self.streams.lock().unwrap() { - if let Some(stream) = stream.as_ref() { - if stream.device_id == device.audio_device_id { - let description = "cannot change device sample rate for stream as an \ - existing stream is already running at the current sample rate" - .into(); - let err = BackendSpecificError { description }; - return Err(err.into()); - } - } - } - // Get available sample rate ranges. property_address.mSelector = kAudioDevicePropertyAvailableNominalSampleRates; let data_size = 0u32; let status = AudioObjectGetPropertyDataSize( - device.audio_device_id, + self.audio_device_id, &property_address as *const _, 0, null(), @@ -665,7 +518,7 @@ impl EventLoop { let mut ranges: Vec = vec![]; ranges.reserve_exact(data_size as usize); let status = AudioObjectGetPropertyData( - device.audio_device_id, + self.audio_device_id, &property_address as *const _, 0, null(), @@ -719,7 +572,7 @@ impl EventLoop { // Add our sample rate change listener callback. let reported_rate: f64 = 0.0; let status = AudioObjectAddPropertyListener( - device.audio_device_id, + self.audio_device_id, &property_address as *const _, Some(rate_listener), &reported_rate as *const _ as *mut _, @@ -729,7 +582,7 @@ impl EventLoop { // Finally, set the sample rate. let sample_rate = sample_rate as f64; let status = AudioObjectSetPropertyData( - device.audio_device_id, + self.audio_device_id, &property_address as *const _, 0, null(), @@ -753,7 +606,7 @@ impl EventLoop { // Remove the `rate_listener` callback. let status = AudioObjectRemovePropertyListener( - device.audio_device_id, + self.audio_device_id, &property_address as *const _, Some(rate_listener), &reported_rate as *const _ as *mut _, @@ -762,18 +615,14 @@ impl EventLoop { } } - let mut audio_unit = audio_unit_from_device(device, true)?; + let mut audio_unit = audio_unit_from_device(self, true)?; // Set the stream in interleaved mode. let asbd = asbd_from_format(format); audio_unit.set_property(kAudioUnitProperty_StreamFormat, scope, element, Some(&asbd))?; - // Determine the future ID of the stream. - let stream_id = self.next_stream_id(); - // Register the callback that is being called by coreaudio whenever it needs data to be // fed to the audio buffer. - let user_callback = self.user_callback.clone(); let sample_format = format.data_type; let bytes_per_channel = format.data_type.sample_size(); type Args = render_callback::Args; @@ -789,20 +638,14 @@ impl EventLoop { mData: data } = buffers[0]; - let mut user_callback = user_callback.lock().unwrap(); - // 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 callback = match *user_callback { - UserCallback::Active(ref mut cb) => cb, - UserCallback::Inactive => return Ok(()), - }; let unknown_type_buffer = UnknownTypeInputBuffer::$SampleFormat(::InputBuffer { buffer: data_slice }); let stream_data = StreamData::Input { buffer: unknown_type_buffer }; - callback(StreamId(stream_id), Ok(stream_data)); + data_callback(stream_data); }}; } @@ -815,23 +658,17 @@ impl EventLoop { Ok(()) })?; - // TODO: start playing now? is that consistent with the other backends? audio_unit.start()?; - // Add the stream to the list of streams within `self`. - self.add_stream(stream_id, audio_unit, device.audio_device_id); - - Ok(StreamId(stream_id)) + Ok(Stream::new(StreamInner { + playing: true, + audio_unit, + device_id: self.audio_device_id, + })) } - #[inline] - fn build_output_stream( - &self, - device: &Device, - format: &Format, - ) -> Result - { - let mut audio_unit = audio_unit_from_device(device, false)?; + 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 { + let mut audio_unit = audio_unit_from_device(self, false)?; // The scope and element for working with a device's output stream. let scope = Scope::Input; @@ -841,12 +678,8 @@ impl EventLoop { let asbd = asbd_from_format(format); audio_unit.set_property(kAudioUnitProperty_StreamFormat, scope, element, Some(&asbd))?; - // Determine the future ID of the stream. - let stream_id = self.next_stream_id(); - // Register the callback that is being called by coreaudio whenever it needs data to be // fed to the audio buffer. - let user_callback = self.user_callback.clone(); let sample_format = format.data_type; let bytes_per_channel = format.data_type.sample_size(); type Args = render_callback::Args; @@ -860,25 +693,14 @@ impl EventLoop { mData: data } = (*args.data.data).mBuffers[0]; - let mut user_callback = user_callback.lock().unwrap(); - // 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 callback = match *user_callback { - UserCallback::Active(ref mut cb) => cb, - UserCallback::Inactive => { - for sample in data_slice.iter_mut() { - *sample = $equilibrium; - } - return Ok(()); - } - }; let unknown_type_buffer = UnknownTypeOutputBuffer::$SampleFormat(::OutputBuffer { buffer: data_slice }); let stream_data = StreamData::Output { buffer: unknown_type_buffer }; - callback(StreamId(stream_id), Ok(stream_data)); + data_callback(stream_data); }}; } @@ -891,25 +713,31 @@ impl EventLoop { Ok(()) })?; - // TODO: start playing now? is that consistent with the other backends? audio_unit.start()?; - // Add the stream to the list of streams within `self`. - self.add_stream(stream_id, audio_unit, device.audio_device_id); - - Ok(StreamId(stream_id)) + Ok(Stream::new(StreamInner { + playing: true, + audio_unit, + device_id: self.audio_device_id, + })) } +} - fn destroy_stream(&self, stream_id: StreamId) { - { - let mut streams = self.streams.lock().unwrap(); - streams[stream_id.0] = None; +pub struct Stream { + inner: RefCell, +} + +impl Stream { + fn new(inner: StreamInner) -> Self { + Self { + inner: RefCell::new(inner), } } +} - fn play_stream(&self, stream_id: StreamId) -> Result<(), PlayStreamError> { - let mut streams = self.streams.lock().unwrap(); - let stream = streams[stream_id.0].as_mut().unwrap(); +impl StreamTrait for Stream { + fn play(&self) -> Result<(), PlayStreamError> { + let mut stream = self.inner.borrow_mut(); if !stream.playing { if let Err(e) = stream.audio_unit.start() { @@ -922,9 +750,8 @@ impl EventLoop { Ok(()) } - fn pause_stream(&self, stream_id: StreamId) -> Result<(), PauseStreamError> { - let mut streams = self.streams.lock().unwrap(); - let stream = streams[stream_id.0].as_mut().unwrap(); + fn pause(&self) -> Result<(), PauseStreamError> { + let mut stream = self.inner.borrow_mut(); if stream.playing { if let Err(e) = stream.audio_unit.stop() { diff --git a/src/platform/mod.rs b/src/platform/mod.rs index 45d91ed..cfa88c4 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -414,9 +414,8 @@ mod platform_impl { pub use crate::host::coreaudio::{ Device as CoreAudioDevice, Devices as CoreAudioDevices, - EventLoop as CoreAudioEventLoop, Host as CoreAudioHost, - StreamId as CoreAudioStreamId, + Stream as CoreAudioStream, SupportedInputFormats as CoreAudioSupportedInputFormats, SupportedOutputFormats as CoreAudioSupportedOutputFormats, }; From 37d80b98cff6ac067c19f702936f3422256755a9 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Sat, 14 Dec 2019 11:50:57 +0100 Subject: [PATCH 30/41] Update the `emscripten` backend for the removal of the `EventLoop` For the most part, behaviour should be largely unchanged, however each individual stream now has its own `set_timeout` callback loop, rather than using one for processing all streams at once. Many TODOs remain within the `emscripten` backend. These were left untouched for the most part in favour of addressing this in a more web-focused, future PR. --- src/host/emscripten/mod.rs | 444 ++++++++++++++++--------------------- src/platform/mod.rs | 3 +- 2 files changed, 189 insertions(+), 258 deletions(-) diff --git a/src/host/emscripten/mod.rs b/src/host/emscripten/mod.rs index 136c88f..eb51b58 100644 --- a/src/host/emscripten/mod.rs +++ b/src/host/emscripten/mod.rs @@ -1,7 +1,6 @@ use std::mem; use std::os::raw::c_void; use std::slice::from_raw_parts; -use std::sync::Mutex; use stdweb; use stdweb::Reference; use stdweb::unstable::TryInto; @@ -17,25 +16,102 @@ use PauseStreamError; use PlayStreamError; use SupportedFormatsError; use StreamData; -use StreamDataResult; +use StreamError; use SupportedFormat; use UnknownTypeOutputBuffer; -use traits::{DeviceTrait, EventLoopTrait, HostTrait, StreamIdTrait}; +use traits::{DeviceTrait, HostTrait, StreamTrait}; + +// The emscripten backend currently works by instantiating an `AudioContext` object per `Stream`. +// Creating a stream creates a new `AudioContext`. Destroying a stream destroys it. Creation of a +// `Host` instance initializes the `stdweb` context. /// The default emscripten host type. #[derive(Debug)] pub struct Host; +/// Content is false if the iterator is empty. +pub struct Devices(bool); + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Device; + +pub struct Stream { + // A reference to an `AudioContext` object. + audio_ctxt_ref: Reference, +} + +// Index within the `streams` array of the events loop. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct StreamId(usize); + +pub type SupportedInputFormats = ::std::vec::IntoIter; +pub type SupportedOutputFormats = ::std::vec::IntoIter; + impl Host { pub fn new() -> Result { + stdweb::initialize(); Ok(Host) } } +impl Devices { + fn new() -> Result { + Ok(Self::default()) + } +} + +impl Device { + #[inline] + fn name(&self) -> Result { + Ok("Default Device".to_owned()) + } + + #[inline] + fn supported_input_formats(&self) -> Result { + unimplemented!(); + } + + #[inline] + fn supported_output_formats(&self) -> Result { + // TODO: right now cpal's API doesn't allow flexibility here + // "44100" and "2" (channels) have also been hard-coded in the rest of the code ; if + // this ever becomes more flexible, don't forget to change that + // According to https://developer.mozilla.org/en-US/docs/Web/API/BaseAudioContext/createBuffer + // browsers must support 1 to 32 channels at leats and 8,000 Hz to 96,000 Hz. + // + // UPDATE: We can do this now. Might be best to use `crate::COMMON_SAMPLE_RATES` and + // filter out those that lay outside the range specified above. + Ok( + vec![ + SupportedFormat { + channels: 2, + min_sample_rate: ::SampleRate(44100), + max_sample_rate: ::SampleRate(44100), + data_type: ::SampleFormat::F32, + }, + ].into_iter(), + ) + } + + fn default_input_format(&self) -> Result { + unimplemented!(); + } + + fn default_output_format(&self) -> Result { + // TODO: because it is hard coded, see supported_output_formats. + Ok( + Format { + channels: 2, + sample_rate: ::SampleRate(44100), + data_type: ::SampleFormat::F32, + }, + ) + } +} + impl HostTrait for Host { type Devices = Devices; type Device = Device; - type EventLoop = EventLoop; fn is_available() -> bool { // Assume this host is always available on emscripten. @@ -53,15 +129,12 @@ impl HostTrait for Host { fn default_output_device(&self) -> Option { default_output_device() } - - fn event_loop(&self) -> Self::EventLoop { - EventLoop::new() - } } impl DeviceTrait for Device { type SupportedInputFormats = SupportedInputFormats; type SupportedOutputFormats = SupportedOutputFormats; + type Stream = Stream; fn name(&self) -> Result { Device::name(self) @@ -82,224 +155,124 @@ impl DeviceTrait for Device { fn default_output_format(&self) -> Result { Device::default_output_format(self) } -} -impl EventLoopTrait for EventLoop { - type Device = Device; - type StreamId = StreamId; - - fn build_input_stream( + fn build_input_stream( &self, - device: &Self::Device, - format: &Format, - ) -> Result { - EventLoop::build_input_stream(self, device, format) - } - - fn build_output_stream( - &self, - device: &Self::Device, - format: &Format, - ) -> Result { - EventLoop::build_output_stream(self, device, format) - } - - fn play_stream(&self, stream: Self::StreamId) -> Result<(), PlayStreamError> { - EventLoop::play_stream(self, stream) - } - - fn pause_stream(&self, stream: Self::StreamId) -> Result<(), PauseStreamError> { - EventLoop::pause_stream(self, stream) - } - - fn destroy_stream(&self, stream: Self::StreamId) { - EventLoop::destroy_stream(self, stream) - } - - fn run(&self, callback: F) -> ! + _format: &Format, + _data_callback: D, + _error_callback: E, + ) -> Result where - F: FnMut(Self::StreamId, StreamDataResult) + Send, + D: FnMut(StreamData) + Send + 'static, + E: FnMut(StreamError) + Send + 'static, { - EventLoop::run(self, callback) + unimplemented!() + } + + 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, + { + // Create the stream. + let audio_ctxt_ref = js!(return new AudioContext()).into_reference().unwrap(); + let stream = Stream { audio_ctxt_ref }; + + // Specify the callback. + let mut user_data = (self, data_callback, error_callback); + let user_data_ptr = &mut user_data as *mut (_, _, _); + + // Use `set_timeout` to invoke a Rust callback repeatedly. + // + // The job of this callback is to fill the content of the audio buffers. + // + // 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); + + Ok(stream) } } -impl StreamIdTrait for StreamId {} - -// The emscripten backend works by having a global variable named `_cpal_audio_contexts`, which -// is an array of `AudioContext` objects. A stream ID corresponds to an entry in this array. -// -// Creating a stream creates a new `AudioContext`. Destroying a stream destroys it. - -// 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 - -pub struct EventLoop { - streams: Mutex>>, -} - -impl EventLoop { - #[inline] - pub fn new() -> EventLoop { - stdweb::initialize(); - EventLoop { - streams: Mutex::new(Vec::new()), - } +impl StreamTrait for Stream { + fn play(&self) -> Result<(), PlayStreamError> { + let audio_ctxt = &self.audio_ctxt_ref; + js!(@{audio_ctxt}.resume()); + Ok(()) } - #[inline] - fn run(&self, callback: F) -> ! - where F: FnMut(StreamId, StreamDataResult), - { - // The `run` function uses `set_timeout` to invoke a Rust callback repeatidely. The job - // of this callback is to fill the content of the audio buffers. + fn pause(&self) -> Result<(), PauseStreamError> { + let audio_ctxt = &self.audio_ctxt_ref; + js!(@{audio_ctxt}.suspend()); + Ok(()) + } +} - // 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`. +// 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) +where + D: FnMut(StreamData) + Send + 'static, + E: FnMut(StreamError) + Send + 'static, +{ + unsafe { + let user_data_ptr2 = user_data_ptr as *mut (&Stream, D, E); + let user_data = &mut *user_data_ptr2; + let (ref stream, ref mut data_cb, ref mut _err_cb) = user_data; + 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]; - fn callback_fn(user_data_ptr: *mut c_void) - where F: FnMut(StreamId, StreamDataResult) { - unsafe { - let user_data_ptr2 = user_data_ptr as *mut (&EventLoop, F); - let user_data = &mut *user_data_ptr2; - let user_cb = &mut user_data.1; - - let streams = user_data.0.streams.lock().unwrap().clone(); - for (stream_id, stream) in streams.iter().enumerate() { - let stream = match stream.as_ref() { - Some(v) => v, - None => continue, - }; - - let mut temporary_buffer = vec![0.0; 44100 * 2 / 3]; - - { - let buffer = UnknownTypeOutputBuffer::F32(::OutputBuffer { buffer: &mut temporary_buffer }); - let data = StreamData::Output { buffer: buffer }; - user_cb(StreamId(stream_id), Ok(data)); - // TODO: directly use a TypedArray once this is supported by stdweb - } - - let typed_array = { - let f32_slice = temporary_buffer.as_slice(); - let u8_slice: &[u8] = from_raw_parts( - f32_slice.as_ptr() as *const _, - f32_slice.len() * mem::size_of::(), - ); - let typed_array: TypedArray = u8_slice.into(); - typed_array - }; - - let num_channels = 2u32; // TODO: correct value - debug_assert_eq!(temporary_buffer.len() % num_channels as usize, 0); - - js!( - var src_buffer = new Float32Array(@{typed_array}.buffer); - var context = @{stream}; - var buf_len = @{temporary_buffer.len() as u32}; - var num_channels = @{num_channels}; - - var buffer = context.createBuffer(num_channels, buf_len / num_channels, 44100); - for (var channel = 0; channel < num_channels; ++channel) { - var buffer_content = buffer.getChannelData(channel); - for (var i = 0; i < buf_len / num_channels; ++i) { - buffer_content[i] = src_buffer[i * num_channels + channel]; - } - } - - var node = context.createBufferSource(); - node.buffer = buffer; - node.connect(context.destination); - node.start(); - ); - } - - set_timeout(|| callback_fn::(user_data_ptr), 330); - } + let buffer = UnknownTypeOutputBuffer::F32(::OutputBuffer { buffer: &mut temporary_buffer }); + let data = StreamData::Output { buffer: buffer }; + data_cb(data); } - let mut user_data = (self, callback); - let user_data_ptr = &mut user_data as *mut (_, _); - - set_timeout(|| callback_fn::(user_data_ptr as *mut _), 10); - - stdweb::event_loop(); - } - - #[inline] - fn build_input_stream(&self, _: &Device, _format: &Format) -> Result { - unimplemented!(); - } - - #[inline] - fn build_output_stream(&self, _: &Device, _format: &Format) -> Result { - let stream = js!(return new AudioContext()).into_reference().unwrap(); - - let mut streams = self.streams.lock().unwrap(); - let stream_id = if let Some(pos) = streams.iter().position(|v| v.is_none()) { - streams[pos] = Some(stream); - pos - } else { - let l = streams.len(); - streams.push(Some(stream)); - l + // TODO: directly use a TypedArray once this is supported by stdweb + let typed_array = { + let f32_slice = temporary_buffer.as_slice(); + let u8_slice: &[u8] = from_raw_parts( + f32_slice.as_ptr() as *const _, + f32_slice.len() * mem::size_of::(), + ); + let typed_array: TypedArray = u8_slice.into(); + typed_array }; - Ok(StreamId(stream_id)) - } + let num_channels = 2u32; // TODO: correct value + debug_assert_eq!(temporary_buffer.len() % num_channels as usize, 0); - #[inline] - fn destroy_stream(&self, stream_id: StreamId) { - self.streams.lock().unwrap()[stream_id.0] = None; - } + js!( + var src_buffer = new Float32Array(@{typed_array}.buffer); + var context = @{audio_ctxt}; + var buf_len = @{temporary_buffer.len() as u32}; + var num_channels = @{num_channels}; - #[inline] - fn play_stream(&self, stream_id: StreamId) -> Result<(), PlayStreamError> { - let streams = self.streams.lock().unwrap(); - let stream = streams - .get(stream_id.0) - .and_then(|v| v.as_ref()) - .expect("invalid stream ID"); - js!(@{stream}.resume()); - Ok(()) - } + var buffer = context.createBuffer(num_channels, buf_len / num_channels, 44100); + for (var channel = 0; channel < num_channels; ++channel) { + var buffer_content = buffer.getChannelData(channel); + for (var i = 0; i < buf_len / num_channels; ++i) { + buffer_content[i] = src_buffer[i * num_channels + channel]; + } + } - #[inline] - fn pause_stream(&self, stream_id: StreamId) -> Result<(), PauseStreamError> { - let streams = self.streams.lock().unwrap(); - let stream = streams - .get(stream_id.0) - .and_then(|v| v.as_ref()) - .expect("invalid stream ID"); - js!(@{stream}.suspend()); - Ok(()) - } -} + var node = context.createBufferSource(); + node.buffer = buffer; + node.connect(context.destination); + node.start(); + ); -// Index within the `streams` array of the events loop. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct StreamId(usize); - -// Detects whether the `AudioContext` global variable is available. -fn is_webaudio_available() -> bool { - stdweb::initialize(); - - js!(if (!AudioContext) { - return false; - } else { - return true; - }).try_into() - .unwrap() -} - -// Content is false if the iterator is empty. -pub struct Devices(bool); - -impl Devices { - fn new() -> Result { - Ok(Self::default()) + // 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); } } @@ -336,54 +309,13 @@ fn default_output_device() -> Option { } } -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct Device; - -impl Device { - #[inline] - fn name(&self) -> Result { - Ok("Default Device".to_owned()) - } - - #[inline] - fn supported_input_formats(&self) -> Result { - unimplemented!(); - } - - #[inline] - fn supported_output_formats(&self) -> Result { - // TODO: right now cpal's API doesn't allow flexibility here - // "44100" and "2" (channels) have also been hard-coded in the rest of the code ; if - // this ever becomes more flexible, don't forget to change that - // According to https://developer.mozilla.org/en-US/docs/Web/API/BaseAudioContext/createBuffer - // browsers must support 1 to 32 channels at leats and 8,000 Hz to 96,000 Hz. - Ok( - vec![ - SupportedFormat { - channels: 2, - min_sample_rate: ::SampleRate(44100), - max_sample_rate: ::SampleRate(44100), - data_type: ::SampleFormat::F32, - }, - ].into_iter(), - ) - } - - fn default_input_format(&self) -> Result { - unimplemented!(); - } - - fn default_output_format(&self) -> Result { - // TODO: because it is hard coded, see supported_output_formats. - Ok( - Format { - channels: 2, - sample_rate: ::SampleRate(44100), - data_type: ::SampleFormat::F32, - }, - ) - } +// Detects whether the `AudioContext` global variable is available. +fn is_webaudio_available() -> bool { + stdweb::initialize(); + js!(if (!AudioContext) { + return false; + } else { + return true; + }).try_into() + .unwrap() } - -pub type SupportedInputFormats = ::std::vec::IntoIter; -pub type SupportedOutputFormats = ::std::vec::IntoIter; diff --git a/src/platform/mod.rs b/src/platform/mod.rs index cfa88c4..1571c11 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -435,9 +435,8 @@ mod platform_impl { pub use crate::host::emscripten::{ Device as EmscriptenDevice, Devices as EmscriptenDevices, - EventLoop as EmscriptenEventLoop, Host as EmscriptenHost, - StreamId as EmscriptenStreamId, + Stream as EmscriptenStream, SupportedInputFormats as EmscriptenSupportedInputFormats, SupportedOutputFormats as EmscriptenSupportedOutputFormats, }; From 27245444a734ac18712caa0ec3cb2c4b0cca3b5c Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Sat, 14 Dec 2019 16:48:43 +0100 Subject: [PATCH 31/41] Remove redundant from backend --- src/host/null/mod.rs | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/host/null/mod.rs b/src/host/null/mod.rs index 054008d..84ce071 100644 --- a/src/host/null/mod.rs +++ b/src/host/null/mod.rs @@ -1,5 +1,3 @@ -#![allow(dead_code)] - use BuildStreamError; use DefaultFormatError; use DevicesError; @@ -19,8 +17,6 @@ pub struct Devices; #[derive(Clone, Debug, PartialEq, Eq)] pub struct Device; -pub struct EventLoop; - pub struct Host; #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -30,6 +26,7 @@ pub struct SupportedInputFormats; pub struct SupportedOutputFormats; impl Host { + #[allow(dead_code)] pub fn new() -> Result { Ok(Host) } @@ -41,12 +38,6 @@ impl Devices { } } -impl EventLoop { - pub fn new() -> EventLoop { - EventLoop - } -} - impl DeviceTrait for Device { type SupportedInputFormats = SupportedInputFormats; type SupportedOutputFormats = SupportedOutputFormats; From f34a062fac18faf04468fb07304b202b0d05fd52 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Sat, 14 Dec 2019 17:30:25 +0100 Subject: [PATCH 32/41] Update docs for removal of the `EventLoop` --- src/lib.rs | 161 ++++++++++++++++++++++++++------------------------ src/traits.rs | 11 +++- 2 files changed, 93 insertions(+), 79 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 36f1ac8..83ed26d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,25 +7,21 @@ //! least one [**DefaultHost**](./struct.Host.html) that is guaranteed to be available. //! - A [**Device**](./struct.Device.html) is an audio device that may have any number of input and //! output streams. -//! - A stream is an open flow of audio data. Input streams allow you to receive audio data, output -//! streams allow you to play audio data. You must choose which **Device** will run your stream -//! before you can create one. Often, a default device can be retrieved via the **Host**. -//! - An [**EventLoop**](./struct.EventLoop.html) is a collection of streams being run by one or -//! more **Device**s under a single **Host**. Each stream must belong to an **EventLoop**, and -//! all the streams that belong to an **EventLoop** are managed together. +//! - A [**Stream**](./trait.Stream.html) is an open flow of audio data. Input streams allow you to +//! receive audio data, output streams allow you to play audio data. You must choose which +//! **Device** will run your stream before you can create one. Often, a default device can be +//! retrieved via the **Host**. //! -//! The first step is to initialise the `Host` (for accessing audio devices) and create an -//! `EventLoop`: +//! The first step is to initialise the `Host`: //! //! ``` //! use cpal::traits::HostTrait; //! let host = cpal::default_host(); -//! let event_loop = host.event_loop(); //! ``` //! -//! Then choose a `Device`. The easiest way is to use the default input or output `Device` via the -//! `default_input_device()` or `default_output_device()` functions. Alternatively you can -//! enumerate all the available devices with the `devices()` function. Beware that the +//! Then choose an available `Device`. The easiest way is to use the default input or output +//! `Device` via the `default_input_device()` or `default_output_device()` functions. Alternatively +//! you can enumerate all the available devices with the `devices()` function. Beware that the //! `default_*_device()` functions return an `Option` in case no device is available for that //! stream type on the system. //! @@ -56,87 +52,96 @@ //! .with_max_sample_rate(); //! ``` //! -//! Now that we have everything for the stream, we can create it from our event loop: +//! Now that we have everything for the stream, we are ready to create it from our selected device: //! //! ```no_run -//! use cpal::traits::{DeviceTrait, EventLoopTrait, HostTrait}; +//! use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; //! # let host = cpal::default_host(); -//! # let event_loop = host.event_loop(); //! # let device = host.default_output_device().unwrap(); -//! # let format = device.supported_output_formats().unwrap().next().unwrap().with_max_sample_rate(); -//! let stream_id = event_loop.build_output_stream(&device, &format).unwrap(); +//! # let format = device.default_output_format().unwrap(); +//! let stream = device.build_output_stream( +//! &format, +//! move |data| { +//! // react to stream events and read or write stream data here. +//! }, +//! move |err| { +//! // react to errors here. +//! }, +//! ); //! ``` //! -//! The value returned by `build_output_stream()` is of type `StreamId` and is an identifier that -//! will allow you to control the stream. +//! 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`. //! -//! Now we must start the stream. This is done with the `play_stream()` method on the event loop. -//! -//! ```no_run -//! # use cpal::traits::{EventLoopTrait, HostTrait}; -//! # let host = cpal::default_host(); -//! # let event_loop = host.event_loop(); -//! # let stream_id = unimplemented!(); -//! event_loop.play_stream(stream_id).expect("failed to play_stream"); -//! ``` -//! -//! Now everything is ready! We call `run()` on the `event_loop` to begin processing. -//! -//! ```no_run -//! # use cpal::traits::{EventLoopTrait, HostTrait}; -//! # let host = cpal::default_host(); -//! # let event_loop = host.event_loop(); -//! event_loop.run(move |_stream_id, _stream_result| { -//! // react to stream events and read or write stream data here -//! }); -//! ``` -//! -//! > **Note**: Calling `run()` will block the thread forever, so it's usually best done in a -//! > separate thread. -//! -//! While `run()` is running, the audio device of the user will from time to time call the callback -//! that you passed to this function. The callback gets passed the stream ID and 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`. +//! > **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 +//! > audio data to the system's audio device in a timely manner. On older platforms that only +//! > provide a blocking API (e.g. ALSA), CPAL will create a thread in order to consistently +//! > provide non-blocking behaviour. *If this is an issue for your platform or design, 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. //! //! ```no_run //! use cpal::{StreamData, UnknownTypeOutputBuffer}; -//! use cpal::traits::{EventLoopTrait, HostTrait}; +//! use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; //! # let host = cpal::default_host(); -//! # let event_loop = host.event_loop(); -//! event_loop.run(move |stream_id, stream_result| { -//! let stream_data = match stream_result { -//! Ok(data) => data, -//! Err(err) => { -//! eprintln!("an error occurred on stream {:?}: {}", stream_id, err); -//! return; +//! # 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; +//! } +//! }, +//! _ => (), //! } -//! _ => return, -//! }; -//! -//! match stream_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); +//! }, +//! ); //! ``` +//! +//! Not all platforms automatically run the stream upon creation. To ensure the stream has started, +//! we can use `Stream::play`. +//! +//! ```no_run +//! # 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 |_err| {}).unwrap(); +//! stream.play().unwrap(); +//! ``` +//! +//! Some devices support pausing the audio stream. This can be useful for saving energy in moments +//! of silence. +//! +//! ```no_run +//! # 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 |_err| {}).unwrap(); +//! stream.pause().unwrap(); #![recursion_limit = "512"] diff --git a/src/traits.rs b/src/traits.rs index d90d782..ed492d1 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -128,7 +128,16 @@ pub trait DeviceTrait { /// A stream created from `Device`, with methods to control playback. pub trait StreamTrait { + /// Run the stream. + /// + /// Note: Not all platforms automatically run the stream upon creation, so it is important to + /// call `play` after creation if it is expected that the stream should run immediately. fn play(&self) -> Result<(), PlayStreamError>; + /// Some devices support pausing the audio stream. This can be useful for saving energy in + /// moments of silence. + /// + /// Note: Not all devices support suspending the stream at the hardware level. This method may + /// fail in these cases. fn pause(&self) -> Result<(), PauseStreamError>; -} \ No newline at end of file +} From cede7b41dbb632a099069f69df604bd848323af8 Mon Sep 17 00:00:00 2001 From: Tatsuyuki Ishi Date: Tue, 9 Jul 2019 15:47:33 +0900 Subject: [PATCH 33/41] Remove EventLoop and port the ALSA backend --- examples/beep.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/beep.rs b/examples/beep.rs index 686230c..671f1bf 100644 --- a/examples/beep.rs +++ b/examples/beep.rs @@ -49,7 +49,7 @@ fn main() -> Result<(), anyhow::Error> { eprintln!("an error occurred on stream: {}", err); })?; stream.play()?; - + std::thread::sleep(std::time::Duration::from_millis(1000)); Ok(()) From 64ba84de8125357ac34601a5724312078080fa86 Mon Sep 17 00:00:00 2001 From: Viktor Lazarev Date: Wed, 28 Aug 2019 20:37:08 +0200 Subject: [PATCH 34/41] Move code from stream to device --- src/host/wasapi/device.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/host/wasapi/device.rs b/src/host/wasapi/device.rs index a510f4e..50dd239 100644 --- a/src/host/wasapi/device.rs +++ b/src/host/wasapi/device.rs @@ -7,7 +7,7 @@ use std::ops::{Deref, DerefMut}; use std::os::windows::ffi::OsStringExt; use std::ptr; use std::slice; -use std::sync::{Arc, Mutex, MutexGuard}; +use std::sync::{Arc, Mutex, MutexGuard, atomic::Ordering}; use BackendSpecificError; use DefaultFormatError; From bfda57521870ffbbb55e3149e545554045557875 Mon Sep 17 00:00:00 2001 From: msiglreith Date: Fri, 4 Oct 2019 18:18:18 +0200 Subject: [PATCH 35/41] Port ASIO host to new stream-based API --- src/host/asio/device.rs | 14 +- src/host/asio/mod.rs | 70 ++---- src/host/asio/stream.rs | 527 ++++++++++++++-------------------------- src/lib.rs | 5 + src/platform/mod.rs | 3 +- 5 files changed, 219 insertions(+), 400 deletions(-) diff --git a/src/host/asio/device.rs b/src/host/asio/device.rs index ad6159d..f1b69f1 100644 --- a/src/host/asio/device.rs +++ b/src/host/asio/device.rs @@ -3,7 +3,7 @@ pub type SupportedInputFormats = std::vec::IntoIter; pub type SupportedOutputFormats = std::vec::IntoIter; use std::hash::{Hash, Hasher}; -use std::sync::Arc; +use std::sync::{Mutex, Arc}; use BackendSpecificError; use DefaultFormatError; use DeviceNameError; @@ -16,10 +16,14 @@ use SupportedFormatsError; use super::sys; /// A ASIO Device -#[derive(Debug)] pub struct Device { /// The driver represented by this device. pub driver: Arc, + + // Input and/or Output stream. + // An driver can only have one of each. + // They need to be created at the same time. + pub asio_streams: Arc>>, } /// All available devices. @@ -148,7 +152,11 @@ impl Iterator for Devices { loop { match self.drivers.next() { Some(name) => match self.asio.load_driver(&name) { - Ok(driver) => return Some(Device { driver: Arc::new(driver) }), + Ok(driver) => { + let driver = Arc::new(driver); + let asio_streams = Arc::new(Mutex::new(None)); + return Some(Device { driver, asio_streams }); + } Err(_) => continue, } None => return None, diff --git a/src/host/asio/mod.rs b/src/host/asio/mod.rs index f752cd9..e47b81e 100644 --- a/src/host/asio/mod.rs +++ b/src/host/asio/mod.rs @@ -8,18 +8,18 @@ use { Format, PauseStreamError, PlayStreamError, - StreamDataResult, SupportedFormatsError, + StreamData, + StreamError, }; use traits::{ DeviceTrait, - EventLoopTrait, HostTrait, - StreamIdTrait, + StreamTrait, }; pub use self::device::{Device, Devices, SupportedInputFormats, SupportedOutputFormats}; -pub use self::stream::{EventLoop, StreamId}; +pub use self::stream::Stream; use std::sync::Arc; mod device; @@ -42,7 +42,6 @@ impl Host { impl HostTrait for Host { type Devices = Devices; type Device = Device; - type EventLoop = EventLoop; fn is_available() -> bool { true @@ -62,15 +61,12 @@ impl HostTrait for Host { // ASIO has no concept of a default device, so just use the first. self.output_devices().ok().and_then(|mut ds| ds.next()) } - - fn event_loop(&self) -> Self::EventLoop { - EventLoop::new() - } } impl DeviceTrait for Device { type SupportedInputFormats = SupportedInputFormats; type SupportedOutputFormats = SupportedOutputFormats; + type Stream = Stream; fn name(&self) -> Result { Device::name(self) @@ -91,46 +87,28 @@ impl DeviceTrait for Device { fn default_output_format(&self) -> Result { Device::default_output_format(self) } -} -impl EventLoopTrait for EventLoop { - type Device = Device; - type StreamId = StreamId; - - fn build_input_stream( - &self, - device: &Self::Device, - format: &Format, - ) -> Result { - EventLoop::build_input_stream(self, device, format) - } - - fn build_output_stream( - &self, - device: &Self::Device, - format: &Format, - ) -> Result { - EventLoop::build_output_stream(self, device, format) - } - - fn play_stream(&self, stream: Self::StreamId) -> Result<(), PlayStreamError> { - EventLoop::play_stream(self, stream) - } - - fn pause_stream(&self, stream: Self::StreamId) -> Result<(), PauseStreamError> { - EventLoop::pause_stream(self, stream) - } - - fn destroy_stream(&self, stream: Self::StreamId) { - EventLoop::destroy_stream(self, stream) - } - - fn run(&self, callback: F) -> ! + fn build_input_stream(&self, format: &Format, data_callback: D, error_callback: E) -> Result where - F: FnMut(Self::StreamId, StreamDataResult) + Send, + D: FnMut(StreamData) + Send + 'static, E: FnMut(StreamError) + Send + 'static { - EventLoop::run(self, callback) + 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 + { + Device::build_output_stream(self, format, data_callback, error_callback) } } -impl StreamIdTrait for StreamId {} +impl StreamTrait for Stream { + fn play(&self) -> Result<(), PlayStreamError> { + Stream::play(self) + } + + fn pause(&self) -> Result<(), PauseStreamError> { + Stream::pause(self) + } +} diff --git a/src/host/asio/stream.rs b/src/host/asio/stream.rs index 661620a..dbd99e5 100644 --- a/src/host/asio/stream.rs +++ b/src/host/asio/stream.rs @@ -4,11 +4,8 @@ extern crate num_traits; use self::num_traits::PrimInt; use super::Device; use std; -use std::mem; -use std::sync::atomic::{AtomicUsize, Ordering}; -use std::sync::{Arc, Mutex}; -use std::thread; -use std::time::Duration; +use std::sync::atomic::{Ordering, AtomicBool}; +use std::sync::Arc; use BackendSpecificError; use BuildStreamError; use Format; @@ -16,9 +13,9 @@ use PauseStreamError; use PlayStreamError; use SampleFormat; use StreamData; -use StreamDataResult; use UnknownTypeInputBuffer; use UnknownTypeOutputBuffer; +use StreamError; /// Sample types whose constant silent value is known. trait Silence { @@ -34,35 +31,6 @@ trait InterleavedSample: Clone + Copy + Silence { /// Constraints on the ASIO sample types. trait AsioSample: Clone + Copy + Silence + std::ops::Add {} -/// Controls all streams -pub struct EventLoop { - /// The input and output ASIO streams - asio_streams: Arc>, - /// List of all CPAL streams - cpal_streams: Arc>>>, - /// Total stream count. - stream_count: AtomicUsize, - /// The CPAL callback that the user gives to fill the buffers. - callbacks: Arc>>, -} - -/// Id for each stream. -/// Created depending on the number they are created. -/// Starting at one! not zero. -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub struct StreamId(usize); - -/// CPAL stream. -/// This decouples the many cpal streams -/// from the single input and single output -/// ASIO streams. -/// Each stream can be playing or paused. -struct Stream { - playing: bool, - // The driver associated with this stream. - driver: Arc, -} - // Used to keep track of whether or not the current current asio stream buffer requires // being silencing before summing audio. #[derive(Default)] @@ -71,114 +39,36 @@ struct SilenceAsioBuffer { second: bool, } -impl EventLoop { - pub fn new() -> EventLoop { - EventLoop { - asio_streams: Arc::new(Mutex::new(sys::AsioStreams { - input: None, - output: None, - })), - cpal_streams: Arc::new(Mutex::new(Vec::new())), - // This is why the Id's count from one not zero - // because at this point there is no streams - stream_count: AtomicUsize::new(0), - callbacks: Arc::new(Mutex::new(None)), - } +pub struct Stream { + playing: Arc, +} + +impl Stream { + pub fn play(&self) -> Result<(), PlayStreamError> { + self.playing.store(true, Ordering::SeqCst); + Ok(()) } - /// Create a new CPAL Input Stream. - /// - /// If there is no existing ASIO Input Stream it will be created. - /// - /// On success, the buffer size of the stream is returned. - fn get_or_create_input_stream( - &self, - driver: &sys::Driver, - format: &Format, - device: &Device, - ) -> Result { - match device.default_input_format() { - Ok(f) => { - let num_asio_channels = f.channels; - check_format(driver, format, num_asio_channels) - }, - Err(_) => Err(BuildStreamError::FormatNotSupported), - }?; - let num_channels = format.channels as usize; - let ref mut streams = *self.asio_streams.lock().unwrap(); - // Either create a stream if thers none or had back the - // size of the current one. - match streams.input { - Some(ref input) => Ok(input.buffer_size as usize), - None => { - let output = streams.output.take(); - driver - .prepare_input_stream(output, num_channels) - .map(|new_streams| { - let bs = match new_streams.input { - Some(ref inp) => inp.buffer_size as usize, - None => unreachable!(), - }; - *streams = new_streams; - bs - }).map_err(|ref e| { - println!("Error preparing stream: {}", e); - BuildStreamError::DeviceNotAvailable - }) - } - } + pub fn pause(&self) -> Result<(), PauseStreamError> { + self.playing.store(false, Ordering::SeqCst); + Ok(()) } +} - /// Create a new CPAL Output Stream. - /// - /// If there is no existing ASIO Output Stream it will be created. - /// - /// On success, the buffer size of the stream is returned. - fn get_or_create_output_stream( - &self, - driver: &sys::Driver, - format: &Format, - device: &Device, - ) -> Result { - match device.default_output_format() { - Ok(f) => { - let num_asio_channels = f.channels; - check_format(driver, format, num_asio_channels) - }, - Err(_) => Err(BuildStreamError::FormatNotSupported), - }?; - let num_channels = format.channels as usize; - let ref mut streams = *self.asio_streams.lock().unwrap(); - // Either create a stream if there's none or return the size of the current one. - match streams.output { - Some(ref output) => Ok(output.buffer_size as usize), - None => { - let input = streams.input.take(); - driver - .prepare_output_stream(input, num_channels) - .map(|new_streams| { - let bs = match new_streams.output { - Some(ref out) => out.buffer_size as usize, - None => unreachable!(), - }; - *streams = new_streams; - bs - }).map_err(|ref e| { - println!("Error preparing stream: {}", e); - BuildStreamError::DeviceNotAvailable - }) - } - } - } +// TODO: drop implementation - /// Builds a new cpal input stream - pub fn build_input_stream( +impl Device { + pub fn build_input_stream( &self, - device: &Device, format: &Format, - ) -> Result { - let Device { driver, .. } = device; - let stream_type = driver.input_data_type().map_err(build_stream_err)?; + mut data_callback: D, + _error_callback: E, + ) -> Result + where + D: FnMut(StreamData) + Send + 'static, + E: FnMut(StreamError) + Send + 'static + { + let stream_type = self.driver.input_data_type().map_err(build_stream_err)?; // Ensure that the desired sample type is supported. let data_type = super::device::convert_data_type(&stream_type) @@ -188,48 +78,28 @@ impl EventLoop { } let num_channels = format.channels.clone(); - let stream_buffer_size = self.get_or_create_input_stream(&driver, format, device)?; - let cpal_num_samples = stream_buffer_size * num_channels as usize; - let count = self.stream_count.fetch_add(1, Ordering::SeqCst); - let asio_streams = self.asio_streams.clone(); - let cpal_streams = self.cpal_streams.clone(); - let callbacks = self.callbacks.clone(); + let asio_stream = self.get_or_create_input_stream(format)?; + let cpal_num_samples = asio_stream.buffer_size as usize * num_channels as usize; // Create the buffer depending on the size of the data type. - let stream_id = StreamId(count); let len_bytes = cpal_num_samples * data_type.sample_size(); let mut interleaved = vec![0u8; len_bytes]; + let stream_playing = Arc::new(AtomicBool::new(false)); + let playing = Arc::clone(&stream_playing); + // Set the input callback. // This is most performance critical part of the ASIO bindings. - driver.set_callback(move |buffer_index| unsafe { + self.driver.set_callback(move |buffer_index| unsafe { // If not playing return early. - // TODO: Don't assume `count` is valid - we should search for the matching `StreamId`. - if let Some(s) = cpal_streams.lock().unwrap().get(count) { - if let Some(s) = s { - if !s.playing { - return; - } - } + if !playing.load(Ordering::SeqCst) { + return } - // Acquire the stream and callback. - let stream_lock = asio_streams.lock().unwrap(); - let ref asio_stream = match stream_lock.input { - Some(ref asio_stream) => asio_stream, - None => return, - }; - let mut callbacks = callbacks.lock().unwrap(); - let callback = match callbacks.as_mut() { - Some(callback) => callback, - None => return, - }; - /// 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( - stream_id: StreamId, - callback: &mut (dyn FnMut(StreamId, StreamDataResult) + Send), + unsafe fn process_input_callback( + callback: &mut D, interleaved: &mut [u8], asio_stream: &sys::AsioStream, buffer_index: usize, @@ -239,6 +109,7 @@ impl EventLoop { where A: AsioSample, B: InterleavedSample, + D: FnMut(StreamData) + Send + 'static, F: Fn(A) -> A, G: Fn(A) -> B, { @@ -254,29 +125,26 @@ impl EventLoop { // 2. Deliver the interleaved buffer to the callback. callback( - stream_id, - Ok(StreamData::Input { buffer: B::unknown_type_input_buffer(interleaved) }), + StreamData::Input { buffer: B::unknown_type_input_buffer(interleaved) }, ); } match (&stream_type, data_type) { (&sys::AsioSampleType::ASIOSTInt16LSB, SampleFormat::I16) => { - process_input_callback::( - stream_id, - callback, + process_input_callback::( + &mut data_callback, &mut interleaved, - asio_stream, + &asio_stream, buffer_index as usize, from_le, std::convert::identity::, ); } (&sys::AsioSampleType::ASIOSTInt16MSB, SampleFormat::I16) => { - process_input_callback::( - stream_id, - callback, + process_input_callback::( + &mut data_callback, &mut interleaved, - asio_stream, + &asio_stream, buffer_index as usize, from_be, std::convert::identity::, @@ -287,11 +155,10 @@ impl EventLoop { // 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::( - stream_id, - callback, + process_input_callback::( + &mut data_callback, &mut interleaved, - asio_stream, + &asio_stream, buffer_index as usize, std::convert::identity::, std::convert::identity::, @@ -302,22 +169,20 @@ impl EventLoop { // `process_output_callback` function above by removing the unnecessary sample // conversion function. (&sys::AsioSampleType::ASIOSTInt32LSB, SampleFormat::I16) => { - process_input_callback::( - stream_id, - callback, + process_input_callback::( + &mut data_callback, &mut interleaved, - asio_stream, + &asio_stream, buffer_index as usize, from_le, |s| (s >> 16) as i16, ); } (&sys::AsioSampleType::ASIOSTInt32MSB, SampleFormat::I16) => { - process_input_callback::( - stream_id, - callback, + process_input_callback::( + &mut data_callback, &mut interleaved, - asio_stream, + &asio_stream, buffer_index as usize, from_be, |s| (s >> 16) as i16, @@ -327,11 +192,10 @@ impl EventLoop { // 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::( - stream_id, - callback, + process_input_callback::( + &mut data_callback, &mut interleaved, - asio_stream, + &asio_stream, buffer_index as usize, std::convert::identity::, |s| s as f32, @@ -345,23 +209,23 @@ impl EventLoop { } }); - // Create stream and set to paused - self.cpal_streams - .lock() - .unwrap() - .push(Some(Stream { driver: driver.clone(), playing: false })); + // Immediately start the device? + self.driver.start().map_err(build_stream_err)?; - Ok(StreamId(count)) + Ok(Stream { playing: stream_playing }) } - /// Create the an output cpal stream. - pub fn build_output_stream( + pub fn build_output_stream( &self, - device: &Device, format: &Format, - ) -> Result { - let Device { driver, .. } = device; - let stream_type = driver.output_data_type().map_err(build_stream_err)?; + mut data_callback: D, + _error_callback: E, + ) -> Result + where + D: FnMut(StreamData) + Send + 'static, + E: FnMut(StreamError) + Send + 'static, + { + let stream_type = self.driver.output_data_type().map_err(build_stream_err)?; // Ensure that the desired sample type is supported. let data_type = super::device::convert_data_type(&stream_type) @@ -371,38 +235,22 @@ impl EventLoop { } let num_channels = format.channels.clone(); - let stream_buffer_size = self.get_or_create_output_stream(&driver, format, device)?; - let cpal_num_samples = stream_buffer_size * num_channels as usize; - let count = self.stream_count.fetch_add(1, Ordering::SeqCst); - let asio_streams = self.asio_streams.clone(); - let cpal_streams = self.cpal_streams.clone(); - let callbacks = self.callbacks.clone(); + let asio_stream = self.get_or_create_output_stream(format)?; + let cpal_num_samples = asio_stream.buffer_size as usize * num_channels as usize; // Create buffers depending on data type. - let stream_id = StreamId(count); let len_bytes = cpal_num_samples * data_type.sample_size(); let mut interleaved = vec![0u8; len_bytes]; let mut silence_asio_buffer = SilenceAsioBuffer::default(); - driver.set_callback(move |buffer_index| unsafe { - // If not playing, return early. - // TODO: Don't assume `count` is valid - we should search for the matching `StreamId`. - if let Some(s) = cpal_streams.lock().unwrap().get(count) { - if let Some(s) = s { - if !s.playing { - return (); - } - } - } + let stream_playing = Arc::new(AtomicBool::new(false)); + let playing = Arc::clone(&stream_playing); - // Acquire the stream and callback. - let stream_lock = asio_streams.lock().unwrap(); - let ref asio_stream = match stream_lock.output { - Some(ref asio_stream) => asio_stream, - None => return, - }; - let mut callbacks = callbacks.lock().unwrap(); - let callback = callbacks.as_mut(); + self.driver.set_callback(move |buffer_index| unsafe { + // If not playing, return early. + if !playing.load(Ordering::SeqCst) { + return + } // Silence the ASIO buffer that is about to be used. // @@ -430,9 +278,8 @@ impl EventLoop { /// 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( - stream_id: StreamId, - callback: Option<&mut &mut (dyn FnMut(StreamId, StreamDataResult) + Send)>, + unsafe fn process_output_callback( + callback: &mut D, interleaved: &mut [u8], silence_asio_buffer: bool, asio_stream: &sys::AsioStream, @@ -443,18 +290,14 @@ impl EventLoop { where A: InterleavedSample, B: AsioSample, + D: FnMut(StreamData) + Send + 'static, F: Fn(A) -> B, G: Fn(B) -> B, { // 1. Render interleaved buffer from callback. let interleaved: &mut [A] = cast_slice_mut(interleaved); - match callback { - None => interleaved.iter_mut().for_each(|s| *s = A::SILENCE), - Some(callback) => { - let buffer = A::unknown_type_output_buffer(interleaved); - callback(stream_id, Ok(StreamData::Output { buffer })); - } - } + let buffer = A::unknown_type_output_buffer(interleaved); + callback(StreamData::Output { buffer }); // 2. Silence ASIO channels if necessary. let n_channels = interleaved.len() / asio_stream.buffer_size as usize; @@ -478,24 +321,22 @@ impl EventLoop { match (data_type, &stream_type) { (SampleFormat::I16, &sys::AsioSampleType::ASIOSTInt16LSB) => { - process_output_callback::( - stream_id, - callback, + process_output_callback::( + &mut data_callback, &mut interleaved, silence, - asio_stream, + &asio_stream, buffer_index as usize, std::convert::identity::, to_le, ); } (SampleFormat::I16, &sys::AsioSampleType::ASIOSTInt16MSB) => { - process_output_callback::( - stream_id, - callback, + process_output_callback::( + &mut data_callback, &mut interleaved, silence, - asio_stream, + &asio_stream, buffer_index as usize, std::convert::identity::, to_be, @@ -506,12 +347,11 @@ impl EventLoop { // 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::( - stream_id, - callback, + process_output_callback::( + &mut data_callback, &mut interleaved, silence, - asio_stream, + &asio_stream, buffer_index as usize, std::convert::identity::, std::convert::identity::, @@ -522,24 +362,22 @@ impl EventLoop { // `process_output_callback` function above by removing the unnecessary sample // conversion function. (SampleFormat::I16, &sys::AsioSampleType::ASIOSTInt32LSB) => { - process_output_callback::( - stream_id, - callback, + process_output_callback::( + &mut data_callback, &mut interleaved, silence, - asio_stream, + &asio_stream, buffer_index as usize, |s| (s as i32) << 16, to_le, ); } (SampleFormat::I16, &sys::AsioSampleType::ASIOSTInt32MSB) => { - process_output_callback::( - stream_id, - callback, + process_output_callback::( + &mut data_callback, &mut interleaved, silence, - asio_stream, + &asio_stream, buffer_index as usize, |s| (s as i32) << 16, to_be, @@ -549,12 +387,11 @@ impl EventLoop { // 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::( - stream_id, - callback, + process_output_callback::( + &mut data_callback, &mut interleaved, silence, - asio_stream, + &asio_stream, buffer_index as usize, |s| s as f64, std::convert::identity::, @@ -568,78 +405,92 @@ impl EventLoop { } }); - // Create the stream paused - self.cpal_streams - .lock() - .unwrap() - .push(Some(Stream { driver: driver.clone(), playing: false })); + // Immediately start the device? + self.driver.start().map_err(build_stream_err)?; - // Give the ID based on the stream count - Ok(StreamId(count)) + Ok(Stream { playing: stream_playing }) } - /// Play the cpal stream for the given ID. - pub fn play_stream(&self, stream_id: StreamId) -> Result<(), PlayStreamError> { - let mut streams = self.cpal_streams.lock().unwrap(); - if let Some(s) = streams.get_mut(stream_id.0).expect("Bad play stream index") { - s.playing = true; - // Calling play when already playing is a no-op - s.driver.start().map_err(play_stream_err)?; - } - Ok(()) - } - - /// Pause the cpal stream for the given ID. + /// Create a new CPAL Input Stream. /// - /// Pause the ASIO streams if there are no other CPAL streams playing, as ASIO only allows - /// stopping the entire driver. - pub fn pause_stream(&self, stream_id: StreamId) -> Result<(), PauseStreamError> { - let mut streams = self.cpal_streams.lock().unwrap(); - let streams_playing = streams.iter() - .filter(|s| s.as_ref().map(|s| s.playing).unwrap_or(false)) - .count(); - if let Some(s) = streams.get_mut(stream_id.0).expect("Bad pause stream index") { - if streams_playing <= 1 { - s.driver.stop().map_err(pause_stream_err)?; + /// If there is no existing ASIO Input Stream it will be created. + /// + /// On success, the buffer size of the stream is returned. + fn get_or_create_input_stream( + &self, + format: &Format, + ) -> Result { + match self.default_input_format() { + Ok(f) => { + let num_asio_channels = f.channels; + check_format(&self.driver, format, num_asio_channels) + }, + Err(_) => Err(BuildStreamError::FormatNotSupported), + }?; + let num_channels = format.channels as usize; + let ref mut streams = *self.asio_streams.lock().unwrap(); + match streams { + Some(streams) => match streams.input.take() { + Some(input) => Ok(input), + None => { + println!("ASIO streams have been already created"); + Err(BuildStreamError::DeviceNotAvailable) + } + }, + None => { + match self.driver.prepare_input_stream(None, num_channels) { + Ok(mut new_streams) => { + let input = new_streams.input.take().expect("missing input stream"); + *streams = Some(new_streams); + Ok(input) + } + Err(e) => { + println!("Error preparing stream: {}", e); + Err(BuildStreamError::DeviceNotAvailable) + } + } } - s.playing = false; - } - Ok(()) - } - - /// Destroy the cpal stream based on the ID. - pub fn destroy_stream(&self, stream_id: StreamId) { - // TODO: Should we not also remove an ASIO stream here? - // Yes, and we should update the logic in the callbacks to search for the stream with - // the matching ID, rather than assuming the index associated with the ID is valid. - let mut streams = self.cpal_streams.lock().unwrap(); - streams.get_mut(stream_id.0).take(); - } - - /// Run the cpal callbacks - pub fn run(&self, mut callback: F) -> ! - where - F: FnMut(StreamId, StreamDataResult) + Send, - { - let callback: &mut (FnMut(StreamId, StreamDataResult) + Send) = &mut callback; - // Transmute needed to convince the compiler that the callback has a static lifetime - *self.callbacks.lock().unwrap() = Some(unsafe { mem::transmute(callback) }); - loop { - // A sleep here to prevent the loop being - // removed in --release - thread::sleep(Duration::new(1u64, 0u32)); } } -} -/// Clean up if event loop is dropped. -/// Currently event loop is never dropped. -impl Drop for EventLoop { - fn drop(&mut self) { - *self.asio_streams.lock().unwrap() = sys::AsioStreams { - output: None, - input: None, - }; + /// Create a new CPAL Output Stream. + /// + /// If there is no existing ASIO Output Stream it will be created. + fn get_or_create_output_stream( + &self, + format: &Format, + ) -> Result { + match self.default_output_format() { + Ok(f) => { + let num_asio_channels = f.channels; + check_format(&self.driver, format, num_asio_channels) + }, + Err(_) => Err(BuildStreamError::FormatNotSupported), + }?; + let num_channels = format.channels as usize; + let ref mut streams = *self.asio_streams.lock().unwrap(); + match streams { + Some(streams) => match streams.output.take() { + Some(output) => Ok(output), + None => { + println!("ASIO streams have been already created"); + Err(BuildStreamError::DeviceNotAvailable) + } + }, + None => { + match self.driver.prepare_output_stream(None, num_channels) { + Ok(mut new_streams) => { + let output = new_streams.output.take().expect("missing output stream"); + *streams = Some(new_streams); + Ok(output) + } + Err(e) => { + println!("Error preparing stream: {}", e); + Err(BuildStreamError::DeviceNotAvailable) + } + } + } + } } } @@ -790,25 +641,3 @@ fn build_stream_err(e: sys::AsioError) -> BuildStreamError { } } } - -fn pause_stream_err(e: sys::AsioError) -> PauseStreamError { - match e { - sys::AsioError::NoDrivers | - sys::AsioError::HardwareMalfunction => PauseStreamError::DeviceNotAvailable, - err => { - let description = format!("{}", err); - BackendSpecificError { description }.into() - } - } -} - -fn play_stream_err(e: sys::AsioError) -> PlayStreamError { - match e { - sys::AsioError::NoDrivers | - sys::AsioError::HardwareMalfunction => PlayStreamError::DeviceNotAvailable, - err => { - let description = format!("{}", err); - BackendSpecificError { description }.into() - } - } -} diff --git a/src/lib.rs b/src/lib.rs index 83ed26d..0f6ce08 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -202,6 +202,7 @@ pub struct SupportedFormat { } /// Stream data passed to the `EventLoop::run` callback. +#[derive(Debug)] pub enum StreamData<'a> { Input { buffer: UnknownTypeInputBuffer<'a>, @@ -217,6 +218,7 @@ pub enum StreamData<'a> { /// 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 +#[derive(Debug)] pub struct InputBuffer<'a, T: 'a> where T: Sample, @@ -232,6 +234,7 @@ where // TODO: explain audio stuff in general // TODO: remove the wrapper and just use slices #[must_use] +#[derive(Debug)] pub struct OutputBuffer<'a, T: 'a> where T: Sample, @@ -242,6 +245,7 @@ where /// 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>), @@ -254,6 +258,7 @@ pub enum UnknownTypeInputBuffer<'a> { /// 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>), diff --git a/src/platform/mod.rs b/src/platform/mod.rs index 1571c11..d28e58e 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -457,9 +457,8 @@ mod platform_impl { pub use crate::host::asio::{ Device as AsioDevice, Devices as AsioDevices, - EventLoop as AsioEventLoop, + Stream as AsioStream, Host as AsioHost, - StreamId as AsioStreamId, SupportedInputFormats as AsioSupportedInputFormats, SupportedOutputFormats as AsioSupportedOutputFormats, }; From 972cce0f8c49e42be14007f9d11a592355d6b535 Mon Sep 17 00:00:00 2001 From: msiglreith Date: Tue, 8 Oct 2019 16:22:06 +0200 Subject: [PATCH 36/41] asio: Re-add possibility to create stream individually --- Cargo.toml | 1 + src/host/asio/device.rs | 10 +++- src/host/asio/mod.rs | 1 + src/host/asio/stream.rs | 126 ++++++++++++++++++++++------------------ 4 files changed, 79 insertions(+), 59 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index aa1fe22..7fc69ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ ringbuf = "0.1.6" [target.'cfg(target_os = "windows")'.dependencies] winapi = { version = "0.3", features = ["audiosessiontypes", "audioclient", "coml2api", "combaseapi", "debug", "devpkey", "handleapi", "ksmedia", "mmdeviceapi", "objbase", "std", "synchapi", "winbase", "winuser"] } asio-sys = { version = "0.1", path = "asio-sys", optional = true } +parking_lot = "0.9" [target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd"))'.dependencies] alsa-sys = { version = "0.1", path = "alsa-sys" } diff --git a/src/host/asio/device.rs b/src/host/asio/device.rs index f1b69f1..4025a74 100644 --- a/src/host/asio/device.rs +++ b/src/host/asio/device.rs @@ -3,7 +3,7 @@ pub type SupportedInputFormats = std::vec::IntoIter; pub type SupportedOutputFormats = std::vec::IntoIter; use std::hash::{Hash, Hasher}; -use std::sync::{Mutex, Arc}; +use std::sync::{Arc}; use BackendSpecificError; use DefaultFormatError; use DeviceNameError; @@ -14,6 +14,7 @@ use SampleRate; use SupportedFormat; use SupportedFormatsError; use super::sys; +use super::parking_lot::Mutex; /// A ASIO Device pub struct Device { @@ -23,7 +24,7 @@ pub struct Device { // Input and/or Output stream. // An driver can only have one of each. // They need to be created at the same time. - pub asio_streams: Arc>>, + pub asio_streams: Arc>, } /// All available devices. @@ -154,7 +155,10 @@ impl Iterator for Devices { Some(name) => match self.asio.load_driver(&name) { Ok(driver) => { let driver = Arc::new(driver); - let asio_streams = Arc::new(Mutex::new(None)); + let asio_streams = Arc::new(Mutex::new(sys::AsioStreams { + input: None, + output: None, + })); return Some(Device { driver, asio_streams }); } Err(_) => continue, diff --git a/src/host/asio/mod.rs b/src/host/asio/mod.rs index e47b81e..f4ac830 100644 --- a/src/host/asio/mod.rs +++ b/src/host/asio/mod.rs @@ -1,4 +1,5 @@ extern crate asio_sys as sys; +extern crate parking_lot; use { BuildStreamError, diff --git a/src/host/asio/stream.rs b/src/host/asio/stream.rs index dbd99e5..95025b2 100644 --- a/src/host/asio/stream.rs +++ b/src/host/asio/stream.rs @@ -78,8 +78,8 @@ impl Device { } let num_channels = format.channels.clone(); - let asio_stream = self.get_or_create_input_stream(format)?; - let cpal_num_samples = asio_stream.buffer_size as usize * num_channels as usize; + let buffer_size = self.get_or_create_input_stream(format)?; + let cpal_num_samples = buffer_size * num_channels as usize; // Create the buffer depending on the size of the data type. let len_bytes = cpal_num_samples * data_type.sample_size(); @@ -87,6 +87,7 @@ impl Device { let stream_playing = Arc::new(AtomicBool::new(false)); let playing = Arc::clone(&stream_playing); + let asio_streams = self.asio_streams.clone(); // Set the input callback. // This is most performance critical part of the ASIO bindings. @@ -96,6 +97,13 @@ impl Device { return } + // There is 0% chance of lock contention the host only locks when recreating streams. + let stream_lock = asio_streams.lock(); + let ref asio_stream = match stream_lock.input { + Some(ref asio_stream) => asio_stream, + None => return, + }; + /// 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( @@ -134,7 +142,7 @@ impl Device { process_input_callback::( &mut data_callback, &mut interleaved, - &asio_stream, + asio_stream, buffer_index as usize, from_le, std::convert::identity::, @@ -144,7 +152,7 @@ impl Device { process_input_callback::( &mut data_callback, &mut interleaved, - &asio_stream, + asio_stream, buffer_index as usize, from_be, std::convert::identity::, @@ -158,7 +166,7 @@ impl Device { process_input_callback::( &mut data_callback, &mut interleaved, - &asio_stream, + asio_stream, buffer_index as usize, std::convert::identity::, std::convert::identity::, @@ -172,7 +180,7 @@ impl Device { process_input_callback::( &mut data_callback, &mut interleaved, - &asio_stream, + asio_stream, buffer_index as usize, from_le, |s| (s >> 16) as i16, @@ -182,7 +190,7 @@ impl Device { process_input_callback::( &mut data_callback, &mut interleaved, - &asio_stream, + asio_stream, buffer_index as usize, from_be, |s| (s >> 16) as i16, @@ -195,7 +203,7 @@ impl Device { process_input_callback::( &mut data_callback, &mut interleaved, - &asio_stream, + asio_stream, buffer_index as usize, std::convert::identity::, |s| s as f32, @@ -235,8 +243,8 @@ impl Device { } let num_channels = format.channels.clone(); - let asio_stream = self.get_or_create_output_stream(format)?; - let cpal_num_samples = asio_stream.buffer_size as usize * num_channels as usize; + let buffer_size = self.get_or_create_output_stream(format)?; + let cpal_num_samples = buffer_size * num_channels as usize; // Create buffers depending on data type. let len_bytes = cpal_num_samples * data_type.sample_size(); @@ -245,6 +253,7 @@ impl Device { let stream_playing = Arc::new(AtomicBool::new(false)); let playing = Arc::clone(&stream_playing); + let asio_streams = self.asio_streams.clone(); self.driver.set_callback(move |buffer_index| unsafe { // If not playing, return early. @@ -252,6 +261,13 @@ impl Device { return } + // There is 0% chance of lock contention the host only locks when recreating streams. + let stream_lock = asio_streams.lock(); + let ref asio_stream = match stream_lock.output { + Some(ref asio_stream) => asio_stream, + None => return, + }; + // Silence the ASIO buffer that is about to be used. // // This checks if any other callbacks have already silenced the buffer associated with @@ -325,7 +341,7 @@ impl Device { &mut data_callback, &mut interleaved, silence, - &asio_stream, + asio_stream, buffer_index as usize, std::convert::identity::, to_le, @@ -336,7 +352,7 @@ impl Device { &mut data_callback, &mut interleaved, silence, - &asio_stream, + asio_stream, buffer_index as usize, std::convert::identity::, to_be, @@ -351,7 +367,7 @@ impl Device { &mut data_callback, &mut interleaved, silence, - &asio_stream, + asio_stream, buffer_index as usize, std::convert::identity::, std::convert::identity::, @@ -366,7 +382,7 @@ impl Device { &mut data_callback, &mut interleaved, silence, - &asio_stream, + asio_stream, buffer_index as usize, |s| (s as i32) << 16, to_le, @@ -377,7 +393,7 @@ impl Device { &mut data_callback, &mut interleaved, silence, - &asio_stream, + asio_stream, buffer_index as usize, |s| (s as i32) << 16, to_be, @@ -391,7 +407,7 @@ impl Device { &mut data_callback, &mut interleaved, silence, - &asio_stream, + asio_stream, buffer_index as usize, |s| s as f64, std::convert::identity::, @@ -419,7 +435,7 @@ impl Device { fn get_or_create_input_stream( &self, format: &Format, - ) -> Result { + ) -> Result { match self.default_input_format() { Ok(f) => { let num_asio_channels = f.channels; @@ -428,27 +444,26 @@ impl Device { Err(_) => Err(BuildStreamError::FormatNotSupported), }?; let num_channels = format.channels as usize; - let ref mut streams = *self.asio_streams.lock().unwrap(); - match streams { - Some(streams) => match streams.input.take() { - Some(input) => Ok(input), - None => { - println!("ASIO streams have been already created"); - Err(BuildStreamError::DeviceNotAvailable) - } - }, + let ref mut streams = *self.asio_streams.lock(); + // Either create a stream if thers none or had back the + // size of the current one. + match streams.input { + Some(ref input) => Ok(input.buffer_size as usize), None => { - match self.driver.prepare_input_stream(None, num_channels) { - Ok(mut new_streams) => { - let input = new_streams.input.take().expect("missing input stream"); - *streams = Some(new_streams); - Ok(input) - } - Err(e) => { + let output = streams.output.take(); + self.driver + .prepare_input_stream(output, num_channels) + .map(|new_streams| { + let bs = match new_streams.input { + Some(ref inp) => inp.buffer_size as usize, + None => unreachable!(), + }; + *streams = new_streams; + bs + }).map_err(|ref e| { println!("Error preparing stream: {}", e); - Err(BuildStreamError::DeviceNotAvailable) - } - } + BuildStreamError::DeviceNotAvailable + }) } } } @@ -459,7 +474,7 @@ impl Device { fn get_or_create_output_stream( &self, format: &Format, - ) -> Result { + ) -> Result { match self.default_output_format() { Ok(f) => { let num_asio_channels = f.channels; @@ -468,27 +483,26 @@ impl Device { Err(_) => Err(BuildStreamError::FormatNotSupported), }?; let num_channels = format.channels as usize; - let ref mut streams = *self.asio_streams.lock().unwrap(); - match streams { - Some(streams) => match streams.output.take() { - Some(output) => Ok(output), - None => { - println!("ASIO streams have been already created"); - Err(BuildStreamError::DeviceNotAvailable) - } - }, + let ref mut streams = *self.asio_streams.lock(); + // Either create a stream if thers none or had back the + // size of the current one. + match streams.output { + Some(ref output) => Ok(output.buffer_size as usize), None => { - match self.driver.prepare_output_stream(None, num_channels) { - Ok(mut new_streams) => { - let output = new_streams.output.take().expect("missing output stream"); - *streams = Some(new_streams); - Ok(output) - } - Err(e) => { + let output = streams.output.take(); + self.driver + .prepare_output_stream(output, num_channels) + .map(|new_streams| { + let bs = match new_streams.output { + Some(ref out) => out.buffer_size as usize, + None => unreachable!(), + }; + *streams = new_streams; + bs + }).map_err(|ref e| { println!("Error preparing stream: {}", e); - Err(BuildStreamError::DeviceNotAvailable) - } - } + BuildStreamError::DeviceNotAvailable + }) } } } From 39ade4934767e7ffe6abaff8863dddf013d044e7 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Sun, 15 Dec 2019 19:50:09 +0100 Subject: [PATCH 37/41] RAII cleanup wthin the ASIO backend - Shares the `Device`'s `driver` and `asio_streams` `Arc`s with the `Stream`s to ensure they remain valid if the `Host` or `Device` are dropped early. - Ensures that a stream's callback is removed upon `Drop`. --- asio-sys/src/bindings/mod.rs | 29 +++++++++++++++++++++------- src/host/asio/stream.rs | 37 ++++++++++++++++++++++++++++++------ 2 files changed, 53 insertions(+), 13 deletions(-) diff --git a/asio-sys/src/bindings/mod.rs b/asio-sys/src/bindings/mod.rs index 6896b60..eee5c8b 100644 --- a/asio-sys/src/bindings/mod.rs +++ b/asio-sys/src/bindings/mod.rs @@ -235,6 +235,9 @@ struct BufferSizes { grans: c_long, } +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct CallbackId(usize); + lazy_static! { /// A global way to access all the callbacks. /// @@ -244,7 +247,7 @@ lazy_static! { /// Options are used so that when a callback is removed we don't change the Vec indices. /// /// The indices are how we match a callback with a stream. - static ref BUFFER_CALLBACK: Mutex>> = Mutex::new(Vec::new()); + static ref BUFFER_CALLBACK: Mutex> = Mutex::new(Vec::new()); } impl Asio { @@ -589,12 +592,26 @@ impl Driver { /// Adds a callback to the list of active callbacks. /// /// The given function receives the index of the buffer currently ready for processing. - pub fn set_callback(&self, callback: F) + /// + /// Returns an ID uniquely associated with the given callback so that it may be removed later. + pub fn add_callback(&self, callback: F) -> CallbackId where F: 'static + FnMut(i32) + Send, { let mut bc = BUFFER_CALLBACK.lock().unwrap(); - bc.push(Some(BufferCallback(Box::new(callback)))); + let id = bc + .last() + .map(|&(id, _)| CallbackId(id.0.checked_add(1).expect("stream ID overflowed"))) + .unwrap_or(CallbackId(0)); + let cb = BufferCallback(Box::new(callback)); + bc.push((id, cb)); + id + } + + /// Remove the callback with the given ID. + pub fn remove_callback(&self, rem_id: CallbackId) { + let mut bc = BUFFER_CALLBACK.lock().unwrap(); + bc.retain(|&(id, _)| id != rem_id); } /// Consumes and destroys the `Driver`, stopping the streams if they are running and releasing @@ -863,10 +880,8 @@ extern "C" fn buffer_switch_time_info( ) -> *mut ai::ASIOTime { // This lock is probably unavoidable, but locks in the audio stream are not great. let mut bcs = BUFFER_CALLBACK.lock().unwrap(); - for mut bc in bcs.iter_mut() { - if let Some(ref mut bc) = bc { - bc.run(double_buffer_index); - } + for &mut (_, ref mut bc) in bcs.iter_mut() { + bc.run(double_buffer_index); } time } diff --git a/src/host/asio/stream.rs b/src/host/asio/stream.rs index 95025b2..fb18e70 100644 --- a/src/host/asio/stream.rs +++ b/src/host/asio/stream.rs @@ -6,6 +6,7 @@ use super::Device; use std; use std::sync::atomic::{Ordering, AtomicBool}; use std::sync::Arc; +use super::parking_lot::Mutex; use BackendSpecificError; use BuildStreamError; use Format; @@ -41,6 +42,10 @@ struct SilenceAsioBuffer { pub struct Stream { playing: Arc, + // Ensure the `Driver` does not terminate until the last stream is dropped. + driver: Arc, + asio_streams: Arc>, + callback_id: sys::CallbackId, } impl Stream { @@ -55,8 +60,6 @@ impl Stream { } } -// TODO: drop implementation - impl Device { pub fn build_input_stream( &self, @@ -91,7 +94,7 @@ impl Device { // Set the input callback. // This is most performance critical part of the ASIO bindings. - self.driver.set_callback(move |buffer_index| unsafe { + let callback_id = self.driver.add_callback(move |buffer_index| unsafe { // If not playing return early. if !playing.load(Ordering::SeqCst) { return @@ -217,10 +220,18 @@ impl Device { } }); + let driver = self.driver.clone(); + let asio_streams = self.asio_streams.clone(); + // Immediately start the device? self.driver.start().map_err(build_stream_err)?; - Ok(Stream { playing: stream_playing }) + Ok(Stream { + playing: stream_playing, + driver, + asio_streams, + callback_id, + }) } pub fn build_output_stream( @@ -255,7 +266,7 @@ impl Device { let playing = Arc::clone(&stream_playing); let asio_streams = self.asio_streams.clone(); - self.driver.set_callback(move |buffer_index| unsafe { + let callback_id = self.driver.add_callback(move |buffer_index| unsafe { // If not playing, return early. if !playing.load(Ordering::SeqCst) { return @@ -421,10 +432,18 @@ impl Device { } }); + let driver = self.driver.clone(); + let asio_streams = self.asio_streams.clone(); + // Immediately start the device? self.driver.start().map_err(build_stream_err)?; - Ok(Stream { playing: stream_playing }) + Ok(Stream { + playing: stream_playing, + driver, + asio_streams, + callback_id, + }) } /// Create a new CPAL Input Stream. @@ -508,6 +527,12 @@ impl Device { } } +impl Drop for Stream { + fn drop(&mut self) { + self.driver.remove_callback(self.callback_id); + } +} + impl Silence for i16 { const SILENCE: Self = 0; } From 2afebb28065582c41470cdeede018cd5151273f6 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Tue, 31 Dec 2019 16:02:08 +0100 Subject: [PATCH 38/41] Clarify the current state of stream behaviour in docs and comments --- src/host/wasapi/stream.rs | 2 ++ src/lib.rs | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/host/wasapi/stream.rs b/src/host/wasapi/stream.rs index b072292..e020846 100644 --- a/src/host/wasapi/stream.rs +++ b/src/host/wasapi/stream.rs @@ -27,6 +27,8 @@ use UnknownTypeOutputBuffer; pub struct Stream { /// The high-priority audio processing thread calling callbacks. /// Option used for moving out in destructor. + /// + /// TODO: Actually set the thread priority. thread: Option>, // Commands processed by the `run()` method that is currently running. diff --git a/src/lib.rs b/src/lib.rs index 0f6ce08..99227f9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -80,8 +80,9 @@ //! > given callback is called by a dedicated, high-priority thread responsible for delivering //! > audio data to the system's audio device in a timely manner. On older platforms that only //! > provide a blocking API (e.g. ALSA), CPAL will create a thread in order to consistently -//! > provide non-blocking behaviour. *If this is an issue for your platform or design, please -//! > share your issue and use-case with the CPAL team on the github issue tracker for +//! > provide non-blocking behaviour (currently this is a thread per stream, but this may change to +//! > use a single thread for all streams). *If this is an issue for your platform or design, +//! > 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. From 33ddf749548d87bf54ce18eb342f954cec1465b2 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Sun, 5 Jan 2020 17:43:14 +0100 Subject: [PATCH 39/41] Explicitly make dynamically dispatched API !Send + !Sync This is in order to ensure consistent restrictions across platforms in a manner that ensures thread safety across each of the supported platforms. Please see added comments in the diff for details on which platforms impose these restrictions. --- src/platform/mod.rs | 89 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 75 insertions(+), 14 deletions(-) diff --git a/src/platform/mod.rs b/src/platform/mod.rs index d28e58e..e6a927d 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -48,11 +48,18 @@ macro_rules! impl_platform_host { /// /// This type may be constructed via the **host_from_id** function. **HostId**s may /// be acquired via the **ALL_HOSTS** const and the **available_hosts** function. - pub struct Host(HostInner); + // `Host` and `Device` cannot assume to be `Sync` as we have not yet been able to confirm + // whether or not the ASIO API is thread-safe. + // + // TODO: Try to contact ASIO to get more information. Review the existing implementation of + // the `asio` backend's `Host` and `Driver` types and see if the existing `Arc`s and + // `Mutex`s don't already make the API `Sync`. + pub struct Host(HostInner, crate::platform::NotSyncAcrossAllPlatforms); /// The **Device** implementation associated with the platform's dynamically dispatched /// **Host** type. - pub struct Device(DeviceInner); + // See comment above `Host` for reasoning behind `NotSyncAcrossAllPlatforms`. + pub struct Device(DeviceInner, crate::platform::NotSyncAcrossAllPlatforms); /// The **Devices** iterator associated with the platform's dynamically dispatched **Host** /// type. @@ -60,7 +67,12 @@ macro_rules! impl_platform_host { /// The **Stream** implementation associated with the platform's dynamically dispatched /// **Host** type. - pub struct Stream(StreamInner); + // Streams cannot be `Send` or `Sync` if we plan to support Android's AAudio API. This is + // because the stream API is not thread-safe, and the API prohibits calling certain + // functions within the callback. + // + // TODO: Confirm this and add more specific detail and references. + pub struct Stream(StreamInner, crate::platform::NotSendSyncAcrossAllPlatforms); /// The **SupportedInputFormats** iterator associated with the platform's dynamically /// dispatched **Host** type. @@ -142,7 +154,7 @@ macro_rules! impl_platform_host { match self.0 { $( DevicesInner::$HostVariant(ref mut d) => { - d.next().map(DeviceInner::$HostVariant).map(Device) + d.next().map(DeviceInner::$HostVariant).map(Device::from) } )* } @@ -256,7 +268,7 @@ macro_rules! impl_platform_host { $( DeviceInner::$HostVariant(ref d) => d.build_input_stream(format, data_callback, error_callback) .map(StreamInner::$HostVariant) - .map(Stream), + .map(Stream::from), )* } } @@ -267,7 +279,7 @@ macro_rules! impl_platform_host { $( DeviceInner::$HostVariant(ref d) => d.build_output_stream(format, data_callback, error_callback) .map(StreamInner::$HostVariant) - .map(Stream), + .map(Stream::from), )* } } @@ -285,7 +297,7 @@ macro_rules! impl_platform_host { match self.0 { $( HostInner::$HostVariant(ref h) => { - h.devices().map(DevicesInner::$HostVariant).map(Devices) + h.devices().map(DevicesInner::$HostVariant).map(Devices::from) } )* } @@ -295,7 +307,7 @@ macro_rules! impl_platform_host { match self.0 { $( HostInner::$HostVariant(ref h) => { - h.default_input_device().map(DeviceInner::$HostVariant).map(Device) + h.default_input_device().map(DeviceInner::$HostVariant).map(Device::from) } )* } @@ -305,7 +317,7 @@ macro_rules! impl_platform_host { match self.0 { $( HostInner::$HostVariant(ref h) => { - h.default_output_device().map(DeviceInner::$HostVariant).map(Device) + h.default_output_device().map(DeviceInner::$HostVariant).map(Device::from) } )* } @@ -334,28 +346,52 @@ macro_rules! impl_platform_host { } } + impl From for Device { + fn from(d: DeviceInner) -> Self { + Device(d, Default::default()) + } + } + + impl From for Devices { + fn from(d: DevicesInner) -> Self { + Devices(d) + } + } + + impl From for Host { + fn from(h: HostInner) -> Self { + Host(h, Default::default()) + } + } + + impl From for Stream { + fn from(s: StreamInner) -> Self { + Stream(s, Default::default()) + } + } + $( impl From for Device { fn from(h: crate::host::$host_mod::Device) -> Self { - Device(DeviceInner::$HostVariant(h)) + DeviceInner::$HostVariant(h).into() } } impl From for Devices { fn from(h: crate::host::$host_mod::Devices) -> Self { - Devices(DevicesInner::$HostVariant(h)) + DevicesInner::$HostVariant(h).into() } } impl From for Host { fn from(h: crate::host::$host_mod::Host) -> Self { - Host(HostInner::$HostVariant(h)) + HostInner::$HostVariant(h).into() } } impl From for Stream { fn from(h: crate::host::$host_mod::Stream) -> Self { - Stream(StreamInner::$HostVariant(h)) + StreamInner::$HostVariant(h).into() } } )* @@ -378,7 +414,7 @@ macro_rules! impl_platform_host { HostId::$HostVariant => { crate::host::$host_mod::Host::new() .map(HostInner::$HostVariant) - .map(Host) + .map(Host::from) } )* } @@ -507,3 +543,28 @@ mod platform_impl { .into() } } + +// The following zero-sized types are for applying Send/Sync restrictions to ensure +// consistent behaviour across different platforms. These verbosely named types are used +// (rather than using the markers directly) in the hope of making the compile errors +// slightly more helpful. +// +// TODO: Remove these in favour of using negative trait bounds if they stabilise. + +// A marker used to remove the `Send` and `Sync` traits. +struct NotSendSyncAcrossAllPlatforms(std::marker::PhantomData<*mut ()>); + +// A marker used to remove the `Sync` traits. +struct NotSyncAcrossAllPlatforms(std::marker::PhantomData>); + +impl Default for NotSyncAcrossAllPlatforms { + fn default() -> Self { + NotSyncAcrossAllPlatforms(std::marker::PhantomData) + } +} + +impl Default for NotSendSyncAcrossAllPlatforms { + fn default() -> Self { + NotSendSyncAcrossAllPlatforms(std::marker::PhantomData) + } +} From ca2aceb5369aa3a3d4ffa2c0b4857842c34a66ca Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Sun, 12 Jan 2020 19:30:04 +0100 Subject: [PATCH 40/41] Fix state transition synchronisation in ASIO This makes some tweaks to the ASIO backend in order to fix some cases where races may have occured. This should allow us to remove the `Sync` bound on the `Device` and `Host` types. --- asio-sys/src/bindings/mod.rs | 89 +++++++++++++++++++++--------------- 1 file changed, 53 insertions(+), 36 deletions(-) diff --git a/asio-sys/src/bindings/mod.rs b/asio-sys/src/bindings/mod.rs index eee5c8b..0d38801 100644 --- a/asio-sys/src/bindings/mod.rs +++ b/asio-sys/src/bindings/mod.rs @@ -7,7 +7,7 @@ use self::errors::{AsioError, AsioErrorWrapper, LoadDriverError}; use std::ffi::CStr; use std::ffi::CString; use std::os::raw::{c_char, c_double, c_long, c_void}; -use std::sync::{Arc, Mutex, Weak}; +use std::sync::{Arc, Mutex, MutexGuard, Weak}; // Bindings import use self::asio_import as ai; @@ -85,7 +85,7 @@ pub struct SampleRate { } /// Holds the pointer to the callbacks that come from cpal -struct BufferCallback(Box); +struct BufferCallback(Box); /// Input and Output streams. /// @@ -422,6 +422,8 @@ impl Driver { // To pass as ai::ASIOCallbacks let mut callbacks = create_asio_callbacks(); + let mut state = self.inner.lock_state(); + // Retrieve the available buffer sizes. let buffer_sizes = asio_get_buffer_sizes()?; if buffer_sizes.pref <= 0 { @@ -432,13 +434,12 @@ impl Driver { } // Ensure the driver is in the `Initialized` state. - if let DriverState::Running = self.inner.state() { - self.stop()?; + if let DriverState::Running = *state { + state.stop()?; } - if let DriverState::Prepared = self.inner.state() { - self.dispose_buffers()?; + if let DriverState::Prepared = *state { + state.dispose_buffers()?; } - unsafe { asio_result!(ai::ASIOCreateBuffers( buffer_infos.as_mut_ptr() as *mut _, @@ -447,8 +448,8 @@ impl Driver { &mut callbacks as *mut _ as *mut _, ))?; } + *state = DriverState::Prepared; - self.inner.set_state(DriverState::Prepared); Ok(buffer_sizes.pref) } @@ -569,13 +570,14 @@ impl Driver { /// /// No-op if already `Running`. pub fn start(&self) -> Result<(), AsioError> { - if let DriverState::Running = self.inner.state() { + let mut state = self.inner.lock_state(); + if let DriverState::Running = *state { return Ok(()); } unsafe { asio_result!(ai::ASIOStart())?; } - self.inner.set_state(DriverState::Running); + *state = DriverState::Running; Ok(()) } @@ -635,55 +637,70 @@ impl Driver { } } -impl DriverInner { - fn state(&self) -> DriverState { - *self.state.lock().expect("failed to lock `DriverState`") - } - - fn set_state(&self, state: DriverState) { - *self.state.lock().expect("failed to lock `DriverState`") = state; - } - - fn stop_inner(&self) -> Result<(), AsioError> { - if let DriverState::Running = self.state() { +impl DriverState { + fn stop(&mut self) -> Result<(), AsioError> { + if let DriverState::Running = *self { unsafe { asio_result!(ai::ASIOStop())?; } - self.set_state(DriverState::Prepared); + *self = DriverState::Prepared; } Ok(()) } - fn dispose_buffers_inner(&self) -> Result<(), AsioError> { - if let DriverState::Initialized = self.state() { + fn dispose_buffers(&mut self) -> Result<(), AsioError> { + if let DriverState::Initialized = *self { return Ok(()); } - if let DriverState::Running = self.state() { - self.stop_inner()?; + if let DriverState::Running = *self { + self.stop()?; } unsafe { asio_result!(ai::ASIODisposeBuffers())?; } - self.set_state(DriverState::Initialized); + *self = DriverState::Initialized; Ok(()) } - fn destroy_inner(&mut self) -> Result<(), AsioError> { - // Drop back through the driver state machine one state at a time. - if let DriverState::Running = self.state() { - self.stop_inner()?; + fn destroy(&mut self) -> Result<(), AsioError> { + if let DriverState::Running = *self { + self.stop()?; } - if let DriverState::Prepared = self.state() { - self.dispose_buffers_inner()?; + if let DriverState::Prepared = *self { + self.dispose_buffers()?; } unsafe { asio_result!(ai::ASIOExit())?; ai::remove_current_driver(); } + Ok(()) + } +} - // Clear any existing stream callbacks. - if let Ok(mut bcs) = BUFFER_CALLBACK.lock() { - bcs.clear(); +impl DriverInner { + fn lock_state(&self) -> MutexGuard { + self.state.lock().expect("failed to lock `DriverState`") + } + + fn stop_inner(&self) -> Result<(), AsioError> { + let mut state = self.lock_state(); + state.stop() + } + + fn dispose_buffers_inner(&self) -> Result<(), AsioError> { + let mut state = self.lock_state(); + state.dispose_buffers() + } + + fn destroy_inner(&mut self) -> Result<(), AsioError> { + { + let mut state = self.lock_state(); + state.destroy()?; + + // Clear any existing stream callbacks. + if let Ok(mut bcs) = BUFFER_CALLBACK.lock() { + bcs.clear(); + } } // Signal that the driver has been destroyed. From 32d39bcfd34681215e97077f302d999daeb373ce Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Sun, 12 Jan 2020 22:43:05 +0100 Subject: [PATCH 41/41] Relax `Sync` restriction on `Device` and `Host` Originally this restriction was placed due to uncertainty around the thread safety of the ASIO API. While the ASIO API itself makes no thread-safety guarantees that we are aware of, the `asio-sys` high-level bindings enforce synchronised access to the API and state transitions via a mutex. --- src/platform/mod.rs | 24 ++++-------------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/src/platform/mod.rs b/src/platform/mod.rs index e6a927d..6447f15 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -48,18 +48,11 @@ macro_rules! impl_platform_host { /// /// This type may be constructed via the **host_from_id** function. **HostId**s may /// be acquired via the **ALL_HOSTS** const and the **available_hosts** function. - // `Host` and `Device` cannot assume to be `Sync` as we have not yet been able to confirm - // whether or not the ASIO API is thread-safe. - // - // TODO: Try to contact ASIO to get more information. Review the existing implementation of - // the `asio` backend's `Host` and `Driver` types and see if the existing `Arc`s and - // `Mutex`s don't already make the API `Sync`. - pub struct Host(HostInner, crate::platform::NotSyncAcrossAllPlatforms); + pub struct Host(HostInner); /// The **Device** implementation associated with the platform's dynamically dispatched /// **Host** type. - // See comment above `Host` for reasoning behind `NotSyncAcrossAllPlatforms`. - pub struct Device(DeviceInner, crate::platform::NotSyncAcrossAllPlatforms); + pub struct Device(DeviceInner); /// The **Devices** iterator associated with the platform's dynamically dispatched **Host** /// type. @@ -348,7 +341,7 @@ macro_rules! impl_platform_host { impl From for Device { fn from(d: DeviceInner) -> Self { - Device(d, Default::default()) + Device(d) } } @@ -360,7 +353,7 @@ macro_rules! impl_platform_host { impl From for Host { fn from(h: HostInner) -> Self { - Host(h, Default::default()) + Host(h) } } @@ -554,15 +547,6 @@ mod platform_impl { // A marker used to remove the `Send` and `Sync` traits. struct NotSendSyncAcrossAllPlatforms(std::marker::PhantomData<*mut ()>); -// A marker used to remove the `Sync` traits. -struct NotSyncAcrossAllPlatforms(std::marker::PhantomData>); - -impl Default for NotSyncAcrossAllPlatforms { - fn default() -> Self { - NotSyncAcrossAllPlatforms(std::marker::PhantomData) - } -} - impl Default for NotSendSyncAcrossAllPlatforms { fn default() -> Self { NotSendSyncAcrossAllPlatforms(std::marker::PhantomData)