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