SDL_wasapi_winrt.cpp (11725B)
1 /* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2020 Sam Lantinga <slouken@libsdl.org> 4 5 This software is provided 'as-is', without any express or implied 6 warranty. In no event will the authors be held liable for any damages 7 arising from the use of this software. 8 9 Permission is granted to anyone to use this software for any purpose, 10 including commercial applications, and to alter it and redistribute it 11 freely, subject to the following restrictions: 12 13 1. The origin of this software must not be misrepresented; you must not 14 claim that you wrote the original software. If you use this software 15 in a product, an acknowledgment in the product documentation would be 16 appreciated but is not required. 17 2. Altered source versions must be plainly marked as such, and must not be 18 misrepresented as being the original software. 19 3. This notice may not be removed or altered from any source distribution. 20 */ 21 #include "../../SDL_internal.h" 22 23 // This is C++/CX code that the WinRT port uses to talk to WASAPI-related 24 // system APIs. The C implementation of these functions, for non-WinRT apps, 25 // is in SDL_wasapi_win32.c. The code in SDL_wasapi.c is used by both standard 26 // Windows and WinRT builds to deal with audio and calls into these functions. 27 28 #if SDL_AUDIO_DRIVER_WASAPI && defined(__WINRT__) 29 30 #include <Windows.h> 31 #include <windows.ui.core.h> 32 #include <windows.devices.enumeration.h> 33 #include <windows.media.devices.h> 34 #include <wrl/implements.h> 35 36 extern "C" { 37 #include "../../core/windows/SDL_windows.h" 38 #include "SDL_audio.h" 39 #include "SDL_timer.h" 40 #include "../SDL_audio_c.h" 41 #include "../SDL_sysaudio.h" 42 } 43 44 #define COBJMACROS 45 #include <mmdeviceapi.h> 46 #include <audioclient.h> 47 48 #include "SDL_wasapi.h" 49 50 using namespace Windows::Devices::Enumeration; 51 using namespace Windows::Media::Devices; 52 using namespace Windows::Foundation; 53 using namespace Microsoft::WRL; 54 55 class SDL_WasapiDeviceEventHandler 56 { 57 public: 58 SDL_WasapiDeviceEventHandler(const SDL_bool _iscapture); 59 ~SDL_WasapiDeviceEventHandler(); 60 void OnDeviceAdded(DeviceWatcher^ sender, DeviceInformation^ args); 61 void OnDeviceRemoved(DeviceWatcher^ sender, DeviceInformationUpdate^ args); 62 void OnDeviceUpdated(DeviceWatcher^ sender, DeviceInformationUpdate^ args); 63 void OnEnumerationCompleted(DeviceWatcher^ sender, Platform::Object^ args); 64 void OnDefaultRenderDeviceChanged(Platform::Object^ sender, DefaultAudioRenderDeviceChangedEventArgs^ args); 65 void OnDefaultCaptureDeviceChanged(Platform::Object^ sender, DefaultAudioCaptureDeviceChangedEventArgs^ args); 66 SDL_semaphore* completed; 67 68 private: 69 const SDL_bool iscapture; 70 DeviceWatcher^ watcher; 71 Windows::Foundation::EventRegistrationToken added_handler; 72 Windows::Foundation::EventRegistrationToken removed_handler; 73 Windows::Foundation::EventRegistrationToken updated_handler; 74 Windows::Foundation::EventRegistrationToken completed_handler; 75 Windows::Foundation::EventRegistrationToken default_changed_handler; 76 }; 77 78 SDL_WasapiDeviceEventHandler::SDL_WasapiDeviceEventHandler(const SDL_bool _iscapture) 79 : iscapture(_iscapture) 80 , completed(SDL_CreateSemaphore(0)) 81 , watcher(DeviceInformation::CreateWatcher(_iscapture ? DeviceClass::AudioCapture : DeviceClass::AudioRender)) 82 { 83 if (!watcher || !completed) 84 return; // uhoh. 85 86 // !!! FIXME: this doesn't need a lambda here, I think, if I make SDL_WasapiDeviceEventHandler a proper C++/CX class. --ryan. 87 added_handler = watcher->Added += ref new TypedEventHandler<DeviceWatcher^, DeviceInformation^>([this](DeviceWatcher^ sender, DeviceInformation^ args) { OnDeviceAdded(sender, args); } ); 88 removed_handler = watcher->Removed += ref new TypedEventHandler<DeviceWatcher^, DeviceInformationUpdate^>([this](DeviceWatcher^ sender, DeviceInformationUpdate^ args) { OnDeviceRemoved(sender, args); } ); 89 updated_handler = watcher->Updated += ref new TypedEventHandler<DeviceWatcher^, DeviceInformationUpdate^>([this](DeviceWatcher^ sender, DeviceInformationUpdate^ args) { OnDeviceUpdated(sender, args); } ); 90 completed_handler = watcher->EnumerationCompleted += ref new TypedEventHandler<DeviceWatcher^, Platform::Object^>([this](DeviceWatcher^ sender, Platform::Object^ args) { OnEnumerationCompleted(sender, args); } ); 91 if (iscapture) { 92 default_changed_handler = MediaDevice::DefaultAudioCaptureDeviceChanged += ref new TypedEventHandler<Platform::Object^, DefaultAudioCaptureDeviceChangedEventArgs^>([this](Platform::Object^ sender, DefaultAudioCaptureDeviceChangedEventArgs^ args) { OnDefaultCaptureDeviceChanged(sender, args); } ); 93 } else { 94 default_changed_handler = MediaDevice::DefaultAudioRenderDeviceChanged += ref new TypedEventHandler<Platform::Object^, DefaultAudioRenderDeviceChangedEventArgs^>([this](Platform::Object^ sender, DefaultAudioRenderDeviceChangedEventArgs^ args) { OnDefaultRenderDeviceChanged(sender, args); } ); 95 } 96 watcher->Start(); 97 } 98 99 SDL_WasapiDeviceEventHandler::~SDL_WasapiDeviceEventHandler() 100 { 101 if (watcher) { 102 watcher->Added -= added_handler; 103 watcher->Removed -= removed_handler; 104 watcher->Updated -= updated_handler; 105 watcher->EnumerationCompleted -= completed_handler; 106 watcher->Stop(); 107 watcher = nullptr; 108 } 109 if (completed) { 110 SDL_DestroySemaphore(completed); 111 completed = nullptr; 112 } 113 114 if (iscapture) { 115 MediaDevice::DefaultAudioCaptureDeviceChanged -= default_changed_handler; 116 } else { 117 MediaDevice::DefaultAudioRenderDeviceChanged -= default_changed_handler; 118 } 119 } 120 121 void 122 SDL_WasapiDeviceEventHandler::OnDeviceAdded(DeviceWatcher^ sender, DeviceInformation^ info) 123 { 124 SDL_assert(sender == this->watcher); 125 char *utf8dev = WIN_StringToUTF8(info->Name->Data()); 126 if (utf8dev) { 127 WASAPI_AddDevice(this->iscapture, utf8dev, info->Id->Data()); 128 SDL_free(utf8dev); 129 } 130 } 131 132 void 133 SDL_WasapiDeviceEventHandler::OnDeviceRemoved(DeviceWatcher^ sender, DeviceInformationUpdate^ info) 134 { 135 SDL_assert(sender == this->watcher); 136 WASAPI_RemoveDevice(this->iscapture, info->Id->Data()); 137 } 138 139 void 140 SDL_WasapiDeviceEventHandler::OnDeviceUpdated(DeviceWatcher^ sender, DeviceInformationUpdate^ args) 141 { 142 SDL_assert(sender == this->watcher); 143 } 144 145 void 146 SDL_WasapiDeviceEventHandler::OnEnumerationCompleted(DeviceWatcher^ sender, Platform::Object^ args) 147 { 148 SDL_assert(sender == this->watcher); 149 SDL_SemPost(this->completed); 150 } 151 152 void 153 SDL_WasapiDeviceEventHandler::OnDefaultRenderDeviceChanged(Platform::Object^ sender, DefaultAudioRenderDeviceChangedEventArgs^ args) 154 { 155 SDL_assert(this->iscapture); 156 SDL_AtomicAdd(&WASAPI_DefaultPlaybackGeneration, 1); 157 } 158 159 void 160 SDL_WasapiDeviceEventHandler::OnDefaultCaptureDeviceChanged(Platform::Object^ sender, DefaultAudioCaptureDeviceChangedEventArgs^ args) 161 { 162 SDL_assert(!this->iscapture); 163 SDL_AtomicAdd(&WASAPI_DefaultCaptureGeneration, 1); 164 } 165 166 167 static SDL_WasapiDeviceEventHandler *playback_device_event_handler; 168 static SDL_WasapiDeviceEventHandler *capture_device_event_handler; 169 170 int WASAPI_PlatformInit(void) 171 { 172 return 0; 173 } 174 175 void WASAPI_PlatformDeinit(void) 176 { 177 delete playback_device_event_handler; 178 playback_device_event_handler = nullptr; 179 delete capture_device_event_handler; 180 capture_device_event_handler = nullptr; 181 } 182 183 void WASAPI_EnumerateEndpoints(void) 184 { 185 // DeviceWatchers will fire an Added event for each existing device at 186 // startup, so we don't need to enumerate them separately before 187 // listening for updates. 188 playback_device_event_handler = new SDL_WasapiDeviceEventHandler(SDL_FALSE); 189 capture_device_event_handler = new SDL_WasapiDeviceEventHandler(SDL_TRUE); 190 SDL_SemWait(playback_device_event_handler->completed); 191 SDL_SemWait(capture_device_event_handler->completed); 192 } 193 194 struct SDL_WasapiActivationHandler : public RuntimeClass< RuntimeClassFlags< ClassicCom >, FtmBase, IActivateAudioInterfaceCompletionHandler > 195 { 196 SDL_WasapiActivationHandler() : device(nullptr) {} 197 STDMETHOD(ActivateCompleted)(IActivateAudioInterfaceAsyncOperation *operation); 198 SDL_AudioDevice *device; 199 }; 200 201 HRESULT 202 SDL_WasapiActivationHandler::ActivateCompleted(IActivateAudioInterfaceAsyncOperation *async) 203 { 204 // 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. 205 SDL_AtomicSet(&device->hidden->just_activated, 1); 206 WASAPI_UnrefDevice(device); 207 return S_OK; 208 } 209 210 void 211 WASAPI_PlatformDeleteActivationHandler(void *handler) 212 { 213 ((SDL_WasapiActivationHandler *) handler)->Release(); 214 } 215 216 int 217 WASAPI_ActivateDevice(_THIS, const SDL_bool isrecovery) 218 { 219 LPCWSTR devid = _this->hidden->devid; 220 Platform::String^ defdevid; 221 222 if (devid == nullptr) { 223 defdevid = _this->iscapture ? MediaDevice::GetDefaultAudioCaptureId(AudioDeviceRole::Default) : MediaDevice::GetDefaultAudioRenderId(AudioDeviceRole::Default); 224 if (defdevid) { 225 devid = defdevid->Data(); 226 } 227 } 228 229 SDL_AtomicSet(&_this->hidden->just_activated, 0); 230 231 ComPtr<SDL_WasapiActivationHandler> handler = Make<SDL_WasapiActivationHandler>(); 232 if (handler == nullptr) { 233 return SDL_SetError("Failed to allocate WASAPI activation handler"); 234 } 235 236 handler.Get()->AddRef(); // we hold a reference after ComPtr destructs on return, causing a Release, and Release ourselves in WASAPI_PlatformDeleteActivationHandler(), etc. 237 handler.Get()->device = _this; 238 _this->hidden->activation_handler = handler.Get(); 239 240 WASAPI_RefDevice(_this); /* completion handler will unref it. */ 241 IActivateAudioInterfaceAsyncOperation *async = nullptr; 242 const HRESULT ret = ActivateAudioInterfaceAsync(devid, __uuidof(IAudioClient), nullptr, handler.Get(), &async); 243 244 if (FAILED(ret) || async == nullptr) { 245 if (async != nullptr) { 246 async->Release(); 247 } 248 handler.Get()->Release(); 249 WASAPI_UnrefDevice(_this); 250 return WIN_SetErrorFromHRESULT("WASAPI can't activate requested audio endpoint", ret); 251 } 252 253 /* Spin until the async operation is complete. 254 * If we don't PrepDevice before leaving this function, the bug list gets LONG: 255 * - device.spec is not filled with the correct information 256 * - The 'obtained' spec will be wrong for ALLOW_CHANGE properties 257 * - SDL_AudioStreams will/will not be allocated at the right time 258 * - SDL_assert(device->callbackspec.size == device->spec.size) will fail 259 * - When the assert is ignored, skipping or a buffer overflow will occur 260 */ 261 while (!SDL_AtomicCAS(&_this->hidden->just_activated, 1, 0)) { 262 SDL_Delay(1); 263 } 264 265 HRESULT activateRes = S_OK; 266 IUnknown *iunknown = nullptr; 267 const HRESULT getActivateRes = async->GetActivateResult(&activateRes, &iunknown); 268 async->Release(); 269 if (FAILED(getActivateRes)) { 270 return WIN_SetErrorFromHRESULT("Failed to get WASAPI activate result", getActivateRes); 271 } else if (FAILED(activateRes)) { 272 return WIN_SetErrorFromHRESULT("Failed to activate WASAPI device", activateRes); 273 } 274 275 iunknown->QueryInterface(IID_PPV_ARGS(&_this->hidden->client)); 276 if (!_this->hidden->client) { 277 return SDL_SetError("Failed to query WASAPI client interface"); 278 } 279 280 if (WASAPI_PrepDevice(_this, isrecovery) == -1) { 281 return -1; 282 } 283 284 return 0; 285 } 286 287 void 288 WASAPI_PlatformThreadInit(_THIS) 289 { 290 // !!! FIXME: set this thread to "Pro Audio" priority. 291 } 292 293 void 294 WASAPI_PlatformThreadDeinit(_THIS) 295 { 296 // !!! FIXME: set this thread to "Pro Audio" priority. 297 } 298 299 #endif // SDL_AUDIO_DRIVER_WASAPI && defined(__WINRT__) 300 301 /* vi: set ts=4 sw=4 expandtab: */