* Remove ChannelPosition API This removes the ChannelPosition API from the lib root and updates the ALSA backend and examples accordingly. The other backends have not yet been updated. Related discussion at #187. * Update windows backend to removal of ChannelPosition API The windows backend now assumes the channel position order is equal to the channel position mask order. E.g. channel 0 will always be front left, channel 1 will always be front right, etc. Compiled and ran both examples successfully. * Update coreaudio backend to removal of ChannelPosition API Compiled and ran both examples successfully. * Update emscriptem backend for removal of ChannelPosition API * Update CHANGELOG for ChannelPosition removal
350 lines
11 KiB
Rust
350 lines
11 KiB
Rust
use std::ffi::OsString;
|
|
use std::io::Error as IoError;
|
|
use std::mem;
|
|
use std::option::IntoIter as OptionIntoIter;
|
|
use std::os::windows::ffi::OsStringExt;
|
|
use std::ptr;
|
|
use std::slice;
|
|
use std::sync::{Arc, Mutex, MutexGuard};
|
|
|
|
use ChannelsCount;
|
|
use FormatsEnumerationError;
|
|
use SampleFormat;
|
|
use SamplesRate;
|
|
use SupportedFormat;
|
|
|
|
use super::check_result;
|
|
use super::com;
|
|
use super::ole32;
|
|
use super::winapi;
|
|
|
|
pub type SupportedFormatsIterator = OptionIntoIter<SupportedFormat>;
|
|
|
|
/// Wrapper because of that stupid decision to remove `Send` and `Sync` from raw pointers.
|
|
#[derive(Copy, Clone)]
|
|
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<Mutex<Option<IAudioClientWrapper>>>, // TODO: add NonZero around the ptr
|
|
}
|
|
|
|
unsafe impl Send for Endpoint {
|
|
}
|
|
unsafe impl Sync for Endpoint {
|
|
}
|
|
|
|
impl Endpoint {
|
|
// TODO: this function returns a GUID of the endpoin
|
|
// instead it should use the property store and return the friendly name
|
|
pub fn name(&self) -> String {
|
|
unsafe {
|
|
let mut name_ptr = mem::uninitialized();
|
|
// can only fail if wrong params or out of memory
|
|
check_result((*self.device).GetId(&mut name_ptr)).unwrap();
|
|
|
|
// finding the length of the name
|
|
let mut len = 0;
|
|
while *name_ptr.offset(len) != 0 {
|
|
len += 1;
|
|
}
|
|
|
|
// building a slice containing the name
|
|
let name_slice = slice::from_raw_parts(name_ptr, len as usize);
|
|
|
|
// and turning it into a string
|
|
let name_string: OsString = OsStringExt::from_wide(name_slice);
|
|
ole32::CoTaskMemFree(name_ptr as *mut _);
|
|
name_string.into_string().unwrap()
|
|
}
|
|
}
|
|
|
|
#[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)
|
|
-> Result<MutexGuard<Option<IAudioClientWrapper>>, 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
|
|
check_result(hresult)?;
|
|
assert!(!audio_client.is_null());
|
|
audio_client as *mut _
|
|
};
|
|
|
|
*lock = Some(IAudioClientWrapper(audio_client));
|
|
Ok(lock)
|
|
}
|
|
|
|
/// Returns an uninitialized `IAudioClient`.
|
|
#[inline]
|
|
pub(crate) fn build_audioclient(&self) -> Result<*mut winapi::IAudioClient, IoError> {
|
|
let mut lock = self.ensure_future_audio_client()?;
|
|
let client = lock.unwrap().0;
|
|
*lock = None;
|
|
Ok(client)
|
|
}
|
|
|
|
pub fn supported_formats(&self) -> Result<SupportedFormatsIterator, FormatsEnumerationError> {
|
|
// 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 (channels, data_type) = match (*format_ptr).wFormatTag {
|
|
winapi::WAVE_FORMAT_PCM => {
|
|
(2, SampleFormat::I16)
|
|
},
|
|
winapi::WAVE_FORMAT_EXTENSIBLE => {
|
|
let format_ptr = format_ptr as *const winapi::WAVEFORMATEXTENSIBLE;
|
|
let channels = (*format_ptr).Format.nChannels as ChannelsCount;
|
|
let format = {
|
|
fn cmp_guid(a: &winapi::GUID, b: &winapi::GUID) -> bool {
|
|
a.Data1 == b.Data1 && a.Data2 == b.Data2 && a.Data3 == b.Data3 &&
|
|
a.Data4 == b.Data4
|
|
}
|
|
if cmp_guid(&(*format_ptr).SubFormat,
|
|
&winapi::KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)
|
|
{
|
|
SampleFormat::F32
|
|
} else if cmp_guid(&(*format_ptr).SubFormat,
|
|
&winapi::KSDATAFORMAT_SUBTYPE_PCM)
|
|
{
|
|
SampleFormat::I16
|
|
} else {
|
|
panic!("Unknown SubFormat GUID returned by GetMixFormat: {:?}",
|
|
(*format_ptr).SubFormat)
|
|
}
|
|
};
|
|
|
|
(channels, format)
|
|
},
|
|
|
|
f => panic!("Unknown data format returned by GetMixFormat: {:?}", f),
|
|
};
|
|
|
|
SupportedFormat {
|
|
channels: channels,
|
|
min_samples_rate: SamplesRate((*format_ptr).nSamplesPerSec),
|
|
max_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 {
|
|
#[inline]
|
|
fn eq(&self, other: &Endpoint) -> bool {
|
|
self.device == other.device
|
|
}
|
|
}
|
|
|
|
impl Eq for Endpoint {
|
|
}
|
|
|
|
impl Clone for Endpoint {
|
|
#[inline]
|
|
fn clone(&self) -> Endpoint {
|
|
unsafe {
|
|
(*self.device).AddRef();
|
|
}
|
|
|
|
Endpoint {
|
|
device: self.device,
|
|
future_audio_client: self.future_audio_client.clone(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Drop for Endpoint {
|
|
#[inline]
|
|
fn drop(&mut self) {
|
|
unsafe {
|
|
(*self.device).Release();
|
|
}
|
|
|
|
if let Some(client) = self.future_audio_client.lock().unwrap().take() {
|
|
unsafe {
|
|
(*client.0).Release();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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 {
|
|
#[inline]
|
|
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 {
|
|
#[inline]
|
|
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::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<Endpoint> {
|
|
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<usize>) {
|
|
let num = self.total_count - self.next_item;
|
|
let num = num as usize;
|
|
(num, Some(num))
|
|
}
|
|
}
|
|
|
|
pub fn default_endpoint() -> Option<Endpoint> {
|
|
unsafe {
|
|
let mut device = mem::uninitialized();
|
|
let hres = (*ENUMERATOR.0)
|
|
.GetDefaultAudioEndpoint(winapi::eRender, winapi::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))
|
|
}
|
|
}
|