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