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
37extern "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
53using namespace Windows::Devices::Enumeration;
54using namespace Windows::Media::Devices;
55using namespace Windows::Foundation;
56using namespace Microsoft::WRL;
57
58class SDL_WasapiDeviceEventHandler
59{
60public:
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
69private:
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
78SDL_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
97SDL_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
114void
115SDL_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
125void
126SDL_WasapiDeviceEventHandler::OnDeviceRemoved(DeviceWatcher^ sender, DeviceInformationUpdate^ info)
127{
128 SDL_assert(sender == this->watcher);
129 WASAPI_RemoveDevice(this->iscapture, info->Id->Data());
130}
131
132void
133SDL_WasapiDeviceEventHandler::OnDeviceUpdated(DeviceWatcher^ sender, DeviceInformationUpdate^ args)
134{
135 SDL_assert(sender == this->watcher);
136}
137
138void
139SDL_WasapiDeviceEventHandler::OnDefaultRenderDeviceChanged(Platform::Object^ sender, DefaultAudioRenderDeviceChangedEventArgs^ args)
140{
141 SDL_assert(this->iscapture);
143}
144
145void
146SDL_WasapiDeviceEventHandler::OnDefaultCaptureDeviceChanged(Platform::Object^ sender, DefaultAudioCaptureDeviceChangedEventArgs^ args)
147{
148 SDL_assert(!this->iscapture);
150}
151
152
153static SDL_WasapiDeviceEventHandler *playback_device_event_handler;
154static SDL_WasapiDeviceEventHandler *capture_device_event_handler;
155
156int WASAPI_PlatformInit(void)
157{
158 return 0;
159}
160
161void 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
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
178struct SDL_WasapiActivationHandler : public RuntimeClass< RuntimeClassFlags< ClassicCom >, FtmBase, IActivateAudioInterfaceCompletionHandler >
179{
180 SDL_WasapiActivationHandler() : device(nullptr) {}
181 STDMETHOD(ActivateCompleted)(IActivateAudioInterfaceAsyncOperation *operation);
183};
184
185HRESULT
186SDL_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
194void
196{
197 ((SDL_WasapiActivationHandler *) handler)->Release();
198}
199
200int
201WASAPI_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
271void
273{
274 // !!! FIXME: set this thread to "Pro Audio" priority.
275}
276
277void
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: */
#define _THIS
#define SDL_assert(condition)
Definition: SDL_assert.h:169
#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_free
#define SDL_Delay
#define SDL_AtomicAdd
GLenum GLint ref
SDL_bool
Definition: SDL_stdinc.h:162
@ SDL_TRUE
Definition: SDL_stdinc.h:164
@ SDL_FALSE
Definition: SDL_stdinc.h:163
static SDL_VideoDevice * _this
Definition: SDL_video.c:118
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