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));