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