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