From de6db3378ee981e7b1a6a529cb5e3e4a8ab2243c Mon Sep 17 00:00:00 2001 From: Jesper Nordenberg Date: Wed, 21 Feb 2024 13:36:21 +0100 Subject: [PATCH] Added WASAPI exclusive host --- src/host/mod.rs | 2 + src/host/wasapi/device.rs | 339 +++++++++++++++---------------- src/host/wasapi/mod.rs | 14 +- src/host/wasapi/stream.rs | 64 ++++-- src/host/wasapi_exclusive/mod.rs | 149 ++++++++++++++ src/lib.rs | 2 +- src/platform/mod.rs | 3 +- 7 files changed, 378 insertions(+), 195 deletions(-) create mode 100644 src/host/wasapi_exclusive/mod.rs diff --git a/src/host/mod.rs b/src/host/mod.rs index 8de06cbe0..bc1b73e7c 100644 --- a/src/host/mod.rs +++ b/src/host/mod.rs @@ -26,5 +26,7 @@ pub(crate) mod null; pub(crate) mod oboe; #[cfg(windows)] pub(crate) mod wasapi; +#[cfg(windows)] +pub(crate) mod wasapi_exclusive; #[cfg(all(target_arch = "wasm32", feature = "wasm-bindgen"))] pub(crate) mod webaudio; diff --git a/src/host/wasapi/device.rs b/src/host/wasapi/device.rs index 1ab9b6411..381b5c18f 100644 --- a/src/host/wasapi/device.rs +++ b/src/host/wasapi/device.rs @@ -15,13 +15,13 @@ use std::sync::OnceLock; use std::sync::{Arc, Mutex, MutexGuard}; use std::time::Duration; -use super::com; +use super::{com, ShareMode}; use super::{windows_err_to_cpal_err, windows_err_to_cpal_err_message}; use windows::core::ComInterface; use windows::core::GUID; use windows::Win32::Devices::Properties; use windows::Win32::Foundation; -use windows::Win32::Media::Audio::IAudioRenderClient; +use windows::Win32::Media::Audio::{IAudioRenderClient, AUDCLNT_SHAREMODE}; use windows::Win32::Media::{Audio, KernelStreaming, Multimedia}; use windows::Win32::System::Com; use windows::Win32::System::Com::{StructuredStorage, STGM_READ}; @@ -44,6 +44,7 @@ unsafe impl Sync for IAudioClientWrapper {} #[derive(Clone)] pub struct Device { device: Audio::IMMDevice, + share_mode: ShareMode, /// We cache an uninitialized `IAudioClient` so that we can call functions from it without /// having to create/destroy audio clients all the time. future_audio_client: Arc>>, // TODO: add NonZero around the ptr @@ -90,7 +91,7 @@ impl DeviceTrait for Device { D: FnMut(&Data, &InputCallbackInfo) + Send + 'static, E: FnMut(StreamError) + Send + 'static, { - let stream_inner = self.build_input_stream_raw_inner(config, sample_format)?; + let stream_inner = self.build_stream(config, sample_format, true)?; Ok(Stream::new_input( stream_inner, data_callback, @@ -110,7 +111,7 @@ impl DeviceTrait for Device { D: FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static, E: FnMut(StreamError) + Send + 'static, { - let stream_inner = self.build_output_stream_raw_inner(config, sample_format)?; + let stream_inner = self.build_stream(config, sample_format, false)?; Ok(Stream::new_output( stream_inner, data_callback, @@ -149,12 +150,13 @@ unsafe fn data_flow_from_immendpoint(endpoint: &Audio::IMMEndpoint) -> Audio::ED // Given the audio client and format, returns whether or not the format is supported. pub unsafe fn is_format_supported( client: &Audio::IAudioClient, + share_mode: ShareMode, waveformatex_ptr: *const Audio::WAVEFORMATEX, ) -> Result { // Check if the given format is supported. let is_supported = |waveformatex_ptr, closest_waveformatex_ptr| { let result = client.IsFormatSupported( - Audio::AUDCLNT_SHAREMODE_SHARED, + to_winapi_share_mode(share_mode), waveformatex_ptr, Some(closest_waveformatex_ptr), ); @@ -278,6 +280,13 @@ unsafe fn format_from_waveformatex_ptr( unsafe impl Send for Device {} unsafe impl Sync for Device {} +fn to_winapi_share_mode(share_mode: ShareMode) -> AUDCLNT_SHAREMODE { + match share_mode { + ShareMode::Shared => Audio::AUDCLNT_SHAREMODE_SHARED, + ShareMode::Exclusive => Audio::AUDCLNT_SHAREMODE_EXCLUSIVE, + } +} + impl Device { pub fn name(&self) -> Result { unsafe { @@ -332,10 +341,11 @@ impl Device { } #[inline] - fn from_immdevice(device: Audio::IMMDevice) -> Self { + fn from_immdevice(device: Audio::IMMDevice, share_mode: ShareMode) -> Self { Device { device, future_audio_client: Arc::new(Mutex::new(None)), + share_mode, } } @@ -402,7 +412,9 @@ impl Device { .map_err(windows_err_to_cpal_err::)?; // If the default format can't succeed we have no hope of finding other formats. - if !is_format_supported(client, default_waveformatex_ptr.0)? { + if self.share_mode == ShareMode::Shared + && !is_format_supported(client, self.share_mode, default_waveformatex_ptr.0)? + { let description = "Could not determine support for default `WAVEFORMATEX`".to_string(); let err = BackendSpecificError { description }; @@ -446,13 +458,14 @@ impl Device { ) { if is_format_supported( client, + self.share_mode, &waveformat.Format as *const Audio::WAVEFORMATEX, )? { supported_formats.push(SupportedStreamConfigRange { channels: format.channels, min_sample_rate: sample_rate, max_sample_rate: sample_rate, - buffer_size: format.buffer_size.clone(), + buffer_size: format.buffer_size, sample_format, }) } @@ -512,8 +525,45 @@ impl Device { .map(WaveFormatExPtr) .map_err(windows_err_to_cpal_err::)?; - format_from_waveformatex_ptr(format_ptr.0, client) - .ok_or(DefaultStreamConfigError::StreamTypeNotSupported) + // For exclusive mode the default mix sample format is probably not correct, + // so pick the supported sample format with the highest precision + if self.share_mode == ShareMode::Exclusive { + for sample_format in [ + SampleFormat::I64, + SampleFormat::I32, + SampleFormat::F32, + SampleFormat::I16, + SampleFormat::U8, + ] { + if let Some(format) = config_to_waveformatextensible( + &StreamConfig { + channels: (*format_ptr.0).nChannels, + sample_rate: SampleRate((*format_ptr.0).nSamplesPerSec), + buffer_size: BufferSize::Default, + }, + sample_format, + ) { + if is_format_supported( + client, + self.share_mode, + &format.Format as *const Audio::WAVEFORMATEX, + ) + .map_err(|_| DefaultStreamConfigError::DeviceNotAvailable)? + { + return format_from_waveformatex_ptr( + &format.Format as *const _, + client, + ) + .ok_or(DefaultStreamConfigError::StreamTypeNotSupported); + } + } + } + + Err(DefaultStreamConfigError::StreamTypeNotSupported) + } else { + format_from_waveformatex_ptr(format_ptr.0, client) + .ok_or(DefaultStreamConfigError::StreamTypeNotSupported) + } } } @@ -539,10 +589,11 @@ impl Device { } } - pub(crate) fn build_input_stream_raw_inner( + fn build_stream( &self, config: &StreamConfig, sample_format: SampleFormat, + is_capture: bool, ) -> Result { unsafe { // Making sure that COM is initialized. @@ -563,150 +614,67 @@ impl Device { }; let buffer_duration = - buffer_size_to_duration(&config.buffer_size, config.sample_rate.0); + buffer_size_to_duration(&audio_client, &config.buffer_size, config.sample_rate.0) + .map_err(|_| BuildStreamError::DeviceNotAvailable)?; let mut stream_flags = Audio::AUDCLNT_STREAMFLAGS_EVENTCALLBACK; - if self.data_flow() == Audio::eRender { + if is_capture + && self.share_mode == ShareMode::Shared + && self.data_flow() == Audio::eRender + { stream_flags |= Audio::AUDCLNT_STREAMFLAGS_LOOPBACK; } - // Computing the format and initializing the device. - let waveformatex = { - let format_attempt = config_to_waveformatextensible(config, sample_format) - .ok_or(BuildStreamError::StreamConfigNotSupported)?; - let share_mode = Audio::AUDCLNT_SHAREMODE_SHARED; - - // Ensure the format is supported. - match super::device::is_format_supported(&audio_client, &format_attempt.Format) { - Ok(false) => return Err(BuildStreamError::StreamConfigNotSupported), - Err(_) => return Err(BuildStreamError::DeviceNotAvailable), - _ => (), - } + let share_mode = to_winapi_share_mode(self.share_mode); - // Finally, initializing the audio client - let hresult = audio_client.Initialize( - share_mode, - stream_flags, - buffer_duration, - 0, - &format_attempt.Format, - None, - ); - match hresult { - Err(ref e) if e.code() == Audio::AUDCLNT_E_DEVICE_INVALIDATED => { - return Err(BuildStreamError::DeviceNotAvailable); - } - Err(e) => { - let description = format!("{}", e); - let err = BackendSpecificError { description }; - return Err(err.into()); - } - Ok(()) => (), - }; + // Computing the format and initializing the device. + let format = config_to_waveformatextensible(config, sample_format) + .ok_or(BuildStreamError::StreamConfigNotSupported)?; + + // Ensure the format is supported. + match super::device::is_format_supported(&audio_client, self.share_mode, &format.Format) + { + Ok(false) => return Err(BuildStreamError::StreamConfigNotSupported), + Err(_) => return Err(BuildStreamError::DeviceNotAvailable), + _ => (), + } - format_attempt.Format + let periodicity = if self.share_mode == ShareMode::Shared { + 0 + } else { + buffer_duration }; - // obtaining the size of the samples buffer in number of frames - let max_frames_in_buffer = audio_client - .GetBufferSize() - .map_err(windows_err_to_cpal_err::)?; - - // Creating the event that will be signalled whenever we need to submit some samples. - let event = { - let event = - Threading::CreateEventA(None, false, false, windows::core::PCSTR(ptr::null())) - .map_err(|e| { - let description = format!("failed to create event: {}", e); - let err = BackendSpecificError { description }; - BuildStreamError::from(err) - })?; + // Finally, initializing the audio client + let hresult = audio_client.Initialize( + share_mode, + stream_flags, + buffer_duration, + periodicity, + &format.Format, + None, + ); - if let Err(e) = audio_client.SetEventHandle(event) { - let description = format!("failed to call SetEventHandle: {}", e); + match hresult { + Err(ref e) if e.code() == Audio::AUDCLNT_E_DEVICE_INVALIDATED => { + return Err(BuildStreamError::DeviceNotAvailable); + } + Err(e) => { + let description = format!("{}", e); let err = BackendSpecificError { description }; return Err(err.into()); } - - event + Ok(()) => (), }; - // Building a `IAudioCaptureClient` that will be used to read captured samples. - let capture_client = audio_client - .GetService::() - .map_err(|e| { - windows_err_to_cpal_err_message::( - e, - "failed to build capture client: ", - ) - })?; - - // Once we built the `StreamInner`, we add a command that will be picked up by the - // `run()` method and added to the `RunContext`. - let client_flow = AudioClientFlow::Capture { capture_client }; - - let audio_clock = get_audio_clock(&audio_client)?; - - Ok(StreamInner { - audio_client, - audio_clock, - client_flow, - event, - playing: false, - max_frames_in_buffer, - bytes_per_frame: waveformatex.nBlockAlign, - config: config.clone(), - sample_format, - }) - } - } - - pub(crate) fn build_output_stream_raw_inner( - &self, - config: &StreamConfig, - sample_format: SampleFormat, - ) -> Result { - unsafe { - // Making sure that COM is initialized. - // It's not actually sure that this is required, but when in doubt do it. - com::com_initialized(); - - // Obtaining a `IAudioClient`. - let audio_client = self - .build_audioclient() - .map_err(windows_err_to_cpal_err::)?; - - let buffer_duration = - buffer_size_to_duration(&config.buffer_size, config.sample_rate.0); - - // Computing the format and initializing the device. - let waveformatex = { - let format_attempt = config_to_waveformatextensible(config, sample_format) - .ok_or(BuildStreamError::StreamConfigNotSupported)?; - let share_mode = Audio::AUDCLNT_SHAREMODE_SHARED; - - // Ensure the format is supported. - match super::device::is_format_supported(&audio_client, &format_attempt.Format) { - Ok(false) => return Err(BuildStreamError::StreamConfigNotSupported), - Err(_) => return Err(BuildStreamError::DeviceNotAvailable), - _ => (), - } - - // Finally, initializing the audio client - audio_client - .Initialize( - share_mode, - Audio::AUDCLNT_STREAMFLAGS_EVENTCALLBACK, - buffer_duration, - 0, - &format_attempt.Format, - None, - ) - .map_err(windows_err_to_cpal_err::)?; - - format_attempt.Format - }; + // obtaining the size of the samples buffer in number of frames + let max_frames_in_buffer = audio_client.GetBufferSize().map_err(|e| { + windows_err_to_cpal_err_message::( + e, + "failed to obtain buffer size: ", + ) + })?; // Creating the event that will be signalled whenever we need to submit some samples. let event = { @@ -727,27 +695,36 @@ impl Device { event }; - // obtaining the size of the samples buffer in number of frames - let max_frames_in_buffer = audio_client.GetBufferSize().map_err(|e| { - windows_err_to_cpal_err_message::( - e, - "failed to obtain buffer size: ", - ) - })?; - - // Building a `IAudioRenderClient` that will be used to fill the samples buffer. - let render_client = audio_client - .GetService::() - .map_err(|e| { - windows_err_to_cpal_err_message::( - e, - "failed to build render client: ", - ) - })?; + let client_flow = if is_capture { + // Building a `IAudioCaptureClient` that will be used to read captured samples. + let capture_client = audio_client + .GetService::() + .map_err(|e| { + windows_err_to_cpal_err_message::( + e, + "failed to build capture client: ", + ) + })?; + + // Once we built the `StreamInner`, we add a command that will be picked up by the + // `run()` method and added to the `RunContext`. + AudioClientFlow::Capture { capture_client } + } else { + // Building a `IAudioRenderClient` that will be used to fill the samples buffer. + let render_client = + audio_client + .GetService::() + .map_err(|e| { + windows_err_to_cpal_err_message::( + e, + "failed to build render client: ", + ) + })?; - // Once we built the `StreamInner`, we add a command that will be picked up by the - // `run()` method and added to the `RunContext`. - let client_flow = AudioClientFlow::Render { render_client }; + // Once we built the `StreamInner`, we add a command that will be picked up by the + // `run()` method and added to the `RunContext`. + AudioClientFlow::Render { render_client } + }; let audio_clock = get_audio_clock(&audio_client)?; @@ -758,9 +735,10 @@ impl Device { event, playing: false, max_frames_in_buffer, - bytes_per_frame: waveformatex.nBlockAlign, + bytes_per_frame: format.Format.nBlockAlign, config: config.clone(), sample_format, + share_mode: self.share_mode, }) } } @@ -864,12 +842,13 @@ unsafe impl Sync for Enumerator {} /// WASAPI implementation for `Devices`. pub struct Devices { collection: Audio::IMMDeviceCollection, + share_mode: ShareMode, total_count: u32, next_item: u32, } impl Devices { - pub fn new() -> Result { + pub(crate) fn new(share_mode: ShareMode) -> Result { unsafe { // can fail because of wrong parameters (should never happen) or out of memory let collection = get_enumerator() @@ -881,6 +860,7 @@ impl Devices { Ok(Devices { collection, + share_mode, total_count: count, next_item: 0, }) @@ -902,7 +882,7 @@ impl Iterator for Devices { unsafe { let device = self.collection.Item(self.next_item).unwrap(); self.next_item += 1; - Some(Device::from_immdevice(device)) + Some(Device::from_immdevice(device, self.share_mode)) } } @@ -914,23 +894,23 @@ impl Iterator for Devices { } } -fn default_device(data_flow: Audio::EDataFlow) -> Option { +fn default_device(data_flow: Audio::EDataFlow, share_mode: ShareMode) -> Option { unsafe { let device = get_enumerator() .0 .GetDefaultAudioEndpoint(data_flow, Audio::eConsole) .ok()?; // TODO: check specifically for `E_NOTFOUND`, and panic otherwise - Some(Device::from_immdevice(device)) + Some(Device::from_immdevice(device, share_mode)) } } -pub fn default_input_device() -> Option { - default_device(Audio::eCapture) +pub fn default_input_device(share_mode: ShareMode) -> Option { + default_device(Audio::eCapture, share_mode) } -pub fn default_output_device() -> Option { - default_device(Audio::eRender) +pub fn default_output_device(share_mode: ShareMode) -> Option { + default_device(Audio::eRender, share_mode) } /// Get the audio clock used to produce `StreamInstant`s. @@ -1009,10 +989,27 @@ fn config_to_waveformatextensible( Some(waveformatextensible) } -fn buffer_size_to_duration(buffer_size: &BufferSize, sample_rate: u32) -> i64 { +fn buffer_size_to_duration( + audio_client: &Audio::IAudioClient, + buffer_size: &BufferSize, + sample_rate: u32, +) -> Result { match buffer_size { - BufferSize::Fixed(frames) => *frames as i64 * (1_000_000_000 / 100) / sample_rate as i64, - BufferSize::Default => 0, + BufferSize::Fixed(frames) => { + Ok(*frames as i64 * (1_000_000_000 / 100) / sample_rate as i64) + } + + BufferSize::Default => { + let mut default_device_period = 0; + + unsafe { + audio_client + .GetDevicePeriod(Some(&mut default_device_period as *mut _), None) + .map_err(windows_err_to_cpal_err::)?; + } + + Ok(default_device_period) + } } } diff --git a/src/host/wasapi/mod.rs b/src/host/wasapi/mod.rs index e80760267..6d7f5f91d 100644 --- a/src/host/wasapi/mod.rs +++ b/src/host/wasapi/mod.rs @@ -10,9 +10,15 @@ use std::io::Error as IoError; use windows::Win32::Media::Audio; mod com; -mod device; +pub(crate) mod device; mod stream; +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub(crate) enum ShareMode { + Shared, + Exclusive, +} + /// The WASAPI host, the default windows host type. /// /// Note: If you use a WASAPI output device as an input device it will @@ -37,15 +43,15 @@ impl HostTrait for Host { } fn devices(&self) -> Result { - Devices::new() + Devices::new(ShareMode::Shared) } fn default_input_device(&self) -> Option { - default_input_device() + default_input_device(ShareMode::Shared) } fn default_output_device(&self) -> Option { - default_output_device() + default_output_device(ShareMode::Shared) } } diff --git a/src/host/wasapi/stream.rs b/src/host/wasapi/stream.rs index 57332a192..eebb25d3b 100644 --- a/src/host/wasapi/stream.rs +++ b/src/host/wasapi/stream.rs @@ -1,4 +1,4 @@ -use super::windows_err_to_cpal_err; +use super::{windows_err_to_cpal_err, ShareMode}; use crate::traits::StreamTrait; use crate::{ BackendSpecificError, Data, InputCallbackInfo, OutputCallbackInfo, PauseStreamError, @@ -75,6 +75,9 @@ pub struct StreamInner { pub bytes_per_frame: u16, // The configuration with which the stream was created. pub config: crate::StreamConfig, + + pub share_mode: ShareMode, + // The sample format with which the stream was created. pub sample_format: SampleFormat, } @@ -267,6 +270,15 @@ fn get_available_frames(stream: &StreamInner) -> Result { } } +fn get_buffer_size(stream: &StreamInner) -> Result { + unsafe { + stream + .audio_client + .GetBufferSize() + .map_err(windows_err_to_cpal_err::) + } +} + fn run_input( mut run_ctxt: RunContext, data_callback: &mut dyn FnMut(&Data, &InputCallbackInfo), @@ -303,25 +315,36 @@ fn run_output( ) { boost_current_thread_priority(); + // If exclusive mode make sure we fill first buffer before start is called + let mut skip_commands = run_ctxt.stream.share_mode == ShareMode::Exclusive; + loop { - match process_commands_and_await_signal(&mut run_ctxt, error_callback) { - Some(ControlFlow::Break) => break, - Some(ControlFlow::Continue) => continue, - None => (), + if !skip_commands { + match process_commands_and_await_signal(&mut run_ctxt, error_callback) { + Some(ControlFlow::Break) => break, + Some(ControlFlow::Continue) => continue, + None => (), + } } - let render_client = match run_ctxt.stream.client_flow { - AudioClientFlow::Render { ref render_client } => render_client.clone(), + + match &run_ctxt.stream.client_flow { + AudioClientFlow::Render { render_client } => { + let res = process_output( + &run_ctxt.stream, + render_client, + data_callback, + error_callback, + ); + + if matches!(res, ControlFlow::Break) { + break; + } + } + _ => unreachable!(), - }; - match process_output( - &run_ctxt.stream, - render_client, - data_callback, - error_callback, - ) { - ControlFlow::Break => break, - ControlFlow::Continue => continue, } + + skip_commands = false; } } @@ -445,12 +468,17 @@ fn process_input( // The loop for writing output data. fn process_output( stream: &StreamInner, - render_client: Audio::IAudioRenderClient, + render_client: &Audio::IAudioRenderClient, data_callback: &mut dyn FnMut(&mut Data, &OutputCallbackInfo), error_callback: &mut dyn FnMut(StreamError), ) -> ControlFlow { + let frame_count = match stream.share_mode { + ShareMode::Shared => get_available_frames(stream), + ShareMode::Exclusive => get_buffer_size(stream), + }; + // The number of frames available for writing. - let frames_available = match get_available_frames(stream) { + let frames_available = match frame_count { Ok(0) => return ControlFlow::Continue, // TODO: Can this happen? Ok(n) => n, Err(err) => { diff --git a/src/host/wasapi_exclusive/mod.rs b/src/host/wasapi_exclusive/mod.rs new file mode 100644 index 000000000..d99e7306b --- /dev/null +++ b/src/host/wasapi_exclusive/mod.rs @@ -0,0 +1,149 @@ +use std::time::Duration; + +pub use crate::host::wasapi::device::{SupportedInputConfigs, SupportedOutputConfigs}; +use crate::traits::DeviceTrait; +use crate::traits::HostTrait; +use crate::traits::StreamTrait; +use crate::DevicesError; + +use super::wasapi; +use super::wasapi::ShareMode; + +/// The WASAPI exclusive host. +/// +/// In exclusive mode only one stream can be opened per device, no mixing of multiple streams are performed. +#[derive(Debug)] +pub struct Host; + +impl Host { + pub fn new() -> Result { + Ok(Host) + } +} + +impl HostTrait for Host { + type Devices = Devices; + type Device = Device; + + fn is_available() -> bool { + // Assume WASAPI is always available on Windows. + true + } + + fn devices(&self) -> Result { + wasapi::Devices::new(ShareMode::Exclusive).map(Devices) + } + + fn default_input_device(&self) -> Option { + wasapi::default_input_device(ShareMode::Exclusive).map(Device) + } + + fn default_output_device(&self) -> Option { + wasapi::default_output_device(ShareMode::Exclusive).map(Device) + } +} + +pub struct Devices(wasapi::Devices); + +impl Iterator for Devices { + type Item = Device; + + fn next(&mut self) -> Option { + self.0.next().map(Device) + } +} + +#[derive(Clone)] +pub struct Device(wasapi::Device); + +impl DeviceTrait for Device { + type SupportedInputConfigs = ::SupportedInputConfigs; + type SupportedOutputConfigs = ::SupportedOutputConfigs; + type Stream = Stream; + + fn name(&self) -> Result { + self.0.name() + } + + fn supported_input_configs( + &self, + ) -> Result { + self.0.supported_input_configs() + } + + fn supported_output_configs( + &self, + ) -> Result { + self.0.supported_output_configs() + } + + fn default_input_config( + &self, + ) -> Result { + self.0.default_input_config() + } + + fn default_output_config( + &self, + ) -> Result { + self.0.default_output_config() + } + + fn build_input_stream_raw( + &self, + config: &crate::StreamConfig, + sample_format: crate::SampleFormat, + data_callback: D, + error_callback: E, + timeout: Option, + ) -> Result + where + D: FnMut(&crate::Data, &crate::InputCallbackInfo) + Send + 'static, + E: FnMut(crate::StreamError) + Send + 'static, + { + self.0 + .build_input_stream_raw( + config, + sample_format, + data_callback, + error_callback, + timeout, + ) + .map(Stream) + } + + fn build_output_stream_raw( + &self, + config: &crate::StreamConfig, + sample_format: crate::SampleFormat, + data_callback: D, + error_callback: E, + timeout: Option, + ) -> Result + where + D: FnMut(&mut crate::Data, &crate::OutputCallbackInfo) + Send + 'static, + E: FnMut(crate::StreamError) + Send + 'static, + { + self.0 + .build_output_stream_raw( + config, + sample_format, + data_callback, + error_callback, + timeout, + ) + .map(Stream) + } +} + +pub struct Stream(wasapi::Stream); + +impl StreamTrait for Stream { + fn play(&self) -> Result<(), crate::PlayStreamError> { + self.0.play() + } + + fn pause(&self) -> Result<(), crate::PauseStreamError> { + self.0.pause() + } +} diff --git a/src/lib.rs b/src/lib.rs index 3b6683c5e..74405a4a2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -742,7 +742,7 @@ impl SupportedStreamConfigRange { #[test] fn test_cmp_default_heuristics() { - let mut formats = vec![ + let mut formats = [ SupportedStreamConfigRange { buffer_size: SupportedBufferSize::Range { min: 256, max: 512 }, channels: 2, diff --git a/src/platform/mod.rs b/src/platform/mod.rs index 4cb4d5a29..5a4b2e027 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -667,8 +667,9 @@ mod platform_impl { Stream as WasapiStream, SupportedInputConfigs as WasapiSupportedInputConfigs, SupportedOutputConfigs as WasapiSupportedOutputConfigs, }; + pub use crate::host::wasapi_exclusive::Host as WasapiExclusiveHost; - impl_platform_host!(#[cfg(feature = "asio")] Asio asio "ASIO", Wasapi wasapi "WASAPI"); + impl_platform_host!(#[cfg(feature = "asio")] Asio asio "ASIO", Wasapi wasapi "WASAPI", WasapiExclusive wasapi_exclusive "WASAPI_exclusive"); /// The default host for the current compilation target platform. pub fn default_host() -> Host {