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 #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);
157 }
158 
159 void
160 SDL_WasapiDeviceEventHandler::OnDefaultCaptureDeviceChanged(Platform::Object^ sender, DefaultAudioCaptureDeviceChangedEventArgs^ args)
161 {
162  SDL_assert(!this->iscapture);
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);
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);
207  return S_OK;
208 }
209 
210 void
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();
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
289 {
290  // !!! FIXME: set this thread to "Pro Audio" priority.
291 }
292 
293 void
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: */
#define _THIS
#define SDL_assert(condition)
Definition: SDL_assert.h:171
#define S_OK
Definition: SDL_directx.h:47
#define FAILED(x)
Definition: SDL_directx.h:54
#define SDL_AtomicSet
#define SDL_SetError
#define SDL_AtomicCAS
#define SDL_SemPost
#define SDL_SemWait
#define SDL_DestroySemaphore
#define SDL_free
#define SDL_CreateSemaphore
#define SDL_Delay
#define SDL_AtomicAdd
GLenum GLint ref
SDL_bool
Definition: SDL_stdinc.h:168
@ SDL_TRUE
Definition: SDL_stdinc.h:170
@ SDL_FALSE
Definition: SDL_stdinc.h:169
static SDL_VideoDevice * _this
Definition: SDL_video.c:126
void WASAPI_PlatformDeleteActivationHandler(void *handler)
void WASAPI_RemoveDevice(const SDL_bool iscapture, LPCWSTR devid)
SDL_atomic_t WASAPI_DefaultPlaybackGeneration
void WASAPI_PlatformThreadDeinit(_THIS)
int WASAPI_ActivateDevice(_THIS, const SDL_bool isrecovery)
void WASAPI_RefDevice(_THIS)
SDL_atomic_t WASAPI_DefaultCaptureGeneration
void WASAPI_PlatformThreadInit(_THIS)
int WASAPI_PlatformInit(void)
int WASAPI_PrepDevice(_THIS, const SDL_bool updatestream)
void WASAPI_EnumerateEndpoints(void)
void WASAPI_PlatformDeinit(void)
void WASAPI_AddDevice(const SDL_bool iscapture, const char *devname, LPCWSTR devid)
void WASAPI_UnrefDevice(_THIS)
#define WIN_StringToUTF8(S)
Definition: SDL_windows.h:46
int WIN_SetErrorFromHRESULT(const char *prefix, HRESULT hr)
static SDL_AudioDeviceID device
Definition: loopwave.c:37