cpal/src/host/wasapi/device.rs
mitchmindtree e8a05379c2 [WIP] Introduce a Host API
This is an implementation of the API described at #204. Please see that
issue for more details on the motivation.

-----

A **Host** provides access to the available audio devices on the system.
Some platforms have more than one host available, e.g.
wasapi/asio/dsound on windows, alsa/pulse/jack on linux and so on. As a
result, some audio devices are only available on certain hosts, while
others are only available on other hosts. Every platform supported by
CPAL has at least one **DefaultHost** that is guaranteed to be available
(alsa, wasapi and coreaudio). Currently, the default hosts are the only
hosts supported by CPAL, however this will change as of landing #221 (cc
@freesig). These changes should also accommodate support for other hosts
such as jack #250 (cc @derekdreery) and pulseaudio (cc @knappador) #259.

This introduces a suite of traits allowing for both compile time and
runtime dispatch of different hosts and their uniquely associated device
and event loop types.

A new private **host** module has been added containing the individual
host implementations, each in their own submodule gated to the platforms
on which they are available.

A new **platform** module has been added containing platform-specific
items, including a dynamically dispatched host type that allows for
easily switching between hosts at runtime.

The **ALL_HOSTS** slice contains a **HostId** for each host supported on
the current platform. The **available_hosts** function produces a
**HostId** for each host that is currently *available* on the platform.
The **host_from_id** function allows for initialising a host from its
associated ID, failing with a **HostUnavailable** error. The
**default_host** function returns the default host and should never
fail.

Please see the examples for a demonstration of the change in usage. For
the most part, things look the same at the surface level, however the
role of device enumeration and creating the event loop have been moved
from global functions to host methods. The enumerate.rs example has been
updated to enumerate all devices for each host, not just the default.

**TODO**

- [x] Add the new **Host** API
- [x] Update examples for the new API.
- [x] ALSA host
- [ ] WASAPI host
- [ ] CoreAudio host
- [ ] Emscripten host **Follow-up PR**
- [ ] ASIO host #221

cc @ishitatsuyuki more to review for you if you're interested, but it
might be easier after #288 lands and this gets rebased.
2019-06-24 21:45:04 +02:00

819 lines
28 KiB
Rust

use std;
use std::ffi::OsString;
use std::fmt;
use std::io::Error as IoError;
use std::mem;
use std::ops::{Deref, DerefMut};
use std::os::windows::ffi::OsStringExt;
use std::ptr;
use std::slice;
use std::sync::{Arc, Mutex, MutexGuard};
use BackendSpecificError;
use DefaultFormatError;
use DeviceNameError;
use DevicesError;
use Format;
use SupportedFormatsError;
use SampleFormat;
use SampleRate;
use SupportedFormat;
use COMMON_SAMPLE_RATES;
use super::check_result;
use super::check_result_backend_specific;
use super::com;
use super::winapi::Interface;
use super::winapi::ctypes::c_void;
use super::winapi::shared::devpkey;
use super::winapi::shared::ksmedia;
use super::winapi::shared::guiddef::{
GUID,
};
use super::winapi::shared::winerror;
use super::winapi::shared::minwindef::{
DWORD,
};
use super::winapi::shared::mmreg;
use super::winapi::shared::wtypes;
// https://msdn.microsoft.com/en-us/library/cc230355.aspx
use super::winapi::um::winnt::LPWSTR;
use super::winapi::um::winnt::WCHAR;
use super::winapi::um::coml2api;
use super::winapi::um::audioclient::{
IAudioClient,
IID_IAudioClient,
AUDCLNT_E_DEVICE_INVALIDATED,
};
use super::winapi::um::audiosessiontypes::{
AUDCLNT_SHAREMODE_SHARED,
};
use super::winapi::um::combaseapi::{
CoCreateInstance,
CoTaskMemFree,
CLSCTX_ALL,
PropVariantClear,
};
use super::winapi::um::mmdeviceapi::{
eAll,
eCapture,
eConsole,
eRender,
CLSID_MMDeviceEnumerator,
DEVICE_STATE_ACTIVE,
EDataFlow,
IMMDevice,
IMMDeviceCollection,
IMMDeviceEnumerator,
IMMEndpoint,
};
pub type SupportedInputFormats = std::vec::IntoIter<SupportedFormat>;
pub type SupportedOutputFormats = std::vec::IntoIter<SupportedFormat>;
/// Wrapper because of that stupid decision to remove `Send` and `Sync` from raw pointers.
#[derive(Copy, Clone)]
struct IAudioClientWrapper(*mut IAudioClient);
unsafe impl Send for IAudioClientWrapper {
}
unsafe impl Sync for IAudioClientWrapper {
}
/// An opaque type that identifies an end point.
pub struct Device {
device: *mut 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
}
struct Endpoint {
endpoint: *mut IMMEndpoint,
}
enum WaveFormat {
Ex(mmreg::WAVEFORMATEX),
Extensible(mmreg::WAVEFORMATEXTENSIBLE),
}
// Use RAII to make sure CoTaskMemFree is called when we are responsible for freeing.
struct WaveFormatExPtr(*mut mmreg::WAVEFORMATEX);
impl Drop for WaveFormatExPtr {
fn drop(&mut self) {
unsafe {
CoTaskMemFree(self.0 as *mut _);
}
}
}
impl WaveFormat {
// Given a pointer to some format, returns a valid copy of the format.
pub fn copy_from_waveformatex_ptr(ptr: *const mmreg::WAVEFORMATEX) -> Option<Self> {
unsafe {
match (*ptr).wFormatTag {
mmreg::WAVE_FORMAT_PCM | mmreg::WAVE_FORMAT_IEEE_FLOAT => {
Some(WaveFormat::Ex(*ptr))
},
mmreg::WAVE_FORMAT_EXTENSIBLE => {
let extensible_ptr = ptr as *const mmreg::WAVEFORMATEXTENSIBLE;
Some(WaveFormat::Extensible(*extensible_ptr))
},
_ => None,
}
}
}
// Get the pointer to the WAVEFORMATEX struct.
pub fn as_ptr(&self) -> *const mmreg::WAVEFORMATEX {
self.deref() as *const _
}
}
impl Deref for WaveFormat {
type Target = mmreg::WAVEFORMATEX;
fn deref(&self) -> &Self::Target {
match *self {
WaveFormat::Ex(ref f) => f,
WaveFormat::Extensible(ref f) => &f.Format,
}
}
}
impl DerefMut for WaveFormat {
fn deref_mut(&mut self) -> &mut Self::Target {
match *self {
WaveFormat::Ex(ref mut f) => f,
WaveFormat::Extensible(ref mut f) => &mut f.Format,
}
}
}
unsafe fn immendpoint_from_immdevice(device: *const IMMDevice) -> *mut IMMEndpoint {
let mut endpoint: *mut IMMEndpoint = mem::uninitialized();
check_result((*device).QueryInterface(&IMMEndpoint::uuidof(), &mut endpoint as *mut _ as *mut _))
.expect("could not query IMMDevice interface for IMMEndpoint");
endpoint
}
unsafe fn data_flow_from_immendpoint(endpoint: *const IMMEndpoint) -> EDataFlow {
let mut data_flow = mem::uninitialized();
check_result((*endpoint).GetDataFlow(&mut data_flow))
.expect("could not get endpoint data_flow");
data_flow
}
// Given the audio client and format, returns whether or not the format is supported.
pub unsafe fn is_format_supported(
client: *const IAudioClient,
waveformatex_ptr: *const mmreg::WAVEFORMATEX,
) -> Result<bool, SupportedFormatsError>
{
/*
// `IsFormatSupported` checks whether the format is supported and fills
// a `WAVEFORMATEX`
let mut dummy_fmt_ptr: *mut mmreg::WAVEFORMATEX = mem::uninitialized();
let hresult =
(*audio_client)
.IsFormatSupported(share_mode, &format_attempt.Format, &mut dummy_fmt_ptr);
// we free that `WAVEFORMATEX` immediately after because we don't need it
if !dummy_fmt_ptr.is_null() {
CoTaskMemFree(dummy_fmt_ptr as *mut _);
}
// `IsFormatSupported` can return `S_FALSE` (which means that a compatible format
// has been found) but we also treat this as an error
match (hresult, check_result(hresult)) {
(_, Err(ref e))
if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => {
(*audio_client).Release();
return Err(BuildStreamError::DeviceNotAvailable);
},
(_, Err(e)) => {
(*audio_client).Release();
panic!("{:?}", e);
},
(winerror::S_FALSE, _) => {
(*audio_client).Release();
return Err(BuildStreamError::FormatNotSupported);
},
(_, Ok(())) => (),
};
*/
// Check if the given format is supported.
let is_supported = |waveformatex_ptr, mut closest_waveformatex_ptr| {
let result = (*client).IsFormatSupported(
AUDCLNT_SHAREMODE_SHARED,
waveformatex_ptr,
&mut closest_waveformatex_ptr,
);
// `IsFormatSupported` can return `S_FALSE` (which means that a compatible format
// has been found, but not an exact match) so we also treat this as unsupported.
match (result, check_result(result)) {
(_, Err(ref e)) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => {
return Err(SupportedFormatsError::DeviceNotAvailable);
},
(_, Err(_)) => {
Ok(false)
},
(winerror::S_FALSE, _) => {
Ok(false)
},
(_, Ok(())) => {
Ok(true)
},
}
};
// First we want to retrieve a pointer to the `WAVEFORMATEX`.
// Although `GetMixFormat` writes the format to a given `WAVEFORMATEX` pointer,
// the pointer itself may actually point to a `WAVEFORMATEXTENSIBLE` structure.
// We check the wFormatTag to determine this and get a pointer to the correct type.
match (*waveformatex_ptr).wFormatTag {
mmreg::WAVE_FORMAT_PCM | mmreg::WAVE_FORMAT_IEEE_FLOAT => {
let mut closest_waveformatex = *waveformatex_ptr;
let closest_waveformatex_ptr = &mut closest_waveformatex as *mut _;
is_supported(waveformatex_ptr, closest_waveformatex_ptr)
},
mmreg::WAVE_FORMAT_EXTENSIBLE => {
let waveformatextensible_ptr =
waveformatex_ptr as *const mmreg::WAVEFORMATEXTENSIBLE;
let mut closest_waveformatextensible = *waveformatextensible_ptr;
let closest_waveformatextensible_ptr =
&mut closest_waveformatextensible as *mut _;
let closest_waveformatex_ptr =
closest_waveformatextensible_ptr as *mut mmreg::WAVEFORMATEX;
is_supported(waveformatex_ptr, closest_waveformatex_ptr)
},
_ => Ok(false),
}
}
// Get a cpal Format from a WAVEFORMATEX.
unsafe fn format_from_waveformatex_ptr(
waveformatex_ptr: *const mmreg::WAVEFORMATEX,
) -> Option<Format>
{
fn cmp_guid(a: &GUID, b: &GUID) -> bool {
a.Data1 == b.Data1
&& a.Data2 == b.Data2
&& a.Data3 == b.Data3
&& a.Data4 == b.Data4
}
let data_type = match ((*waveformatex_ptr).wBitsPerSample, (*waveformatex_ptr).wFormatTag) {
(16, mmreg::WAVE_FORMAT_PCM) => SampleFormat::I16,
(32, mmreg::WAVE_FORMAT_IEEE_FLOAT) => SampleFormat::F32,
(n_bits, mmreg::WAVE_FORMAT_EXTENSIBLE) => {
let waveformatextensible_ptr = waveformatex_ptr as *const mmreg::WAVEFORMATEXTENSIBLE;
let sub = (*waveformatextensible_ptr).SubFormat;
if n_bits == 16 && cmp_guid(&sub, &ksmedia::KSDATAFORMAT_SUBTYPE_PCM) {
SampleFormat::I16
} else if n_bits == 32 && cmp_guid(&sub, &ksmedia::KSDATAFORMAT_SUBTYPE_IEEE_FLOAT) {
SampleFormat::F32
} else {
return None;
}
},
// Unknown data format returned by GetMixFormat.
_ => return None,
};
let format = Format {
channels: (*waveformatex_ptr).nChannels as _,
sample_rate: SampleRate((*waveformatex_ptr).nSamplesPerSec),
data_type: data_type,
};
Some(format)
}
unsafe impl Send for Device {
}
unsafe impl Sync for Device {
}
impl Device {
pub fn name(&self) -> Result<String, DeviceNameError> {
unsafe {
// Open the device's property store.
let mut property_store = ptr::null_mut();
(*self.device).OpenPropertyStore(coml2api::STGM_READ, &mut property_store);
// Get the endpoint's friendly-name property.
let mut property_value = mem::zeroed();
if let Err(err) = check_result(
(*property_store).GetValue(
&devpkey::DEVPKEY_Device_FriendlyName as *const _ as *const _,
&mut property_value
)
) {
let description = format!("failed to retrieve name from property store: {}", err);
let err = BackendSpecificError { description };
return Err(err.into());
}
// Read the friendly-name from the union data field, expecting a *const u16.
if property_value.vt != wtypes::VT_LPWSTR as _ {
let description =
format!("property store produced invalid data: {:?}", property_value.vt);
let err = BackendSpecificError { description };
return Err(err.into());
}
let ptr_usize: usize = *(&property_value.data as *const _ as *const usize);
let ptr_utf16 = ptr_usize as *const u16;
// Find the length of the friendly name.
let mut len = 0;
while *ptr_utf16.offset(len) != 0 {
len += 1;
}
// Create the utf16 slice and covert it into a string.
let name_slice = slice::from_raw_parts(ptr_utf16, len as usize);
let name_os_string: OsString = OsStringExt::from_wide(name_slice);
let name_string = match name_os_string.into_string() {
Ok(string) => string,
Err(os_string) => os_string.to_string_lossy().into(),
};
// Clean up the property.
PropVariantClear(&mut property_value);
Ok(name_string)
}
}
#[inline]
fn from_immdevice(device: *mut IMMDevice) -> Self {
Device {
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 IAudioClient = unsafe {
let mut audio_client = mem::uninitialized();
let hresult = (*self.device).Activate(&IID_IAudioClient,
CLSCTX_ALL,
ptr::null_mut(),
&mut audio_client);
// can fail if the device has been disconnected since we enumerated it, or if
// the device doesn't support playback for some reason
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 IAudioClient, IoError> {
let mut lock = self.ensure_future_audio_client()?;
let client = lock.unwrap().0;
*lock = None;
Ok(client)
}
// There is no way to query the list of all formats that are supported by the
// audio processor, so instead we just trial some commonly supported formats.
//
// Common formats are trialed by first getting the default format (returned via
// `GetMixFormat`) and then mutating that format with common sample rates and
// querying them via `IsFormatSupported`.
//
// When calling `IsFormatSupported` with the shared-mode audio engine, only the default
// number of channels seems to be supported. Any more or less returns an invalid
// parameter error. Thus we just assume that the default number of channels is the only
// number supported.
fn supported_formats(&self) -> Result<SupportedInputFormats, SupportedFormatsError> {
// initializing COM because we call `CoTaskMemFree` to release the format.
com::com_initialized();
// Retrieve the `IAudioClient`.
let lock = match self.ensure_future_audio_client() {
Ok(lock) => lock,
Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => {
return Err(SupportedFormatsError::DeviceNotAvailable)
}
Err(e) => {
let description = format!("{}", e);
let err = BackendSpecificError { description };
return Err(err.into());
},
};
let client = lock.unwrap().0;
unsafe {
// Retrieve the pointer to the default WAVEFORMATEX.
let mut default_waveformatex_ptr = WaveFormatExPtr(mem::uninitialized());
match check_result((*client).GetMixFormat(&mut default_waveformatex_ptr.0)) {
Ok(()) => (),
Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => {
return Err(SupportedFormatsError::DeviceNotAvailable);
},
Err(e) => {
let description = format!("{}", e);
let err = BackendSpecificError { description };
return Err(err.into());
},
};
// If the default format can't succeed we have no hope of finding other formats.
assert_eq!(try!(is_format_supported(client, default_waveformatex_ptr.0)), true);
// Copy the format to use as a test format (as to avoid mutating the original format).
let mut test_format = {
match WaveFormat::copy_from_waveformatex_ptr(default_waveformatex_ptr.0) {
Some(f) => f,
// If the format is neither EX or EXTENSIBLE we don't know how to work with it.
None => return Ok(vec![].into_iter()),
}
};
// Begin testing common sample rates.
//
// NOTE: We should really be testing for whole ranges here, but it is infeasible to
// test every sample rate up to the overflow limit as the `IsFormatSupported` method is
// quite slow.
let mut supported_sample_rates: Vec<u32> = Vec::new();
for &rate in COMMON_SAMPLE_RATES {
let rate = rate.0 as DWORD;
test_format.nSamplesPerSec = rate;
test_format.nAvgBytesPerSec =
rate * (*default_waveformatex_ptr.0).nBlockAlign as DWORD;
if try!(is_format_supported(client, test_format.as_ptr())) {
supported_sample_rates.push(rate);
}
}
// If the common rates don't include the default one, add the default.
let default_sr = (*default_waveformatex_ptr.0).nSamplesPerSec as _;
if !supported_sample_rates.iter().any(|&r| r == default_sr) {
supported_sample_rates.push(default_sr);
}
// Reset the sample rate on the test format now that we're done.
test_format.nSamplesPerSec = (*default_waveformatex_ptr.0).nSamplesPerSec;
test_format.nAvgBytesPerSec = (*default_waveformatex_ptr.0).nAvgBytesPerSec;
// TODO: Test the different sample formats?
// Create the supported formats.
let mut format = match format_from_waveformatex_ptr(default_waveformatex_ptr.0) {
Some(fmt) => fmt,
None => {
let description =
"could not create a `cpal::Format` from a `WAVEFORMATEX`".to_string();
let err = BackendSpecificError { description };
return Err(err.into());
}
};
let mut supported_formats = Vec::with_capacity(supported_sample_rates.len());
for rate in supported_sample_rates {
format.sample_rate = SampleRate(rate as _);
supported_formats.push(SupportedFormat::from(format.clone()));
}
Ok(supported_formats.into_iter())
}
}
pub fn supported_input_formats(&self) -> Result<SupportedInputFormats, SupportedFormatsError> {
if self.data_flow() == eCapture {
self.supported_formats()
// If it's an output device, assume no input formats.
} else {
Ok(vec![].into_iter())
}
}
pub fn supported_output_formats(&self) -> Result<SupportedOutputFormats, SupportedFormatsError> {
if self.data_flow() == eRender {
self.supported_formats()
// If it's an input device, assume no output formats.
} else {
Ok(vec![].into_iter())
}
}
// We always create voices in shared mode, therefore all samples go through an audio
// processor to mix them together.
//
// One format is guaranteed to be supported, the one returned by `GetMixFormat`.
fn default_format(&self) -> Result<Format, DefaultFormatError> {
// initializing COM because we call `CoTaskMemFree`
com::com_initialized();
let lock = match self.ensure_future_audio_client() {
Ok(lock) => lock,
Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => {
return Err(DefaultFormatError::DeviceNotAvailable)
}
Err(e) => {
let description = format!("{}", e);
let err = BackendSpecificError { description };
return Err(err.into());
}
};
let client = lock.unwrap().0;
unsafe {
let mut format_ptr = WaveFormatExPtr(mem::uninitialized());
match check_result((*client).GetMixFormat(&mut format_ptr.0)) {
Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => {
return Err(DefaultFormatError::DeviceNotAvailable);
},
Err(e) => {
let description = format!("{}", e);
let err = BackendSpecificError { description };
return Err(err.into());
},
Ok(()) => (),
};
format_from_waveformatex_ptr(format_ptr.0)
.ok_or(DefaultFormatError::StreamTypeNotSupported)
}
}
fn data_flow(&self) -> EDataFlow {
let endpoint = Endpoint::from(self.device as *const _);
endpoint.data_flow()
}
pub fn default_input_format(&self) -> Result<Format, DefaultFormatError> {
if self.data_flow() == eCapture {
self.default_format()
} else {
Err(DefaultFormatError::StreamTypeNotSupported)
}
}
pub fn default_output_format(&self) -> Result<Format, DefaultFormatError> {
let data_flow = self.data_flow();
if data_flow == eRender {
self.default_format()
} else {
Err(DefaultFormatError::StreamTypeNotSupported)
}
}
}
impl PartialEq for Device {
#[inline]
fn eq(&self, other: &Device) -> bool {
// Use case: In oder to check whether the default device has changed
// the client code might need to compare the previous default device with the current one.
// The pointer comparison (`self.device == other.device`) don't work there,
// because the pointers are different even when the default device stays the same.
//
// In this code section we're trying to use the GetId method for the device comparison, cf.
// https://docs.microsoft.com/en-us/windows/desktop/api/mmdeviceapi/nf-mmdeviceapi-immdevice-getid
unsafe {
struct IdRAII (LPWSTR);
/// RAII for device IDs.
impl Drop for IdRAII {
fn drop(&mut self) {
unsafe {CoTaskMemFree(self.0 as *mut c_void)}
}
}
let mut id1: LPWSTR = ptr::null_mut();
let rc1 = (*self.device).GetId(&mut id1);
// GetId only fails with E_OUTOFMEMORY and if it does, we're probably dead already.
// Plus it won't do to change the device comparison logic unexpectedly.
if rc1 != winerror::S_OK {panic! ("cpal: GetId failure: {}", rc1)}
let id1 = IdRAII(id1);
let mut id2: LPWSTR = ptr::null_mut();
let rc2 = (*other.device).GetId(&mut id2);
if rc2 != winerror::S_OK {panic! ("cpal: GetId failure: {}", rc1)}
let id2 = IdRAII(id2);
// 16-bit null-terminated comparison.
let mut offset = 0;
loop {
let w1: WCHAR = *id1.0.offset(offset);
let w2: WCHAR = *id2.0.offset(offset);
if w1 == 0 && w2 == 0 {return true}
if w1 != w2 {return false}
offset += 1;
}
}
}
}
impl Eq for Device {
}
impl Clone for Device {
#[inline]
fn clone(&self) -> Device {
unsafe {
(*self.device).AddRef();
}
Device {
device: self.device,
future_audio_client: self.future_audio_client.clone(),
}
}
}
impl fmt::Debug for Device {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Device")
.field("device", &self.device)
.field("name", &self.name())
.finish()
}
}
impl Drop for Device {
#[inline]
fn drop(&mut self) {
unsafe {
(*self.device).Release();
}
if let Some(client) = self.future_audio_client.lock().unwrap().take() {
unsafe {
(*client.0).Release();
}
}
}
}
impl Drop for Endpoint {
fn drop(&mut self) {
unsafe {
(*self.endpoint).Release();
}
}
}
impl From<*const IMMDevice> for Endpoint {
fn from(device: *const IMMDevice) -> Self {
unsafe {
let endpoint = immendpoint_from_immdevice(device);
Endpoint { endpoint: endpoint }
}
}
}
impl Endpoint {
fn data_flow(&self) -> EDataFlow {
unsafe {
data_flow_from_immendpoint(self.endpoint)
}
}
}
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 IMMDeviceEnumerator = mem::uninitialized();
let hresult = CoCreateInstance(
&CLSID_MMDeviceEnumerator,
ptr::null_mut(),
CLSCTX_ALL,
&IMMDeviceEnumerator::uuidof(),
&mut enumerator as *mut *mut IMMDeviceEnumerator as *mut _,
);
check_result(hresult).unwrap();
Enumerator(enumerator)
}
};
}
/// RAII object around `IMMDeviceEnumerator`.
struct Enumerator(*mut 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 `Devices`.
pub struct Devices {
collection: *mut IMMDeviceCollection,
total_count: u32,
next_item: u32,
}
impl Devices {
pub fn new() -> Result<Self, DevicesError> {
unsafe {
let mut collection: *mut IMMDeviceCollection = mem::uninitialized();
// can fail because of wrong parameters (should never happen) or out of memory
check_result_backend_specific(
(*ENUMERATOR.0).EnumAudioEndpoints(
eAll,
DEVICE_STATE_ACTIVE,
&mut collection,
)
)?;
let mut count = mem::uninitialized();
// can fail if the parameter is null, which should never happen
check_result_backend_specific((*collection).GetCount(&mut count))?;
Ok(Devices {
collection: collection,
total_count: count,
next_item: 0,
})
}
}
}
unsafe impl Send for Devices {
}
unsafe impl Sync for Devices {
}
impl Drop for Devices {
#[inline]
fn drop(&mut self) {
unsafe {
(*self.collection).Release();
}
}
}
impl Iterator for Devices {
type Item = Device;
fn next(&mut self) -> Option<Device> {
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(Device::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))
}
}
fn default_device(data_flow: EDataFlow) -> Option<Device> {
unsafe {
let mut device = mem::uninitialized();
let hres = (*ENUMERATOR.0)
.GetDefaultAudioEndpoint(data_flow, eConsole, &mut device);
if let Err(_err) = check_result(hres) {
return None; // TODO: check specifically for `E_NOTFOUND`, and panic otherwise
}
Some(Device::from_immdevice(device))
}
}
pub fn default_input_device() -> Option<Device> {
default_device(eCapture)
}
pub fn default_output_device() -> Option<Device> {
default_device(eRender)
}