use num_integer::Integer;
use std::cmp;
use std::collections::VecDeque;
use std::mem::{size_of, ManuallyDrop};
use std::ops::Deref;
use std::pin::Pin;
use std::sync::{Arc, Condvar, Mutex};
use std::{fmt, ptr, slice};
use windows::Win32::Foundation::{CloseHandle, E_INVALIDARG, E_NOINTERFACE, FALSE, PROPERTYKEY};
use windows::Win32::Media::Audio::{
ActivateAudioInterfaceAsync, AudioCategory_Alerts, AudioCategory_Communications,
AudioCategory_FarFieldSpeech, AudioCategory_ForegroundOnlyMedia, AudioCategory_GameChat,
AudioCategory_GameEffects, AudioCategory_GameMedia, AudioCategory_Media, AudioCategory_Movie,
AudioCategory_Other, AudioCategory_SoundEffects, AudioCategory_Speech,
AudioCategory_UniformSpeech, AudioCategory_VoiceTyping, EDataFlow, ERole,
IAcousticEchoCancellationControl, IActivateAudioInterfaceAsyncOperation,
IActivateAudioInterfaceCompletionHandler, IActivateAudioInterfaceCompletionHandler_Impl,
IAudioClient2, IAudioEffectsManager, IAudioSessionControl2, IAudioSessionEnumerator,
IAudioSessionManager, IAudioSessionManager2, IMMEndpoint, PKEY_AudioEngine_DeviceFormat,
AUDCLNT_STREAMOPTIONS, AUDCLNT_STREAMOPTIONS_AMBISONICS, AUDCLNT_STREAMOPTIONS_MATCH_FORMAT,
AUDCLNT_STREAMOPTIONS_NONE, AUDCLNT_STREAMOPTIONS_RAW, AUDIOCLIENT_ACTIVATION_PARAMS,
AUDIOCLIENT_ACTIVATION_PARAMS_0, AUDIOCLIENT_ACTIVATION_TYPE_PROCESS_LOOPBACK,
AUDIOCLIENT_PROCESS_LOOPBACK_PARAMS, AUDIO_EFFECT, AUDIO_STREAM_CATEGORY,
PROCESS_LOOPBACK_MODE_EXCLUDE_TARGET_PROCESS_TREE,
PROCESS_LOOPBACK_MODE_INCLUDE_TARGET_PROCESS_TREE, VIRTUAL_AUDIO_DEVICE_PROCESS_LOOPBACK,
};
use windows::Win32::Media::KernelStreaming::AUDIO_EFFECT_TYPE_ACOUSTIC_ECHO_CANCELLATION;
use windows::Win32::System::Com::CoTaskMemFree;
use windows::Win32::System::Com::StructuredStorage::PropVariantClear;
use windows::Win32::System::Variant::VT_BLOB;
use windows::{
core::{HRESULT, PCSTR},
Win32::Devices::FunctionDiscovery::{
PKEY_DeviceInterface_FriendlyName, PKEY_Device_DeviceDesc, PKEY_Device_FriendlyName,
},
Win32::Foundation::{HANDLE, WAIT_OBJECT_0},
Win32::Media::Audio::{
eCapture, eCommunications, eConsole, eMultimedia, eRender, AudioSessionStateActive,
AudioSessionStateExpired, AudioSessionStateInactive, IAudioCaptureClient, IAudioClient,
IAudioClock, IAudioRenderClient, IAudioSessionControl, IAudioSessionEvents, IMMDevice,
IMMDeviceCollection, IMMDeviceEnumerator, MMDeviceEnumerator,
AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY, AUDCLNT_BUFFERFLAGS_SILENT,
AUDCLNT_BUFFERFLAGS_TIMESTAMP_ERROR, AUDCLNT_SHAREMODE_EXCLUSIVE, AUDCLNT_SHAREMODE_SHARED,
AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM, AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
AUDCLNT_STREAMFLAGS_LOOPBACK, AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY, DEVICE_STATE_ACTIVE,
DEVICE_STATE_DISABLED, DEVICE_STATE_NOTPRESENT, DEVICE_STATE_UNPLUGGED, WAVEFORMATEX,
WAVEFORMATEXTENSIBLE,
},
Win32::Media::KernelStreaming::WAVE_FORMAT_EXTENSIBLE,
Win32::System::Com::StructuredStorage::{
PropVariantToStringAlloc, PROPVARIANT, PROPVARIANT_0, PROPVARIANT_0_0, PROPVARIANT_0_0_0,
},
Win32::System::Com::{
CoCreateInstance, CoInitializeEx, CoUninitialize, CLSCTX_ALL, COINIT_APARTMENTTHREADED,
COINIT_MULTITHREADED,
},
Win32::System::Com::{BLOB, STGM_READ},
Win32::System::Threading::{CreateEventA, WaitForSingleObject},
};
use windows_core::{implement, IUnknown, Interface, Ref, HSTRING, PCWSTR};
use crate::{make_channelmasks, AudioSessionEvents, EventCallbacks, WasapiError, WaveFormat};
pub(crate) type WasapiRes<T> = Result<T, WasapiError>;
pub fn initialize_mta() -> HRESULT {
unsafe { CoInitializeEx(None, COINIT_MULTITHREADED) }
}
pub fn initialize_sta() -> HRESULT {
unsafe { CoInitializeEx(None, COINIT_APARTMENTTHREADED) }
}
pub fn deinitialize() {
unsafe { CoUninitialize() }
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Direction {
Render,
Capture,
}
impl fmt::Display for Direction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Direction::Render => write!(f, "Render"),
Direction::Capture => write!(f, "Capture"),
}
}
}
impl TryFrom<&EDataFlow> for Direction {
type Error = WasapiError;
fn try_from(value: &EDataFlow) -> Result<Self, Self::Error> {
match value {
EDataFlow(0) => Ok(Self::Render),
EDataFlow(1) => Ok(Self::Capture),
x => Err(WasapiError::IllegalDeviceDirection(x.0)),
}
}
}
impl TryFrom<EDataFlow> for Direction {
type Error = WasapiError;
fn try_from(value: EDataFlow) -> Result<Self, Self::Error> {
Self::try_from(&value)
}
}
impl From<&Direction> for EDataFlow {
fn from(value: &Direction) -> Self {
match value {
Direction::Capture => eCapture,
Direction::Render => eRender,
}
}
}
impl From<Direction> for EDataFlow {
fn from(value: Direction) -> Self {
Self::from(&value)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Role {
Console,
Multimedia,
Communications,
}
impl fmt::Display for Role {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Role::Console => write!(f, "Console"),
Role::Multimedia => write!(f, "Multimedia"),
Role::Communications => write!(f, "Communications"),
}
}
}
impl TryFrom<&ERole> for Role {
type Error = WasapiError;
fn try_from(value: &ERole) -> Result<Self, Self::Error> {
match value {
ERole(0) => Ok(Self::Console),
ERole(1) => Ok(Self::Multimedia),
ERole(2) => Ok(Self::Communications),
x => Err(WasapiError::IllegalDeviceRole(x.0)),
}
}
}
impl TryFrom<ERole> for Role {
type Error = WasapiError;
fn try_from(value: ERole) -> Result<Self, Self::Error> {
Self::try_from(&value)
}
}
impl From<&Role> for ERole {
fn from(value: &Role) -> Self {
match value {
Role::Communications => eCommunications,
Role::Multimedia => eMultimedia,
Role::Console => eConsole,
}
}
}
impl From<Role> for ERole {
fn from(value: Role) -> Self {
Self::from(&value)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum StreamMode {
PollingShared {
autoconvert: bool,
buffer_duration_hns: i64,
},
PollingExclusive {
buffer_duration_hns: i64,
period_hns: i64,
},
EventsShared {
autoconvert: bool,
buffer_duration_hns: i64,
},
EventsExclusive { period_hns: i64 },
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ShareMode {
Shared,
Exclusive,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum TimingMode {
Polling,
Events,
}
impl fmt::Display for ShareMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
ShareMode::Shared => write!(f, "Shared"),
ShareMode::Exclusive => write!(f, "Exclusive"),
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum SampleType {
Float,
Int,
}
impl fmt::Display for SampleType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
SampleType::Float => write!(f, "Float"),
SampleType::Int => write!(f, "Int"),
}
}
}
#[derive(Debug, Eq, PartialEq)]
pub enum SessionState {
Active,
Inactive,
Expired,
}
impl fmt::Display for SessionState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
SessionState::Active => write!(f, "Active"),
SessionState::Inactive => write!(f, "Inactive"),
SessionState::Expired => write!(f, "Expired"),
}
}
}
#[derive(Debug, Eq, PartialEq)]
pub enum DeviceState {
Active,
Disabled,
NotPresent,
Unplugged,
}
impl fmt::Display for DeviceState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
DeviceState::Active => write!(f, "Active"),
DeviceState::Disabled => write!(f, "Disabled"),
DeviceState::NotPresent => write!(f, "NotPresent"),
DeviceState::Unplugged => write!(f, "Unplugged"),
}
}
}
pub fn calculate_period_100ns(frames: i64, samplerate: i64) -> i64 {
((10000.0 * 1000.0 / samplerate as f64 * frames as f64) + 0.5) as i64
}
pub struct DeviceEnumerator {
enumerator: IMMDeviceEnumerator,
}
impl DeviceEnumerator {
pub fn new() -> WasapiRes<DeviceEnumerator> {
let enumerator: IMMDeviceEnumerator =
unsafe { CoCreateInstance(&MMDeviceEnumerator, None, CLSCTX_ALL)? };
Ok(DeviceEnumerator { enumerator })
}
pub fn get_device_collection(&self, direction: &Direction) -> WasapiRes<DeviceCollection> {
let dir: EDataFlow = direction.into();
let devs = unsafe {
self.enumerator
.EnumAudioEndpoints(dir, DEVICE_STATE_ACTIVE)?
};
Ok(DeviceCollection {
collection: devs,
direction: *direction,
})
}
pub fn get_default_device(&self, direction: &Direction) -> WasapiRes<Device> {
self.get_default_device_for_role(direction, &Role::Console)
}
pub fn get_default_device_for_role(
&self,
direction: &Direction,
role: &Role,
) -> WasapiRes<Device> {
let dir = direction.into();
let e_role = role.into();
let device = unsafe { self.enumerator.GetDefaultAudioEndpoint(dir, e_role)? };
let dev = Device {
device,
direction: *direction,
};
debug!("default device {:?}", dev.get_friendlyname());
Ok(dev)
}
pub fn get_device(&self, device_id: &str) -> WasapiRes<Device> {
let w_id = PCWSTR::from_raw(HSTRING::from(device_id).as_ptr());
let immdevice = unsafe { self.enumerator.GetDevice(w_id)? };
let device = Device::from_immdevice(immdevice)?;
Ok(device)
}
}
pub struct DeviceCollection {
collection: IMMDeviceCollection,
direction: Direction,
}
impl DeviceCollection {
pub fn get_nbr_devices(&self) -> WasapiRes<u32> {
let count = unsafe { self.collection.GetCount()? };
Ok(count)
}
pub fn get_device_at_index(&self, idx: u32) -> WasapiRes<Device> {
let device = unsafe { self.collection.Item(idx)? };
Ok(Device {
device,
direction: self.direction,
})
}
pub fn get_device_with_name(&self, name: &str) -> WasapiRes<Device> {
let count = unsafe { self.collection.GetCount()? };
trace!("nbr devices {count}");
for n in 0..count {
let device = self.get_device_at_index(n)?;
let devname = device.get_friendlyname()?;
if name == devname {
return Ok(device);
}
}
Err(WasapiError::DeviceNotFound(name.to_owned()))
}
pub fn get_direction(&self) -> Direction {
self.direction
}
}
pub struct DeviceCollectionIter<'a> {
collection: &'a DeviceCollection,
index: u32,
}
impl Iterator for DeviceCollectionIter<'_> {
type Item = WasapiRes<Device>;
fn next(&mut self) -> Option<Self::Item> {
if self.index < self.collection.get_nbr_devices().unwrap() {
let device = self.collection.get_device_at_index(self.index);
self.index += 1;
Some(device)
} else {
None
}
}
}
impl<'a> IntoIterator for &'a DeviceCollection {
type Item = WasapiRes<Device>;
type IntoIter = DeviceCollectionIter<'a>;
fn into_iter(self) -> Self::IntoIter {
DeviceCollectionIter {
collection: self,
index: 0,
}
}
}
pub struct Device {
device: IMMDevice,
direction: Direction,
}
impl Device {
pub unsafe fn from_raw(device: IMMDevice, direction: Direction) -> Device {
Device { device, direction }
}
pub fn from_immdevice(device: IMMDevice) -> WasapiRes<Device> {
let endpoint: IMMEndpoint = device.cast()?;
let direction: Direction = unsafe { endpoint.GetDataFlow()? }.try_into()?;
Ok(Device { device, direction })
}
pub fn get_iaudioclient(&self) -> WasapiRes<AudioClient> {
let audio_client = unsafe { self.device.Activate::<IAudioClient>(CLSCTX_ALL, None)? };
Ok(AudioClient {
client: audio_client,
direction: self.direction,
sharemode: None,
timingmode: None,
bytes_per_frame: None,
})
}
pub fn get_iaudiosessionmanager(&self) -> WasapiRes<AudioSessionManager> {
let session_manager = unsafe {
self.device
.Activate::<IAudioSessionManager>(CLSCTX_ALL, None)?
};
Ok(AudioSessionManager { session_manager })
}
pub fn get_state(&self) -> WasapiRes<DeviceState> {
let state = unsafe { self.device.GetState()? };
trace!("state: {state:?}");
let state_enum = match state {
_ if state == DEVICE_STATE_ACTIVE => DeviceState::Active,
_ if state == DEVICE_STATE_DISABLED => DeviceState::Disabled,
_ if state == DEVICE_STATE_NOTPRESENT => DeviceState::NotPresent,
_ if state == DEVICE_STATE_UNPLUGGED => DeviceState::Unplugged,
x => return Err(WasapiError::IllegalDeviceState(x.0)),
};
Ok(state_enum)
}
pub fn get_friendlyname(&self) -> WasapiRes<String> {
self.get_string_property(&PKEY_Device_FriendlyName)
}
pub fn get_interface_friendlyname(&self) -> WasapiRes<String> {
self.get_string_property(&PKEY_DeviceInterface_FriendlyName)
}
pub fn get_description(&self) -> WasapiRes<String> {
self.get_string_property(&PKEY_Device_DeviceDesc)
}
pub fn get_device_format(&self) -> WasapiRes<WaveFormat> {
let data = self.get_blob_property(&PKEY_AudioEngine_DeviceFormat)?;
WaveFormat::parse_from_blob_bytes(&data)
}
fn get_string_property(&self, key: &PROPERTYKEY) -> WasapiRes<String> {
self.get_property(key, Self::parse_string_property)
}
fn get_blob_property(&self, key: &PROPERTYKEY) -> WasapiRes<Vec<u8>> {
self.get_property(key, Self::parse_blob_property)
}
fn get_property<T>(
&self,
key: &PROPERTYKEY,
parse: impl FnOnce(&PROPVARIANT) -> WasapiRes<T>,
) -> WasapiRes<T> {
let store = unsafe { self.device.OpenPropertyStore(STGM_READ)? };
let mut prop = unsafe { store.GetValue(key)? };
let ret = parse(&prop);
unsafe { PropVariantClear(&mut prop) }?;
ret
}
fn parse_string_property(prop: &PROPVARIANT) -> WasapiRes<String> {
let propstr = unsafe { PropVariantToStringAlloc(prop)? };
let name = unsafe { propstr.to_string()? };
unsafe { CoTaskMemFree(Some(propstr.0.cast())) };
trace!("name: {name}");
Ok(name)
}
fn parse_blob_property(prop: &PROPVARIANT) -> WasapiRes<Vec<u8>> {
if prop.vt() != VT_BLOB {
return Err(windows::core::Error::from(E_INVALIDARG).into());
}
let blob = unsafe { prop.Anonymous.Anonymous.Anonymous.blob };
let blob_slice = unsafe { slice::from_raw_parts(blob.pBlobData, blob.cbSize as usize) };
let data = blob_slice.to_vec();
Ok(data)
}
pub fn get_id(&self) -> WasapiRes<String> {
let idstr = unsafe { self.device.GetId()? };
let id = unsafe { idstr.to_string()? };
unsafe { CoTaskMemFree(Some(idstr.0.cast())) };
trace!("id: {id}");
Ok(id)
}
pub fn get_direction(&self) -> Direction {
self.direction
}
}
#[implement(IActivateAudioInterfaceCompletionHandler)]
struct Handler(Arc<(Mutex<bool>, Condvar)>);
impl Handler {
pub fn new(object: Arc<(Mutex<bool>, Condvar)>) -> Handler {
Handler(object)
}
}
impl IActivateAudioInterfaceCompletionHandler_Impl for Handler_Impl {
fn ActivateCompleted(
&self,
_activateoperation: Ref<IActivateAudioInterfaceAsyncOperation>,
) -> windows::core::Result<()> {
let (lock, cvar) = &*self.0;
let mut completed = lock.lock().unwrap();
*completed = true;
drop(completed);
cvar.notify_one();
Ok(())
}
}
pub struct AudioClient {
client: IAudioClient,
direction: Direction,
sharemode: Option<ShareMode>,
timingmode: Option<TimingMode>,
bytes_per_frame: Option<usize>,
}
impl AudioClient {
pub fn new_application_loopback_client(process_id: u32, include_tree: bool) -> WasapiRes<Self> {
unsafe {
let mut audio_client_activation_params = AUDIOCLIENT_ACTIVATION_PARAMS {
ActivationType: AUDIOCLIENT_ACTIVATION_TYPE_PROCESS_LOOPBACK,
Anonymous: AUDIOCLIENT_ACTIVATION_PARAMS_0 {
ProcessLoopbackParams: AUDIOCLIENT_PROCESS_LOOPBACK_PARAMS {
TargetProcessId: process_id,
ProcessLoopbackMode: if include_tree {
PROCESS_LOOPBACK_MODE_INCLUDE_TARGET_PROCESS_TREE
} else {
PROCESS_LOOPBACK_MODE_EXCLUDE_TARGET_PROCESS_TREE
},
},
},
};
let pinned_params = Pin::new(&mut audio_client_activation_params);
let raw_prop = PROPVARIANT {
Anonymous: PROPVARIANT_0 {
Anonymous: ManuallyDrop::new(PROPVARIANT_0_0 {
vt: VT_BLOB,
wReserved1: 0,
wReserved2: 0,
wReserved3: 0,
Anonymous: PROPVARIANT_0_0_0 {
blob: BLOB {
cbSize: size_of::<AUDIOCLIENT_ACTIVATION_PARAMS>() as u32,
pBlobData: std::ptr::from_mut(pinned_params.get_mut()).cast(),
},
},
}),
},
};
let activation_prop = ManuallyDrop::new(raw_prop);
let pinned_prop = Pin::new(activation_prop.deref());
let activation_params = Some(std::ptr::from_ref(pinned_prop.get_ref()));
let setup = Arc::new((Mutex::new(false), Condvar::new()));
let callback: IActivateAudioInterfaceCompletionHandler =
Handler::new(setup.clone()).into();
let operation = ActivateAudioInterfaceAsync(
VIRTUAL_AUDIO_DEVICE_PROCESS_LOOPBACK,
&IAudioClient::IID,
activation_params,
&callback,
)?;
let (lock, cvar) = &*setup;
let mut completed = lock.lock().unwrap();
while !*completed {
completed = cvar.wait(completed).unwrap();
}
drop(completed);
let mut audio_client: Option<IUnknown> = Default::default();
let mut result: HRESULT = Default::default();
operation.GetActivateResult(&mut result, &mut audio_client)?;
result.ok()?;
let audio_client: IAudioClient = audio_client.unwrap().cast()?;
Ok(AudioClient {
client: audio_client,
direction: Direction::Render,
sharemode: Some(ShareMode::Shared),
timingmode: None,
bytes_per_frame: None,
})
}
}
pub fn get_mixformat(&self) -> WasapiRes<WaveFormat> {
let temp_fmt_ptr = unsafe { self.client.GetMixFormat()? };
let temp_fmt = unsafe { *temp_fmt_ptr };
let mix_format =
if temp_fmt.cbSize == 22 && temp_fmt.wFormatTag as u32 == WAVE_FORMAT_EXTENSIBLE {
let format = unsafe {
WaveFormat {
wave_fmt: temp_fmt_ptr.cast::<WAVEFORMATEXTENSIBLE>().read(),
}
};
unsafe { CoTaskMemFree(Some(temp_fmt_ptr.cast())) };
format
} else {
unsafe { CoTaskMemFree(Some(temp_fmt_ptr.cast())) };
WaveFormat::from_waveformatex(temp_fmt)?
};
Ok(mix_format)
}
pub fn is_supported(
&self,
wave_fmt: &WaveFormat,
sharemode: &ShareMode,
) -> WasapiRes<Option<WaveFormat>> {
let supported = match sharemode {
ShareMode::Exclusive => {
unsafe {
self.client
.IsFormatSupported(
AUDCLNT_SHAREMODE_EXCLUSIVE,
wave_fmt.as_waveformatex_ref(),
None,
)
.ok()?
};
None
}
ShareMode::Shared => {
let mut supported_format: *mut WAVEFORMATEX = std::ptr::null_mut();
unsafe {
self.client
.IsFormatSupported(
AUDCLNT_SHAREMODE_SHARED,
wave_fmt.as_waveformatex_ref(),
Some(&mut supported_format),
)
.ok()?
};
if supported_format.is_null() {
debug!("The requested format is supported");
None
} else {
let temp_fmt: WAVEFORMATEX = unsafe { supported_format.read() };
debug!("The requested format is not supported but a simular one is");
let new_fmt = if temp_fmt.cbSize == 22
&& temp_fmt.wFormatTag as u32 == WAVE_FORMAT_EXTENSIBLE
{
debug!("got the nearest matching format as a WAVEFORMATEXTENSIBLE");
let temp_fmt_ext: WAVEFORMATEXTENSIBLE =
unsafe { supported_format.cast::<WAVEFORMATEXTENSIBLE>().read() };
unsafe { CoTaskMemFree(Some(supported_format.cast())) };
WaveFormat {
wave_fmt: temp_fmt_ext,
}
} else {
unsafe { CoTaskMemFree(Some(supported_format.cast())) };
debug!("got the nearest matching format as a WAVEFORMATEX, converting..");
WaveFormat::from_waveformatex(temp_fmt)?
};
Some(new_fmt)
}
}
};
Ok(supported)
}
pub fn is_supported_exclusive_with_quirks(
&self,
wave_fmt: &WaveFormat,
) -> WasapiRes<WaveFormat> {
let mut wave_fmt = wave_fmt.clone();
let supported_direct = self.is_supported(&wave_fmt, &ShareMode::Exclusive);
if supported_direct.is_ok() {
debug!("The requested format is supported as provided");
return Ok(wave_fmt);
}
if wave_fmt.get_nchannels() <= 2 {
debug!("Repeating query with format as WAVEFORMATEX");
let wave_formatex = wave_fmt.to_waveformatex().unwrap();
if self
.is_supported(&wave_formatex, &ShareMode::Exclusive)
.is_ok()
{
debug!("The requested format is supported as WAVEFORMATEX");
return Ok(wave_formatex);
}
}
let masks = make_channelmasks(wave_fmt.get_nchannels() as usize);
for mask in masks {
debug!("Repeating query with channel mask: {mask:#010b}");
wave_fmt.wave_fmt.dwChannelMask = mask;
if self.is_supported(&wave_fmt, &ShareMode::Exclusive).is_ok() {
debug!("The requested format is supported with a modified mask: {mask:#010b}");
return Ok(wave_fmt);
}
}
Err(WasapiError::UnsupportedFormat)
}
pub fn get_device_period(&self) -> WasapiRes<(i64, i64)> {
let mut def_time = 0;
let mut min_time = 0;
unsafe {
self.client
.GetDevicePeriod(Some(&mut def_time), Some(&mut min_time))?
};
trace!("default period {def_time}, min period {min_time}");
Ok((def_time, min_time))
}
#[deprecated(
since = "0.17.0",
note = "please use the new function name `get_device_period` instead"
)]
pub fn get_periods(&self) -> WasapiRes<(i64, i64)> {
self.get_device_period()
}
pub fn calculate_aligned_period_near(
&self,
desired_period: i64,
align_bytes: Option<u32>,
wave_fmt: &WaveFormat,
) -> WasapiRes<i64> {
let (_default_period, min_period) = self.get_device_period()?;
let adjusted_desired_period = cmp::max(desired_period, min_period);
let frame_bytes = wave_fmt.get_blockalign();
let period_alignment_bytes = match align_bytes {
Some(0) => frame_bytes,
Some(bytes) => frame_bytes.lcm(&bytes),
None => frame_bytes,
};
let period_alignment_frames = period_alignment_bytes as i64 / frame_bytes as i64;
let desired_period_frames =
(adjusted_desired_period as f64 * wave_fmt.get_samplespersec() as f64 / 10000000.0)
.round() as i64;
let min_period_frames =
(min_period as f64 * wave_fmt.get_samplespersec() as f64 / 10000000.0).ceil() as i64;
let mut nbr_segments = desired_period_frames / period_alignment_frames;
if nbr_segments * period_alignment_frames < min_period_frames {
nbr_segments += 1;
}
let aligned_period = calculate_period_100ns(
period_alignment_frames * nbr_segments,
wave_fmt.get_samplespersec() as i64,
);
Ok(aligned_period)
}
pub fn initialize_client(
&mut self,
wavefmt: &WaveFormat,
direction: &Direction,
stream_mode: &StreamMode,
) -> WasapiRes<()> {
let sharemode = match stream_mode {
StreamMode::PollingShared { .. } | StreamMode::EventsShared { .. } => ShareMode::Shared,
StreamMode::PollingExclusive { .. } | StreamMode::EventsExclusive { .. } => {
ShareMode::Exclusive
}
};
let timing = match stream_mode {
StreamMode::PollingShared { .. } | StreamMode::PollingExclusive { .. } => {
TimingMode::Polling
}
StreamMode::EventsShared { .. } | StreamMode::EventsExclusive { .. } => {
TimingMode::Events
}
};
let mut streamflags = match (&self.direction, direction, sharemode) {
(Direction::Render, Direction::Capture, ShareMode::Shared) => {
AUDCLNT_STREAMFLAGS_LOOPBACK
}
(Direction::Render, Direction::Capture, ShareMode::Exclusive) => {
return Err(WasapiError::LoopbackWithExclusiveMode);
}
(Direction::Capture, Direction::Render, _) => {
return Err(WasapiError::RenderToCaptureDevice);
}
_ => 0,
};
match stream_mode {
StreamMode::PollingShared { autoconvert, .. }
| StreamMode::EventsShared { autoconvert, .. } => {
if *autoconvert {
streamflags |= AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM
| AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY;
}
}
_ => {}
}
if timing == TimingMode::Events {
streamflags |= AUDCLNT_STREAMFLAGS_EVENTCALLBACK;
}
let mode = match sharemode {
ShareMode::Exclusive => AUDCLNT_SHAREMODE_EXCLUSIVE,
ShareMode::Shared => AUDCLNT_SHAREMODE_SHARED,
};
let (period, buffer_duration) = match stream_mode {
StreamMode::PollingShared {
buffer_duration_hns,
..
} => (0, *buffer_duration_hns),
StreamMode::EventsShared {
buffer_duration_hns,
..
} => (0, *buffer_duration_hns),
StreamMode::PollingExclusive {
period_hns,
buffer_duration_hns,
} => (*period_hns, *buffer_duration_hns),
StreamMode::EventsExclusive { period_hns, .. } => (*period_hns, *period_hns),
};
unsafe {
self.client.Initialize(
mode,
streamflags,
buffer_duration,
period,
wavefmt.as_waveformatex_ref(),
None,
)?;
}
self.direction = *direction;
self.sharemode = Some(sharemode);
self.timingmode = Some(timing);
self.bytes_per_frame = Some(wavefmt.get_blockalign() as usize);
Ok(())
}
pub fn set_get_eventhandle(&self) -> WasapiRes<Handle> {
let h_event = unsafe { CreateEventA(None, false, false, PCSTR::null())? };
unsafe { self.client.SetEventHandle(h_event)? };
Ok(Handle { handle: h_event })
}
pub fn get_buffer_size(&self) -> WasapiRes<u32> {
let buffer_frame_count = unsafe { self.client.GetBufferSize()? };
trace!("buffer_frame_count {buffer_frame_count}");
Ok(buffer_frame_count)
}
#[deprecated(
since = "0.17.0",
note = "please use the new function name `get_buffer_size` instead"
)]
pub fn get_bufferframecount(&self) -> WasapiRes<u32> {
self.get_buffer_size()
}
pub fn get_current_padding(&self) -> WasapiRes<u32> {
let padding_count = unsafe { self.client.GetCurrentPadding()? };
trace!("padding_count {padding_count}");
Ok(padding_count)
}
pub fn get_available_space_in_frames(&self) -> WasapiRes<u32> {
let frames = match (self.sharemode, self.timingmode) {
(Some(ShareMode::Exclusive), Some(TimingMode::Events)) => {
let buffer_frame_count = unsafe { self.client.GetBufferSize()? };
trace!("buffer_frame_count {buffer_frame_count}");
buffer_frame_count
}
(Some(_), Some(_)) => {
let padding_count = unsafe { self.client.GetCurrentPadding()? };
let buffer_frame_count = unsafe { self.client.GetBufferSize()? };
buffer_frame_count - padding_count
}
_ => return Err(WasapiError::ClientNotInit),
};
Ok(frames)
}
pub fn start_stream(&self) -> WasapiRes<()> {
unsafe { self.client.Start()? };
Ok(())
}
pub fn stop_stream(&self) -> WasapiRes<()> {
unsafe { self.client.Stop()? };
Ok(())
}
pub fn reset_stream(&self) -> WasapiRes<()> {
unsafe { self.client.Reset()? };
Ok(())
}
pub fn get_audiorenderclient(&self) -> WasapiRes<AudioRenderClient> {
let client = unsafe { self.client.GetService::<IAudioRenderClient>()? };
Ok(AudioRenderClient {
client,
bytes_per_frame: self.bytes_per_frame.unwrap_or_default(),
})
}
pub fn get_audiocaptureclient(&self) -> WasapiRes<AudioCaptureClient> {
let client = unsafe { self.client.GetService::<IAudioCaptureClient>()? };
Ok(AudioCaptureClient {
client,
sharemode: self.sharemode,
bytes_per_frame: self.bytes_per_frame.unwrap_or_default(),
})
}
pub fn get_audiosessioncontrol(&self) -> WasapiRes<AudioSessionControl> {
let control = unsafe { self.client.GetService::<IAudioSessionControl>()? };
Ok(AudioSessionControl { control })
}
pub fn get_audioclock(&self) -> WasapiRes<AudioClock> {
let clock = unsafe { self.client.GetService::<IAudioClock>()? };
Ok(AudioClock { clock })
}
pub fn get_direction(&self) -> Direction {
self.direction
}
pub fn get_sharemode(&self) -> Option<ShareMode> {
self.sharemode
}
pub fn get_timing_mode(&self) -> Option<TimingMode> {
self.timingmode
}
pub fn get_aec_control(&self) -> WasapiRes<AcousticEchoCancellationControl> {
let control = unsafe {
self.client
.GetService::<IAcousticEchoCancellationControl>()?
};
Ok(AcousticEchoCancellationControl { control })
}
pub fn get_audio_effects_manager(&self) -> WasapiRes<AudioEffectsManager> {
let manager = unsafe { self.client.GetService::<IAudioEffectsManager>()? };
Ok(AudioEffectsManager { manager })
}
#[deprecated(
since = "0.20.0",
note = "please use the new function name `set_properties` instead"
)]
pub fn set_audio_stream_category(&self, category: AUDIO_STREAM_CATEGORY) -> WasapiRes<()> {
let audio_client_2 = self.client.cast::<IAudioClient2>()?;
let properties = AudioClientProperties::new().set_category(category);
unsafe { audio_client_2.SetClientProperties(&properties.0)? };
Ok(())
}
pub fn set_properties(&self, properties: AudioClientProperties) -> WasapiRes<()> {
let audio_client_2 = self.client.cast::<IAudioClient2>()?;
unsafe { audio_client_2.SetClientProperties(&properties.0)? };
Ok(())
}
pub fn is_aec_supported(&self) -> WasapiRes<bool> {
if !self.is_aec_effect_present()? {
return Ok(false);
}
match unsafe { self.client.GetService::<IAcousticEchoCancellationControl>() } {
Ok(_) => Ok(true),
Err(err) if err == E_NOINTERFACE.into() => Ok(false),
Err(err) => Err(err.into()),
}
}
fn is_aec_effect_present(&self) -> WasapiRes<bool> {
let audio_effects_manager = match self.get_audio_effects_manager() {
Ok(manager) => manager,
Err(WasapiError::Windows(win_err)) if win_err == E_NOINTERFACE.into() => {
return Ok(false);
}
Err(err) => return Err(err),
};
if let Some(audio_effects) = audio_effects_manager.get_audio_effects()? {
let is_present = audio_effects
.iter()
.any(|effect| effect.id == AUDIO_EFFECT_TYPE_ACOUSTIC_ECHO_CANCELLATION);
return Ok(is_present);
}
Ok(false)
}
}
#[derive(Copy, Clone, Debug)]
pub struct AudioClientProperties(windows::Win32::Media::Audio::AudioClientProperties);
impl AudioClientProperties {
pub fn new() -> Self {
Self(windows::Win32::Media::Audio::AudioClientProperties {
cbSize: size_of::<windows::Win32::Media::Audio::AudioClientProperties>() as u32,
bIsOffload: FALSE,
eCategory: AudioCategory_Other,
Options: AUDCLNT_STREAMOPTIONS_NONE,
})
}
pub fn set_offload(mut self, is_offload: bool) -> Self {
self.0.bIsOffload = is_offload.into();
self
}
pub fn set_category<T>(mut self, category: T) -> Self
where
T: Into<AUDIO_STREAM_CATEGORY>,
{
self.0.eCategory = category.into();
self
}
pub fn set_option<T>(mut self, option: T) -> Self
where
T: Into<AUDCLNT_STREAMOPTIONS>,
{
self.0.Options |= option.into();
self
}
}
impl Default for AudioClientProperties {
fn default() -> Self {
Self::new()
}
}
pub struct AudioSessionManager {
session_manager: IAudioSessionManager,
}
impl AudioSessionManager {
pub fn get_audiosessionenumerator(&self) -> WasapiRes<AudioSessionEnumerator> {
let session_manager2: IAudioSessionManager2 = self.session_manager.cast()?;
let session_enumerator = unsafe { session_manager2.GetSessionEnumerator()? };
Ok(AudioSessionEnumerator { session_enumerator })
}
}
pub struct AudioSessionEnumerator {
session_enumerator: IAudioSessionEnumerator,
}
impl AudioSessionEnumerator {
pub fn get_count(&self) -> WasapiRes<i32> {
Ok(unsafe { self.session_enumerator.GetCount()? })
}
pub fn get_session(&self, index: i32) -> WasapiRes<AudioSessionControl> {
let session = unsafe { self.session_enumerator.GetSession(index)? };
Ok(AudioSessionControl { control: session })
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum StreamCategory {
Other,
#[deprecated = "See `Remarks` in the Microsoft documentation."]
ForegroundOnlyMedia,
Communications,
Alerts,
SoundEffects,
GameEffects,
GameMedia,
GameChat,
Speech,
Movie,
Media,
FarFieldSpeech,
UniformSpeech,
VoiceTyping,
}
impl From<StreamCategory> for AUDIO_STREAM_CATEGORY {
fn from(category: StreamCategory) -> Self {
#[allow(deprecated)]
match category {
StreamCategory::Other => AudioCategory_Other,
StreamCategory::ForegroundOnlyMedia => AudioCategory_ForegroundOnlyMedia,
StreamCategory::Communications => AudioCategory_Communications,
StreamCategory::Alerts => AudioCategory_Alerts,
StreamCategory::SoundEffects => AudioCategory_SoundEffects,
StreamCategory::GameEffects => AudioCategory_GameEffects,
StreamCategory::GameMedia => AudioCategory_GameMedia,
StreamCategory::GameChat => AudioCategory_GameChat,
StreamCategory::Speech => AudioCategory_Speech,
StreamCategory::Movie => AudioCategory_Movie,
StreamCategory::Media => AudioCategory_Media,
StreamCategory::FarFieldSpeech => AudioCategory_FarFieldSpeech,
StreamCategory::UniformSpeech => AudioCategory_UniformSpeech,
StreamCategory::VoiceTyping => AudioCategory_VoiceTyping,
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum StreamOption {
Raw,
MatchFormat,
Ambisonics,
}
impl From<StreamOption> for AUDCLNT_STREAMOPTIONS {
fn from(option: StreamOption) -> Self {
match option {
StreamOption::Raw => AUDCLNT_STREAMOPTIONS_RAW,
StreamOption::MatchFormat => AUDCLNT_STREAMOPTIONS_MATCH_FORMAT,
StreamOption::Ambisonics => AUDCLNT_STREAMOPTIONS_AMBISONICS,
}
}
}
pub struct AudioSessionControl {
control: IAudioSessionControl,
}
impl AudioSessionControl {
pub fn get_state(&self) -> WasapiRes<SessionState> {
let state = unsafe { self.control.GetState()? };
#[allow(non_upper_case_globals)]
let sessionstate = match state {
_ if state == AudioSessionStateActive => SessionState::Active,
_ if state == AudioSessionStateInactive => SessionState::Inactive,
_ if state == AudioSessionStateExpired => SessionState::Expired,
x => return Err(WasapiError::IllegalSessionState(x.0)),
};
Ok(sessionstate)
}
pub fn register_session_notification(
&self,
callbacks: EventCallbacks,
) -> WasapiRes<EventRegistration> {
let events: IAudioSessionEvents = AudioSessionEvents::new(callbacks).into();
match unsafe { self.control.RegisterAudioSessionNotification(&events) } {
Ok(()) => Ok(EventRegistration {
events,
control: self.control.clone(),
}),
Err(err) => Err(WasapiError::RegisterNotifications(err)),
}
}
pub fn get_process_id(&self) -> WasapiRes<u32> {
let control2: IAudioSessionControl2 = self.control.cast()?;
Ok(unsafe { control2.GetProcessId()? })
}
pub fn set_ducking_preference(&self, preference: bool) -> WasapiRes<()> {
let control2: IAudioSessionControl2 = self.control.cast()?;
unsafe { control2.SetDuckingPreference(preference)? };
Ok(())
}
}
pub struct EventRegistration {
events: IAudioSessionEvents,
control: IAudioSessionControl,
}
impl Drop for EventRegistration {
fn drop(&mut self) {
let _ = unsafe {
self.control
.UnregisterAudioSessionNotification(&self.events)
};
}
}
pub struct AudioClock {
clock: IAudioClock,
}
impl AudioClock {
pub fn get_frequency(&self) -> WasapiRes<u64> {
let freq = unsafe { self.clock.GetFrequency()? };
Ok(freq)
}
pub fn get_position(&self) -> WasapiRes<(u64, u64)> {
let mut pos = 0;
let mut timer = 0;
unsafe { self.clock.GetPosition(&mut pos, Some(&mut timer))? };
Ok((pos, timer))
}
}
pub struct AudioRenderClient {
client: IAudioRenderClient,
bytes_per_frame: usize,
}
impl AudioRenderClient {
pub fn write_to_device(
&self,
nbr_frames: usize,
data: &[u8],
buffer_flags: Option<BufferFlags>,
) -> WasapiRes<()> {
if nbr_frames == 0 {
return Ok(());
}
let nbr_bytes = nbr_frames * self.bytes_per_frame;
if nbr_bytes != data.len() {
return Err(WasapiError::DataLengthMismatch {
received: data.len(),
expected: nbr_bytes,
});
}
let bufferptr = unsafe { self.client.GetBuffer(nbr_frames as u32)? };
let bufferslice = unsafe { slice::from_raw_parts_mut(bufferptr, nbr_bytes) };
bufferslice.copy_from_slice(data);
let flags = match buffer_flags {
Some(bflags) => bflags.to_u32(),
None => 0,
};
unsafe { self.client.ReleaseBuffer(nbr_frames as u32, flags)? };
trace!("wrote {nbr_frames} frames");
Ok(())
}
pub fn write_to_device_from_deque(
&self,
nbr_frames: usize,
data: &mut VecDeque<u8>,
buffer_flags: Option<BufferFlags>,
) -> WasapiRes<()> {
if nbr_frames == 0 {
return Ok(());
}
let nbr_bytes = nbr_frames * self.bytes_per_frame;
if nbr_bytes > data.len() {
return Err(WasapiError::DataLengthTooShort {
received: data.len(),
expected: nbr_bytes,
});
}
let bufferptr = unsafe { self.client.GetBuffer(nbr_frames as u32)? };
let bufferslice = unsafe { slice::from_raw_parts_mut(bufferptr, nbr_bytes) };
for element in bufferslice.iter_mut() {
*element = data.pop_front().unwrap();
}
let flags = match buffer_flags {
Some(bflags) => bflags.to_u32(),
None => 0,
};
unsafe { self.client.ReleaseBuffer(nbr_frames as u32, flags)? };
trace!("wrote {nbr_frames} frames");
Ok(())
}
}
#[derive(Debug)]
pub struct BufferInfo {
pub flags: BufferFlags,
pub index: u64,
pub timestamp: u64,
}
impl BufferInfo {
pub fn new(flags: u32, index: u64, timestamp: u64) -> Self {
Self {
flags: BufferFlags::new(flags),
index,
timestamp,
}
}
pub fn none() -> Self {
Self {
flags: BufferFlags::none(),
index: 0,
timestamp: 0,
}
}
}
#[derive(Debug)]
pub struct BufferFlags {
pub data_discontinuity: bool,
pub silent: bool,
pub timestamp_error: bool,
}
impl BufferFlags {
pub fn new(flags: u32) -> Self {
BufferFlags {
data_discontinuity: flags & AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY.0 as u32 > 0,
silent: flags & AUDCLNT_BUFFERFLAGS_SILENT.0 as u32 > 0,
timestamp_error: flags & AUDCLNT_BUFFERFLAGS_TIMESTAMP_ERROR.0 as u32 > 0,
}
}
pub fn none() -> Self {
BufferFlags {
data_discontinuity: false,
silent: false,
timestamp_error: false,
}
}
pub fn to_u32(&self) -> u32 {
let mut value = 0;
if self.data_discontinuity {
value += AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY.0 as u32;
}
if self.silent {
value += AUDCLNT_BUFFERFLAGS_SILENT.0 as u32;
}
if self.timestamp_error {
value += AUDCLNT_BUFFERFLAGS_TIMESTAMP_ERROR.0 as u32;
}
value
}
}
pub struct AudioCaptureClient {
client: IAudioCaptureClient,
sharemode: Option<ShareMode>,
bytes_per_frame: usize,
}
impl AudioCaptureClient {
pub fn get_next_packet_size(&self) -> WasapiRes<Option<u32>> {
if let Some(ShareMode::Exclusive) = self.sharemode {
return Ok(None);
}
let nbr_frames = unsafe { self.client.GetNextPacketSize()? };
Ok(Some(nbr_frames))
}
#[deprecated(
since = "0.17.0",
note = "please use the new function name `get_next_packet_size` instead"
)]
pub fn get_next_nbr_frames(&self) -> WasapiRes<Option<u32>> {
self.get_next_packet_size()
}
pub fn read_from_device(&self, data: &mut [u8]) -> WasapiRes<(u32, BufferInfo)> {
let data_len_in_frames = data.len() / self.bytes_per_frame;
if data_len_in_frames == 0 {
return Ok((0, BufferInfo::none()));
}
let mut buffer_ptr = ptr::null_mut();
let mut nbr_frames_returned = 0;
let mut index: u64 = 0;
let mut timestamp: u64 = 0;
let mut flags = 0;
unsafe {
self.client.GetBuffer(
&mut buffer_ptr,
&mut nbr_frames_returned,
&mut flags,
Some(&mut index),
Some(&mut timestamp),
)?
};
let buffer_info = BufferInfo::new(flags, index, timestamp);
if nbr_frames_returned == 0 {
unsafe { self.client.ReleaseBuffer(nbr_frames_returned)? };
return Ok((0, buffer_info));
}
if data_len_in_frames < nbr_frames_returned as usize {
unsafe { self.client.ReleaseBuffer(nbr_frames_returned)? };
return Err(WasapiError::DataLengthTooShort {
received: data_len_in_frames,
expected: nbr_frames_returned as usize,
});
}
let len_in_bytes = nbr_frames_returned as usize * self.bytes_per_frame;
let bufferslice = unsafe { slice::from_raw_parts(buffer_ptr, len_in_bytes) };
data[..len_in_bytes].copy_from_slice(bufferslice);
if nbr_frames_returned > 0 {
unsafe { self.client.ReleaseBuffer(nbr_frames_returned)? };
}
trace!("read {nbr_frames_returned} frames");
Ok((nbr_frames_returned, buffer_info))
}
pub fn read_from_device_to_deque(&self, data: &mut VecDeque<u8>) -> WasapiRes<BufferInfo> {
let mut buffer_ptr = ptr::null_mut();
let mut nbr_frames_returned = 0;
let mut index: u64 = 0;
let mut timestamp: u64 = 0;
let mut flags = 0;
unsafe {
self.client.GetBuffer(
&mut buffer_ptr,
&mut nbr_frames_returned,
&mut flags,
Some(&mut index),
Some(&mut timestamp),
)?
};
let buffer_info = BufferInfo::new(flags, index, timestamp);
if nbr_frames_returned == 0 {
return Ok(buffer_info);
}
let len_in_bytes = nbr_frames_returned as usize * self.bytes_per_frame;
let bufferslice = unsafe { slice::from_raw_parts(buffer_ptr, len_in_bytes) };
for element in bufferslice.iter() {
data.push_back(*element);
}
if nbr_frames_returned > 0 {
unsafe { self.client.ReleaseBuffer(nbr_frames_returned).unwrap() };
}
trace!("read {nbr_frames_returned} frames");
Ok(buffer_info)
}
pub fn get_sharemode(&self) -> Option<ShareMode> {
self.sharemode
}
}
pub struct Handle {
handle: HANDLE,
}
impl Drop for Handle {
fn drop(&mut self) {
let _ = unsafe { CloseHandle(self.handle) };
}
}
impl Handle {
pub fn wait_for_event(&self, timeout_ms: u32) -> WasapiRes<()> {
let retval = unsafe { WaitForSingleObject(self.handle, timeout_ms) };
if retval.0 != WAIT_OBJECT_0.0 {
return Err(WasapiError::EventTimeout);
}
Ok(())
}
}
pub struct AudioEffectsManager {
manager: IAudioEffectsManager,
}
impl AudioEffectsManager {
pub fn get_audio_effects(&self) -> WasapiRes<Option<Vec<AUDIO_EFFECT>>> {
let mut audio_effects: *mut AUDIO_EFFECT = std::ptr::null_mut();
let mut num_effects: u32 = 0;
unsafe {
self.manager
.GetAudioEffects(&mut audio_effects, &mut num_effects)?;
}
let effects = if num_effects > 0 {
let effects_slice =
unsafe { slice::from_raw_parts(audio_effects, num_effects as usize) };
let effects_vec = effects_slice.to_vec();
Some(effects_vec)
} else {
None
};
if !audio_effects.is_null() {
unsafe { CoTaskMemFree(Some(audio_effects.cast())) };
}
Ok(effects)
}
}
pub struct AcousticEchoCancellationControl {
control: IAcousticEchoCancellationControl,
}
impl AcousticEchoCancellationControl {
pub fn set_echo_cancellation_render_endpoint(
&self,
endpoint_id: Option<String>,
) -> WasapiRes<()> {
let endpoint_id = if let Some(endpoint_id) = endpoint_id {
PCWSTR::from_raw(HSTRING::from(endpoint_id).as_ptr())
} else {
PCWSTR::null()
};
unsafe {
self.control
.SetEchoCancellationRenderEndpoint(endpoint_id)?
};
Ok(())
}
}