diff --git a/Cargo.toml b/Cargo.toml index 5a0f1e5..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" @@ -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..09e3312 100644 --- a/examples/beep.rs +++ b/examples/beep.rs @@ -1,7 +1,9 @@ extern crate cpal; fn main() { - let mut channel = cpal::Voice::new(); + let endpoint = cpal::get_default_endpoint().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. let mut data_source = (0u64..).map(|t| t as f32 * 0.03) 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, - } + }) } } diff --git a/src/lib.rs b/src/lib.rs index e3e8a90..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(); ``` @@ -28,8 +37,13 @@ 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::fmt; +use std::error::Error; use std::ops::{Deref, DerefMut}; mod samples_formats; @@ -50,6 +64,46 @@ mod cpal_impl; #[path="null/mod.rs"] mod cpal_impl; +/// 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() + } +} + +/// 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); + +impl Endpoint { + /// Returns an iterator that produces the list of formats that are supported by the backend. + pub fn get_supported_formats_list(&self) -> Result + { + Ok(SupportedFormatsIterator(try!(self.0.get_supported_formats_list()))) + } +} + /// Number of channels. pub type ChannelsCount = u16; @@ -57,6 +111,31 @@ 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 @@ -79,6 +158,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. /// @@ -95,15 +229,17 @@ pub struct Voice(cpal_impl::Voice); impl Voice { /// Builds a new channel. - pub fn new() -> Voice { - let channel = cpal_impl::Voice::new(); - Voice(channel) + #[inline] + pub fn new(endpoint: &Endpoint, format: &Format) -> Result { + let channel = try!(cpal_impl::Voice::new(&endpoint.0, format)); + Ok(Voice(channel)) } /// Returns the number of channels. /// /// 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() } @@ -112,6 +248,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() } @@ -120,6 +257,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() } @@ -164,6 +302,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() } @@ -173,6 +312,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() } @@ -181,18 +321,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/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/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..0c5e966 --- /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::from_immdevice(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::from_immdevice(device)) + } +} diff --git a/src/wasapi/mod.rs b/src/wasapi/mod.rs index 28997d6..b5035d2 100644 --- a/src/wasapi/mod.rs +++ b/src/wasapi/mod.rs @@ -2,255 +2,175 @@ 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; +use std::sync::{Arc, Mutex, MutexGuard}; +use std::ptr; +use std::mem; -// 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, -} +use Format; +use FormatsEnumerationError; +use SamplesRate; +use SampleFormat; -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>, -} +pub use std::option::IntoIter as OptionIntoIter; +pub use self::enumerate::{EndpointsIterator, get_default_endpoint}; +pub use self::voice::{Voice, Buffer}; -impl Voice { - pub fn new() -> Voice { - init().unwrap() - } +pub type SupportedFormatsIterator = OptionIntoIter; - pub fn get_channels(&self) -> ::ChannelsCount { - self.num_channels as ::ChannelsCount - } +mod com; +mod enumerate; +mod voice; - 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; - } -} - -unsafe impl Send for Voice {} -unsafe impl Sync for Voice {} - -impl Drop for Voice { - fn drop(&mut self) { - unsafe { - (*self.render_client).Release(); - (*self.audio_client).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(()) + } +} + +/// 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)), + } } - Ok(()) + /// Ensures that `future_audio_client` contains a `Some` and returns a locked mutex to it. + fn ensure_future_audio_client(&self) -> Result>, IoError> { + let mut lock = self.future_audio_client.lock().unwrap(); + if lock.is_some() { + 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 + try!(check_result(hresult)); + assert!(!audio_client.is_null()); + audio_client as *mut _ + }; + + *lock = Some(IAudioClientWrapper(audio_client)); + Ok(lock) + } + + /// 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 + // 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 = 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 { + let mut format_ptr = mem::uninitialized(); + 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 { + 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) + }; + + Format { + channels: (*format_ptr).nChannels, + samples_rate: SamplesRate((*format_ptr).nSamplesPerSec), + data_type: data_type, + } + }; + + ole32::CoTaskMemFree(format_ptr as *mut _); + + Ok(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 new file mode 100644 index 0000000..1cd3154 --- /dev/null +++ b/src/wasapi/voice.rs @@ -0,0 +1,298 @@ +use super::com; +use super::ole32; +use super::winapi; +use super::Endpoint; +use super::check_result; + +use std::cmp; +use std::slice; +use std::mem; +use std::ptr; +use std::marker::PhantomData; + +use CreationError; +use Format; +use SampleFormat; + +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, 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(); + + // 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 + let format = { + 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.Format, &mut format_ptr); + + if !format_ptr.is_null() { + ole32::CoTaskMemFree(format_ptr as *mut _); + } + + 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) => + { + (*audio_client).Release(); + return Err(CreationError::DeviceNotAvailable); + }, + Err(e) => { + (*audio_client).Release(); + panic!("{:?}", e); + }, + Ok(()) => (), + }; + + let hresult = (*audio_client).Initialize(winapi::AUDCLNT_SHAREMODE::AUDCLNT_SHAREMODE_SHARED, + 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) => + { + (*audio_client).Release(); + return Err(CreationError::DeviceNotAvailable); + }, + Err(e) => { + (*audio_client).Release(); + panic!("{:?}", e); + }, + Ok(()) => (), + }; + + format_attempt.Format + }; + + // + 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) => + { + (*audio_client).Release(); + return Err(CreationError::DeviceNotAvailable); + }, + Err(e) => { + (*audio_client).Release(); + panic!("{:?}", e); + }, + Ok(()) => (), + }; + + max_frames_in_buffer + }; + + // + let render_client = { + let mut render_client: *mut winapi::IAudioRenderClient = mem::uninitialized(); + 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) => + { + (*audio_client).Release(); + return Err(CreationError::DeviceNotAvailable); + }, + Err(e) => { + (*audio_client).Release(); + panic!("{:?}", e); + }, + Ok(()) => (), + }; + + &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, + 32 => ::SampleFormat::F32, + _ => 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(); + }; + } +}