nandi/jolt-nativepublic Fork 0
fa4ecdfed6a830e6099d5fab9be24ef72ea1923b
Commits
Clone
git clone https://git.rickub.com/nandi/jolt-native.git
git clone ssh://git@rickub.com/nandi/jolt-native.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

device.rs · 1285 lines · 46.4 KBRust Blame HistoryRaw
Lift freeq's AV media plane out of sleek 90f8b89 nandi 20d ago1use 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
9impl 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}
20use std::ffi::OsString;
21use std::fmt;
22use std::mem;
23use std::os::windows::ffi::OsStringExt;
24use std::ptr;
25use std::slice;
26use std::sync::OnceLock;
27use std::sync::{Arc, Mutex, MutexGuard};
28use std::time::Duration;
29
30use super::{windows_err_to_cpal_err, windows_err_to_cpal_err_message};
31use crate::host::com;
32use windows::core::Interface;
33use windows::core::GUID;
34use windows::Win32::Devices::Properties;
35use windows::Win32::Foundation::PROPERTYKEY;
36use windows::Win32::Media::Audio::IAudioRenderClient;
37use windows::Win32::Media::{Audio, KernelStreaming, Multimedia};
38use windows::Win32::System::Com;
39use windows::Win32::System::Com::{StructuredStorage, STGM_READ};
40use windows::Win32::System::Threading;
41use windows::Win32::System::Variant::{VT_LPWSTR, VT_UI4};
42use windows::Win32::UI::Shell::PropertiesSystem::IPropertyStore;
43
44use super::stream::{AudioClientFlow, Stream, StreamInner};
45use crate::{traits::DeviceTrait, BuildStreamError, StreamError};
46
47pub 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
52const 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
58const PKEY_AUDIOENDPOINT_JACKSUBTYPE: PROPERTYKEY = PROPERTYKEY {
59 fmtid: GUID::from_u128(0x1da5d803_d492_4edd_8c23_e0c0ffee7f0e),
60 pid: 8,
61};
62
63const 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)]
69struct IAudioClientWrapper(Audio::IAudioClient);
70unsafe impl Send for IAudioClientWrapper {}
71unsafe impl Sync for IAudioClientWrapper {}
72
73/// An opaque type that identifies an end point.
74#[derive(Clone)]
75pub 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
82impl 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
164struct Endpoint {
165 endpoint: Audio::IMMEndpoint,
166}
167
168// Use RAII to make sure CoTaskMemFree is called when we are responsible for freeing.
169struct WaveFormatExPtr(*mut Audio::WAVEFORMATEX);
170
171impl Drop for WaveFormatExPtr {
172 fn drop(&mut self) {
173 unsafe {
174 Com::CoTaskMemFree(Some(self.0 as *mut _));
175 }
176 }
177}
178
179unsafe 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
185unsafe 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.
192pub 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.
203unsafe 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
286unsafe impl Send for Device {}
287unsafe 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.
293fn 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.
306fn 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.
323fn 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
334impl 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
917impl 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
957impl Eq for Device {}
958
959impl 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
991impl 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
1000impl 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
1009impl Endpoint {
1010 fn data_flow(&self) -> Audio::EDataFlow {
1011 unsafe { data_flow_from_immendpoint(&self.endpoint) }
1012 }
1013}
1014
1015static ENUMERATOR: OnceLock<Enumerator> = OnceLock::new();
1016
1017fn 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
1038unsafe 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
1059unsafe 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`.
1099struct Enumerator(Audio::IMMDeviceEnumerator);
1100
1101unsafe impl Send for Enumerator {}
1102unsafe impl Sync for Enumerator {}
1103
1104/// WASAPI implementation for `Devices`.
1105pub struct Devices {
1106 collection: Audio::IMMDeviceCollection,
1107 total_count: u32,
1108 next_item: u32,
1109}
1110
1111impl 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
1131unsafe impl Send for Devices {}
1132unsafe impl Sync for Devices {}
1133
1134impl 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
1156fn 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
1167pub fn default_input_device() -> Option<Device> {
1168 default_device(Audio::eCapture)
1169}
1170
1171pub fn default_output_device() -> Option<Device> {
1172 default_device(Audio::eRender)
1173}
1174
1175/// Get the audio clock used to produce `StreamInstant`s.
1176unsafe 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.
1189fn 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.
1261fn 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
1276fn 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
1283fn 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}