SDL  2.0
SDL_wasapi_winrt.cpp
Go to the documentation of this file.
1 /*
2  Simple DirectMedia Layer
3  Copyright (C) 1997-2018 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  HRESULT result = S_OK;
189  IUnknown *iunknown = nullptr;
190  const HRESULT ret = async->GetActivateResult(&result, &iunknown);
191 
192  if (SUCCEEDED(ret) && SUCCEEDED(result)) {
193  iunknown->QueryInterface(IID_PPV_ARGS(&device->hidden->client));
194  if (device->hidden->client) {
195  // 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.
196  SDL_AtomicSet(&device->hidden->just_activated, 1);
197  }
198  }
199 
201 
202  return S_OK;
203 }
204 
205 void
207 {
208  ((SDL_WasapiActivationHandler *) handler)->Release();
209 }
210 
211 int
212 WASAPI_ActivateDevice(_THIS, const SDL_bool isrecovery)
213 {
214  LPCWSTR devid = _this->hidden->devid;
215  Platform::String^ defdevid;
216 
217  if (devid == nullptr) {
218  defdevid = _this->iscapture ? MediaDevice::GetDefaultAudioCaptureId(AudioDeviceRole::Default) : MediaDevice::GetDefaultAudioRenderId(AudioDeviceRole::Default);
219  if (defdevid) {
220  devid = defdevid->Data();
221  }
222  }
223 
224  SDL_AtomicSet(&_this->hidden->just_activated, 0);
225 
226  ComPtr<SDL_WasapiActivationHandler> handler = Make<SDL_WasapiActivationHandler>();
227  if (handler == nullptr) {
228  return SDL_SetError("Failed to allocate WASAPI activation handler");
229  }
230 
231  handler.Get()->AddRef(); // we hold a reference after ComPtr destructs on return, causing a Release, and Release ourselves in WASAPI_PlatformDeleteActivationHandler(), etc.
232  handler.Get()->device = _this;
233  _this->hidden->activation_handler = handler.Get();
234 
235  WASAPI_RefDevice(_this); /* completion handler will unref it. */
236  IActivateAudioInterfaceAsyncOperation *async = nullptr;
237  const HRESULT ret = ActivateAudioInterfaceAsync(devid, __uuidof(IAudioClient), nullptr, handler.Get(), &async);
238 
239  if (async != nullptr) {
240  async->Release();
241  }
242 
243  if (FAILED(ret)) {
244  handler.Get()->Release();
246  return WIN_SetErrorFromHRESULT("WASAPI can't activate requested audio endpoint", ret);
247  }
248 
249  return 0;
250 }
251 
252 void
254 {
255  if (SDL_AtomicCAS(&_this->hidden->just_activated, 1, 0)) {
256  if (WASAPI_PrepDevice(_this, SDL_TRUE) == -1) {
258  }
259  }
260 }
261 
262 void
264 {
265  // !!! FIXME: set this thread to "Pro Audio" priority.
266 }
267 
268 void
270 {
271  // !!! FIXME: set this thread to "Pro Audio" priority.
272 }
273 
274 #endif // SDL_AUDIO_DRIVER_WASAPI && defined(__WINRT__)
275 
276 /* vi: set ts=4 sw=4 expandtab: */
void WASAPI_AddDevice(const SDL_bool iscapture, const char *devname, LPCWSTR devid)
GLuint64EXT * result
void WASAPI_UnrefDevice(_THIS)
void WASAPI_BeginLoopIteration(_THIS)
#define SDL_AtomicCAS
int WIN_SetErrorFromHRESULT(const char *prefix, HRESULT hr)
GLenum GLint ref
void SDL_OpenedAudioDeviceDisconnected(SDL_AudioDevice *device)
Definition: SDL_audio.c:449
int WASAPI_ActivateDevice(_THIS, const SDL_bool isrecovery)
void WASAPI_PlatformThreadDeinit(_THIS)
void WASAPI_PlatformDeinit(void)
int WASAPI_PrepDevice(_THIS, const SDL_bool updatestream)
static SDL_VideoDevice * _this
Definition: SDL_video.c:121
static SDL_AudioDeviceID device
Definition: loopwave.c:37
#define FAILED(x)
Definition: SDL_directx.h:54
void WASAPI_RefDevice(_THIS)
void WASAPI_EnumerateEndpoints(void)
#define _THIS
#define SDL_free
#define WIN_StringToUTF8(S)
Definition: SDL_windows.h:46
void WASAPI_RemoveDevice(const SDL_bool iscapture, LPCWSTR devid)
#define S_OK
Definition: SDL_directx.h:47
int WASAPI_PlatformInit(void)
SDL_atomic_t WASAPI_DefaultPlaybackGeneration
#define SDL_assert(condition)
Definition: SDL_assert.h:169
#define SDL_AtomicAdd
SDL_bool
Definition: SDL_stdinc.h:139
#define SUCCEEDED(x)
Definition: SDL_directx.h:51
#define SDL_SetError
SDL_atomic_t WASAPI_DefaultCaptureGeneration
void WASAPI_PlatformDeleteActivationHandler(void *handler)
#define SDL_AtomicSet
void WASAPI_PlatformThreadInit(_THIS)