use crate::{
error::ResultExt,
host::{com::ComString, ErrorCallbackArc},
BufferSize, Data, DeviceDescription, DeviceDescriptionBuilder, DeviceDirection, DeviceId,
DeviceType, Error, ErrorKind, FrameCount, InputCallbackInfo, InterfaceType, OutputCallbackInfo,
SampleFormat, SampleRate, StreamConfig, SupportedBufferSize, SupportedStreamConfig,
SupportedStreamConfigRange, COMMON_SAMPLE_RATES,
};
impl From<Audio::EDataFlow> for DeviceDirection {
fn from(data_flow: Audio::EDataFlow) -> Self {
if data_flow == Audio::eCapture {
DeviceDirection::Input
} else if data_flow == Audio::eRender {
DeviceDirection::Output
} else {
DeviceDirection::Unknown
}
}
}
use std::{
ffi::OsString,
fmt,
hash::Hash,
mem,
os::windows::ffi::OsStringExt,
ptr, slice,
sync::{Arc, Mutex, MutexGuard, OnceLock},
time::Duration,
};
use windows::{
core::{Interface, GUID},
Win32::{
Devices::Properties,
Foundation::{ERROR_TIMEOUT, PROPERTYKEY},
Media::{Audio, Audio::IAudioRenderClient, KernelStreaming, Multimedia},
System::{
Com,
Com::{StructuredStorage, STGM_READ},
Threading,
Variant::{VT_LPWSTR, VT_UI4},
},
UI::Shell::PropertiesSystem::IPropertyStore,
},
};
use super::stream::{AudioClientFlow, DefaultDeviceMonitor, Stream, StreamInner};
pub use crate::iter::{SupportedInputConfigs, SupportedOutputConfigs};
use crate::{host::com, traits::DeviceTrait};
const PKEY_AUDIOENDPOINT_FORMFACTOR: PROPERTYKEY = PROPERTYKEY {
fmtid: GUID::from_u128(0x1da5d803_d492_4edd_8c23_e0c0ffee7f0e),
pid: 0,
};
const PKEY_AUDIOENDPOINT_JACKSUBTYPE: PROPERTYKEY = PROPERTYKEY {
fmtid: GUID::from_u128(0x1da5d803_d492_4edd_8c23_e0c0ffee7f0e),
pid: 8,
};
const DEFAULT_FLAGS: u32 = Audio::AUDCLNT_STREAMFLAGS_EVENTCALLBACK;
#[derive(Clone)]
struct IAudioClientWrapper(Audio::IAudioClient);
unsafe impl Send for IAudioClientWrapper {}
unsafe impl Sync for IAudioClientWrapper {}
#[derive(Clone, Debug)]
enum DeviceHandle {
DefaultOutput,
DefaultInput,
Specific(Audio::IMMDevice),
}
#[derive(Clone)]
pub struct Device {
device: DeviceHandle,
future_audio_client: Arc<Mutex<Option<IAudioClientWrapper>>>, }
impl DeviceTrait for Device {
type SupportedInputConfigs = SupportedInputConfigs;
type SupportedOutputConfigs = SupportedOutputConfigs;
type Stream = Stream;
fn description(&self) -> Result<DeviceDescription, Error> {
Device::description(self)
}
fn id(&self) -> Result<DeviceId, Error> {
Self::id(self)
}
fn supports_input(&self) -> bool {
self.data_flow() == Audio::eCapture
}
fn supports_output(&self) -> bool {
self.data_flow() == Audio::eRender
}
fn supported_input_configs(&self) -> Result<Self::SupportedInputConfigs, Error> {
Self::supported_input_configs(self)
}
fn supported_output_configs(&self) -> Result<Self::SupportedOutputConfigs, Error> {
Self::supported_output_configs(self)
}
fn default_input_config(&self) -> Result<SupportedStreamConfig, Error> {
Self::default_input_config(self)
}
fn default_output_config(&self) -> Result<SupportedStreamConfig, Error> {
Self::default_output_config(self)
}
fn build_input_stream_raw<D, E>(
&self,
config: StreamConfig,
sample_format: SampleFormat,
data_callback: D,
error_callback: E,
timeout: Option<Duration>,
) -> Result<Self::Stream, Error>
where
D: FnMut(&Data, &InputCallbackInfo) + Send + 'static,
E: FnMut(Error) + Send + 'static,
{
let stream_inner = self.build_input_stream_raw_inner(config, sample_format, timeout)?;
let error_callback: ErrorCallbackArc = Arc::new(Mutex::new(error_callback));
let monitor = self.default_device_monitor()?;
let stream = Stream::new_input(stream_inner, data_callback, error_callback, monitor)?;
stream.signal_ready();
Ok(stream)
}
fn build_output_stream_raw<D, E>(
&self,
config: StreamConfig,
sample_format: SampleFormat,
data_callback: D,
error_callback: E,
timeout: Option<Duration>,
) -> Result<Self::Stream, Error>
where
D: FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static,
E: FnMut(Error) + Send + 'static,
{
let stream_inner = self.build_output_stream_raw_inner(config, sample_format, timeout)?;
let error_callback: ErrorCallbackArc = Arc::new(Mutex::new(error_callback));
let monitor = self.default_device_monitor()?;
let stream = Stream::new_output(stream_inner, data_callback, error_callback, monitor)?;
stream.signal_ready();
Ok(stream)
}
}
struct Endpoint {
endpoint: Audio::IMMEndpoint,
}
struct WaveFormatExPtr(*mut Audio::WAVEFORMATEX);
impl Drop for WaveFormatExPtr {
fn drop(&mut self) {
unsafe {
Com::CoTaskMemFree(Some(self.0 as *mut _));
}
}
}
unsafe fn immendpoint_from_immdevice(device: Audio::IMMDevice) -> Audio::IMMEndpoint {
device
.cast::<Audio::IMMEndpoint>()
.expect("could not query IMMDevice interface for IMMEndpoint")
}
unsafe fn data_flow_from_immendpoint(endpoint: &Audio::IMMEndpoint) -> Audio::EDataFlow {
endpoint
.GetDataFlow()
.expect("could not get endpoint data_flow")
}
pub unsafe fn is_format_supported(
client: &Audio::IAudioClient,
waveformatex_ptr: *const Audio::WAVEFORMATEX,
) -> Result<bool, Error> {
let mut closest_match: *mut Audio::WAVEFORMATEX = ptr::null_mut();
let hr = client.IsFormatSupported(
Audio::AUDCLNT_SHAREMODE_SHARED,
waveformatex_ptr,
Some(&mut closest_match),
);
if !closest_match.is_null() {
let _free = WaveFormatExPtr(closest_match);
}
Ok(hr.0 == 0)
}
unsafe fn format_from_waveformatex_ptr(
waveformatex_ptr: *const Audio::WAVEFORMATEX,
audio_client: &Audio::IAudioClient,
) -> Option<SupportedStreamConfig> {
fn cmp_guid(a: &GUID, b: &GUID) -> bool {
(a.data1, a.data2, a.data3, a.data4) == (b.data1, b.data2, b.data3, b.data4)
}
let sample_format = match (
(*waveformatex_ptr).wBitsPerSample,
(*waveformatex_ptr).wFormatTag as u32,
) {
(8, Audio::WAVE_FORMAT_PCM) => SampleFormat::U8,
(16, Audio::WAVE_FORMAT_PCM) => SampleFormat::I16,
(32, Multimedia::WAVE_FORMAT_IEEE_FLOAT) => SampleFormat::F32,
(64, Multimedia::WAVE_FORMAT_IEEE_FLOAT) => SampleFormat::F64,
(n_bits, KernelStreaming::WAVE_FORMAT_EXTENSIBLE) => {
let waveformatextensible_ptr = waveformatex_ptr as *const Audio::WAVEFORMATEXTENSIBLE;
let sub = unsafe { (*waveformatextensible_ptr).SubFormat };
let valid_bits = unsafe { (*waveformatextensible_ptr).Samples.wValidBitsPerSample };
if cmp_guid(&sub, &KernelStreaming::KSDATAFORMAT_SUBTYPE_PCM) {
match n_bits {
8 => SampleFormat::U8,
16 => SampleFormat::I16,
24 => SampleFormat::I24,
32 if valid_bits == 24 => SampleFormat::I24,
32 => SampleFormat::I32,
64 => SampleFormat::I64,
_ => return None,
}
} else if cmp_guid(&sub, &Multimedia::KSDATAFORMAT_SUBTYPE_IEEE_FLOAT) {
match n_bits {
32 => SampleFormat::F32,
64 => SampleFormat::F64,
_ => return None,
}
} else {
return None;
}
}
_ => return None,
};
let sample_rate = (*waveformatex_ptr).nSamplesPerSec;
let (mut min_buffer_duration, mut max_buffer_duration) = (0, 0);
let buffer_size_is_limited = audio_client
.cast::<Audio::IAudioClient2>()
.and_then(|audio_client| {
audio_client.GetBufferSizeLimits(
waveformatex_ptr,
true,
&mut min_buffer_duration,
&mut max_buffer_duration,
)
})
.is_ok();
let buffer_size = if buffer_size_is_limited {
SupportedBufferSize::Range {
min: buffer_duration_to_frames(min_buffer_duration, sample_rate),
max: buffer_duration_to_frames(max_buffer_duration, sample_rate),
}
} else {
SupportedBufferSize::Unknown
};
let format = SupportedStreamConfig {
channels: (*waveformatex_ptr).nChannels as _,
sample_rate,
buffer_size,
sample_format,
};
Some(format)
}
unsafe impl Send for Device {}
unsafe impl Sync for Device {}
fn jacksubtype_to_interface_type(guid_str: &str) -> Option<InterfaceType> {
let guid_upper = guid_str.to_uppercase();
let typ = match guid_upper.as_str() {
"{D9E55EA0-0C89-4692-84FF-EB3C4B0D172F}" => InterfaceType::Hdmi,
"{E47E4031-3EA6-418D-8F9B-B73843CCB2AD}" => InterfaceType::DisplayPort,
"{DFF21CE1-F70F-11D0-B917-00A0C9223196}" => InterfaceType::Spdif,
_ => return None,
};
Some(typ)
}
fn form_factor_to_types(form_factor: u32) -> (DeviceType, Option<InterfaceType>) {
match form_factor {
0 => (DeviceType::Unknown, Some(InterfaceType::Network)), 1 => (DeviceType::Speaker, None), 2 => (DeviceType::Unknown, Some(InterfaceType::Line)), 3 => (DeviceType::Headphones, None), 4 => (DeviceType::Microphone, None), 5 => (DeviceType::Headset, None), 6 => (DeviceType::Handset, None), 7 => (DeviceType::Unknown, None), 8 => (DeviceType::Unknown, Some(InterfaceType::Spdif)), 9 => (DeviceType::Unknown, Some(InterfaceType::Hdmi)), _ => (DeviceType::Unknown, None), }
}
fn enumerator_to_interface_type(enumerator: &str) -> Option<InterfaceType> {
let typ = match enumerator.to_uppercase().as_str() {
"HDAUDIO" => InterfaceType::BuiltIn,
"USB" => InterfaceType::Usb,
"BTHENUM" => InterfaceType::Bluetooth,
"MMDEVAPI" | "SW" => InterfaceType::Virtual,
_ => return None,
};
Some(typ)
}
unsafe fn activate_audio_interface_sync(
device_interface_path: windows::core::PWSTR,
activation_timeout: Option<Duration>,
) -> windows::core::Result<Audio::IAudioClient> {
use windows::core::IUnknown;
#[windows::core::implement(Audio::IActivateAudioInterfaceCompletionHandler)]
struct CompletionHandler(std::sync::mpsc::Sender<windows::core::Result<IUnknown>>);
fn retrieve_result(
operation: &Audio::IActivateAudioInterfaceAsyncOperation,
) -> windows::core::Result<IUnknown> {
let mut result = windows::core::HRESULT::default();
let mut interface: Option<IUnknown> = None;
unsafe {
operation.GetActivateResult(&mut result, &mut interface)?;
}
result.ok()?;
interface.ok_or_else(|| {
windows::core::Error::new(
Audio::AUDCLNT_E_DEVICE_INVALIDATED,
"audio interface not available after activation",
)
})
}
impl Audio::IActivateAudioInterfaceCompletionHandler_Impl for CompletionHandler_Impl {
fn ActivateCompleted(
&self,
operation: windows::core::Ref<'_, Audio::IActivateAudioInterfaceAsyncOperation>,
) -> windows::core::Result<()> {
let result = operation.ok().and_then(retrieve_result);
let _ = self.0.send(result);
Ok(())
}
}
let (tx, rx) = std::sync::mpsc::channel();
let handler: Audio::IActivateAudioInterfaceCompletionHandler = CompletionHandler(tx).into();
Audio::ActivateAudioInterfaceAsync(
device_interface_path,
&Audio::IAudioClient::IID,
None,
&handler,
)?;
let result = if let Some(dur) = activation_timeout {
rx.recv_timeout(dur).map_err(|_| {
windows::core::Error::new(
ERROR_TIMEOUT.to_hresult(),
"timeout waiting for audio interface activation",
)
})?
} else {
rx.recv().expect("activation channel closed; this is a bug")
};
result?.cast()
}
impl Device {
pub fn description(&self) -> Result<DeviceDescription, Error> {
let device = self.immdevice().ok_or_else(|| {
Error::with_message(ErrorKind::DeviceNotAvailable, "Default device not found")
})?;
unsafe {
let property_store = device
.OpenPropertyStore(STGM_READ)
.expect("could not open property store");
let friendly_name = get_property_string(
&property_store,
&Properties::DEVPKEY_Device_FriendlyName as *const _ as *const _,
);
let device_desc = get_property_string(
&property_store,
&Properties::DEVPKEY_Device_DeviceDesc as *const _ as *const _,
);
let interface_name = get_property_string(
&property_store,
&Properties::DEVPKEY_DeviceInterface_FriendlyName as *const _ as *const _,
);
let enumerator_name = get_property_string(
&property_store,
&Properties::DEVPKEY_Device_EnumeratorName as *const _ as *const _,
);
let form_factor = get_property_u32(
&property_store,
&PKEY_AUDIOENDPOINT_FORMFACTOR as *const _ as *const _,
);
let jack_subtype = get_property_string(
&property_store,
&PKEY_AUDIOENDPOINT_JACKSUBTYPE as *const _ as *const _,
);
let name = friendly_name.or(device_desc).ok_or_else(|| {
Error::with_message(
ErrorKind::DeviceNotAvailable,
"Failed to retrieve device name",
)
})?;
let direction = self.data_flow().into();
let (device_type, mut interface_type) = form_factor
.map(form_factor_to_types)
.unwrap_or((DeviceType::Unknown, None));
if let Some(ref enumerator) = enumerator_name {
if let Some(itype) = enumerator_to_interface_type(enumerator) {
interface_type = Some(itype);
}
}
if let Some(ref jack_guid) = jack_subtype {
if let Some(itype) = jacksubtype_to_interface_type(jack_guid) {
interface_type = Some(itype);
}
}
let mut builder = DeviceDescriptionBuilder::new(name)
.direction(direction)
.device_type(device_type);
if let Some(itype) = interface_type {
builder = builder.interface_type(itype);
}
if let Some(iface_name) = interface_name {
builder = builder.driver(iface_name);
}
Ok(builder.build())
}
}
fn id(&self) -> Result<DeviceId, Error> {
let device = self.immdevice().ok_or_else(|| {
Error::with_message(ErrorKind::DeviceNotAvailable, "Default device not found")
})?;
unsafe {
match device.GetId() {
Ok(pwstr) => match pwstr.to_string() {
Ok(id_str) => Ok(DeviceId::new(crate::platform::HostId::Wasapi, id_str)),
Err(e) => Err(Error::with_message(
ErrorKind::BackendError,
format!("Failed to convert device ID to string: {e}"),
)),
},
Err(e) => Err(Error::from(e)),
}
}
}
fn from_immdevice(device: Audio::IMMDevice) -> Self {
Device {
device: DeviceHandle::Specific(device),
future_audio_client: Arc::new(Mutex::new(None)),
}
}
fn default_output() -> Self {
Device {
device: DeviceHandle::DefaultOutput,
future_audio_client: Arc::new(Mutex::new(None)),
}
}
fn default_input() -> Self {
Device {
device: DeviceHandle::DefaultInput,
future_audio_client: Arc::new(Mutex::new(None)),
}
}
pub fn immdevice(&self) -> Option<Audio::IMMDevice> {
match &self.device {
DeviceHandle::DefaultOutput => current_default_endpoint(Audio::eRender),
DeviceHandle::DefaultInput => current_default_endpoint(Audio::eCapture),
DeviceHandle::Specific(device) => Some(device.clone()),
}
}
fn default_device_monitor(&self) -> Result<Option<DefaultDeviceMonitor>, Error> {
let flow = match &self.device {
DeviceHandle::DefaultOutput => Audio::eRender,
DeviceHandle::DefaultInput => Audio::eCapture,
DeviceHandle::Specific(_) => return Ok(None),
};
let enumerator = get_enumerator().0.clone();
DefaultDeviceMonitor::new(enumerator, flow).map(Some)
}
fn ensure_future_audio_client(
&self,
activation_timeout: Option<Duration>,
) -> Result<MutexGuard<'_, Option<IAudioClientWrapper>>, Error> {
let mut lock = self.future_audio_client.lock().map_err(|_| {
Error::with_message(ErrorKind::StreamInvalidated, "Stream lock poisoned")
})?;
if lock.is_some() {
return Ok(lock);
}
let audio_client: Audio::IAudioClient = unsafe {
match &self.device {
DeviceHandle::DefaultOutput => {
let path = Com::StringFromIID(&Audio::DEVINTERFACE_AUDIO_RENDER)
.map_err(Error::from)?;
let _guard = ComString(path);
activate_audio_interface_sync(path, activation_timeout).map_err(Error::from)?
}
DeviceHandle::DefaultInput => {
let path = Com::StringFromIID(&Audio::DEVINTERFACE_AUDIO_CAPTURE)
.map_err(Error::from)?;
let _guard = ComString(path);
activate_audio_interface_sync(path, activation_timeout).map_err(Error::from)?
}
DeviceHandle::Specific(device) => {
device
.Activate(Com::CLSCTX_ALL, None)
.map_err(Error::from)?
}
}
};
*lock = Some(IAudioClientWrapper(audio_client));
Ok(lock)
}
pub(crate) fn build_audioclient(
&self,
activation_timeout: Option<Duration>,
) -> Result<Audio::IAudioClient, Error> {
let mut lock = self.ensure_future_audio_client(activation_timeout)?;
Ok(lock.take().unwrap().0)
}
fn supported_formats(&self) -> Result<SupportedInputConfigs, Error> {
com::com_initialized();
let lock = self
.ensure_future_audio_client(None)
.context("Failed to get audio client")?;
let client = &lock.as_ref().unwrap().0;
unsafe {
let default_waveformatex_ptr = client
.GetMixFormat()
.map(WaveFormatExPtr)
.context("Failed to get mix format")?;
if !is_format_supported(client, default_waveformatex_ptr.0)? {
return Err(Error::with_message(
ErrorKind::UnsupportedConfig,
"Could not determine support for default audio format",
));
}
let format = match format_from_waveformatex_ptr(default_waveformatex_ptr.0, client) {
Some(fmt) => fmt,
None => {
return Err(Error::with_message(
ErrorKind::UnsupportedConfig,
"Default audio format could not be mapped to a supported configuration",
));
}
};
let is_output = self.data_flow() == Audio::eRender;
let mut sample_rates: Vec<SampleRate> = COMMON_SAMPLE_RATES
.iter()
.copied()
.filter(|&r| {
!is_output || (OUTPUT_MIN_SAMPLE_RATE..=OUTPUT_MAX_SAMPLE_RATE).contains(&r)
})
.collect();
if !sample_rates.contains(&format.sample_rate) {
sample_rates.push(format.sample_rate);
}
let mut default_period_hns: i64 = 0;
let device_period_hns = if client
.GetDevicePeriod(Some(&mut default_period_hns), None)
.is_ok()
&& default_period_hns > 0
{
Some(default_period_hns)
} else {
None
};
let mut supported_formats = Vec::new();
for sample_rate in sample_rates {
let buffer_size = match format.buffer_size {
SupportedBufferSize::Unknown => device_period_hns
.map(|p_hns| {
let frames = buffer_duration_to_frames(p_hns, sample_rate);
SupportedBufferSize::Range {
min: frames,
max: frames,
}
})
.unwrap_or(SupportedBufferSize::Unknown),
other => other,
};
for sample_format in WAVEFORMATEXTENSIBLE_SAMPLE_FORMATS {
if let Some(waveformat) = config_to_waveformatextensible(
StreamConfig {
channels: format.channels,
sample_rate,
buffer_size: BufferSize::Default,
},
sample_format,
) {
let usable = is_output
|| is_format_supported(
client,
&waveformat.Format as *const Audio::WAVEFORMATEX,
)?;
if usable {
supported_formats.push(SupportedStreamConfigRange {
channels: format.channels,
min_sample_rate: sample_rate,
max_sample_rate: sample_rate,
buffer_size,
sample_format,
});
}
}
}
}
Ok(supported_formats.into_iter())
}
}
pub fn supported_input_configs(&self) -> Result<SupportedInputConfigs, Error> {
if self.data_flow() == Audio::eCapture {
self.supported_formats()
} else {
Ok(vec![].into_iter())
}
}
pub fn supported_output_configs(&self) -> Result<SupportedOutputConfigs, Error> {
if self.data_flow() == Audio::eRender {
self.supported_formats()
} else {
Ok(vec![].into_iter())
}
}
fn default_format(&self) -> Result<SupportedStreamConfig, Error> {
com::com_initialized();
let lock = self
.ensure_future_audio_client(None)
.context("Failed to get audio client")?;
let client = &lock.as_ref().unwrap().0;
unsafe {
let format_ptr = client
.GetMixFormat()
.map(WaveFormatExPtr)
.context("Failed to get mix format")?;
let mut config =
format_from_waveformatex_ptr(format_ptr.0, client).ok_or_else(|| {
Error::with_message(
ErrorKind::UnsupportedConfig,
"Device audio format could not be mapped to a supported format",
)
})?;
if config.buffer_size == SupportedBufferSize::Unknown {
let mut default_period_hns: i64 = 0;
if client
.GetDevicePeriod(Some(&mut default_period_hns), None)
.is_ok()
&& default_period_hns > 0
{
let frames = buffer_duration_to_frames(default_period_hns, config.sample_rate);
config.buffer_size = SupportedBufferSize::Range {
min: frames,
max: frames,
};
}
}
Ok(config)
}
}
pub(crate) fn data_flow(&self) -> Audio::EDataFlow {
match &self.device {
DeviceHandle::DefaultOutput => Audio::eRender,
DeviceHandle::DefaultInput => Audio::eCapture,
DeviceHandle::Specific(device) => {
let endpoint = Endpoint::from(device.clone());
endpoint.data_flow()
}
}
}
pub fn default_input_config(&self) -> Result<SupportedStreamConfig, Error> {
if self.data_flow() == Audio::eCapture {
self.default_format()
} else {
Err(Error::with_message(
ErrorKind::UnsupportedOperation,
"Device does not support input",
))
}
}
pub fn default_output_config(&self) -> Result<SupportedStreamConfig, Error> {
let data_flow = self.data_flow();
if data_flow == Audio::eRender {
self.default_format()
} else {
Err(Error::with_message(
ErrorKind::UnsupportedOperation,
"Device does not support output",
))
}
}
pub(crate) fn build_input_stream_raw_inner(
&self,
config: StreamConfig,
sample_format: SampleFormat,
activation_timeout: Option<Duration>,
) -> Result<StreamInner, Error> {
crate::validate_stream_config(&config)?;
unsafe {
com::com_initialized();
let audio_client = self
.build_audioclient(activation_timeout)
.context("Failed to build audio client")?;
let buffer_duration = buffer_size_to_duration(&config.buffer_size, config.sample_rate);
let mut stream_flags = DEFAULT_FLAGS;
if self.data_flow() == Audio::eRender {
stream_flags |= Audio::AUDCLNT_STREAMFLAGS_LOOPBACK;
}
let waveformatex = {
let format_attempt = config_to_waveformatextensible(config, sample_format)
.ok_or_else(|| {
Error::with_message(
ErrorKind::UnsupportedConfig,
"Stream configuration could not be converted to a compatible format",
)
})?;
let share_mode = Audio::AUDCLNT_SHAREMODE_SHARED;
match super::device::is_format_supported(&audio_client, &format_attempt.Format) {
Ok(false) => {
return Err(Error::with_message(
ErrorKind::UnsupportedConfig,
"Stream configuration is not supported in shared mode",
))
}
Err(e) => return Err(e),
_ => (),
}
audio_client
.Initialize(
share_mode,
stream_flags,
buffer_duration,
0,
&format_attempt.Format,
None,
)
.context("Failed to initialize audio client")?;
format_attempt.Format
};
let max_frames_in_buffer = audio_client
.GetBufferSize()
.context("Failed to get buffer size")?;
let period_frames =
shared_mode_period_frames(&audio_client, config.sample_rate, max_frames_in_buffer);
let event =
Threading::CreateEventA(None, false, false, windows::core::PCSTR(ptr::null()))
.context("Failed to create event")?;
audio_client
.SetEventHandle(event)
.context("Failed to set event handle")?;
let capture_client = audio_client
.GetService::<Audio::IAudioCaptureClient>()
.context("Failed to get capture client")?;
let client_flow = AudioClientFlow::Capture { capture_client };
let audio_clock = get_audio_clock(&audio_client)?;
let stream_latency = {
let hns = audio_client
.GetStreamLatency()
.context("Failed to get stream latency")?;
Duration::from_nanos(hns.max(0) as u64 * 100)
};
Ok(StreamInner {
audio_client,
audio_clock,
client_flow,
event,
playing: false,
max_frames_in_buffer,
period_frames,
bytes_per_frame: waveformatex.nBlockAlign,
config,
sample_format,
stream_latency,
})
}
}
pub(crate) fn build_output_stream_raw_inner(
&self,
config: StreamConfig,
sample_format: SampleFormat,
activation_timeout: Option<Duration>,
) -> Result<StreamInner, Error> {
crate::validate_stream_config(&config)?;
unsafe {
com::com_initialized();
let audio_client = self
.build_audioclient(activation_timeout)
.context("Failed to build audio client")?;
let buffer_duration = buffer_size_to_duration(&config.buffer_size, config.sample_rate);
let waveformatex = {
let format_attempt = config_to_waveformatextensible(config, sample_format)
.ok_or_else(|| {
Error::with_message(
ErrorKind::UnsupportedConfig,
"Stream configuration could not be converted to a compatible format",
)
})?;
let share_mode = Audio::AUDCLNT_SHAREMODE_SHARED;
match super::device::is_format_supported(&audio_client, &format_attempt.Format) {
Ok(false) => {
return Err(Error::with_message(
ErrorKind::UnsupportedConfig,
"Stream configuration is not supported in shared mode",
))
}
Err(e) => return Err(e),
_ => (),
}
audio_client
.Initialize(
share_mode,
DEFAULT_FLAGS
| Audio::AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY
| Audio::AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM,
buffer_duration,
0,
&format_attempt.Format,
None,
)
.context("Failed to initialize audio client")?;
format_attempt.Format
};
let event =
Threading::CreateEventA(None, false, false, windows::core::PCSTR(ptr::null()))
.context("Failed to create event")?;
audio_client
.SetEventHandle(event)
.context("Failed to set event handle")?;
let max_frames_in_buffer = audio_client
.GetBufferSize()
.context("Failed to get buffer size")?;
let period_frames =
shared_mode_period_frames(&audio_client, config.sample_rate, max_frames_in_buffer);
let render_client = audio_client
.GetService::<IAudioRenderClient>()
.context("Failed to get render client")?;
let client_flow = AudioClientFlow::Render { render_client };
let audio_clock = get_audio_clock(&audio_client)?;
let stream_latency = {
let hns = audio_client
.GetStreamLatency()
.context("Failed to get stream latency")?;
Duration::from_nanos(hns.max(0) as u64 * 100)
};
Ok(StreamInner {
audio_client,
audio_clock,
client_flow,
event,
playing: false,
max_frames_in_buffer,
period_frames,
bytes_per_frame: waveformatex.nBlockAlign,
config,
sample_format,
stream_latency,
})
}
}
}
unsafe fn endpoint_ids_equal(a: &Audio::IMMDevice, b: &Audio::IMMDevice) -> bool {
let id_a = a.GetId().expect("cpal: GetId failure");
let id_b = b.GetId().expect("cpal: GetId failure");
let _ga = ComString(id_a);
let _gb = ComString(id_b);
let mut off = 0isize;
loop {
let wa = *id_a.0.offset(off);
let wb = *id_b.0.offset(off);
if wa != wb {
return false;
}
if wa == 0 {
return true;
}
off += 1;
}
}
unsafe fn hash_endpoint_id<H: std::hash::Hasher>(device: &Audio::IMMDevice, state: &mut H) {
let id = device.GetId().expect("cpal: GetId failure");
let _g = ComString(id);
let mut off = 0isize;
loop {
let w = *id.0.offset(off);
if w == 0 {
break;
}
w.hash(state);
off += 1;
}
}
impl PartialEq for Device {
fn eq(&self, other: &Device) -> bool {
match (&self.device, &other.device) {
(DeviceHandle::DefaultOutput, DeviceHandle::DefaultOutput)
| (DeviceHandle::DefaultInput, DeviceHandle::DefaultInput) => true,
(DeviceHandle::Specific(a), DeviceHandle::Specific(b)) => {
unsafe { endpoint_ids_equal(a, b) }
}
_ => false,
}
}
}
impl Eq for Device {}
impl std::hash::Hash for Device {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
match &self.device {
DeviceHandle::DefaultOutput | DeviceHandle::DefaultInput => {
mem::discriminant(&self.device).hash(state);
}
DeviceHandle::Specific(device) => {
unsafe { hash_endpoint_id(device, state) }
}
}
}
}
impl fmt::Display for Device {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let desc = self.description().map_err(|_| fmt::Error)?;
f.write_str(desc.name())
}
}
impl fmt::Debug for Device {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Device")
.field("device", &self.device)
.field("description", &self.description())
.finish()
}
}
impl From<Audio::IMMDevice> for Endpoint {
fn from(device: Audio::IMMDevice) -> Self {
unsafe {
let endpoint = immendpoint_from_immdevice(device);
Endpoint { endpoint }
}
}
}
impl Endpoint {
fn data_flow(&self) -> Audio::EDataFlow {
unsafe { data_flow_from_immendpoint(&self.endpoint) }
}
}
static ENUMERATOR: OnceLock<Enumerator> = OnceLock::new();
pub(super) fn current_default_endpoint(flow: Audio::EDataFlow) -> Option<Audio::IMMDevice> {
com::com_initialized();
unsafe {
get_enumerator()
.0
.GetDefaultAudioEndpoint(flow, Audio::eConsole)
.ok()
}
}
fn get_enumerator() -> &'static Enumerator {
ENUMERATOR.get_or_init(|| {
com::com_initialized();
unsafe {
let enumerator = Com::CoCreateInstance::<_, Audio::IMMDeviceEnumerator>(
&Audio::MMDeviceEnumerator,
None,
Com::CLSCTX_ALL,
)
.unwrap();
Enumerator(enumerator)
}
})
}
unsafe fn get_property_u32(
property_store: &IPropertyStore,
property_key: *const PROPERTYKEY,
) -> Option<u32> {
let mut property_value = property_store.GetValue(property_key).ok()?;
let prop_variant = &property_value.Anonymous.Anonymous;
if prop_variant.vt != VT_UI4 {
return None;
}
let value = *(&prop_variant.Anonymous as *const _ as *const u32);
StructuredStorage::PropVariantClear(&mut property_value).ok();
Some(value)
}
unsafe fn get_property_string(
property_store: &IPropertyStore,
property_key: *const PROPERTYKEY,
) -> Option<String> {
let mut property_value = property_store.GetValue(property_key).ok()?;
let prop_variant = &property_value.Anonymous.Anonymous;
if prop_variant.vt != VT_LPWSTR {
return None;
}
let ptr_utf16 = *(&prop_variant.Anonymous as *const _ as *const *const u16);
const MAX_STRING_LEN: usize = 32768; let mut len = 0;
while len < MAX_STRING_LEN && *ptr_utf16.add(len) != 0 {
len += 1;
}
if len >= MAX_STRING_LEN {
return None;
}
let string_slice = slice::from_raw_parts(ptr_utf16, len);
let os_string: OsString = OsStringExt::from_wide(string_slice);
let result = match os_string.into_string() {
Ok(string) => Some(string),
Err(os_string) => Some(os_string.to_string_lossy().into()),
};
StructuredStorage::PropVariantClear(&mut property_value).ok();
result
}
struct Enumerator(Audio::IMMDeviceEnumerator);
unsafe impl Send for Enumerator {}
unsafe impl Sync for Enumerator {}
pub struct Devices {
collection: Audio::IMMDeviceCollection,
total_count: u32,
next_item: u32,
}
impl Devices {
pub fn new() -> Result<Self, Error> {
unsafe {
let collection = get_enumerator()
.0
.EnumAudioEndpoints(Audio::eAll, Audio::DEVICE_STATE_ACTIVE)
.context("Failed to enumerate audio endpoints")?;
let count = collection
.GetCount()
.context("Failed to get device count")?;
Ok(Self {
collection,
total_count: count,
next_item: 0,
})
}
}
}
unsafe impl Send for Devices {}
unsafe impl Sync for Devices {}
impl Iterator for Devices {
type Item = Device;
fn next(&mut self) -> Option<Self::Item> {
if self.next_item >= self.total_count {
return None;
}
unsafe {
let device = self.collection.Item(self.next_item).unwrap();
self.next_item += 1;
Some(Self::Item::from_immdevice(device))
}
}
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_input_device() -> Option<Device> {
current_default_endpoint(Audio::eCapture).map(|_| Device::default_input())
}
pub fn default_output_device() -> Option<Device> {
current_default_endpoint(Audio::eRender).map(|_| Device::default_output())
}
unsafe fn get_audio_clock(audio_client: &Audio::IAudioClient) -> Result<Audio::IAudioClock, Error> {
audio_client
.GetService::<Audio::IAudioClock>()
.context("Failed to get audio clock")
}
const OUTPUT_MIN_SAMPLE_RATE: SampleRate = 8_000;
const OUTPUT_MAX_SAMPLE_RATE: SampleRate = 384_000;
const WAVEFORMATEXTENSIBLE_SAMPLE_FORMATS: [SampleFormat; 7] = [
SampleFormat::U8,
SampleFormat::I16,
SampleFormat::I24,
SampleFormat::I32,
SampleFormat::I64,
SampleFormat::F32,
SampleFormat::F64,
];
fn config_to_waveformatextensible(
config: StreamConfig,
sample_format: SampleFormat,
) -> Option<Audio::WAVEFORMATEXTENSIBLE> {
let format_tag = match sample_format {
SampleFormat::U8 | SampleFormat::I16 => Audio::WAVE_FORMAT_PCM,
SampleFormat::I24
| SampleFormat::I32
| SampleFormat::I64
| SampleFormat::F32
| SampleFormat::F64 => KernelStreaming::WAVE_FORMAT_EXTENSIBLE,
_ => return None,
};
let channels = config.channels;
let sample_rate = config.sample_rate;
let sample_bytes = sample_format.sample_size() as u16;
let avg_bytes_per_sec = u32::from(channels) * sample_rate * u32::from(sample_bytes);
let block_align = channels * sample_bytes;
let container_bits = 8 * sample_bytes;
let valid_bits = sample_format.bits_per_sample() as u16;
let cb_size = if format_tag == Audio::WAVE_FORMAT_PCM {
0
} else {
let extensible_size = mem::size_of::<Audio::WAVEFORMATEXTENSIBLE>();
let ex_size = mem::size_of::<Audio::WAVEFORMATEX>();
(extensible_size - ex_size) as u16
};
let waveformatex = Audio::WAVEFORMATEX {
wFormatTag: format_tag as u16,
nChannels: channels,
nSamplesPerSec: sample_rate,
nAvgBytesPerSec: avg_bytes_per_sec,
nBlockAlign: block_align,
wBitsPerSample: container_bits,
cbSize: cb_size,
};
let channel_mask = KernelStreaming::KSAUDIO_SPEAKER_DIRECTOUT;
let sub_format = match sample_format {
SampleFormat::U8
| SampleFormat::I16
| SampleFormat::I24
| SampleFormat::I32
| SampleFormat::I64 => KernelStreaming::KSDATAFORMAT_SUBTYPE_PCM,
SampleFormat::F32 | SampleFormat::F64 => Multimedia::KSDATAFORMAT_SUBTYPE_IEEE_FLOAT,
_ => return None,
};
let waveformatextensible = Audio::WAVEFORMATEXTENSIBLE {
Format: waveformatex,
Samples: Audio::WAVEFORMATEXTENSIBLE_0 {
wValidBitsPerSample: valid_bits,
},
dwChannelMask: channel_mask,
SubFormat: sub_format,
};
Some(waveformatextensible)
}
fn shared_mode_period_frames(
audio_client: &Audio::IAudioClient,
sample_rate: SampleRate,
max_frames_in_buffer: FrameCount,
) -> FrameCount {
let mut default_period = 0i64;
if unsafe { audio_client.GetDevicePeriod(Some(&mut default_period), None) }.is_ok()
&& default_period > 0
{
buffer_duration_to_frames(default_period, sample_rate)
} else {
max_frames_in_buffer
}
}
fn buffer_size_to_duration(buffer_size: &BufferSize, sample_rate: SampleRate) -> i64 {
match buffer_size {
BufferSize::Fixed(frames) => *frames as i64 * (1_000_000_000 / 100) / sample_rate as i64,
BufferSize::Default => 0,
}
}
fn buffer_duration_to_frames(buffer_duration: i64, sample_rate: SampleRate) -> FrameCount {
(buffer_duration * sample_rate as i64 * 100 / 1_000_000_000) as FrameCount
}