| Lift freeq's AV media plane out of sleek 90f8b89 nandi 20d ago | 1 | use crate::{ |
| 2 | BackendSpecificError, BufferSize, Data, DefaultStreamConfigError, DeviceDescription, |
| 3 | DeviceDescriptionBuilder, DeviceDirection, DeviceId, DeviceIdError, DeviceNameError, |
| 4 | DeviceType, DevicesError, FrameCount, InputCallbackInfo, InterfaceType, OutputCallbackInfo, |
| 5 | SampleFormat, SampleRate, StreamConfig, SupportedBufferSize, SupportedStreamConfig, |
| 6 | SupportedStreamConfigRange, SupportedStreamConfigsError, COMMON_SAMPLE_RATES, |
| 7 | }; |
| 8 | |
| 9 | impl From<Audio::EDataFlow> for DeviceDirection { |
| 10 | fn from(data_flow: Audio::EDataFlow) -> Self { |
| 11 | if data_flow == Audio::eCapture { |
| 12 | DeviceDirection::Input |
| 13 | } else if data_flow == Audio::eRender { |
| 14 | DeviceDirection::Output |
| 15 | } else { |
| 16 | DeviceDirection::Unknown |
| 17 | } |
| 18 | } |
| 19 | } |
| 20 | use std::ffi::OsString; |
| 21 | use std::fmt; |
| 22 | use std::mem; |
| 23 | use std::os::windows::ffi::OsStringExt; |
| 24 | use std::ptr; |
| 25 | use std::slice; |
| 26 | use std::sync::OnceLock; |
| 27 | use std::sync::{Arc, Mutex, MutexGuard}; |
| 28 | use std::time::Duration; |
| 29 | |
| 30 | use super::{windows_err_to_cpal_err, windows_err_to_cpal_err_message}; |
| 31 | use crate::host::com; |
| 32 | use windows::core::Interface; |
| 33 | use windows::core::GUID; |
| 34 | use windows::Win32::Devices::Properties; |
| 35 | use windows::Win32::Foundation::PROPERTYKEY; |
| 36 | use windows::Win32::Media::Audio::IAudioRenderClient; |
| 37 | use windows::Win32::Media::{Audio, KernelStreaming, Multimedia}; |
| 38 | use windows::Win32::System::Com; |
| 39 | use windows::Win32::System::Com::{StructuredStorage, STGM_READ}; |
| 40 | use windows::Win32::System::Threading; |
| 41 | use windows::Win32::System::Variant::{VT_LPWSTR, VT_UI4}; |
| 42 | use windows::Win32::UI::Shell::PropertiesSystem::IPropertyStore; |
| 43 | |
| 44 | use super::stream::{AudioClientFlow, Stream, StreamInner}; |
| 45 | use crate::{traits::DeviceTrait, BuildStreamError, StreamError}; |
| 46 | |
| 47 | pub use crate::iter::{SupportedInputConfigs, SupportedOutputConfigs}; |
| 48 | |
| 49 | // PKEY_AudioEndpoint properties not yet in windows-rs |
| 50 | |
| 51 | /// PKEY_AudioEndpoint_FormFactor (PID 0) - VT_UI4 containing EndpointFormFactor enum |
| 52 | const PKEY_AUDIOENDPOINT_FORMFACTOR: PROPERTYKEY = PROPERTYKEY { |
| 53 | fmtid: GUID::from_u128(0x1da5d803_d492_4edd_8c23_e0c0ffee7f0e), |
| 54 | pid: 0, |
| 55 | }; |
| 56 | |
| 57 | /// PKEY_AudioEndpoint_JackSubType (PID 8) - VT_LPWSTR containing KS node type GUID |
| 58 | const PKEY_AUDIOENDPOINT_JACKSUBTYPE: PROPERTYKEY = PROPERTYKEY { |
| 59 | fmtid: GUID::from_u128(0x1da5d803_d492_4edd_8c23_e0c0ffee7f0e), |
| 60 | pid: 8, |
| 61 | }; |
| 62 | |
| 63 | const DEFAULT_FLAGS: u32 = Audio::AUDCLNT_STREAMFLAGS_EVENTCALLBACK |
| 64 | | Audio::AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY |
| 65 | | Audio::AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM; |
| 66 | |
| 67 | /// Wrapper because of that stupid decision to remove `Send` and `Sync` from raw pointers. |
| 68 | #[derive(Clone)] |
| 69 | struct IAudioClientWrapper(Audio::IAudioClient); |
| 70 | unsafe impl Send for IAudioClientWrapper {} |
| 71 | unsafe impl Sync for IAudioClientWrapper {} |
| 72 | |
| 73 | /// An opaque type that identifies an end point. |
| 74 | #[derive(Clone)] |
| 75 | pub struct Device { |
| 76 | device: Audio::IMMDevice, |
| 77 | /// We cache an uninitialized `IAudioClient` so that we can call functions from it without |
| 78 | /// having to create/destroy audio clients all the time. |
| 79 | future_audio_client: Arc<Mutex<Option<IAudioClientWrapper>>>, // TODO: add NonZero around the ptr |
| 80 | } |
| 81 | |
| 82 | impl DeviceTrait for Device { |
| 83 | type SupportedInputConfigs = SupportedInputConfigs; |
| 84 | type SupportedOutputConfigs = SupportedOutputConfigs; |
| 85 | type Stream = Stream; |
| 86 | |
| 87 | fn description(&self) -> Result<DeviceDescription, DeviceNameError> { |
| 88 | Device::description(self) |
| 89 | } |
| 90 | |
| 91 | fn id(&self) -> Result<DeviceId, DeviceIdError> { |
| 92 | Device::id(self) |
| 93 | } |
| 94 | |
| 95 | fn supports_input(&self) -> bool { |
| 96 | self.data_flow() == Audio::eCapture |
| 97 | } |
| 98 | |
| 99 | fn supports_output(&self) -> bool { |
| 100 | self.data_flow() == Audio::eRender |
| 101 | } |
| 102 | |
| 103 | fn supported_input_configs( |
| 104 | &self, |
| 105 | ) -> Result<Self::SupportedInputConfigs, SupportedStreamConfigsError> { |
| 106 | Device::supported_input_configs(self) |
| 107 | } |
| 108 | |
| 109 | fn supported_output_configs( |
| 110 | &self, |
| 111 | ) -> Result<Self::SupportedOutputConfigs, SupportedStreamConfigsError> { |
| 112 | Device::supported_output_configs(self) |
| 113 | } |
| 114 | |
| 115 | fn default_input_config(&self) -> Result<SupportedStreamConfig, DefaultStreamConfigError> { |
| 116 | Device::default_input_config(self) |
| 117 | } |
| 118 | |
| 119 | fn default_output_config(&self) -> Result<SupportedStreamConfig, DefaultStreamConfigError> { |
| 120 | Device::default_output_config(self) |
| 121 | } |
| 122 | |
| 123 | fn build_input_stream_raw<D, E>( |
| 124 | &self, |
| 125 | config: StreamConfig, |
| 126 | sample_format: SampleFormat, |
| 127 | data_callback: D, |
| 128 | error_callback: E, |
| 129 | _timeout: Option<Duration>, |
| 130 | ) -> Result<Self::Stream, BuildStreamError> |
| 131 | where |
| 132 | D: FnMut(&Data, &InputCallbackInfo) + Send + 'static, |
| 133 | E: FnMut(StreamError) + Send + 'static, |
| 134 | { |
| 135 | let stream_inner = self.build_input_stream_raw_inner(config, sample_format)?; |
| 136 | Ok(Stream::new_input( |
| 137 | stream_inner, |
| 138 | data_callback, |
| 139 | error_callback, |
| 140 | )) |
| 141 | } |
| 142 | |
| 143 | fn build_output_stream_raw<D, E>( |
| 144 | &self, |
| 145 | config: StreamConfig, |
| 146 | sample_format: SampleFormat, |
| 147 | data_callback: D, |
| 148 | error_callback: E, |
| 149 | _timeout: Option<Duration>, |
| 150 | ) -> Result<Self::Stream, BuildStreamError> |
| 151 | where |
| 152 | D: FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static, |
| 153 | E: FnMut(StreamError) + Send + 'static, |
| 154 | { |
| 155 | let stream_inner = self.build_output_stream_raw_inner(config, sample_format)?; |
| 156 | Ok(Stream::new_output( |
| 157 | stream_inner, |
| 158 | data_callback, |
| 159 | error_callback, |
| 160 | )) |
| 161 | } |
| 162 | } |
| 163 | |
| 164 | struct Endpoint { |
| 165 | endpoint: Audio::IMMEndpoint, |
| 166 | } |
| 167 | |
| 168 | // Use RAII to make sure CoTaskMemFree is called when we are responsible for freeing. |
| 169 | struct WaveFormatExPtr(*mut Audio::WAVEFORMATEX); |
| 170 | |
| 171 | impl Drop for WaveFormatExPtr { |
| 172 | fn drop(&mut self) { |
| 173 | unsafe { |
| 174 | Com::CoTaskMemFree(Some(self.0 as *mut _)); |
| 175 | } |
| 176 | } |
| 177 | } |
| 178 | |
| 179 | unsafe fn immendpoint_from_immdevice(device: Audio::IMMDevice) -> Audio::IMMEndpoint { |
| 180 | device |
| 181 | .cast::<Audio::IMMEndpoint>() |
| 182 | .expect("could not query IMMDevice interface for IMMEndpoint") |
| 183 | } |
| 184 | |
| 185 | unsafe fn data_flow_from_immendpoint(endpoint: &Audio::IMMEndpoint) -> Audio::EDataFlow { |
| 186 | endpoint |
| 187 | .GetDataFlow() |
| 188 | .expect("could not get endpoint data_flow") |
| 189 | } |
| 190 | |
| 191 | // Given the audio client and format, returns whether or not the format is supported. |
| 192 | pub unsafe fn is_format_supported( |
| 193 | _client: &Audio::IAudioClient, |
| 194 | _waveformatex_ptr: *const Audio::WAVEFORMATEX, |
| 195 | ) -> Result<bool, SupportedStreamConfigsError> { |
| 196 | // Checking formats is not needed for shared mode with auto-conversion, therefore this check has been removed until someone implements WASAPI exclusive mode support |
| 197 | // I used an NAudio issue as reference: https://github.com/naudio/NAudio/issues/819 |
| 198 | |
| 199 | Ok(true) |
| 200 | } |
| 201 | |
| 202 | // Get a cpal Format from a WAVEFORMATEX. |
| 203 | unsafe fn format_from_waveformatex_ptr( |
| 204 | waveformatex_ptr: *const Audio::WAVEFORMATEX, |
| 205 | audio_client: &Audio::IAudioClient, |
| 206 | ) -> Option<SupportedStreamConfig> { |
| 207 | fn cmp_guid(a: &GUID, b: &GUID) -> bool { |
| 208 | (a.data1, a.data2, a.data3, a.data4) == (b.data1, b.data2, b.data3, b.data4) |
| 209 | } |
| 210 | let sample_format = match ( |
| 211 | (*waveformatex_ptr).wBitsPerSample, |
| 212 | (*waveformatex_ptr).wFormatTag as u32, |
| 213 | ) { |
| 214 | (8, Audio::WAVE_FORMAT_PCM) => SampleFormat::U8, |
| 215 | (16, Audio::WAVE_FORMAT_PCM) => SampleFormat::I16, |
| 216 | (32, Multimedia::WAVE_FORMAT_IEEE_FLOAT) => SampleFormat::F32, |
| 217 | (n_bits, KernelStreaming::WAVE_FORMAT_EXTENSIBLE) => { |
| 218 | let waveformatextensible_ptr = waveformatex_ptr as *const Audio::WAVEFORMATEXTENSIBLE; |
| 219 | let sub = (*waveformatextensible_ptr).SubFormat; |
| 220 | |
| 221 | if cmp_guid(&sub, &KernelStreaming::KSDATAFORMAT_SUBTYPE_PCM) { |
| 222 | match n_bits { |
| 223 | 8 => SampleFormat::U8, |
| 224 | 16 => SampleFormat::I16, |
| 225 | 24 => SampleFormat::I24, |
| 226 | 32 => SampleFormat::I32, |
| 227 | 64 => SampleFormat::I64, |
| 228 | _ => return None, |
| 229 | } |
| 230 | } else if n_bits == 32 && cmp_guid(&sub, &Multimedia::KSDATAFORMAT_SUBTYPE_IEEE_FLOAT) { |
| 231 | SampleFormat::F32 |
| 232 | } else { |
| 233 | return None; |
| 234 | } |
| 235 | } |
| 236 | // Unknown data format returned by GetMixFormat. |
| 237 | _ => return None, |
| 238 | }; |
| 239 | |
| 240 | let sample_rate = (*waveformatex_ptr).nSamplesPerSec; |
| 241 | |
| 242 | // GetBufferSizeLimits is only used for Hardware-Offloaded Audio |
| 243 | // Processing, which was added in Windows 8, which places hardware |
| 244 | // limits on the size of the audio buffer. If the sound system |
| 245 | // *isn't* using offloaded audio, we're using a software audio |
| 246 | // processing stack and have pretty much free rein to set buffer |
| 247 | // size. |
| 248 | // |
| 249 | // In software audio stacks GetBufferSizeLimits returns |
| 250 | // AUDCLNT_E_OFFLOAD_MODE_ONLY. |
| 251 | // |
| 252 | // https://docs.microsoft.com/en-us/windows-hardware/drivers/audio/hardware-offloaded-audio-processing |
| 253 | let (mut min_buffer_duration, mut max_buffer_duration) = (0, 0); |
| 254 | let buffer_size_is_limited = audio_client |
| 255 | .cast::<Audio::IAudioClient2>() |
| 256 | .and_then(|audio_client| { |
| 257 | audio_client.GetBufferSizeLimits( |
| 258 | waveformatex_ptr, |
| 259 | true, |
| 260 | &mut min_buffer_duration, |
| 261 | &mut max_buffer_duration, |
| 262 | ) |
| 263 | }) |
| 264 | .is_ok(); |
| 265 | let buffer_size = if buffer_size_is_limited { |
| 266 | SupportedBufferSize::Range { |
| 267 | min: buffer_duration_to_frames(min_buffer_duration, sample_rate), |
| 268 | max: buffer_duration_to_frames(max_buffer_duration, sample_rate), |
| 269 | } |
| 270 | } else { |
| 271 | SupportedBufferSize::Range { |
| 272 | min: 0, |
| 273 | max: u32::MAX, |
| 274 | } |
| 275 | }; |
| 276 | |
| 277 | let format = SupportedStreamConfig { |
| 278 | channels: (*waveformatex_ptr).nChannels as _, |
| 279 | sample_rate, |
| 280 | buffer_size, |
| 281 | sample_format, |
| 282 | }; |
| 283 | Some(format) |
| 284 | } |
| 285 | |
| 286 | unsafe impl Send for Device {} |
| 287 | unsafe impl Sync for Device {} |
| 288 | |
| 289 | /// Maps PKEY_AudioEndpoint_JackSubType GUID to InterfaceType. |
| 290 | /// |
| 291 | /// The JackSubType property contains a KS node type GUID string from Ksmedia.h |
| 292 | /// that specifies the physical connector type. |
| 293 | fn jacksubtype_to_interface_type(guid_str: &str) -> Option<crate::InterfaceType> { |
| 294 | let guid_upper = guid_str.to_uppercase(); |
| 295 | let typ = match guid_upper.as_str() { |
| 296 | "{D9E55EA0-0C89-4692-84FF-EB3C4B0D172F}" => InterfaceType::Hdmi, |
| 297 | "{E47E4031-3EA6-418D-8F9B-B73843CCB2AD}" => InterfaceType::DisplayPort, |
| 298 | "{DFF21CE1-F70F-11D0-B917-00A0C9223196}" => InterfaceType::Spdif, |
| 299 | _ => return None, |
| 300 | }; |
| 301 | |
| 302 | Some(typ) |
| 303 | } |
| 304 | |
| 305 | /// Maps WASAPI FormFactor values to DeviceType and optionally InterfaceType. |
| 306 | fn form_factor_to_types(form_factor: u32) -> (crate::DeviceType, Option<crate::InterfaceType>) { |
| 307 | match form_factor { |
| 308 | 0 => (DeviceType::Unknown, Some(InterfaceType::Network)), // RemoteNetworkDevice |
| 309 | 1 => (DeviceType::Speaker, None), // Speakers |
| 310 | 2 => (DeviceType::Unknown, Some(InterfaceType::Line)), // LineLevel |
| 311 | 3 => (DeviceType::Headphones, None), // Headphones |
| 312 | 4 => (DeviceType::Microphone, None), // Microphone |
| 313 | 5 => (DeviceType::Headset, None), // Headset |
| 314 | 6 => (DeviceType::Handset, None), // Handset |
| 315 | 7 => (DeviceType::Unknown, None), // UnknownDigitalPassthrough |
| 316 | 8 => (DeviceType::Unknown, Some(InterfaceType::Spdif)), // SPDIF |
| 317 | 9 => (DeviceType::Unknown, Some(InterfaceType::Hdmi)), // DigitalAudioDisplayDevice |
| 318 | _ => (DeviceType::Unknown, None), // UnknownFormFactor or future values |
| 319 | } |
| 320 | } |
| 321 | |
| 322 | /// Maps WASAPI EnumeratorName to InterfaceType. |
| 323 | fn enumerator_to_interface_type(enumerator: &str) -> Option<crate::InterfaceType> { |
| 324 | let typ = match enumerator.to_uppercase().as_str() { |
| 325 | "HDAUDIO" => InterfaceType::BuiltIn, |
| 326 | "USB" => InterfaceType::Usb, |
| 327 | "BTHENUM" => InterfaceType::Bluetooth, |
| 328 | "MMDEVAPI" | "SW" => InterfaceType::Virtual, |
| 329 | _ => return None, |
| 330 | }; |
| 331 | Some(typ) |
| 332 | } |
| 333 | |
| 334 | impl Device { |
| 335 | pub fn description(&self) -> Result<DeviceDescription, DeviceNameError> { |
| 336 | unsafe { |
| 337 | // Open the device's property store. |
| 338 | let property_store = self |
| 339 | .device |
| 340 | .OpenPropertyStore(STGM_READ) |
| 341 | .expect("could not open property store"); |
| 342 | |
| 343 | // Query all available properties |
| 344 | let friendly_name = get_property_string( |
| 345 | &property_store, |
| 346 | &Properties::DEVPKEY_Device_FriendlyName as *const _ as *const _, |
| 347 | ); |
| 348 | |
| 349 | let device_desc = get_property_string( |
| 350 | &property_store, |
| 351 | &Properties::DEVPKEY_Device_DeviceDesc as *const _ as *const _, |
| 352 | ); |
| 353 | |
| 354 | let interface_name = get_property_string( |
| 355 | &property_store, |
| 356 | &Properties::DEVPKEY_DeviceInterface_FriendlyName as *const _ as *const _, |
| 357 | ); |
| 358 | |
| 359 | let enumerator_name = get_property_string( |
| 360 | &property_store, |
| 361 | &Properties::DEVPKEY_Device_EnumeratorName as *const _ as *const _, |
| 362 | ); |
| 363 | |
| 364 | let form_factor = get_property_u32( |
| 365 | &property_store, |
| 366 | &PKEY_AUDIOENDPOINT_FORMFACTOR as *const _ as *const _, |
| 367 | ); |
| 368 | |
| 369 | let jack_subtype = get_property_string( |
| 370 | &property_store, |
| 371 | &PKEY_AUDIOENDPOINT_JACKSUBTYPE as *const _ as *const _, |
| 372 | ); |
| 373 | |
| 374 | // Prefer DeviceDesc for name, fall back to FriendlyName |
| 375 | let name = device_desc |
| 376 | .clone() |
| 377 | .or(friendly_name.clone()) |
| 378 | .ok_or_else(|| DeviceNameError::BackendSpecific { |
| 379 | err: BackendSpecificError { |
| 380 | description: "failed to retrieve device name".to_string(), |
| 381 | }, |
| 382 | })?; |
| 383 | |
| 384 | // Get direction from data flow (eCapture = Input, eRender = Output) |
| 385 | let direction = self.data_flow().into(); |
| 386 | |
| 387 | // Determine device_type and initial interface_type from FormFactor |
| 388 | let (device_type, mut interface_type) = form_factor |
| 389 | .map(form_factor_to_types) |
| 390 | .unwrap_or((crate::DeviceType::Unknown, None)); |
| 391 | |
| 392 | // Override interface_type from EnumeratorName if available |
| 393 | if let Some(ref enumerator) = enumerator_name { |
| 394 | if let Some(itype) = enumerator_to_interface_type(enumerator) { |
| 395 | interface_type = Some(itype); |
| 396 | } |
| 397 | } |
| 398 | |
| 399 | // JackSubType has highest priority for interface_type |
| 400 | if let Some(ref jack_guid) = jack_subtype { |
| 401 | if let Some(itype) = jacksubtype_to_interface_type(jack_guid) { |
| 402 | interface_type = Some(itype); |
| 403 | } |
| 404 | } |
| 405 | |
| 406 | let mut builder = DeviceDescriptionBuilder::new(name) |
| 407 | .direction(direction) |
| 408 | .device_type(device_type); |
| 409 | |
| 410 | if let Some(itype) = interface_type { |
| 411 | builder = builder.interface_type(itype); |
| 412 | } |
| 413 | |
| 414 | // Add interface name to driver field if available |
| 415 | if let Some(iface_name) = interface_name { |
| 416 | builder = builder.driver(iface_name); |
| 417 | } |
| 418 | |
| 419 | // Add FriendlyName to extended if different from the name we used |
| 420 | if let Some(fname) = friendly_name { |
| 421 | if device_desc.is_some() && Some(&fname) != device_desc.as_ref() { |
| 422 | builder = builder.add_extended_line(fname); |
| 423 | } |
| 424 | } |
| 425 | |
| 426 | Ok(builder.build()) |
| 427 | } |
| 428 | } |
| 429 | |
| 430 | fn id(&self) -> Result<DeviceId, DeviceIdError> { |
| 431 | unsafe { |
| 432 | match self.device.GetId() { |
| 433 | Ok(pwstr) => match pwstr.to_string() { |
| 434 | Ok(id_str) => Ok(DeviceId(crate::platform::HostId::Wasapi, id_str)), |
| 435 | Err(e) => Err(DeviceIdError::BackendSpecific { |
| 436 | err: BackendSpecificError { |
| 437 | description: format!("Failed to convert device ID to string: {}", e), |
| 438 | }, |
| 439 | }), |
| 440 | }, |
| 441 | Err(e) => Err(DeviceIdError::BackendSpecific { err: e.into() }), |
| 442 | } |
| 443 | } |
| 444 | } |
| 445 | |
| 446 | fn from_immdevice(device: Audio::IMMDevice) -> Self { |
| 447 | Device { |
| 448 | device, |
| 449 | future_audio_client: Arc::new(Mutex::new(None)), |
| 450 | } |
| 451 | } |
| 452 | |
| 453 | pub fn immdevice(&self) -> &Audio::IMMDevice { |
| 454 | &self.device |
| 455 | } |
| 456 | |
| 457 | /// Ensures that `future_audio_client` contains a `Some` and returns a locked mutex to it. |
| 458 | fn ensure_future_audio_client( |
| 459 | &self, |
| 460 | ) -> Result<MutexGuard<'_, Option<IAudioClientWrapper>>, windows::core::Error> { |
| 461 | let mut lock = self.future_audio_client.lock().unwrap(); |
| 462 | if lock.is_some() { |
| 463 | return Ok(lock); |
| 464 | } |
| 465 | |
| 466 | let audio_client: Audio::IAudioClient = unsafe { |
| 467 | // can fail if the device has been disconnected since we enumerated it, or if |
| 468 | // the device doesn't support playback for some reason |
| 469 | self.device.Activate(Com::CLSCTX_ALL, None)? |
| 470 | }; |
| 471 | |
| 472 | *lock = Some(IAudioClientWrapper(audio_client)); |
| 473 | Ok(lock) |
| 474 | } |
| 475 | |
| 476 | /// Returns an uninitialized `IAudioClient`. |
| 477 | pub(crate) fn build_audioclient(&self) -> Result<Audio::IAudioClient, windows::core::Error> { |
| 478 | let mut lock = self.ensure_future_audio_client()?; |
| 479 | Ok(lock.take().unwrap().0) |
| 480 | } |
| 481 | |
| 482 | // There is no way to query the list of all formats that are supported by the |
| 483 | // audio processor, so instead we just trial some commonly supported formats. |
| 484 | // |
| 485 | // Common formats are trialed by first getting the default format (returned via |
| 486 | // `GetMixFormat`) and then mutating that format with common sample rates and |
| 487 | // querying them via `IsFormatSupported`. |
| 488 | // |
| 489 | // When calling `IsFormatSupported` with the shared-mode audio engine, only the default |
| 490 | // number of channels seems to be supported. Any, more or less returns an invalid |
| 491 | // parameter error. Thus, we just assume that the default number of channels is the only |
| 492 | // number supported. |
| 493 | fn supported_formats(&self) -> Result<SupportedInputConfigs, SupportedStreamConfigsError> { |
| 494 | // initializing COM because we call `CoTaskMemFree` to release the format. |
| 495 | com::com_initialized(); |
| 496 | |
| 497 | // Retrieve the `IAudioClient`. |
| 498 | let lock = match self.ensure_future_audio_client() { |
| 499 | Ok(lock) => lock, |
| 500 | Err(ref e) if e.code() == Audio::AUDCLNT_E_DEVICE_INVALIDATED => { |
| 501 | return Err(SupportedStreamConfigsError::DeviceNotAvailable) |
| 502 | } |
| 503 | Err(e) => { |
| 504 | let description = format!("{}", e); |
| 505 | let err = BackendSpecificError { description }; |
| 506 | return Err(err.into()); |
| 507 | } |
| 508 | }; |
| 509 | let client = &lock.as_ref().unwrap().0; |
| 510 | |
| 511 | unsafe { |
| 512 | // Retrieve the pointer to the default WAVEFORMATEX. |
| 513 | let default_waveformatex_ptr = client |
| 514 | .GetMixFormat() |
| 515 | .map(WaveFormatExPtr) |
| 516 | .map_err(windows_err_to_cpal_err::<SupportedStreamConfigsError>)?; |
| 517 | |
| 518 | // If the default format can't succeed we have no hope of finding other formats. |
| 519 | if !is_format_supported(client, default_waveformatex_ptr.0)? { |
| 520 | let description = |
| 521 | "Could not determine support for default `WAVEFORMATEX`".to_string(); |
| 522 | let err = BackendSpecificError { description }; |
| 523 | return Err(err.into()); |
| 524 | } |
| 525 | |
| 526 | let format = match format_from_waveformatex_ptr(default_waveformatex_ptr.0, client) { |
| 527 | Some(fmt) => fmt, |
| 528 | None => { |
| 529 | let description = |
| 530 | "could not create a `cpal::SupportedStreamConfig` from a `WAVEFORMATEX`" |
| 531 | .to_string(); |
| 532 | let err = BackendSpecificError { description }; |
| 533 | return Err(err.into()); |
| 534 | } |
| 535 | }; |
| 536 | |
| 537 | let mut sample_rates: Vec<SampleRate> = COMMON_SAMPLE_RATES.to_vec(); |
| 538 | |
| 539 | if !sample_rates.contains(&format.sample_rate) { |
| 540 | sample_rates.push(format.sample_rate) |
| 541 | } |
| 542 | |
| 543 | let mut supported_formats = Vec::new(); |
| 544 | |
| 545 | for sample_rate in sample_rates { |
| 546 | for sample_format in [ |
| 547 | SampleFormat::U8, |
| 548 | SampleFormat::I16, |
| 549 | SampleFormat::I24, |
| 550 | SampleFormat::U24, |
| 551 | SampleFormat::I32, |
| 552 | SampleFormat::I64, |
| 553 | SampleFormat::F32, |
| 554 | ] { |
| 555 | if let Some(waveformat) = config_to_waveformatextensible( |
| 556 | StreamConfig { |
| 557 | channels: format.channels, |
| 558 | sample_rate, |
| 559 | buffer_size: BufferSize::Default, |
| 560 | }, |
| 561 | sample_format, |
| 562 | ) { |
| 563 | if is_format_supported( |
| 564 | client, |
| 565 | &waveformat.Format as *const Audio::WAVEFORMATEX, |
| 566 | )? { |
| 567 | supported_formats.push(SupportedStreamConfigRange { |
| 568 | channels: format.channels, |
| 569 | min_sample_rate: sample_rate, |
| 570 | max_sample_rate: sample_rate, |
| 571 | buffer_size: format.buffer_size, |
| 572 | sample_format, |
| 573 | }) |
| 574 | } |
| 575 | } |
| 576 | } |
| 577 | } |
| 578 | Ok(supported_formats.into_iter()) |
| 579 | } |
| 580 | } |
| 581 | |
| 582 | pub fn supported_input_configs( |
| 583 | &self, |
| 584 | ) -> Result<SupportedInputConfigs, SupportedStreamConfigsError> { |
| 585 | if self.data_flow() == Audio::eCapture { |
| 586 | self.supported_formats() |
| 587 | // If it's an output device, assume no input formats. |
| 588 | } else { |
| 589 | Ok(vec![].into_iter()) |
| 590 | } |
| 591 | } |
| 592 | |
| 593 | pub fn supported_output_configs( |
| 594 | &self, |
| 595 | ) -> Result<SupportedOutputConfigs, SupportedStreamConfigsError> { |
| 596 | if self.data_flow() == Audio::eRender { |
| 597 | self.supported_formats() |
| 598 | // If it's an input device, assume no output formats. |
| 599 | } else { |
| 600 | Ok(vec![].into_iter()) |
| 601 | } |
| 602 | } |
| 603 | |
| 604 | // We always create voices in shared mode, therefore all samples go through an audio |
| 605 | // processor to mix them together. |
| 606 | // |
| 607 | // One format is guaranteed to be supported, the one returned by `GetMixFormat`. |
| 608 | fn default_format(&self) -> Result<SupportedStreamConfig, DefaultStreamConfigError> { |
| 609 | // initializing COM because we call `CoTaskMemFree` |
| 610 | com::com_initialized(); |
| 611 | |
| 612 | let lock = match self.ensure_future_audio_client() { |
| 613 | Ok(lock) => lock, |
| 614 | Err(ref e) if e.code() == Audio::AUDCLNT_E_DEVICE_INVALIDATED => { |
| 615 | return Err(DefaultStreamConfigError::DeviceNotAvailable) |
| 616 | } |
| 617 | Err(e) => { |
| 618 | let description = format!("{}", e); |
| 619 | let err = BackendSpecificError { description }; |
| 620 | return Err(err.into()); |
| 621 | } |
| 622 | }; |
| 623 | let client = &lock.as_ref().unwrap().0; |
| 624 | |
| 625 | unsafe { |
| 626 | let format_ptr = client |
| 627 | .GetMixFormat() |
| 628 | .map(WaveFormatExPtr) |
| 629 | .map_err(windows_err_to_cpal_err::<DefaultStreamConfigError>)?; |
| 630 | |
| 631 | format_from_waveformatex_ptr(format_ptr.0, client) |
| 632 | .ok_or(DefaultStreamConfigError::StreamTypeNotSupported) |
| 633 | } |
| 634 | } |
| 635 | |
| 636 | pub(crate) fn data_flow(&self) -> Audio::EDataFlow { |
| 637 | let endpoint = Endpoint::from(self.device.clone()); |
| 638 | endpoint.data_flow() |
| 639 | } |
| 640 | |
| 641 | pub fn default_input_config(&self) -> Result<SupportedStreamConfig, DefaultStreamConfigError> { |
| 642 | if self.data_flow() == Audio::eCapture { |
| 643 | self.default_format() |
| 644 | } else { |
| 645 | Err(DefaultStreamConfigError::StreamTypeNotSupported) |
| 646 | } |
| 647 | } |
| 648 | |
| 649 | pub fn default_output_config(&self) -> Result<SupportedStreamConfig, DefaultStreamConfigError> { |
| 650 | let data_flow = self.data_flow(); |
| 651 | if data_flow == Audio::eRender { |
| 652 | self.default_format() |
| 653 | } else { |
| 654 | Err(DefaultStreamConfigError::StreamTypeNotSupported) |
| 655 | } |
| 656 | } |
| 657 | |
| 658 | pub(crate) fn build_input_stream_raw_inner( |
| 659 | &self, |
| 660 | config: StreamConfig, |
| 661 | sample_format: SampleFormat, |
| 662 | ) -> Result<StreamInner, BuildStreamError> { |
| 663 | unsafe { |
| 664 | // Making sure that COM is initialized. |
| 665 | // It's not actually sure that this is required, but when in doubt do it. |
| 666 | com::com_initialized(); |
| 667 | |
| 668 | // Obtaining a `IAudioClient`. |
| 669 | let audio_client = match self.build_audioclient() { |
| 670 | Ok(client) => client, |
| 671 | Err(ref e) if e.code() == Audio::AUDCLNT_E_DEVICE_INVALIDATED => { |
| 672 | return Err(BuildStreamError::DeviceNotAvailable) |
| 673 | } |
| 674 | Err(e) => { |
| 675 | let description = format!("{}", e); |
| 676 | let err = BackendSpecificError { description }; |
| 677 | return Err(err.into()); |
| 678 | } |
| 679 | }; |
| 680 | |
| 681 | // Note: Buffer size validation is not needed here - `IAudioClient::Initialize` |
| 682 | // will return `AUDCLNT_E_BUFFER_SIZE_ERROR` if the buffer size is not supported. |
| 683 | let buffer_duration = buffer_size_to_duration(&config.buffer_size, config.sample_rate); |
| 684 | |
| 685 | let mut stream_flags = DEFAULT_FLAGS; |
| 686 | |
| 687 | if self.data_flow() == Audio::eRender { |
| 688 | stream_flags |= Audio::AUDCLNT_STREAMFLAGS_LOOPBACK; |
| 689 | } |
| 690 | |
| 691 | // Computing the format and initializing the device. |
| 692 | let waveformatex = { |
| 693 | let format_attempt = config_to_waveformatextensible(config, sample_format) |
| 694 | .ok_or(BuildStreamError::StreamConfigNotSupported)?; |
| 695 | let share_mode = Audio::AUDCLNT_SHAREMODE_SHARED; |
| 696 | |
| 697 | // Ensure the format is supported. |
| 698 | match super::device::is_format_supported(&audio_client, &format_attempt.Format) { |
| 699 | Ok(false) => return Err(BuildStreamError::StreamConfigNotSupported), |
| 700 | Err(_) => return Err(BuildStreamError::DeviceNotAvailable), |
| 701 | _ => (), |
| 702 | } |
| 703 | |
| 704 | // Finally, initializing the audio client |
| 705 | let hresult = audio_client.Initialize( |
| 706 | share_mode, |
| 707 | stream_flags, |
| 708 | buffer_duration, |
| 709 | 0, |
| 710 | &format_attempt.Format, |
| 711 | None, |
| 712 | ); |
| 713 | match hresult { |
| 714 | Err(ref e) if e.code() == Audio::AUDCLNT_E_DEVICE_INVALIDATED => { |
| 715 | return Err(BuildStreamError::DeviceNotAvailable); |
| 716 | } |
| 717 | Err(e) => { |
| 718 | let description = format!("{}", e); |
| 719 | let err = BackendSpecificError { description }; |
| 720 | return Err(err.into()); |
| 721 | } |
| 722 | Ok(()) => (), |
| 723 | }; |
| 724 | |
| 725 | format_attempt.Format |
| 726 | }; |
| 727 | |
| 728 | // obtaining the size of the samples buffer in number of frames |
| 729 | let max_frames_in_buffer = audio_client |
| 730 | .GetBufferSize() |
| 731 | .map_err(windows_err_to_cpal_err::<BuildStreamError>)?; |
| 732 | |
| 733 | let period_frames = |
| 734 | shared_mode_period_frames(&audio_client, config.sample_rate, max_frames_in_buffer); |
| 735 | |
| 736 | // Creating the event that will be signalled whenever we need to submit some samples. |
| 737 | let event = { |
| 738 | let event = |
| 739 | Threading::CreateEventA(None, false, false, windows::core::PCSTR(ptr::null())) |
| 740 | .map_err(|e| { |
| 741 | let description = format!("failed to create event: {}", e); |
| 742 | let err = BackendSpecificError { description }; |
| 743 | BuildStreamError::from(err) |
| 744 | })?; |
| 745 | |
| 746 | if let Err(e) = audio_client.SetEventHandle(event) { |
| 747 | let description = format!("failed to call SetEventHandle: {}", e); |
| 748 | let err = BackendSpecificError { description }; |
| 749 | return Err(err.into()); |
| 750 | } |
| 751 | |
| 752 | event |
| 753 | }; |
| 754 | |
| 755 | // Building a `IAudioCaptureClient` that will be used to read captured samples. |
| 756 | let capture_client = audio_client |
| 757 | .GetService::<Audio::IAudioCaptureClient>() |
| 758 | .map_err(|e| { |
| 759 | windows_err_to_cpal_err_message::<BuildStreamError>( |
| 760 | e, |
| 761 | "failed to build capture client: ", |
| 762 | ) |
| 763 | })?; |
| 764 | |
| 765 | // Once we built the `StreamInner`, we add a command that will be picked up by the |
| 766 | // `run()` method and added to the `RunContext`. |
| 767 | let client_flow = AudioClientFlow::Capture { capture_client }; |
| 768 | |
| 769 | let audio_clock = get_audio_clock(&audio_client)?; |
| 770 | |
| 771 | let stream_latency = { |
| 772 | let hns = audio_client.GetStreamLatency().map_err(|e| { |
| 773 | windows_err_to_cpal_err_message::<BuildStreamError>( |
| 774 | e, |
| 775 | "failed to get stream latency: ", |
| 776 | ) |
| 777 | })?; |
| 778 | Duration::from_nanos(hns.max(0) as u64 * 100) |
| 779 | }; |
| 780 | |
| 781 | Ok(StreamInner { |
| 782 | audio_client, |
| 783 | audio_clock, |
| 784 | client_flow, |
| 785 | event, |
| 786 | playing: false, |
| 787 | max_frames_in_buffer, |
| 788 | period_frames, |
| 789 | bytes_per_frame: waveformatex.nBlockAlign, |
| 790 | config, |
| 791 | sample_format, |
| 792 | stream_latency, |
| 793 | }) |
| 794 | } |
| 795 | } |
| 796 | |
| 797 | pub(crate) fn build_output_stream_raw_inner( |
| 798 | &self, |
| 799 | config: StreamConfig, |
| 800 | sample_format: SampleFormat, |
| 801 | ) -> Result<StreamInner, BuildStreamError> { |
| 802 | unsafe { |
| 803 | // Making sure that COM is initialized. |
| 804 | // It's not actually sure that this is required, but when in doubt do it. |
| 805 | com::com_initialized(); |
| 806 | |
| 807 | // Obtaining a `IAudioClient`. |
| 808 | let audio_client = self |
| 809 | .build_audioclient() |
| 810 | .map_err(windows_err_to_cpal_err::<BuildStreamError>)?; |
| 811 | |
| 812 | // Note: Buffer size validation is not needed here - `IAudioClient::Initialize` |
| 813 | // will return `AUDCLNT_E_BUFFER_SIZE_ERROR` if the buffer size is not supported. |
| 814 | let buffer_duration = buffer_size_to_duration(&config.buffer_size, config.sample_rate); |
| 815 | |
| 816 | // Computing the format and initializing the device. |
| 817 | let waveformatex = { |
| 818 | let format_attempt = config_to_waveformatextensible(config, sample_format) |
| 819 | .ok_or(BuildStreamError::StreamConfigNotSupported)?; |
| 820 | let share_mode = Audio::AUDCLNT_SHAREMODE_SHARED; |
| 821 | |
| 822 | // Ensure the format is supported. |
| 823 | match super::device::is_format_supported(&audio_client, &format_attempt.Format) { |
| 824 | Ok(false) => return Err(BuildStreamError::StreamConfigNotSupported), |
| 825 | Err(_) => return Err(BuildStreamError::DeviceNotAvailable), |
| 826 | _ => (), |
| 827 | } |
| 828 | |
| 829 | // Finally, initializing the audio client |
| 830 | audio_client |
| 831 | .Initialize( |
| 832 | share_mode, |
| 833 | DEFAULT_FLAGS, |
| 834 | buffer_duration, |
| 835 | 0, |
| 836 | &format_attempt.Format, |
| 837 | None, |
| 838 | ) |
| 839 | .map_err(windows_err_to_cpal_err::<BuildStreamError>)?; |
| 840 | |
| 841 | format_attempt.Format |
| 842 | }; |
| 843 | |
| 844 | // Creating the event that will be signalled whenever we need to submit some samples. |
| 845 | let event = { |
| 846 | let event = |
| 847 | Threading::CreateEventA(None, false, false, windows::core::PCSTR(ptr::null())) |
| 848 | .map_err(|e| { |
| 849 | let description = format!("failed to create event: {}", e); |
| 850 | let err = BackendSpecificError { description }; |
| 851 | BuildStreamError::from(err) |
| 852 | })?; |
| 853 | |
| 854 | if let Err(e) = audio_client.SetEventHandle(event) { |
| 855 | let description = format!("failed to call SetEventHandle: {}", e); |
| 856 | let err = BackendSpecificError { description }; |
| 857 | return Err(err.into()); |
| 858 | } |
| 859 | |
| 860 | event |
| 861 | }; |
| 862 | |
| 863 | // obtaining the size of the samples buffer in number of frames |
| 864 | let max_frames_in_buffer = audio_client.GetBufferSize().map_err(|e| { |
| 865 | windows_err_to_cpal_err_message::<BuildStreamError>( |
| 866 | e, |
| 867 | "failed to obtain buffer size: ", |
| 868 | ) |
| 869 | })?; |
| 870 | |
| 871 | let period_frames = |
| 872 | shared_mode_period_frames(&audio_client, config.sample_rate, max_frames_in_buffer); |
| 873 | |
| 874 | // Building a `IAudioRenderClient` that will be used to fill the samples buffer. |
| 875 | let render_client = audio_client |
| 876 | .GetService::<IAudioRenderClient>() |
| 877 | .map_err(|e| { |
| 878 | windows_err_to_cpal_err_message::<BuildStreamError>( |
| 879 | e, |
| 880 | "failed to build render client: ", |
| 881 | ) |
| 882 | })?; |
| 883 | |
| 884 | // Once we built the `StreamInner`, we add a command that will be picked up by the |
| 885 | // `run()` method and added to the `RunContext`. |
| 886 | let client_flow = AudioClientFlow::Render { render_client }; |
| 887 | |
| 888 | let audio_clock = get_audio_clock(&audio_client)?; |
| 889 | |
| 890 | let stream_latency = { |
| 891 | let hns = audio_client.GetStreamLatency().map_err(|e| { |
| 892 | windows_err_to_cpal_err_message::<BuildStreamError>( |
| 893 | e, |
| 894 | "failed to get stream latency: ", |
| 895 | ) |
| 896 | })?; |
| 897 | Duration::from_nanos(hns.max(0) as u64 * 100) |
| 898 | }; |
| 899 | |
| 900 | Ok(StreamInner { |
| 901 | audio_client, |
| 902 | audio_clock, |
| 903 | client_flow, |
| 904 | event, |
| 905 | playing: false, |
| 906 | max_frames_in_buffer, |
| 907 | period_frames, |
| 908 | bytes_per_frame: waveformatex.nBlockAlign, |
| 909 | config, |
| 910 | sample_format, |
| 911 | stream_latency, |
| 912 | }) |
| 913 | } |
| 914 | } |
| 915 | } |
| 916 | |
| 917 | impl PartialEq for Device { |
| 918 | fn eq(&self, other: &Device) -> bool { |
| 919 | // Use case: In order to check whether the default device has changed |
| 920 | // the client code might need to compare the previous default device with the current one. |
| 921 | // The pointer comparison (`self.device == other.device`) don't work there, |
| 922 | // because the pointers are different even when the default device stays the same. |
| 923 | // |
| 924 | // In this code section we're trying to use the GetId method for the device comparison, cf. |
| 925 | // https://docs.microsoft.com/en-us/windows/desktop/api/mmdeviceapi/nf-mmdeviceapi-immdevice-getid |
| 926 | unsafe { |
| 927 | struct IdRAII(windows::core::PWSTR); |
| 928 | /// RAII for device IDs. |
| 929 | impl Drop for IdRAII { |
| 930 | fn drop(&mut self) { |
| 931 | unsafe { Com::CoTaskMemFree(Some(self.0 .0 as *mut _)) } |
| 932 | } |
| 933 | } |
| 934 | // GetId only fails with E_OUTOFMEMORY and if it does, we're probably dead already. |
| 935 | // Plus it won't do to change the device comparison logic unexpectedly. |
| 936 | let id1 = self.device.GetId().expect("cpal: GetId failure"); |
| 937 | let id1 = IdRAII(id1); |
| 938 | let id2 = other.device.GetId().expect("cpal: GetId failure"); |
| 939 | let id2 = IdRAII(id2); |
| 940 | // 16-bit null-terminated comparison. |
| 941 | let mut offset = 0; |
| 942 | loop { |
| 943 | let w1: u16 = *(id1.0).0.offset(offset); |
| 944 | let w2: u16 = *(id2.0).0.offset(offset); |
| 945 | if w1 == 0 && w2 == 0 { |
| 946 | return true; |
| 947 | } |
| 948 | if w1 != w2 { |
| 949 | return false; |
| 950 | } |
| 951 | offset += 1; |
| 952 | } |
| 953 | } |
| 954 | } |
| 955 | } |
| 956 | |
| 957 | impl Eq for Device {} |
| 958 | |
| 959 | impl std::hash::Hash for Device { |
| 960 | fn hash<H: std::hash::Hasher>(&self, state: &mut H) { |
| 961 | // Hash the device ID for consistency with PartialEq |
| 962 | // SAFETY: GetId only fails with E_OUTOFMEMORY, which is unrecoverable. |
| 963 | // We need consistent hash/eq behavior. |
| 964 | unsafe { |
| 965 | use windows::Win32::System::Com; |
| 966 | |
| 967 | struct IdRAII(windows::core::PWSTR); |
| 968 | impl Drop for IdRAII { |
| 969 | fn drop(&mut self) { |
| 970 | unsafe { Com::CoTaskMemFree(Some(self.0 .0 as *mut _)) } |
| 971 | } |
| 972 | } |
| 973 | |
| 974 | let id = self.device.GetId().expect("cpal: GetId failure"); |
| 975 | let id = IdRAII(id); |
| 976 | |
| 977 | // Hash the 16-bit null-terminated string |
| 978 | let mut offset = 0; |
| 979 | loop { |
| 980 | let w: u16 = *(id.0).0.offset(offset); |
| 981 | if w == 0 { |
| 982 | break; |
| 983 | } |
| 984 | w.hash(state); |
| 985 | offset += 1; |
| 986 | } |
| 987 | } |
| 988 | } |
| 989 | } |
| 990 | |
| 991 | impl fmt::Debug for Device { |
| 992 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| 993 | f.debug_struct("Device") |
| 994 | .field("device", &self.device) |
| 995 | .field("description", &self.description()) |
| 996 | .finish() |
| 997 | } |
| 998 | } |
| 999 | |
| 1000 | impl From<Audio::IMMDevice> for Endpoint { |
| 1001 | fn from(device: Audio::IMMDevice) -> Self { |
| 1002 | unsafe { |
| 1003 | let endpoint = immendpoint_from_immdevice(device); |
| 1004 | Endpoint { endpoint } |
| 1005 | } |
| 1006 | } |
| 1007 | } |
| 1008 | |
| 1009 | impl Endpoint { |
| 1010 | fn data_flow(&self) -> Audio::EDataFlow { |
| 1011 | unsafe { data_flow_from_immendpoint(&self.endpoint) } |
| 1012 | } |
| 1013 | } |
| 1014 | |
| 1015 | static ENUMERATOR: OnceLock<Enumerator> = OnceLock::new(); |
| 1016 | |
| 1017 | fn get_enumerator() -> &'static Enumerator { |
| 1018 | ENUMERATOR.get_or_init(|| { |
| 1019 | // COM initialization is thread local, but we only need to have COM initialized in the |
| 1020 | // thread we create the objects in |
| 1021 | com::com_initialized(); |
| 1022 | |
| 1023 | // building the devices enumerator object |
| 1024 | unsafe { |
| 1025 | let enumerator = Com::CoCreateInstance::<_, Audio::IMMDeviceEnumerator>( |
| 1026 | &Audio::MMDeviceEnumerator, |
| 1027 | None, |
| 1028 | Com::CLSCTX_ALL, |
| 1029 | ) |
| 1030 | .unwrap(); |
| 1031 | |
| 1032 | Enumerator(enumerator) |
| 1033 | } |
| 1034 | }) |
| 1035 | } |
| 1036 | |
| 1037 | // Helper function to query a DWORD property from a WASAPI device property store |
| 1038 | unsafe fn get_property_u32( |
| 1039 | property_store: &IPropertyStore, |
| 1040 | property_key: *const PROPERTYKEY, |
| 1041 | ) -> Option<u32> { |
| 1042 | let mut property_value = property_store.GetValue(property_key).ok()?; |
| 1043 | let prop_variant = &property_value.Anonymous.Anonymous; |
| 1044 | |
| 1045 | // Check if it's a UI4 (unsigned 32-bit integer) |
| 1046 | if prop_variant.vt != VT_UI4 { |
| 1047 | return None; |
| 1048 | } |
| 1049 | |
| 1050 | let value = *(&prop_variant.Anonymous as *const _ as *const u32); |
| 1051 | |
| 1052 | // Clean up the property |
| 1053 | StructuredStorage::PropVariantClear(&mut property_value).ok(); |
| 1054 | |
| 1055 | Some(value) |
| 1056 | } |
| 1057 | |
| 1058 | // Helper function to query a string property from a WASAPI device property store |
| 1059 | unsafe fn get_property_string( |
| 1060 | property_store: &IPropertyStore, |
| 1061 | property_key: *const PROPERTYKEY, |
| 1062 | ) -> Option<String> { |
| 1063 | let mut property_value = property_store.GetValue(property_key).ok()?; |
| 1064 | let prop_variant = &property_value.Anonymous.Anonymous; |
| 1065 | |
| 1066 | // Read the string from the union data field, expecting a *const u16. |
| 1067 | if prop_variant.vt != VT_LPWSTR { |
| 1068 | return None; |
| 1069 | } |
| 1070 | let ptr_utf16 = *(&prop_variant.Anonymous as *const _ as *const *const u16); |
| 1071 | |
| 1072 | // Find the length of the null-terminated string with a safety limit |
| 1073 | const MAX_STRING_LEN: usize = 32768; // 32K characters should be more than enough |
| 1074 | let mut len = 0; |
| 1075 | while len < MAX_STRING_LEN && *ptr_utf16.add(len) != 0 { |
| 1076 | len += 1; |
| 1077 | } |
| 1078 | |
| 1079 | // If we hit the limit, the string is likely malformed (not null-terminated) |
| 1080 | if len >= MAX_STRING_LEN { |
| 1081 | return None; |
| 1082 | } |
| 1083 | |
| 1084 | // Create the utf16 slice and convert it into a string. |
| 1085 | let string_slice = slice::from_raw_parts(ptr_utf16, len); |
| 1086 | let os_string: OsString = OsStringExt::from_wide(string_slice); |
| 1087 | let result = match os_string.into_string() { |
| 1088 | Ok(string) => Some(string), |
| 1089 | Err(os_string) => Some(os_string.to_string_lossy().into()), |
| 1090 | }; |
| 1091 | |
| 1092 | // Clean up the property. |
| 1093 | StructuredStorage::PropVariantClear(&mut property_value).ok(); |
| 1094 | |
| 1095 | result |
| 1096 | } |
| 1097 | |
| 1098 | /// Send/Sync wrapper around `IMMDeviceEnumerator`. |
| 1099 | struct Enumerator(Audio::IMMDeviceEnumerator); |
| 1100 | |
| 1101 | unsafe impl Send for Enumerator {} |
| 1102 | unsafe impl Sync for Enumerator {} |
| 1103 | |
| 1104 | /// WASAPI implementation for `Devices`. |
| 1105 | pub struct Devices { |
| 1106 | collection: Audio::IMMDeviceCollection, |
| 1107 | total_count: u32, |
| 1108 | next_item: u32, |
| 1109 | } |
| 1110 | |
| 1111 | impl Devices { |
| 1112 | pub fn new() -> Result<Self, DevicesError> { |
| 1113 | unsafe { |
| 1114 | // can fail because of wrong parameters (should never happen) or out of memory |
| 1115 | let collection = get_enumerator() |
| 1116 | .0 |
| 1117 | .EnumAudioEndpoints(Audio::eAll, Audio::DEVICE_STATE_ACTIVE) |
| 1118 | .map_err(BackendSpecificError::from)?; |
| 1119 | |
| 1120 | let count = collection.GetCount().map_err(BackendSpecificError::from)?; |
| 1121 | |
| 1122 | Ok(Devices { |
| 1123 | collection, |
| 1124 | total_count: count, |
| 1125 | next_item: 0, |
| 1126 | }) |
| 1127 | } |
| 1128 | } |
| 1129 | } |
| 1130 | |
| 1131 | unsafe impl Send for Devices {} |
| 1132 | unsafe impl Sync for Devices {} |
| 1133 | |
| 1134 | impl Iterator for Devices { |
| 1135 | type Item = Device; |
| 1136 | |
| 1137 | fn next(&mut self) -> Option<Device> { |
| 1138 | if self.next_item >= self.total_count { |
| 1139 | return None; |
| 1140 | } |
| 1141 | |
| 1142 | unsafe { |
| 1143 | let device = self.collection.Item(self.next_item).unwrap(); |
| 1144 | self.next_item += 1; |
| 1145 | Some(Device::from_immdevice(device)) |
| 1146 | } |
| 1147 | } |
| 1148 | |
| 1149 | fn size_hint(&self) -> (usize, Option<usize>) { |
| 1150 | let num = self.total_count - self.next_item; |
| 1151 | let num = num as usize; |
| 1152 | (num, Some(num)) |
| 1153 | } |
| 1154 | } |
| 1155 | |
| 1156 | fn default_device(data_flow: Audio::EDataFlow) -> Option<Device> { |
| 1157 | unsafe { |
| 1158 | let device = get_enumerator() |
| 1159 | .0 |
| 1160 | .GetDefaultAudioEndpoint(data_flow, Audio::eConsole) |
| 1161 | .ok()?; |
| 1162 | // TODO: check specifically for `E_NOTFOUND`, and panic otherwise |
| 1163 | Some(Device::from_immdevice(device)) |
| 1164 | } |
| 1165 | } |
| 1166 | |
| 1167 | pub fn default_input_device() -> Option<Device> { |
| 1168 | default_device(Audio::eCapture) |
| 1169 | } |
| 1170 | |
| 1171 | pub fn default_output_device() -> Option<Device> { |
| 1172 | default_device(Audio::eRender) |
| 1173 | } |
| 1174 | |
| 1175 | /// Get the audio clock used to produce `StreamInstant`s. |
| 1176 | unsafe fn get_audio_clock( |
| 1177 | audio_client: &Audio::IAudioClient, |
| 1178 | ) -> Result<Audio::IAudioClock, BuildStreamError> { |
| 1179 | audio_client |
| 1180 | .GetService::<Audio::IAudioClock>() |
| 1181 | .map_err(|e| { |
| 1182 | windows_err_to_cpal_err_message::<BuildStreamError>(e, "failed to build audio clock: ") |
| 1183 | }) |
| 1184 | } |
| 1185 | |
| 1186 | // Turns a `Format` into a `WAVEFORMATEXTENSIBLE`. |
| 1187 | // |
| 1188 | // Returns `None` if the WAVEFORMATEXTENSIBLE does not support the given format. |
| 1189 | fn config_to_waveformatextensible( |
| 1190 | config: StreamConfig, |
| 1191 | sample_format: SampleFormat, |
| 1192 | ) -> Option<Audio::WAVEFORMATEXTENSIBLE> { |
| 1193 | let format_tag = match sample_format { |
| 1194 | SampleFormat::U8 | SampleFormat::I16 => Audio::WAVE_FORMAT_PCM, |
| 1195 | |
| 1196 | SampleFormat::I24 |
| 1197 | | SampleFormat::U24 |
| 1198 | | SampleFormat::I32 |
| 1199 | | SampleFormat::I64 |
| 1200 | | SampleFormat::F32 => KernelStreaming::WAVE_FORMAT_EXTENSIBLE, |
| 1201 | |
| 1202 | _ => return None, |
| 1203 | }; |
| 1204 | let channels = config.channels; |
| 1205 | let sample_rate = config.sample_rate; |
| 1206 | let sample_bytes = sample_format.sample_size() as u16; |
| 1207 | let avg_bytes_per_sec = u32::from(channels) * sample_rate * u32::from(sample_bytes); |
| 1208 | let block_align = channels * sample_bytes; |
| 1209 | let bits_per_sample = match sample_format { |
| 1210 | // 24-bit formats use 32-bit storage but only 24 valid bits |
| 1211 | SampleFormat::I24 | SampleFormat::U24 => 24, |
| 1212 | _ => 8 * sample_bytes, |
| 1213 | }; |
| 1214 | |
| 1215 | let cb_size = if format_tag == Audio::WAVE_FORMAT_PCM { |
| 1216 | 0 |
| 1217 | } else { |
| 1218 | let extensible_size = mem::size_of::<Audio::WAVEFORMATEXTENSIBLE>(); |
| 1219 | let ex_size = mem::size_of::<Audio::WAVEFORMATEX>(); |
| 1220 | (extensible_size - ex_size) as u16 |
| 1221 | }; |
| 1222 | |
| 1223 | let waveformatex = Audio::WAVEFORMATEX { |
| 1224 | wFormatTag: format_tag as u16, |
| 1225 | nChannels: channels, |
| 1226 | nSamplesPerSec: sample_rate, |
| 1227 | nAvgBytesPerSec: avg_bytes_per_sec, |
| 1228 | nBlockAlign: block_align, |
| 1229 | wBitsPerSample: bits_per_sample, |
| 1230 | cbSize: cb_size, |
| 1231 | }; |
| 1232 | |
| 1233 | // CPAL does not care about speaker positions, so pass audio right through. |
| 1234 | let channel_mask = KernelStreaming::KSAUDIO_SPEAKER_DIRECTOUT; |
| 1235 | |
| 1236 | let sub_format = match sample_format { |
| 1237 | SampleFormat::U8 |
| 1238 | | SampleFormat::I16 |
| 1239 | | SampleFormat::I24 |
| 1240 | | SampleFormat::U24 |
| 1241 | | SampleFormat::I32 |
| 1242 | | SampleFormat::I64 => KernelStreaming::KSDATAFORMAT_SUBTYPE_PCM, |
| 1243 | |
| 1244 | SampleFormat::F32 => Multimedia::KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, |
| 1245 | _ => return None, |
| 1246 | }; |
| 1247 | |
| 1248 | let waveformatextensible = Audio::WAVEFORMATEXTENSIBLE { |
| 1249 | Format: waveformatex, |
| 1250 | Samples: Audio::WAVEFORMATEXTENSIBLE_0 { |
| 1251 | wSamplesPerBlock: bits_per_sample, |
| 1252 | }, |
| 1253 | dwChannelMask: channel_mask, |
| 1254 | SubFormat: sub_format, |
| 1255 | }; |
| 1256 | |
| 1257 | Some(waveformatextensible) |
| 1258 | } |
| 1259 | |
| 1260 | /// Get the default device period in frames for a shared-mode stream. |
| 1261 | fn shared_mode_period_frames( |
| 1262 | audio_client: &Audio::IAudioClient, |
| 1263 | sample_rate: crate::SampleRate, |
| 1264 | max_frames_in_buffer: crate::FrameCount, |
| 1265 | ) -> crate::FrameCount { |
| 1266 | let mut default_period = 0i64; |
| 1267 | if unsafe { audio_client.GetDevicePeriod(Some(&mut default_period), None) }.is_ok() |
| 1268 | && default_period > 0 |
| 1269 | { |
| 1270 | buffer_duration_to_frames(default_period, sample_rate) |
| 1271 | } else { |
| 1272 | max_frames_in_buffer |
| 1273 | } |
| 1274 | } |
| 1275 | |
| 1276 | fn buffer_size_to_duration(buffer_size: &BufferSize, sample_rate: SampleRate) -> i64 { |
| 1277 | match buffer_size { |
| 1278 | BufferSize::Fixed(frames) => *frames as i64 * (1_000_000_000 / 100) / sample_rate as i64, |
| 1279 | BufferSize::Default => 0, |
| 1280 | } |
| 1281 | } |
| 1282 | |
| 1283 | fn buffer_duration_to_frames(buffer_duration: i64, sample_rate: SampleRate) -> FrameCount { |
| 1284 | (buffer_duration * sample_rate as i64 * 100 / 1_000_000_000) as FrameCount |
| 1285 | } |