From 47f966bf7523b23ab6ca5ac3fb95c200d6752788 Mon Sep 17 00:00:00 2001 From: Pierre Krieger Date: Tue, 1 Sep 2015 11:23:41 +0200 Subject: [PATCH 01/11] Correctly enumerate audio devices (core + wasapi) --- Cargo.toml | 1 + examples/beep.rs | 2 +- src/lib.rs | 47 +++++++- src/wasapi/com.rs | 32 +++++ src/wasapi/enumerate.rs | 126 +++++++++++++++++++ src/wasapi/mod.rs | 259 ++++------------------------------------ src/wasapi/voice.rs | 231 +++++++++++++++++++++++++++++++++++ 7 files changed, 457 insertions(+), 241 deletions(-) create mode 100644 src/wasapi/com.rs create mode 100644 src/wasapi/enumerate.rs create mode 100644 src/wasapi/voice.rs diff --git a/Cargo.toml b/Cargo.toml index 5a0f1e5..c937984 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ keywords = ["audio", "sound"] [dependencies] libc = "*" +lazy_static = "0.1" [target.i686-pc-windows-gnu.dependencies] winapi = "0.2.1" diff --git a/examples/beep.rs b/examples/beep.rs index 149c2c4..764f724 100644 --- a/examples/beep.rs +++ b/examples/beep.rs @@ -1,7 +1,7 @@ extern crate cpal; fn main() { - let mut channel = cpal::Voice::new(); + let mut channel = cpal::Voice::new(&cpal::get_default_endpoint().unwrap()).unwrap(); // Produce a sinusoid of maximum amplitude. let mut data_source = (0u64..).map(|t| t as f32 * 0.03) diff --git a/src/lib.rs b/src/lib.rs index e3e8a90..cab4f22 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,8 +28,12 @@ reaches the end of the data, it will stop playing. You must continuously fill th calling `append_data` repeatedly if you don't want the audio to stop playing. */ +#[macro_use] +extern crate lazy_static; + pub use samples_formats::{SampleFormat, Sample}; +use std::error::Error; use std::ops::{Deref, DerefMut}; mod samples_formats; @@ -57,6 +61,43 @@ pub type ChannelsCount = u16; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct SamplesRate(pub u32); +/// Describes a format. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct Format { + pub channels: ChannelsCount, + pub samples_rate: SamplesRate, + pub data_type: SampleFormat, +} + +/// An iterator for the list of formats that are supported by the backend. +pub struct EndpointsIterator(cpal_impl::EndpointsIterator); + +impl Iterator for EndpointsIterator { + type Item = Endpoint; + + fn next(&mut self) -> Option { + self.0.next().map(Endpoint) + } + + fn size_hint(&self) -> (usize, Option) { + self.0.size_hint() + } +} + +/// Return an iterator to the list of formats that are supported by the system. +pub fn get_endpoints_list() -> EndpointsIterator { + EndpointsIterator(Default::default()) +} + +/// Return the default endpoint, or `None` if no device is available. +pub fn get_default_endpoint() -> Option { + cpal_impl::get_default_endpoint().map(Endpoint) +} + +/// An opaque type that identifies an end point. +#[derive(Clone, PartialEq, Eq)] +pub struct Endpoint(cpal_impl::Endpoint); + /// Represents a buffer that must be filled with audio data. /// /// You should destroy this object as soon as possible. Data is only committed when it @@ -95,9 +136,9 @@ pub struct Voice(cpal_impl::Voice); impl Voice { /// Builds a new channel. - pub fn new() -> Voice { - let channel = cpal_impl::Voice::new(); - Voice(channel) + pub fn new(endpoint: &Endpoint) -> Result> { + let channel = try!(cpal_impl::Voice::new(&endpoint.0)); + Ok(Voice(channel)) } /// Returns the number of channels. diff --git a/src/wasapi/com.rs b/src/wasapi/com.rs new file mode 100644 index 0000000..92a6f31 --- /dev/null +++ b/src/wasapi/com.rs @@ -0,0 +1,32 @@ +//! Handles COM initialization and cleanup. + +use std::ptr; +use super::winapi; +use super::ole32; +use super::check_result; + +thread_local!(static COM_INITIALIZED: ComInitialized = { + unsafe { + // this call can fail if another library initialized COM in single-threaded mode + // handling this situation properly would make the API more annoying, so we just don't care + check_result(ole32::CoInitializeEx(ptr::null_mut(), winapi::COINIT_MULTITHREADED)).unwrap(); + ComInitialized(ptr::null_mut()) + } +}); + +/// RAII object that guards the fact that COM is initialized. +/// +// We store a raw pointer because it's the only way at the moment to remove `Send`/`Sync` from the +// object. +struct ComInitialized(*mut ()); + +impl Drop for ComInitialized { + fn drop(&mut self) { + unsafe { ole32::CoUninitialize() }; + } +} + +/// Ensures that COM is initialized in this thread. +pub fn com_initialized() { + COM_INITIALIZED.with(|_| {}); +} diff --git a/src/wasapi/enumerate.rs b/src/wasapi/enumerate.rs new file mode 100644 index 0000000..fbacbc6 --- /dev/null +++ b/src/wasapi/enumerate.rs @@ -0,0 +1,126 @@ +use super::winapi; +use super::ole32; +use super::com; +use super::Endpoint; +use super::check_result; + +use std::mem; +use std::ptr; + +lazy_static! { + static ref ENUMERATOR: Enumerator = { + // COM initialization is thread local, but we only need to have COM initialized in the + // thread we create the objects in + com::com_initialized(); + + // building the devices enumerator object + unsafe { + let mut enumerator: *mut winapi::IMMDeviceEnumerator = mem::uninitialized(); + + let hresult = ole32::CoCreateInstance(&winapi::CLSID_MMDeviceEnumerator, + ptr::null_mut(), winapi::CLSCTX_ALL, + &winapi::IID_IMMDeviceEnumerator, + &mut enumerator + as *mut *mut winapi::IMMDeviceEnumerator + as *mut _); + + check_result(hresult).unwrap(); + Enumerator(enumerator) + } + }; +} + +/// RAII object around `winapi::IMMDeviceEnumerator`. +struct Enumerator(*mut winapi::IMMDeviceEnumerator); + +unsafe impl Send for Enumerator {} +unsafe impl Sync for Enumerator {} + +impl Drop for Enumerator { + fn drop(&mut self) { + unsafe { + (*self.0).Release(); + } + } +} + +/// WASAPI implementation for `EndpointsIterator`. +pub struct EndpointsIterator { + collection: *mut winapi::IMMDeviceCollection, + total_count: u32, + next_item: u32, +} + +unsafe impl Send for EndpointsIterator {} +unsafe impl Sync for EndpointsIterator {} + +impl Drop for EndpointsIterator { + fn drop(&mut self) { + unsafe { + (*self.collection).Release(); + } + } +} + +impl Default for EndpointsIterator { + fn default() -> EndpointsIterator { + unsafe { + let mut collection: *mut winapi::IMMDeviceCollection = mem::uninitialized(); + // can fail because of wrong parameters (should never happen) or out of memory + check_result((*ENUMERATOR.0).EnumAudioEndpoints(winapi::EDataFlow::eRender, + winapi::DEVICE_STATE_ACTIVE, + &mut collection)) + .unwrap(); + + let mut count = mem::uninitialized(); + // can fail if the parameter is null, which should never happen + check_result((*collection).GetCount(&mut count)).unwrap(); + + EndpointsIterator { + collection: collection, + total_count: count, + next_item: 0, + } + } + } +} + +impl Iterator for EndpointsIterator { + type Item = Endpoint; + + fn next(&mut self) -> Option { + if self.next_item >= self.total_count { + return None; + } + + unsafe { + let mut device = mem::uninitialized(); + // can fail if out of range, which we just checked above + check_result((*self.collection).Item(self.next_item, &mut device)).unwrap(); + + self.next_item += 1; + Some(Endpoint(device)) + } + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + let num = self.total_count - self.next_item; + let num = num as usize; + (num, Some(num)) + } +} + +pub fn get_default_endpoint() -> Option { + unsafe { + let mut device = mem::uninitialized(); + let hres = (*ENUMERATOR.0).GetDefaultAudioEndpoint(winapi::EDataFlow::eRender, + winapi::ERole::eConsole, &mut device); + + if let Err(_err) = check_result(hres) { + return None; // TODO: check specifically for `E_NOTFOUND`, and panic otherwise + } + + Some(Endpoint(device)) + } +} diff --git a/src/wasapi/mod.rs b/src/wasapi/mod.rs index 28997d6..9c2917d 100644 --- a/src/wasapi/mod.rs +++ b/src/wasapi/mod.rs @@ -2,255 +2,40 @@ extern crate libc; extern crate winapi; extern crate ole32; -use std::{cmp, slice, mem, ptr}; -use std::marker::PhantomData; +use std::io::Error as IoError; -// TODO: determine if should be NoSend or not -pub struct Voice { - audio_client: *mut winapi::IAudioClient, - render_client: *mut winapi::IAudioRenderClient, - max_frames_in_buffer: winapi::UINT32, - num_channels: winapi::WORD, - bytes_per_frame: winapi::WORD, - samples_per_second: winapi::DWORD, - bits_per_sample: winapi::WORD, - playing: bool, -} +pub use self::enumerate::{EndpointsIterator, get_default_endpoint}; +pub use self::voice::{Voice, Buffer}; -pub struct Buffer<'a, T: 'a> { - render_client: *mut winapi::IAudioRenderClient, - buffer_data: *mut T, - buffer_len: usize, - frames: winapi::UINT32, - marker: PhantomData<&'a mut T>, -} +mod com; +mod enumerate; +mod voice; -impl Voice { - pub fn new() -> Voice { - init().unwrap() - } +/// An opaque type that identifies an end point. +#[derive(PartialEq, Eq)] +#[allow(raw_pointer_derive)] +pub struct Endpoint(*mut winapi::IMMDevice); - pub fn get_channels(&self) -> ::ChannelsCount { - self.num_channels as ::ChannelsCount - } +unsafe impl Send for Endpoint {} +unsafe impl Sync for Endpoint {} - pub fn get_samples_rate(&self) -> ::SamplesRate { - ::SamplesRate(self.samples_per_second as u32) - } - - pub fn get_samples_format(&self) -> ::SampleFormat { - match self.bits_per_sample { - 16 => ::SampleFormat::I16, - _ => panic!("{}-bit format not yet supported", self.bits_per_sample), - } - } - - pub fn append_data<'a, T>(&'a mut self, max_elements: usize) -> Buffer<'a, T> { - unsafe { - loop { - // - let frames_available = { - let mut padding = mem::uninitialized(); - let hresult = (*self.audio_client).GetCurrentPadding(&mut padding); - check_result(hresult).unwrap(); - self.max_frames_in_buffer - padding - }; - - if frames_available == 0 { - // TODO: - ::std::thread::sleep_ms(1); - continue; - } - - let frames_available = cmp::min(frames_available, - max_elements as u32 * mem::size_of::() as u32 / - self.bytes_per_frame as u32); - assert!(frames_available != 0); - - // loading buffer - let (buffer_data, buffer_len) = { - let mut buffer: *mut winapi::BYTE = mem::uninitialized(); - let hresult = (*self.render_client).GetBuffer(frames_available, - &mut buffer as *mut *mut libc::c_uchar); - check_result(hresult).unwrap(); - assert!(!buffer.is_null()); - - (buffer as *mut T, - frames_available as usize * self.bytes_per_frame as usize - / mem::size_of::()) - }; - - let buffer = Buffer { - render_client: self.render_client, - buffer_data: buffer_data, - buffer_len: buffer_len, - frames: frames_available, - marker: PhantomData, - }; - - return buffer; - } - } - } - - pub fn play(&mut self) { - if !self.playing { - unsafe { - let hresult = (*self.audio_client).Start(); - check_result(hresult).unwrap(); - } - } - - self.playing = true; - } - - pub fn pause(&mut self) { - if self.playing { - unsafe { - let hresult = (*self.audio_client).Stop(); - check_result(hresult).unwrap(); - } - } - - self.playing = false; +impl Clone for Endpoint { + fn clone(&self) -> Endpoint { + unsafe { (*self.0).AddRef(); } + Endpoint(self.0) } } -unsafe impl Send for Voice {} -unsafe impl Sync for Voice {} - -impl Drop for Voice { +impl Drop for Endpoint { fn drop(&mut self) { - unsafe { - (*self.render_client).Release(); - (*self.audio_client).Release(); - } + unsafe { (*self.0).Release(); } } } -impl<'a, T> Buffer<'a, T> { - pub fn get_buffer<'b>(&'b mut self) -> &'b mut [T] { - unsafe { - slice::from_raw_parts_mut(self.buffer_data, self.buffer_len) - } - } - - pub fn finish(self) { - // releasing buffer - unsafe { - let hresult = (*self.render_client).ReleaseBuffer(self.frames as u32, 0); - check_result(hresult).unwrap(); - }; - } -} - -fn init() -> Result { - // FIXME: release everything - unsafe { - try!(check_result(ole32::CoInitializeEx(ptr::null_mut(), 0))); - - // building the devices enumerator object - let enumerator = { - let mut enumerator: *mut winapi::IMMDeviceEnumerator = mem::uninitialized(); - - let hresult = ole32::CoCreateInstance(&winapi::CLSID_MMDeviceEnumerator, - ptr::null_mut(), winapi::CLSCTX_ALL, - &winapi::IID_IMMDeviceEnumerator, - mem::transmute(&mut enumerator)); - - try!(check_result(hresult)); - &mut *enumerator - }; - - // getting the default end-point - let device = { - let mut device: *mut winapi::IMMDevice = mem::uninitialized(); - let hresult = enumerator.GetDefaultAudioEndpoint(winapi::EDataFlow::eRender, winapi::ERole::eConsole, - mem::transmute(&mut device)); - try!(check_result(hresult)); - &mut *device - }; - - // activating in order to get a `IAudioClient` - let audio_client: &mut winapi::IAudioClient = { - let mut audio_client: *mut winapi::IAudioClient = mem::uninitialized(); - let hresult = device.Activate(&winapi::IID_IAudioClient, winapi::CLSCTX_ALL, - ptr::null_mut(), mem::transmute(&mut audio_client)); - try!(check_result(hresult)); - &mut *audio_client - }; - - // computing the format and initializing the device - let format = { - let format_attempt = winapi::WAVEFORMATEX { - wFormatTag: 1, // WAVE_FORMAT_PCM ; TODO: replace by constant - nChannels: 2, - nSamplesPerSec: 44100, - nAvgBytesPerSec: 2 * 44100 * 2, - nBlockAlign: (2 * 16) / 8, - wBitsPerSample: 16, - cbSize: 0, - }; - - let mut format_ptr: *mut winapi::WAVEFORMATEX = mem::uninitialized(); - let hresult = audio_client.IsFormatSupported(winapi::AUDCLNT_SHAREMODE::AUDCLNT_SHAREMODE_SHARED, - &format_attempt, &mut format_ptr); - try!(check_result(hresult)); - - let format = if format_ptr.is_null() { - &format_attempt - } else { - &*format_ptr - }; - - let format_copy = ptr::read(format); - - let hresult = audio_client.Initialize(winapi::AUDCLNT_SHAREMODE::AUDCLNT_SHAREMODE_SHARED, - 0, 10000000, 0, format, ptr::null()); - - if !format_ptr.is_null() { - ole32::CoTaskMemFree(format_ptr as *mut _); - } - - try!(check_result(hresult)); - - format_copy - }; - - // - let max_frames_in_buffer = { - let mut max_frames_in_buffer = mem::uninitialized(); - let hresult = audio_client.GetBufferSize(&mut max_frames_in_buffer); - try!(check_result(hresult)); - max_frames_in_buffer - }; - - // - let render_client = { - let mut render_client: *mut winapi::IAudioRenderClient = mem::uninitialized(); - let hresult = audio_client.GetService(&winapi::IID_IAudioRenderClient, - mem::transmute(&mut render_client)); - try!(check_result(hresult)); - &mut *render_client - }; - - Ok(Voice { - audio_client: audio_client, - render_client: render_client, - max_frames_in_buffer: max_frames_in_buffer, - num_channels: format.nChannels, - bytes_per_frame: format.nBlockAlign, - samples_per_second: format.nSamplesPerSec, - bits_per_sample: format.wBitsPerSample, - playing: false, - }) - } -} - -fn check_result(result: winapi::HRESULT) -> Result<(), String> { +fn check_result(result: winapi::HRESULT) -> Result<(), IoError> { if result < 0 { - return Err(format!("Error in winapi call")); // TODO: + Err(IoError::from_raw_os_error(result)) + } else { + Ok(()) } - - Ok(()) } diff --git a/src/wasapi/voice.rs b/src/wasapi/voice.rs new file mode 100644 index 0000000..797fb72 --- /dev/null +++ b/src/wasapi/voice.rs @@ -0,0 +1,231 @@ +use super::com; +use super::ole32; +use super::winapi; +use super::Endpoint; +use super::check_result; + +use std::io::Error as IoError; +use std::cmp; +use std::slice; +use std::mem; +use std::ptr; +use std::marker::PhantomData; + +pub struct Voice { + audio_client: *mut winapi::IAudioClient, + render_client: *mut winapi::IAudioRenderClient, + max_frames_in_buffer: winapi::UINT32, + num_channels: winapi::WORD, + bytes_per_frame: winapi::WORD, + samples_per_second: winapi::DWORD, + bits_per_sample: winapi::WORD, + playing: bool, +} + +unsafe impl Send for Voice {} +unsafe impl Sync for Voice {} + +impl Voice { + pub fn new(end_point: &Endpoint) -> Result { + // FIXME: release everything + 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(); + + // activating the end point in order to get a `IAudioClient` + let audio_client: *mut winapi::IAudioClient = { + let mut audio_client = mem::uninitialized(); + let hresult = (*end_point.0).Activate(&winapi::IID_IAudioClient, winapi::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 + try!(check_result(hresult)); + audio_client as *mut _ + }; + + // computing the format and initializing the device + let format = { + let format_attempt = winapi::WAVEFORMATEX { + wFormatTag: 1, // WAVE_FORMAT_PCM ; TODO: replace by constant + nChannels: 2, + nSamplesPerSec: 44100, + nAvgBytesPerSec: 2 * 44100 * 2, + nBlockAlign: (2 * 16) / 8, + wBitsPerSample: 16, + cbSize: 0, + }; + + let mut format_ptr: *mut winapi::WAVEFORMATEX = mem::uninitialized(); + let hresult = (*audio_client).IsFormatSupported(winapi::AUDCLNT_SHAREMODE::AUDCLNT_SHAREMODE_SHARED, + &format_attempt, &mut format_ptr); + try!(check_result(hresult)); + + let format = if format_ptr.is_null() { + &format_attempt + } else { + &*format_ptr + }; + + let format_copy = ptr::read(format); + + let hresult = (*audio_client).Initialize(winapi::AUDCLNT_SHAREMODE::AUDCLNT_SHAREMODE_SHARED, + 0, 10000000, 0, format, ptr::null()); + + if !format_ptr.is_null() { + ole32::CoTaskMemFree(format_ptr as *mut _); + } + + try!(check_result(hresult)); + + format_copy + }; + + // + let max_frames_in_buffer = { + let mut max_frames_in_buffer = mem::uninitialized(); + let hresult = (*audio_client).GetBufferSize(&mut max_frames_in_buffer); + try!(check_result(hresult)); + max_frames_in_buffer + }; + + // + let render_client = { + let mut render_client: *mut winapi::IAudioRenderClient = mem::uninitialized(); + let hresult = (*audio_client).GetService(&winapi::IID_IAudioRenderClient, + mem::transmute(&mut render_client)); + try!(check_result(hresult)); + &mut *render_client + }; + + Ok(Voice { + audio_client: audio_client, + render_client: render_client, + max_frames_in_buffer: max_frames_in_buffer, + num_channels: format.nChannels, + bytes_per_frame: format.nBlockAlign, + samples_per_second: format.nSamplesPerSec, + bits_per_sample: format.wBitsPerSample, + playing: false, + }) + } + } + + pub fn get_channels(&self) -> ::ChannelsCount { + self.num_channels as ::ChannelsCount + } + + pub fn get_samples_rate(&self) -> ::SamplesRate { + ::SamplesRate(self.samples_per_second as u32) + } + + pub fn get_samples_format(&self) -> ::SampleFormat { + match self.bits_per_sample { + 16 => ::SampleFormat::I16, + _ => panic!("{}-bit format not yet supported", self.bits_per_sample), + } + } + + pub fn append_data<'a, T>(&'a mut self, max_elements: usize) -> Buffer<'a, T> { + unsafe { + loop { + // + let frames_available = { + let mut padding = mem::uninitialized(); + let hresult = (*self.audio_client).GetCurrentPadding(&mut padding); + check_result(hresult).unwrap(); + self.max_frames_in_buffer - padding + }; + + if frames_available == 0 { + // TODO: + ::std::thread::sleep_ms(1); + continue; + } + + let frames_available = cmp::min(frames_available, + max_elements as u32 * mem::size_of::() as u32 / + self.bytes_per_frame as u32); + assert!(frames_available != 0); + + // loading buffer + let (buffer_data, buffer_len) = { + let mut buffer: *mut winapi::BYTE = mem::uninitialized(); + let hresult = (*self.render_client).GetBuffer(frames_available, + &mut buffer as *mut *mut _); + check_result(hresult).unwrap(); + assert!(!buffer.is_null()); + + (buffer as *mut T, + frames_available as usize * self.bytes_per_frame as usize + / mem::size_of::()) + }; + + let buffer = Buffer { + render_client: self.render_client, + buffer_data: buffer_data, + buffer_len: buffer_len, + frames: frames_available, + marker: PhantomData, + }; + + return buffer; + } + } + } + + pub fn play(&mut self) { + if !self.playing { + unsafe { + let hresult = (*self.audio_client).Start(); + check_result(hresult).unwrap(); + } + } + + self.playing = true; + } + + pub fn pause(&mut self) { + if self.playing { + unsafe { + let hresult = (*self.audio_client).Stop(); + check_result(hresult).unwrap(); + } + } + + self.playing = false; + } +} + +impl Drop for Voice { + fn drop(&mut self) { + unsafe { + (*self.render_client).Release(); + (*self.audio_client).Release(); + } + } +} + +pub struct Buffer<'a, T: 'a> { + render_client: *mut winapi::IAudioRenderClient, + buffer_data: *mut T, + buffer_len: usize, + frames: winapi::UINT32, + marker: PhantomData<&'a mut T>, +} + +impl<'a, T> Buffer<'a, T> { + pub fn get_buffer<'b>(&'b mut self) -> &'b mut [T] { + unsafe { + slice::from_raw_parts_mut(self.buffer_data, self.buffer_len) + } + } + + pub fn finish(self) { + // releasing buffer + unsafe { + let hresult = (*self.render_client).ReleaseBuffer(self.frames as u32, 0); + check_result(hresult).unwrap(); + }; + } +} From 1985c346acb1fa133abd8ee6ddff5cda10041d9b Mon Sep 17 00:00:00 2001 From: Pierre Krieger Date: Tue, 1 Sep 2015 13:53:54 +0200 Subject: [PATCH 02/11] Add supported formats enumeration --- examples/beep.rs | 4 +- src/lib.rs | 69 ++++++++++++++----- src/wasapi/enumerate.rs | 4 +- src/wasapi/mod.rs | 144 ++++++++++++++++++++++++++++++++++------ src/wasapi/voice.rs | 8 ++- 5 files changed, 185 insertions(+), 44 deletions(-) diff --git a/examples/beep.rs b/examples/beep.rs index 764f724..5179b2a 100644 --- a/examples/beep.rs +++ b/examples/beep.rs @@ -1,7 +1,9 @@ extern crate cpal; fn main() { - let mut channel = cpal::Voice::new(&cpal::get_default_endpoint().unwrap()).unwrap(); + let endpoint = cpal::get_default_endpoint().unwrap(); + let format = endpoint.get_supported_formats_list().next().unwrap(); + let mut channel = cpal::Voice::new(&endpoint, &format).unwrap(); // Produce a sinusoid of maximum amplitude. let mut data_source = (0u64..).map(|t| t as f32 * 0.03) diff --git a/src/lib.rs b/src/lib.rs index cab4f22..2e680b3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -54,31 +54,18 @@ mod cpal_impl; #[path="null/mod.rs"] mod cpal_impl; -/// Number of channels. -pub type ChannelsCount = u16; - -/// -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub struct SamplesRate(pub u32); - -/// Describes a format. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct Format { - pub channels: ChannelsCount, - pub samples_rate: SamplesRate, - pub data_type: SampleFormat, -} - /// An iterator for the list of formats that are supported by the backend. pub struct EndpointsIterator(cpal_impl::EndpointsIterator); impl Iterator for EndpointsIterator { type Item = Endpoint; + #[inline] fn next(&mut self) -> Option { self.0.next().map(Endpoint) } + #[inline] fn size_hint(&self) -> (usize, Option) { self.0.size_hint() } @@ -98,6 +85,45 @@ pub fn get_default_endpoint() -> Option { #[derive(Clone, PartialEq, Eq)] pub struct Endpoint(cpal_impl::Endpoint); +impl Endpoint { + /// Returns an iterator that produces the list of formats that are supported by the backend. + pub fn get_supported_formats_list(&self) -> SupportedFormatsIterator { + SupportedFormatsIterator(self.0.get_supported_formats_list()) + } +} + +/// Number of channels. +pub type ChannelsCount = u16; + +/// +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct SamplesRate(pub u32); + +/// Describes a format. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct Format { + pub channels: ChannelsCount, + pub samples_rate: SamplesRate, + pub data_type: SampleFormat, +} + +/// An iterator that produces a list of formats supported by the endpoint. +pub struct SupportedFormatsIterator(cpal_impl::SupportedFormatsIterator); + +impl Iterator for SupportedFormatsIterator { + type Item = Format; + + #[inline] + fn next(&mut self) -> Option { + self.0.next() + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.0.size_hint() + } +} + /// Represents a buffer that must be filled with audio data. /// /// You should destroy this object as soon as possible. Data is only committed when it @@ -136,8 +162,9 @@ pub struct Voice(cpal_impl::Voice); impl Voice { /// Builds a new channel. - pub fn new(endpoint: &Endpoint) -> Result> { - let channel = try!(cpal_impl::Voice::new(&endpoint.0)); + #[inline] + pub fn new(endpoint: &Endpoint, format: &Format) -> Result> { + let channel = try!(cpal_impl::Voice::new(&endpoint.0, format)); Ok(Voice(channel)) } @@ -145,6 +172,7 @@ impl Voice { /// /// You can add data with any number of channels, but matching the voice's native format /// will lead to better performances. + #[inline] pub fn get_channels(&self) -> ChannelsCount { self.0.get_channels() } @@ -153,6 +181,7 @@ impl Voice { /// /// You can add data with any samples rate, but matching the voice's native format /// will lead to better performances. + #[inline] pub fn get_samples_rate(&self) -> SamplesRate { self.0.get_samples_rate() } @@ -161,6 +190,7 @@ impl Voice { /// /// You can add data of any format, but matching the voice's native format /// will lead to better performances. + #[inline] pub fn get_samples_format(&self) -> SampleFormat { self.0.get_samples_format() } @@ -205,6 +235,7 @@ impl Voice { /// /// Only call this after you have submitted some data, otherwise you may hear /// some glitches. + #[inline] pub fn play(&mut self) { self.0.play() } @@ -214,6 +245,7 @@ impl Voice { /// Has no effect is the voice was already paused. /// /// If you call `play` afterwards, the playback will resume exactly where it was. + #[inline] pub fn pause(&mut self) { self.0.pause() } @@ -222,18 +254,21 @@ impl Voice { impl<'a, T> Deref for Buffer<'a, T> where T: Sample { type Target = [T]; + #[inline] fn deref(&self) -> &[T] { panic!("It is forbidden to read from the audio buffer"); } } impl<'a, T> DerefMut for Buffer<'a, T> where T: Sample { + #[inline] fn deref_mut(&mut self) -> &mut [T] { self.target.as_mut().unwrap().get_buffer() } } impl<'a, T> Drop for Buffer<'a, T> where T: Sample { + #[inline] fn drop(&mut self) { self.target.take().unwrap().finish(); } diff --git a/src/wasapi/enumerate.rs b/src/wasapi/enumerate.rs index fbacbc6..0c5e966 100644 --- a/src/wasapi/enumerate.rs +++ b/src/wasapi/enumerate.rs @@ -99,7 +99,7 @@ impl Iterator for EndpointsIterator { check_result((*self.collection).Item(self.next_item, &mut device)).unwrap(); self.next_item += 1; - Some(Endpoint(device)) + Some(Endpoint::from_immdevice(device)) } } @@ -121,6 +121,6 @@ pub fn get_default_endpoint() -> Option { return None; // TODO: check specifically for `E_NOTFOUND`, and panic otherwise } - Some(Endpoint(device)) + Some(Endpoint::from_immdevice(device)) } } diff --git a/src/wasapi/mod.rs b/src/wasapi/mod.rs index 9c2917d..b40184c 100644 --- a/src/wasapi/mod.rs +++ b/src/wasapi/mod.rs @@ -3,35 +3,24 @@ extern crate winapi; extern crate ole32; use std::io::Error as IoError; +use std::sync::{Arc, Mutex, MutexGuard}; +use std::ptr; +use std::mem; +use Format; +use SamplesRate; +use SampleFormat; + +pub use std::option::IntoIter as OptionIntoIter; pub use self::enumerate::{EndpointsIterator, get_default_endpoint}; pub use self::voice::{Voice, Buffer}; +pub type SupportedFormatsIterator = OptionIntoIter; + mod com; mod enumerate; mod voice; -/// An opaque type that identifies an end point. -#[derive(PartialEq, Eq)] -#[allow(raw_pointer_derive)] -pub struct Endpoint(*mut winapi::IMMDevice); - -unsafe impl Send for Endpoint {} -unsafe impl Sync for Endpoint {} - -impl Clone for Endpoint { - fn clone(&self) -> Endpoint { - unsafe { (*self.0).AddRef(); } - Endpoint(self.0) - } -} - -impl Drop for Endpoint { - fn drop(&mut self) { - unsafe { (*self.0).Release(); } - } -} - fn check_result(result: winapi::HRESULT) -> Result<(), IoError> { if result < 0 { Err(IoError::from_raw_os_error(result)) @@ -39,3 +28,116 @@ fn check_result(result: winapi::HRESULT) -> Result<(), IoError> { Ok(()) } } + +/// Wrapper because of that stupid decision to remove `Send` and `Sync` from raw pointers. +#[derive(Copy, Clone)] +#[allow(raw_pointer_derive)] +struct IAudioClientWrapper(*mut winapi::IAudioClient); +unsafe impl Send for IAudioClientWrapper {} +unsafe impl Sync for IAudioClientWrapper {} + +/// An opaque type that identifies an end point. +pub struct Endpoint { + device: *mut winapi::IMMDevice, + + /// We cache an uninitialized `IAudioClient` so that we can call functions from it without + /// having to create/destroy audio clients all the time. + future_audio_client: Arc>>, // TODO: add NonZero around the ptr +} + +unsafe impl Send for Endpoint {} +unsafe impl Sync for Endpoint {} + +impl Endpoint { + #[inline] + fn from_immdevice(device: *mut winapi::IMMDevice) -> Endpoint { + Endpoint { + device: device, + future_audio_client: Arc::new(Mutex::new(None)), + } + } + + /// Ensures that `future_audio_client` contains a `Some` and returns a locked mutex to it. + fn ensure_future_audio_client(&self) -> MutexGuard> { + let mut lock = self.future_audio_client.lock().unwrap(); + if lock.is_some() { + return lock; + } + + let audio_client: *mut winapi::IAudioClient = unsafe { + let mut audio_client = mem::uninitialized(); + let hresult = (*self.device).Activate(&winapi::IID_IAudioClient, winapi::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 + check_result(hresult).unwrap(); // FIXME: don't unwrap + audio_client as *mut _ + }; + + *lock = Some(IAudioClientWrapper(audio_client)); + lock + } + + pub fn get_supported_formats_list(&self) -> SupportedFormatsIterator { + // We always create voices in shared mode, therefore all samples go through an audio + // processor to mix them together. + // However there is no way to query the list of all formats that are supported by the + // audio processor, but one format is guaranteed to be supported, the one returned by + // `GetMixFormat`. + + // initializing COM because we call `CoTaskMemFree` + com::com_initialized(); + + let lock = self.ensure_future_audio_client(); + let client = lock.unwrap().0; + + unsafe { + let mut format_ptr = mem::uninitialized(); + check_result((*client).GetMixFormat(&mut format_ptr)).unwrap(); // FIXME: don't unwrap + + let format = { + assert!((*format_ptr).wFormatTag == winapi::WAVE_FORMAT_EXTENSIBLE); + + // FIXME: decode from the format + Format { + channels: 2, + samples_rate: SamplesRate(44100), + data_type: SampleFormat::U16, + } + }; + + ole32::CoTaskMemFree(format_ptr as *mut _); + + Some(format).into_iter() + } + } +} + +impl PartialEq for Endpoint { + fn eq(&self, other: &Endpoint) -> bool { + self.device == other.device + } +} + +impl Eq for Endpoint {} + +impl Clone for Endpoint { + fn clone(&self) -> Endpoint { + unsafe { (*self.device).AddRef(); } + + Endpoint { + device: self.device, + future_audio_client: self.future_audio_client.clone(), + } + } +} + +impl Drop for Endpoint { + fn drop(&mut self) { + unsafe { (*self.device).Release(); } + + if let Some(client) = self.future_audio_client.lock().unwrap().take() { + unsafe { (*client.0).Release(); } + } + } +} diff --git a/src/wasapi/voice.rs b/src/wasapi/voice.rs index 797fb72..ee46c1b 100644 --- a/src/wasapi/voice.rs +++ b/src/wasapi/voice.rs @@ -11,6 +11,8 @@ use std::mem; use std::ptr; use std::marker::PhantomData; +use Format; + pub struct Voice { audio_client: *mut winapi::IAudioClient, render_client: *mut winapi::IAudioRenderClient, @@ -26,7 +28,7 @@ unsafe impl Send for Voice {} unsafe impl Sync for Voice {} impl Voice { - pub fn new(end_point: &Endpoint) -> Result { + pub fn new(end_point: &Endpoint, format: &Format) -> Result { // FIXME: release everything unsafe { // making sure that COM is initialized @@ -36,8 +38,8 @@ impl Voice { // activating the end point in order to get a `IAudioClient` let audio_client: *mut winapi::IAudioClient = { let mut audio_client = mem::uninitialized(); - let hresult = (*end_point.0).Activate(&winapi::IID_IAudioClient, winapi::CLSCTX_ALL, - ptr::null_mut(), &mut audio_client); + let hresult = (*end_point.device).Activate(&winapi::IID_IAudioClient, winapi::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 try!(check_result(hresult)); From 98b931edff8ebada11aab2208cb4d534b42f129d Mon Sep 17 00:00:00 2001 From: Pierre Krieger Date: Tue, 1 Sep 2015 14:17:57 +0200 Subject: [PATCH 03/11] Add proper error handling --- examples/beep.rs | 2 +- src/lib.rs | 64 ++++++++++++++++++++++++++++++++++++++++++--- src/wasapi/mod.rs | 31 +++++++++++++++++----- src/wasapi/voice.rs | 47 +++++++++++++++++++++------------ 4 files changed, 117 insertions(+), 27 deletions(-) diff --git a/examples/beep.rs b/examples/beep.rs index 5179b2a..09e3312 100644 --- a/examples/beep.rs +++ b/examples/beep.rs @@ -2,7 +2,7 @@ extern crate cpal; fn main() { let endpoint = cpal::get_default_endpoint().unwrap(); - let format = endpoint.get_supported_formats_list().next().unwrap(); + let format = endpoint.get_supported_formats_list().unwrap().next().unwrap(); let mut channel = cpal::Voice::new(&endpoint, &format).unwrap(); // Produce a sinusoid of maximum amplitude. diff --git a/src/lib.rs b/src/lib.rs index 2e680b3..92e9b0f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,6 +33,7 @@ extern crate lazy_static; pub use samples_formats::{SampleFormat, Sample}; +use std::fmt; use std::error::Error; use std::ops::{Deref, DerefMut}; @@ -87,8 +88,10 @@ pub struct Endpoint(cpal_impl::Endpoint); impl Endpoint { /// Returns an iterator that produces the list of formats that are supported by the backend. - pub fn get_supported_formats_list(&self) -> SupportedFormatsIterator { - SupportedFormatsIterator(self.0.get_supported_formats_list()) + pub fn get_supported_formats_list(&self) -> Result + { + Ok(SupportedFormatsIterator(try!(self.0.get_supported_formats_list()))) } } @@ -146,6 +149,61 @@ pub enum UnknownTypeBuffer<'a> { F32(Buffer<'a, f32>), } +/// Error that can happen when enumerating the list of supported formats. +#[derive(Debug)] +pub enum FormatsEnumerationError { + /// The device no longer exists. This can happen if the device is disconnected while the + /// program is running. + DeviceNotAvailable, +} + +impl fmt::Display for FormatsEnumerationError { + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!(fmt, "{}", self.description()) + } +} + +impl Error for FormatsEnumerationError { + fn description(&self) -> &str { + match self { + &FormatsEnumerationError::DeviceNotAvailable => { + "The requested device is no longer available (for example, it has been unplugged)." + }, + } + } +} + +/// Error that can happen when creating a `Voice`. +#[derive(Debug)] +pub enum CreationError { + /// The device no longer exists. This can happen if the device is disconnected while the + /// program is running. + DeviceNotAvailable, + + /// The required format is not supported. + FormatNotSupported, +} + +impl fmt::Display for CreationError { + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!(fmt, "{}", self.description()) + } +} + +impl Error for CreationError { + fn description(&self) -> &str { + match self { + &CreationError::DeviceNotAvailable => { + "The requested device is no longer available (for example, it has been unplugged)." + }, + + &CreationError::FormatNotSupported => { + "The requested samples format is not supported by the device." + }, + } + } +} + /// Controls a sound output. A typical application has one `Voice` for each sound /// it wants to output. /// @@ -163,7 +221,7 @@ pub struct Voice(cpal_impl::Voice); impl Voice { /// Builds a new channel. #[inline] - pub fn new(endpoint: &Endpoint, format: &Format) -> Result> { + pub fn new(endpoint: &Endpoint, format: &Format) -> Result { let channel = try!(cpal_impl::Voice::new(&endpoint.0, format)); Ok(Voice(channel)) } diff --git a/src/wasapi/mod.rs b/src/wasapi/mod.rs index b40184c..7729a44 100644 --- a/src/wasapi/mod.rs +++ b/src/wasapi/mod.rs @@ -8,6 +8,7 @@ use std::ptr; use std::mem; use Format; +use FormatsEnumerationError; use SamplesRate; use SampleFormat; @@ -58,27 +59,39 @@ impl Endpoint { } /// Ensures that `future_audio_client` contains a `Some` and returns a locked mutex to it. - fn ensure_future_audio_client(&self) -> MutexGuard> { + fn ensure_future_audio_client(&self) -> Result>, IoError> { let mut lock = self.future_audio_client.lock().unwrap(); if lock.is_some() { - return lock; + return Ok(lock); } let audio_client: *mut winapi::IAudioClient = unsafe { let mut audio_client = mem::uninitialized(); let hresult = (*self.device).Activate(&winapi::IID_IAudioClient, winapi::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 - check_result(hresult).unwrap(); // FIXME: don't unwrap + try!(check_result(hresult)); + assert!(!audio_client.is_null()); audio_client as *mut _ }; *lock = Some(IAudioClientWrapper(audio_client)); - lock + Ok(lock) } - pub fn get_supported_formats_list(&self) -> SupportedFormatsIterator { + /// Returns an uninitialized `IAudioClient`. + fn build_audioclient(&self) -> Result<*mut winapi::IAudioClient, IoError> { + let mut lock = try!(self.ensure_future_audio_client()); + let client = lock.unwrap().0; + *lock = None; + Ok(client) + } + + pub fn get_supported_formats_list(&self) + -> Result + { // We always create voices in shared mode, therefore all samples go through an audio // processor to mix them together. // However there is no way to query the list of all formats that are supported by the @@ -88,7 +101,11 @@ impl Endpoint { // initializing COM because we call `CoTaskMemFree` com::com_initialized(); - let lock = self.ensure_future_audio_client(); + let lock = match self.ensure_future_audio_client() { + Err(ref e) if e.raw_os_error() == Some(winapi::AUDCLNT_E_DEVICE_INVALIDATED) => + return Err(FormatsEnumerationError::DeviceNotAvailable), + e => e.unwrap(), + }; let client = lock.unwrap().0; unsafe { @@ -108,7 +125,7 @@ impl Endpoint { ole32::CoTaskMemFree(format_ptr as *mut _); - Some(format).into_iter() + Ok(Some(format).into_iter()) } } } diff --git a/src/wasapi/voice.rs b/src/wasapi/voice.rs index ee46c1b..79e81f0 100644 --- a/src/wasapi/voice.rs +++ b/src/wasapi/voice.rs @@ -4,13 +4,13 @@ use super::winapi; use super::Endpoint; use super::check_result; -use std::io::Error as IoError; use std::cmp; use std::slice; use std::mem; use std::ptr; use std::marker::PhantomData; +use CreationError; use Format; pub struct Voice { @@ -28,22 +28,18 @@ unsafe impl Send for Voice {} unsafe impl Sync for Voice {} impl Voice { - pub fn new(end_point: &Endpoint, format: &Format) -> Result { + pub fn new(end_point: &Endpoint, format: &Format) -> Result { // FIXME: release everything 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(); - // activating the end point in order to get a `IAudioClient` - let audio_client: *mut winapi::IAudioClient = { - let mut audio_client = mem::uninitialized(); - let hresult = (*end_point.device).Activate(&winapi::IID_IAudioClient, winapi::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 - try!(check_result(hresult)); - audio_client as *mut _ + // obtaining a `IAudioClient` + let audio_client = match end_point.build_audioclient() { + Err(ref e) if e.raw_os_error() == Some(winapi::AUDCLNT_E_DEVICE_INVALIDATED) => + return Err(CreationError::DeviceNotAvailable), + e => e.unwrap(), }; // computing the format and initializing the device @@ -61,7 +57,12 @@ impl Voice { let mut format_ptr: *mut winapi::WAVEFORMATEX = mem::uninitialized(); let hresult = (*audio_client).IsFormatSupported(winapi::AUDCLNT_SHAREMODE::AUDCLNT_SHAREMODE_SHARED, &format_attempt, &mut format_ptr); - try!(check_result(hresult)); + + match check_result(hresult) { + Err(ref e) if e.raw_os_error() == Some(winapi::AUDCLNT_E_DEVICE_INVALIDATED) => + return Err(CreationError::DeviceNotAvailable), + e => e.unwrap(), + }; let format = if format_ptr.is_null() { &format_attempt @@ -78,7 +79,11 @@ impl Voice { ole32::CoTaskMemFree(format_ptr as *mut _); } - try!(check_result(hresult)); + match check_result(hresult) { + Err(ref e) if e.raw_os_error() == Some(winapi::AUDCLNT_E_DEVICE_INVALIDATED) => + return Err(CreationError::DeviceNotAvailable), + e => e.unwrap(), + }; format_copy }; @@ -87,7 +92,11 @@ impl Voice { let max_frames_in_buffer = { let mut max_frames_in_buffer = mem::uninitialized(); let hresult = (*audio_client).GetBufferSize(&mut max_frames_in_buffer); - try!(check_result(hresult)); + match check_result(hresult) { + Err(ref e) if e.raw_os_error() == Some(winapi::AUDCLNT_E_DEVICE_INVALIDATED) => + return Err(CreationError::DeviceNotAvailable), + e => e.unwrap(), + }; max_frames_in_buffer }; @@ -95,8 +104,14 @@ impl Voice { let render_client = { let mut render_client: *mut winapi::IAudioRenderClient = mem::uninitialized(); let hresult = (*audio_client).GetService(&winapi::IID_IAudioRenderClient, - mem::transmute(&mut render_client)); - try!(check_result(hresult)); + &mut render_client as *mut *mut winapi::IAudioRenderClient + as *mut _); + match check_result(hresult) { + Err(ref e) if e.raw_os_error() == Some(winapi::AUDCLNT_E_DEVICE_INVALIDATED) => + return Err(CreationError::DeviceNotAvailable), + e => e.unwrap(), + }; + &mut *render_client }; From 52052b6d07d0a1cfb7ea27c13c1e4fbbe4bd2566 Mon Sep 17 00:00:00 2001 From: Pierre Krieger Date: Tue, 1 Sep 2015 14:26:25 +0200 Subject: [PATCH 04/11] Use the format passed as parameter in Voice::new --- src/samples_formats.rs | 4 +++ src/wasapi/voice.rs | 71 ++++++++++++++++++++++++++++++++---------- 2 files changed, 59 insertions(+), 16 deletions(-) diff --git a/src/samples_formats.rs b/src/samples_formats.rs index 1b986fe..ebf6525 100644 --- a/src/samples_formats.rs +++ b/src/samples_formats.rs @@ -13,6 +13,7 @@ pub enum SampleFormat { impl SampleFormat { /// Returns the size in bytes of a sample of this format. + #[inline] pub fn get_sample_size(&self) -> usize { match self { &SampleFormat::I16 => mem::size_of::(), @@ -29,18 +30,21 @@ pub unsafe trait Sample: Copy + Clone { } unsafe impl Sample for u16 { + #[inline] fn get_format() -> SampleFormat { SampleFormat::U16 } } unsafe impl Sample for i16 { + #[inline] fn get_format() -> SampleFormat { SampleFormat::I16 } } unsafe impl Sample for f32 { + #[inline] fn get_format() -> SampleFormat { SampleFormat::F32 } diff --git a/src/wasapi/voice.rs b/src/wasapi/voice.rs index 79e81f0..6deca18 100644 --- a/src/wasapi/voice.rs +++ b/src/wasapi/voice.rs @@ -45,25 +45,40 @@ impl Voice { // computing the format and initializing the device let format = { let format_attempt = winapi::WAVEFORMATEX { - wFormatTag: 1, // WAVE_FORMAT_PCM ; TODO: replace by constant - nChannels: 2, - nSamplesPerSec: 44100, - nAvgBytesPerSec: 2 * 44100 * 2, - nBlockAlign: (2 * 16) / 8, - wBitsPerSample: 16, + wFormatTag: winapi::WAVE_FORMAT_PCM, + nChannels: format.channels as winapi::WORD, + nSamplesPerSec: format.samples_rate.0 as winapi::DWORD, + nAvgBytesPerSec: format.channels as winapi::DWORD * + format.samples_rate.0 as winapi::DWORD * + format.data_type.get_sample_size() as winapi::DWORD, + nBlockAlign: format.channels as winapi::WORD * + format.data_type.get_sample_size() as winapi::WORD, + wBitsPerSample: 8 * format.data_type.get_sample_size() as winapi::WORD, cbSize: 0, }; let mut format_ptr: *mut winapi::WAVEFORMATEX = mem::uninitialized(); let hresult = (*audio_client).IsFormatSupported(winapi::AUDCLNT_SHAREMODE::AUDCLNT_SHAREMODE_SHARED, - &format_attempt, &mut format_ptr); + &format_attempt, &mut format_ptr); + + if hresult == winapi::S_FALSE { + return Err(CreationError::FormatNotSupported); + } match check_result(hresult) { Err(ref e) if e.raw_os_error() == Some(winapi::AUDCLNT_E_DEVICE_INVALIDATED) => - return Err(CreationError::DeviceNotAvailable), - e => e.unwrap(), + { + (*audio_client).Release(); + return Err(CreationError::DeviceNotAvailable); + }, + Err(e) => { + (*audio_client).Release(); + panic!("{:?}", e); + }, + Ok(()) => (), }; + let format = if format_ptr.is_null() { &format_attempt } else { @@ -73,7 +88,7 @@ impl Voice { let format_copy = ptr::read(format); let hresult = (*audio_client).Initialize(winapi::AUDCLNT_SHAREMODE::AUDCLNT_SHAREMODE_SHARED, - 0, 10000000, 0, format, ptr::null()); + 0, 10000000, 0, format, ptr::null()); if !format_ptr.is_null() { ole32::CoTaskMemFree(format_ptr as *mut _); @@ -81,8 +96,15 @@ impl Voice { match check_result(hresult) { Err(ref e) if e.raw_os_error() == Some(winapi::AUDCLNT_E_DEVICE_INVALIDATED) => - return Err(CreationError::DeviceNotAvailable), - e => e.unwrap(), + { + (*audio_client).Release(); + return Err(CreationError::DeviceNotAvailable); + }, + Err(e) => { + (*audio_client).Release(); + panic!("{:?}", e); + }, + Ok(()) => (), }; format_copy @@ -92,11 +114,20 @@ impl Voice { 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(winapi::AUDCLNT_E_DEVICE_INVALIDATED) => - return Err(CreationError::DeviceNotAvailable), - e => e.unwrap(), + { + (*audio_client).Release(); + return Err(CreationError::DeviceNotAvailable); + }, + Err(e) => { + (*audio_client).Release(); + panic!("{:?}", e); + }, + Ok(()) => (), }; + max_frames_in_buffer }; @@ -106,10 +137,18 @@ impl Voice { let hresult = (*audio_client).GetService(&winapi::IID_IAudioRenderClient, &mut render_client as *mut *mut winapi::IAudioRenderClient as *mut _); + match check_result(hresult) { Err(ref e) if e.raw_os_error() == Some(winapi::AUDCLNT_E_DEVICE_INVALIDATED) => - return Err(CreationError::DeviceNotAvailable), - e => e.unwrap(), + { + (*audio_client).Release(); + return Err(CreationError::DeviceNotAvailable); + }, + Err(e) => { + (*audio_client).Release(); + panic!("{:?}", e); + }, + Ok(()) => (), }; &mut *render_client From bf204319018ab786c9a1ae57169eb7b7d9fcaf48 Mon Sep 17 00:00:00 2001 From: Pierre Krieger Date: Tue, 1 Sep 2015 14:51:35 +0200 Subject: [PATCH 05/11] Handle F32 formats in Voice::new --- src/wasapi/mod.rs | 2 +- src/wasapi/voice.rs | 64 ++++++++++++++++++++++++++------------------- 2 files changed, 38 insertions(+), 28 deletions(-) diff --git a/src/wasapi/mod.rs b/src/wasapi/mod.rs index 7729a44..a3bd886 100644 --- a/src/wasapi/mod.rs +++ b/src/wasapi/mod.rs @@ -119,7 +119,7 @@ impl Endpoint { Format { channels: 2, samples_rate: SamplesRate(44100), - data_type: SampleFormat::U16, + data_type: SampleFormat::I16, } }; diff --git a/src/wasapi/voice.rs b/src/wasapi/voice.rs index 6deca18..d7fcd38 100644 --- a/src/wasapi/voice.rs +++ b/src/wasapi/voice.rs @@ -12,6 +12,7 @@ use std::marker::PhantomData; use CreationError; use Format; +use SampleFormat; pub struct Voice { audio_client: *mut winapi::IAudioClient, @@ -44,22 +45,44 @@ impl Voice { // computing the format and initializing the device let format = { - let format_attempt = winapi::WAVEFORMATEX { - wFormatTag: winapi::WAVE_FORMAT_PCM, - nChannels: format.channels as winapi::WORD, - nSamplesPerSec: format.samples_rate.0 as winapi::DWORD, - nAvgBytesPerSec: format.channels as winapi::DWORD * - format.samples_rate.0 as winapi::DWORD * - format.data_type.get_sample_size() as winapi::DWORD, - nBlockAlign: format.channels as winapi::WORD * - format.data_type.get_sample_size() as winapi::WORD, - wBitsPerSample: 8 * format.data_type.get_sample_size() as winapi::WORD, - cbSize: 0, + let format_attempt = winapi::WAVEFORMATEXTENSIBLE { + Format: winapi::WAVEFORMATEX { + wFormatTag: match format.data_type { + SampleFormat::I16 => winapi::WAVE_FORMAT_PCM, + SampleFormat::F32 => winapi::WAVE_FORMAT_EXTENSIBLE, + SampleFormat::U16 => return Err(CreationError::FormatNotSupported), + }, + nChannels: format.channels as winapi::WORD, + nSamplesPerSec: format.samples_rate.0 as winapi::DWORD, + nAvgBytesPerSec: format.channels as winapi::DWORD * + format.samples_rate.0 as winapi::DWORD * + format.data_type.get_sample_size() as winapi::DWORD, + nBlockAlign: format.channels as winapi::WORD * + format.data_type.get_sample_size() as winapi::WORD, + wBitsPerSample: 8 * format.data_type.get_sample_size() as winapi::WORD, + cbSize: match format.data_type { + SampleFormat::I16 => 0, + SampleFormat::F32 => (mem::size_of::() - + mem::size_of::()) as winapi::WORD, + SampleFormat::U16 => return Err(CreationError::FormatNotSupported), + }, + }, + Samples: 8 * format.data_type.get_sample_size() as winapi::WORD, + dwChannelMask: 3, // LEFT | RIGHT + SubFormat: match format.data_type { + SampleFormat::I16 => winapi::KSDATAFORMAT_SUBTYPE_PCM, + SampleFormat::F32 => winapi::KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, + SampleFormat::U16 => return Err(CreationError::FormatNotSupported), + }, }; let mut format_ptr: *mut winapi::WAVEFORMATEX = mem::uninitialized(); let hresult = (*audio_client).IsFormatSupported(winapi::AUDCLNT_SHAREMODE::AUDCLNT_SHAREMODE_SHARED, - &format_attempt, &mut format_ptr); + &format_attempt.Format, &mut format_ptr); + + if !format_ptr.is_null() { + ole32::CoTaskMemFree(format_ptr as *mut _); + } if hresult == winapi::S_FALSE { return Err(CreationError::FormatNotSupported); @@ -78,21 +101,8 @@ impl Voice { Ok(()) => (), }; - - let format = if format_ptr.is_null() { - &format_attempt - } else { - &*format_ptr - }; - - let format_copy = ptr::read(format); - let hresult = (*audio_client).Initialize(winapi::AUDCLNT_SHAREMODE::AUDCLNT_SHAREMODE_SHARED, - 0, 10000000, 0, format, ptr::null()); - - if !format_ptr.is_null() { - ole32::CoTaskMemFree(format_ptr as *mut _); - } + 0, 10000000, 0, &format_attempt.Format, ptr::null()); match check_result(hresult) { Err(ref e) if e.raw_os_error() == Some(winapi::AUDCLNT_E_DEVICE_INVALIDATED) => @@ -107,7 +117,7 @@ impl Voice { Ok(()) => (), }; - format_copy + format_attempt.Format }; // From dc08fc465258cacdb6df7b903d5d2ac9096a1831 Mon Sep 17 00:00:00 2001 From: Pierre Krieger Date: Tue, 1 Sep 2015 15:32:03 +0200 Subject: [PATCH 06/11] Now decoding the format from the WAVEFORMAT returned by the winapi --- src/wasapi/mod.rs | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/wasapi/mod.rs b/src/wasapi/mod.rs index a3bd886..ad4334b 100644 --- a/src/wasapi/mod.rs +++ b/src/wasapi/mod.rs @@ -113,13 +113,23 @@ impl Endpoint { check_result((*client).GetMixFormat(&mut format_ptr)).unwrap(); // FIXME: don't unwrap let format = { - assert!((*format_ptr).wFormatTag == winapi::WAVE_FORMAT_EXTENSIBLE); + let data_type = match (*format_ptr).wFormatTag { + winapi::WAVE_FORMAT_PCM => SampleFormat::I16, + winapi::WAVE_FORMAT_EXTENSIBLE => { + let format_ptr = format_ptr as *const winapi::WAVEFORMATEXTENSIBLE; + match (*format_ptr).SubFormat { + winapi::KSDATAFORMAT_SUBTYPE_IEEE_FLOAT => SampleFormat::F32, + winapi::KSDATAFORMAT_SUBTYPE_PCM => SampleFormat::I16, + g => panic!("Unknown SubFormat GUID returned by GetMixFormat: {:?}", g) + } + }, + f => panic!("Unknown data format returned by GetMixFormat: {:?}", f) + }; - // FIXME: decode from the format Format { - channels: 2, - samples_rate: SamplesRate(44100), - data_type: SampleFormat::I16, + channels: (*format_ptr).nChannels, + samples_rate: SamplesRate((*format_ptr).nSamplesPerSec), + data_type: data_type, } }; From 77fb55329e601037ed6a59898c0090cd191161d1 Mon Sep 17 00:00:00 2001 From: Pierre Krieger Date: Tue, 1 Sep 2015 15:33:44 +0200 Subject: [PATCH 07/11] Better error handling in format detection --- src/wasapi/mod.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/wasapi/mod.rs b/src/wasapi/mod.rs index ad4334b..b5035d2 100644 --- a/src/wasapi/mod.rs +++ b/src/wasapi/mod.rs @@ -110,7 +110,13 @@ impl Endpoint { unsafe { let mut format_ptr = mem::uninitialized(); - check_result((*client).GetMixFormat(&mut format_ptr)).unwrap(); // FIXME: don't unwrap + match check_result((*client).GetMixFormat(&mut format_ptr)) { + Err(ref e) if e.raw_os_error() == Some(winapi::AUDCLNT_E_DEVICE_INVALIDATED) => { + return Err(FormatsEnumerationError::DeviceNotAvailable); + }, + Err(e) => panic!("{:?}", e), + Ok(()) => (), + }; let format = { let data_type = match (*format_ptr).wFormatTag { From 078769dbbdd2379723c55c1c9d99ced3c064e9be Mon Sep 17 00:00:00 2001 From: Pierre Krieger Date: Tue, 1 Sep 2015 15:58:22 +0200 Subject: [PATCH 08/11] Enable 32bits samples with WASAPI --- src/wasapi/voice.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/wasapi/voice.rs b/src/wasapi/voice.rs index d7fcd38..1cd3154 100644 --- a/src/wasapi/voice.rs +++ b/src/wasapi/voice.rs @@ -188,6 +188,7 @@ impl Voice { pub fn get_samples_format(&self) -> ::SampleFormat { match self.bits_per_sample { 16 => ::SampleFormat::I16, + 32 => ::SampleFormat::F32, _ => panic!("{}-bit format not yet supported", self.bits_per_sample), } } From 3db17889a453c68e7fcb06a436fcd6a82849d759 Mon Sep 17 00:00:00 2001 From: Pierre Krieger Date: Tue, 1 Sep 2015 17:15:49 +0200 Subject: [PATCH 09/11] Make ALSA compile again --- src/alsa/enumerate.rs | 78 +++++++++++++++++++++++++++++++++++++++++++ src/alsa/mod.rs | 38 ++++++++++++++++++--- 2 files changed, 112 insertions(+), 4 deletions(-) create mode 100644 src/alsa/enumerate.rs diff --git a/src/alsa/enumerate.rs b/src/alsa/enumerate.rs new file mode 100644 index 0000000..b087acb --- /dev/null +++ b/src/alsa/enumerate.rs @@ -0,0 +1,78 @@ +use super::alsa; +use super::check_errors; +use super::Endpoint; + +use std::ffi::CStr; +use std::mem; + +/// ALSA implementation for `EndpointsIterator`. +pub struct EndpointsIterator { + // we keep the original list so that we can pass it to the free function + global_list: *const *const u8, + + // pointer to the next string ; contained within `global_list` + next_str: *const *const u8, +} + +unsafe impl Send for EndpointsIterator {} +unsafe impl Sync for EndpointsIterator {} + +impl Drop for EndpointsIterator { + fn drop(&mut self) { + unsafe { + alsa::snd_device_name_free_hint(self.global_list as *mut _); + } + } +} + +impl Default for EndpointsIterator { + fn default() -> EndpointsIterator { + unsafe { + let mut hints = mem::uninitialized(); + // TODO: check in which situation this can fail + check_errors(alsa::snd_device_name_hint(-1, b"pcm\0".as_ptr() as *const _, + &mut hints)).unwrap(); + + let hints = hints as *const *const u8; + + EndpointsIterator { + global_list: hints, + next_str: hints, + } + } + } +} + +impl Iterator for EndpointsIterator { + type Item = Endpoint; + + fn next(&mut self) -> Option { + loop { + unsafe { + if (*self.next_str).is_null() { + return None; + } + + let name = alsa::snd_device_name_get_hint(*self.next_str as *const _, + b"NAME".as_ptr() as *const _); + self.next_str = self.next_str.offset(1); + + if name.is_null() { + continue; + } + + let name = CStr::from_ptr(name).to_bytes().to_vec(); + let name = String::from_utf8(name).unwrap(); + + if name != "null" { + return Some(Endpoint(name)); + } + } + } + } +} + +pub fn get_default_endpoint() -> Option { + // TODO: do in a different way? + Some(Endpoint("default".to_owned())) +} diff --git a/src/alsa/mod.rs b/src/alsa/mod.rs index ce7889e..4472ed5 100644 --- a/src/alsa/mod.rs +++ b/src/alsa/mod.rs @@ -1,9 +1,39 @@ extern crate alsa_sys as alsa; extern crate libc; +pub use self::enumerate::{EndpointsIterator, get_default_endpoint}; + +use CreationError; +use Format; +use FormatsEnumerationError; +use SampleFormat; +use SamplesRate; + use std::{ffi, iter, mem}; +use std::option::IntoIter as OptionIntoIter; use std::sync::Mutex; +pub type SupportedFormatsIterator = OptionIntoIter; + +mod enumerate; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Endpoint(String); + +impl Endpoint { + pub fn get_supported_formats_list(&self) + -> Result + { + let format = Format { + channels: 2, + samples_rate: SamplesRate(44100), + data_type: SampleFormat::I16, + }; + + Ok(Some(format).into_iter()) + } +} + pub struct Voice { channel: Mutex<*mut alsa::snd_pcm_t>, num_channels: u16, @@ -15,9 +45,9 @@ pub struct Buffer<'a, T> { } impl Voice { - pub fn new() -> Voice { + pub fn new(endpoint: &Endpoint, _format: &Format) -> Result { unsafe { - let name = ffi::CString::new(b"default".to_vec()).unwrap(); + let name = ffi::CString::new(endpoint.0.clone()).unwrap(); let mut playback_handle = mem::uninitialized(); check_errors(alsa::snd_pcm_open(&mut playback_handle, name.as_ptr(), alsa::SND_PCM_STREAM_PLAYBACK, alsa::SND_PCM_NONBLOCK)).unwrap(); @@ -34,10 +64,10 @@ impl Voice { check_errors(alsa::snd_pcm_prepare(playback_handle)).unwrap(); - Voice { + Ok(Voice { channel: Mutex::new(playback_handle), num_channels: 2, - } + }) } } From d7a31b47629ebaa8fd696da3aee679d77d4946ae Mon Sep 17 00:00:00 2001 From: Pierre Krieger Date: Tue, 1 Sep 2015 17:47:55 +0200 Subject: [PATCH 10/11] Fix doctests --- src/lib.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 92e9b0f..f3c6c22 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,7 +4,16 @@ In order to play a sound, first you need to create a `Voice`. ```no_run -let mut voice = cpal::Voice::new(); +// getting the default sound output of the system (can return `None` if nothing is supported) +let endpoint = cpal::get_default_endpoint().unwrap(); + +// note that the user can at any moment disconnect the device, therefore all operations return +// a `Result` to handle this situation + +// getting a format for the PCM +let format = endpoint.get_supported_formats_list().unwrap().next().unwrap(); + +let mut voice = cpal::Voice::new(&endpoint, &format).unwrap(); ``` Then you must send raw samples to it by calling `append_data`. You must take the number of channels @@ -19,7 +28,7 @@ this is not some obscure situation that can be ignored. After you have submitted data for the first time, call `play`: ```no_run -# let mut voice = cpal::Voice::new(); +# let mut voice: cpal::Voice = unsafe { std::mem::uninitialized() }; voice.play(); ``` From b73bde48fef5f1e4537d531187cb16dafd2c44c6 Mon Sep 17 00:00:00 2001 From: Pierre Krieger Date: Tue, 1 Sep 2015 18:19:04 +0200 Subject: [PATCH 11/11] Bump to 0.2.0 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index c937984..5688e92 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "cpal" -version = "0.1.2" +version = "0.2.0" authors = ["Pierre Krieger "] description = "Cross-platform audio playing library in pure Rust." repository = "https://github.com/tomaka/cpal"