2022-11-23 10:41:43 -08:00
/*
Simple DirectMedia Layer
2023-01-09 09:41:41 -08:00
Copyright ( C ) 1997 - 2023 Sam Lantinga < slouken @ libsdl . org >
2022-11-23 10:41:43 -08:00
This software is provided ' as - is ' , without any express or implied
warranty . In no event will the authors be held liable for any damages
arising from the use of this software .
Permission is granted to anyone to use this software for any purpose ,
including commercial applications , and to alter it and redistribute it
freely , subject to the following restrictions :
1. The origin of this software must not be misrepresented ; you must not
claim that you wrote the original software . If you use this software
in a product , an acknowledgment in the product documentation would be
appreciated but is not required .
2. Altered source versions must be plainly marked as such , and must not be
misrepresented as being the original software .
3. This notice may not be removed or altered from any source distribution .
*/
2022-11-29 18:34:15 -08:00
# include "SDL_internal.h"
2022-11-23 10:41:43 -08:00
// This is C++/CX code that the WinRT port uses to talk to WASAPI-related
// system APIs. The C implementation of these functions, for non-WinRT apps,
// is in SDL_wasapi_win32.c. The code in SDL_wasapi.c is used by both standard
// Windows and WinRT builds to deal with audio and calls into these functions.
2023-03-29 21:49:01 +00:00
# if defined(SDL_AUDIO_DRIVER_WASAPI) && defined(__WINRT__)
2022-11-23 10:41:43 -08:00
# include <Windows.h>
# include <windows.ui.core.h>
# include <windows.devices.enumeration.h>
# include <windows.media.devices.h>
# include <wrl/implements.h>
# include <collection.h>
extern " C " {
# include "../../core/windows/SDL_windows.h"
# include "../SDL_audio_c.h"
# include "../SDL_sysaudio.h"
}
# define COBJMACROS
# include <mmdeviceapi.h>
# include <audioclient.h>
# include "SDL_wasapi.h"
using namespace Windows : : Devices : : Enumeration ;
using namespace Windows : : Media : : Devices ;
using namespace Windows : : Foundation ;
using namespace Microsoft : : WRL ;
2022-11-30 12:51:59 -08:00
static Platform : : String ^ SDL_PKEY_AudioEngine_DeviceFormat = L " {f19f064d-082c-4e27-bc73-6882a1bb8e4c} 0 " ;
2022-11-23 10:41:43 -08:00
2023-07-14 19:55:30 -04:00
static SDL_bool FindWinRTAudioDeviceCallback ( SDL_AudioDevice * device , void * userdata )
2022-11-23 10:41:43 -08:00
{
2023-07-14 19:55:30 -04:00
return ( SDL_wcscmp ( ( LPCWSTR ) device - > handle , ( LPCWSTR ) userdata ) = = 0 ) ? SDL_TRUE : SDL_FALSE ;
}
2022-11-23 10:41:43 -08:00
2023-07-14 19:55:30 -04:00
static SDL_AudioDevice * FindWinRTAudioDevice ( LPCWSTR devid )
{
return SDL_FindPhysicalAudioDeviceByCallback ( FindWinRTAudioDeviceCallback , ( void * ) devid ) ;
}
2022-11-23 10:41:43 -08:00
class SDL_WasapiDeviceEventHandler
{
2022-11-30 12:51:59 -08:00
public :
2022-11-23 10:41:43 -08:00
SDL_WasapiDeviceEventHandler ( const SDL_bool _iscapture ) ;
~ SDL_WasapiDeviceEventHandler ( ) ;
2022-11-30 12:51:59 -08:00
void OnDeviceAdded ( DeviceWatcher ^ sender , DeviceInformation ^ args ) ;
void OnDeviceRemoved ( DeviceWatcher ^ sender , DeviceInformationUpdate ^ args ) ;
void OnDeviceUpdated ( DeviceWatcher ^ sender , DeviceInformationUpdate ^ args ) ;
void OnEnumerationCompleted ( DeviceWatcher ^ sender , Platform : : Object ^ args ) ;
void OnDefaultRenderDeviceChanged ( Platform : : Object ^ sender , DefaultAudioRenderDeviceChangedEventArgs ^ args ) ;
void OnDefaultCaptureDeviceChanged ( Platform : : Object ^ sender , DefaultAudioCaptureDeviceChangedEventArgs ^ args ) ;
2023-07-14 19:55:30 -04:00
void WaitForCompletion ( ) ;
2022-11-30 12:51:59 -08:00
private :
2023-07-14 19:55:30 -04:00
SDL_Semaphore * completed_semaphore ;
2022-11-23 10:41:43 -08:00
const SDL_bool iscapture ;
2022-11-30 12:51:59 -08:00
DeviceWatcher ^ watcher ;
2022-11-23 10:41:43 -08:00
Windows : : Foundation : : EventRegistrationToken added_handler ;
Windows : : Foundation : : EventRegistrationToken removed_handler ;
Windows : : Foundation : : EventRegistrationToken updated_handler ;
Windows : : Foundation : : EventRegistrationToken completed_handler ;
Windows : : Foundation : : EventRegistrationToken default_changed_handler ;
} ;
SDL_WasapiDeviceEventHandler : : SDL_WasapiDeviceEventHandler ( const SDL_bool _iscapture )
2023-07-14 19:55:30 -04:00
: iscapture ( _iscapture ) , completed_semaphore ( SDL_CreateSemaphore ( 0 ) )
2022-11-23 10:41:43 -08:00
{
2023-07-14 19:55:30 -04:00
if ( ! completed_semaphore ) {
2022-11-30 12:51:59 -08:00
return ; // uhoh.
2023-07-14 19:55:30 -04:00
}
2022-11-23 10:41:43 -08:00
2022-11-30 12:51:59 -08:00
Platform : : String ^ selector = _iscapture ? MediaDevice : : GetAudioCaptureSelector ( ) : MediaDevice : : GetAudioRenderSelector ( ) ;
Platform : : Collections : : Vector < Platform : : String ^ > properties ;
2022-11-23 10:41:43 -08:00
properties . Append ( SDL_PKEY_AudioEngine_DeviceFormat ) ;
watcher = DeviceInformation : : CreateWatcher ( selector , properties . GetView ( ) ) ;
if ( ! watcher )
2022-11-30 12:51:59 -08:00
return ; // uhoh.
2022-11-23 10:41:43 -08:00
// !!! FIXME: this doesn't need a lambda here, I think, if I make SDL_WasapiDeviceEventHandler a proper C++/CX class. --ryan.
2022-11-30 12:51:59 -08:00
added_handler = watcher - > Added + = ref new TypedEventHandler < DeviceWatcher ^ , DeviceInformation ^ > ( [ this ] ( DeviceWatcher ^ sender , DeviceInformation ^ args ) { OnDeviceAdded ( sender , args ) ; } ) ;
removed_handler = watcher - > Removed + = ref new TypedEventHandler < DeviceWatcher ^ , DeviceInformationUpdate ^ > ( [ this ] ( DeviceWatcher ^ sender , DeviceInformationUpdate ^ args ) { OnDeviceRemoved ( sender , args ) ; } ) ;
updated_handler = watcher - > Updated + = ref new TypedEventHandler < DeviceWatcher ^ , DeviceInformationUpdate ^ > ( [ this ] ( DeviceWatcher ^ sender , DeviceInformationUpdate ^ args ) { OnDeviceUpdated ( sender , args ) ; } ) ;
completed_handler = watcher - > EnumerationCompleted + = ref new TypedEventHandler < DeviceWatcher ^ , Platform : : Object ^ > ( [ this ] ( DeviceWatcher ^ sender , Platform : : Object ^ args ) { OnEnumerationCompleted ( sender , args ) ; } ) ;
2022-11-23 10:41:43 -08:00
if ( iscapture ) {
2022-11-30 12:51:59 -08:00
default_changed_handler = MediaDevice : : DefaultAudioCaptureDeviceChanged + = ref new TypedEventHandler < Platform : : Object ^ , DefaultAudioCaptureDeviceChangedEventArgs ^ > ( [ this ] ( Platform : : Object ^ sender , DefaultAudioCaptureDeviceChangedEventArgs ^ args ) { OnDefaultCaptureDeviceChanged ( sender , args ) ; } ) ;
2022-11-23 10:41:43 -08:00
} else {
2022-11-30 12:51:59 -08:00
default_changed_handler = MediaDevice : : DefaultAudioRenderDeviceChanged + = ref new TypedEventHandler < Platform : : Object ^ , DefaultAudioRenderDeviceChangedEventArgs ^ > ( [ this ] ( Platform : : Object ^ sender , DefaultAudioRenderDeviceChangedEventArgs ^ args ) { OnDefaultRenderDeviceChanged ( sender , args ) ; } ) ;
2022-11-23 10:41:43 -08:00
}
watcher - > Start ( ) ;
}
SDL_WasapiDeviceEventHandler : : ~ SDL_WasapiDeviceEventHandler ( )
{
if ( watcher ) {
watcher - > Added - = added_handler ;
watcher - > Removed - = removed_handler ;
watcher - > Updated - = updated_handler ;
watcher - > EnumerationCompleted - = completed_handler ;
watcher - > Stop ( ) ;
watcher = nullptr ;
}
2023-07-14 19:55:30 -04:00
if ( completed_semaphore ) {
SDL_DestroySemaphore ( completed_semaphore ) ;
completed_semaphore = nullptr ;
2022-11-23 10:41:43 -08:00
}
if ( iscapture ) {
MediaDevice : : DefaultAudioCaptureDeviceChanged - = default_changed_handler ;
} else {
MediaDevice : : DefaultAudioRenderDeviceChanged - = default_changed_handler ;
}
}
2022-11-30 12:51:59 -08:00
void SDL_WasapiDeviceEventHandler : : OnDeviceAdded ( DeviceWatcher ^ sender , DeviceInformation ^ info )
2022-11-23 10:41:43 -08:00
{
2023-07-14 19:55:30 -04:00
/* You can have multiple endpoints on a device that are mutually exclusive ("Speakers" vs "Line Out" or whatever).
In a perfect world , things that are unplugged won ' t be in this collection . The only gotcha is probably for
phones and tablets , where you might have an internal speaker and a headphone jack and expect both to be
available and switch automatically . ( ! ! ! FIXME . . . ? ) */
2022-11-23 10:41:43 -08:00
SDL_assert ( sender = = this - > watcher ) ;
char * utf8dev = WIN_StringToUTF8 ( info - > Name - > Data ( ) ) ;
if ( utf8dev ) {
2023-07-14 19:55:30 -04:00
SDL_AudioSpec spec ;
SDL_zero ( spec ) ;
2022-11-30 12:51:59 -08:00
Platform : : Object ^ obj = info - > Properties - > Lookup ( SDL_PKEY_AudioEngine_DeviceFormat ) ;
2022-11-23 10:41:43 -08:00
if ( obj ) {
2022-11-30 12:51:59 -08:00
IPropertyValue ^ property = ( IPropertyValue ^ ) obj ;
Platform : : Array < unsigned char > ^ data ;
2022-11-23 10:41:43 -08:00
property - > GetUInt8Array ( & data ) ;
2023-07-14 19:55:30 -04:00
WAVEFORMATEXTENSIBLE fmt ;
2022-11-23 10:41:43 -08:00
SDL_zero ( fmt ) ;
2023-07-14 19:55:30 -04:00
SDL_memcpy ( & fmt , data - > Data , SDL_min ( data - > Length , sizeof ( WAVEFORMATEXTENSIBLE ) ) ) ;
spec . channels = ( Uint8 ) fmt - > Format . nChannels ;
spec . freq = fmt - > Format . nSamplesPerSec ;
spec . format = SDL_WaveFormatExToSDLFormat ( ( WAVEFORMATEX * ) fmt ) ;
2022-11-23 10:41:43 -08:00
}
2023-07-14 19:55:30 -04:00
LPWSTR devid = SDL_wcsdup ( devid ) ;
if ( devid ) {
SDL_AddAudioDevice ( this - > iscapture , utf8dev , spec . channels ? & spec : NULL , devid ) ;
}
2022-11-23 10:41:43 -08:00
SDL_free ( utf8dev ) ;
}
}
2022-11-30 12:51:59 -08:00
void SDL_WasapiDeviceEventHandler : : OnDeviceRemoved ( DeviceWatcher ^ sender , DeviceInformationUpdate ^ info )
2022-11-23 10:41:43 -08:00
{
SDL_assert ( sender = = this - > watcher ) ;
2023-07-14 19:55:30 -04:00
WASAPI_DisconnectDevice ( FindWinRTAudioDevice ( info - > Id - > Data ( ) ) ) ;
2022-11-23 10:41:43 -08:00
}
2022-11-30 12:51:59 -08:00
void SDL_WasapiDeviceEventHandler : : OnDeviceUpdated ( DeviceWatcher ^ sender , DeviceInformationUpdate ^ args )
2022-11-23 10:41:43 -08:00
{
SDL_assert ( sender = = this - > watcher ) ;
}
2022-11-30 12:51:59 -08:00
void SDL_WasapiDeviceEventHandler : : OnEnumerationCompleted ( DeviceWatcher ^ sender , Platform : : Object ^ args )
2022-11-23 10:41:43 -08:00
{
SDL_assert ( sender = = this - > watcher ) ;
2023-07-14 19:55:30 -04:00
if ( this - > completed_semaphore ) {
SDL_PostSemaphore ( this - > completed_semaphore ) ;
}
2022-11-23 10:41:43 -08:00
}
2022-11-30 12:51:59 -08:00
void SDL_WasapiDeviceEventHandler : : OnDefaultRenderDeviceChanged ( Platform : : Object ^ sender , DefaultAudioRenderDeviceChangedEventArgs ^ args )
2022-11-23 10:41:43 -08:00
{
2022-12-12 16:07:48 -05:00
SDL_assert ( ! this - > iscapture ) ;
2023-07-14 19:55:30 -04:00
SDL_DefaultAudioDeviceChanged ( FindWinRTAudioDevice ( args - > Id - > Data ( ) ) ) ;
2022-11-23 10:41:43 -08:00
}
2022-11-30 12:51:59 -08:00
void SDL_WasapiDeviceEventHandler : : OnDefaultCaptureDeviceChanged ( Platform : : Object ^ sender , DefaultAudioCaptureDeviceChangedEventArgs ^ args )
2022-11-23 10:41:43 -08:00
{
2022-12-12 16:07:48 -05:00
SDL_assert ( this - > iscapture ) ;
2023-07-14 19:55:30 -04:00
SDL_DefaultAudioDeviceChanged ( FindWinRTAudioDevice ( args - > Id - > Data ( ) ) ) ;
}
void SDL_WasapiDeviceEventHandler : : WaitForCompletion ( )
{
if ( this - > completed_semaphore ) {
SDL_WaitSemaphore ( this - > completed_semaphore ) ;
SDL_DestroySemaphore ( this - > completed_semaphore ) ;
this - > completed_semaphore = nullptr ;
}
2022-11-23 10:41:43 -08:00
}
static SDL_WasapiDeviceEventHandler * playback_device_event_handler ;
static SDL_WasapiDeviceEventHandler * capture_device_event_handler ;
int WASAPI_PlatformInit ( void )
{
return 0 ;
}
void WASAPI_PlatformDeinit ( void )
{
delete playback_device_event_handler ;
playback_device_event_handler = nullptr ;
delete capture_device_event_handler ;
capture_device_event_handler = nullptr ;
}
2023-07-14 19:55:30 -04:00
void WASAPI_EnumerateEndpoints ( SDL_AudioDevice * * default_output , SDL_AudioDevice * * default_capture )
2022-11-23 10:41:43 -08:00
{
2023-07-14 19:55:30 -04:00
Platform : : String ^ defdevid ;
2022-11-23 10:41:43 -08:00
// DeviceWatchers will fire an Added event for each existing device at
// startup, so we don't need to enumerate them separately before
// listening for updates.
playback_device_event_handler = new SDL_WasapiDeviceEventHandler ( SDL_FALSE ) ;
2023-07-14 19:55:30 -04:00
playback_device_event_handler - > WaitForCompletion ( ) ;
defdevid = MediaDevice : : GetDefaultAudioRenderId ( AudioDeviceRole : : Default ) ;
if ( defdevid ) {
* default_output = FindWinRTAudioDevice ( defdevid - > Data ( ) ) ;
}
2022-11-23 10:41:43 -08:00
capture_device_event_handler = new SDL_WasapiDeviceEventHandler ( SDL_TRUE ) ;
2023-07-14 19:55:30 -04:00
capture_device_event_handler - > WaitForCompletion ( ) ;
defdevid = MediaDevice : : GetDefaultAudioCaptureId ( AudioDeviceRole : : Default )
if ( defdevid ) {
* default_capture = FindWinRTAudioDevice ( defdevid - > Data ( ) ) ;
}
2022-11-23 10:41:43 -08:00
}
2023-07-14 19:55:30 -04:00
class SDL_WasapiActivationHandler : public RuntimeClass < RuntimeClassFlags < ClassicCom > , FtmBase , IActivateAudioInterfaceCompletionHandler >
2022-11-23 10:41:43 -08:00
{
2023-07-14 19:55:30 -04:00
public :
SDL_WasapiActivationHandler ( ) : completion_semaphore ( SDL_CreateSemaphore ( 0 ) ) { SDL_assert ( completion_semaphore ! = NULL ) ; }
STDMETHOD ( ActivateCompleted ) ( IActivateAudioInterfaceAsyncOperation * operation ) ;
void WaitForCompletion ( ) ;
private :
SDL_Semaphore * completion_semaphore ;
2022-11-23 10:41:43 -08:00
} ;
2023-07-14 19:55:30 -04:00
void SDL_WasapiActivationHandler : : WaitForCompletion ( )
{
if ( completion_semaphore ) {
SDL_WaitSemaphore ( completion_semaphore ) ;
SDL_DestroySemaphore ( completion_semaphore ) ;
completion_semaphore = NULL ;
}
}
2022-11-23 10:41:43 -08:00
HRESULT
SDL_WasapiActivationHandler : : ActivateCompleted ( IActivateAudioInterfaceAsyncOperation * async )
{
// Just set a flag, since we're probably in a different thread. We'll pick it up and init everything on our own thread to prevent races.
2023-07-14 19:55:30 -04:00
SDL_PostSemaphore ( completion_semaphore ) ;
2022-11-23 10:41:43 -08:00
return S_OK ;
}
2022-11-30 12:51:59 -08:00
void WASAPI_PlatformDeleteActivationHandler ( void * handler )
2022-11-23 10:41:43 -08:00
{
2022-11-30 12:51:59 -08:00
( ( SDL_WasapiActivationHandler * ) handler ) - > Release ( ) ;
2022-11-23 10:41:43 -08:00
}
2023-07-14 19:55:30 -04:00
int WASAPI_ActivateDevice ( SDL_AudioDevice * device )
2022-11-23 10:41:43 -08:00
{
2023-07-14 19:55:30 -04:00
LPCWSTR devid = ( LPCWSTR ) device - > handle ;
SDL_assert ( devid ! = NULL ) ;
2022-11-23 10:41:43 -08:00
ComPtr < SDL_WasapiActivationHandler > handler = Make < SDL_WasapiActivationHandler > ( ) ;
if ( handler = = nullptr ) {
return SDL_SetError ( " Failed to allocate WASAPI activation handler " ) ;
}
2022-11-30 12:51:59 -08:00
handler . Get ( ) - > AddRef ( ) ; // we hold a reference after ComPtr destructs on return, causing a Release, and Release ourselves in WASAPI_PlatformDeleteActivationHandler(), etc.
2023-07-14 19:55:30 -04:00
device - > hidden - > activation_handler = handler . Get ( ) ;
2022-11-23 10:41:43 -08:00
IActivateAudioInterfaceAsyncOperation * async = nullptr ;
const HRESULT ret = ActivateAudioInterfaceAsync ( devid , __uuidof ( IAudioClient ) , nullptr , handler . Get ( ) , & async ) ;
if ( FAILED ( ret ) | | async = = nullptr ) {
if ( async ! = nullptr ) {
async - > Release ( ) ;
}
handler . Get ( ) - > Release ( ) ;
return WIN_SetErrorFromHRESULT ( " WASAPI can't activate requested audio endpoint " , ret ) ;
}
2023-07-14 19:55:30 -04:00
// !!! FIXME: the problems in SDL2 that needed this to be synchronous are _probably_ solved by SDL3, and this can block indefinitely if a user prompt is shown to get permission to use a microphone.
handler . WaitForCompletion ( ) ; // block here until we have an answer, so this is synchronous to us after all.
2022-11-23 10:41:43 -08:00
HRESULT activateRes = S_OK ;
IUnknown * iunknown = nullptr ;
const HRESULT getActivateRes = async - > GetActivateResult ( & activateRes , & iunknown ) ;
async - > Release ( ) ;
if ( FAILED ( getActivateRes ) ) {
return WIN_SetErrorFromHRESULT ( " Failed to get WASAPI activate result " , getActivateRes ) ;
} else if ( FAILED ( activateRes ) ) {
return WIN_SetErrorFromHRESULT ( " Failed to activate WASAPI device " , activateRes ) ;
}
2023-07-14 19:55:30 -04:00
iunknown - > QueryInterface ( IID_PPV_ARGS ( & device - > hidden - > client ) ) ;
if ( ! device - > hidden - > client ) {
2022-11-23 10:41:43 -08:00
return SDL_SetError ( " Failed to query WASAPI client interface " ) ;
}
2023-07-14 19:55:30 -04:00
if ( WASAPI_PrepDevice ( device ) = = - 1 ) {
2022-11-23 10:41:43 -08:00
return - 1 ;
}
return 0 ;
}
2023-07-14 19:55:30 -04:00
void WASAPI_PlatformThreadInit ( SDL_AudioDevice * device )
2022-11-23 10:41:43 -08:00
{
// !!! FIXME: set this thread to "Pro Audio" priority.
}
2023-07-14 19:55:30 -04:00
void WASAPI_PlatformThreadDeinit ( SDL_AudioDevice * device )
2022-11-23 10:41:43 -08:00
{
// !!! FIXME: set this thread to "Pro Audio" priority.
}
2023-07-14 19:55:30 -04:00
void WASAPI_PlatformFreeDeviceHandle ( SDL_AudioDevice * device )
2022-11-23 10:41:43 -08:00
{
2023-07-14 19:55:30 -04:00
SDL_free ( device - > handle ) ;
2022-11-23 10:41:43 -08:00
}
2022-11-30 12:51:59 -08:00
# endif // SDL_AUDIO_DRIVER_WASAPI && defined(__WINRT__)