1 /*****************************************************************************
2 * winstore.c : Windows Multimedia Device API audio output plugin for VLC
3 *****************************************************************************
4 * Copyright (C) 2012 Rémi Denis-Courmont
5 *
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU Lesser General Public License as published by
8 * the Free Software Foundation; either version 2.1 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
19 *****************************************************************************/
20
21 #ifdef HAVE_CONFIG_H
22 # include <config.h>
23 #endif
24
25 #define INITGUID
26 #define COBJMACROS
27 #define CONST_VTABLE
28
29 #include <audiopolicy.h>
30
31 #include <vlc_common.h>
32 #include <vlc_plugin.h>
33 #include <vlc_aout.h>
34 #include <vlc_charset.h> // ToWide
35 #include <vlc_modules.h>
36 #include "audio_output/mmdevice.h"
37
38 #include <audioclient.h>
39 #include <mmdeviceapi.h>
40
41 DEFINE_GUID (GUID_VLC_AUD_OUT, 0x4533f59d, 0x59ee, 0x00c6,
42 0xad, 0xb2, 0xc6, 0x8b, 0x50, 0x1a, 0x66, 0x55);
43
EnterMTA(void)44 static void EnterMTA(void)
45 {
46 HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
47 if (unlikely(FAILED(hr)))
48 abort();
49 }
50
LeaveMTA(void)51 static void LeaveMTA(void)
52 {
53 CoUninitialize();
54 }
55
56 struct aout_sys_t
57 {
58 aout_stream_t *stream; /**< Underlying audio output stream */
59 module_t *module;
60 IAudioClient *client;
61 wchar_t* acquired_device;
62 wchar_t* requested_device;
63 wchar_t* default_device; // read once on open
64
65 float gain;
66
67 // IActivateAudioInterfaceCompletionHandler interface
68 IActivateAudioInterfaceCompletionHandler client_locator;
69 vlc_sem_t async_completed;
70 LONG refs;
71 CRITICAL_SECTION lock;
72 };
73
74
75 /* MMDeviceLocator IUnknown methods */
MMDeviceLocator_AddRef(IActivateAudioInterfaceCompletionHandler * This)76 static STDMETHODIMP_(ULONG) MMDeviceLocator_AddRef(IActivateAudioInterfaceCompletionHandler *This)
77 {
78 aout_sys_t *sys = container_of(This, aout_sys_t, client_locator);
79 return InterlockedIncrement(&sys->refs);
80 }
81
MMDeviceLocator_Release(IActivateAudioInterfaceCompletionHandler * This)82 static STDMETHODIMP_(ULONG) MMDeviceLocator_Release(IActivateAudioInterfaceCompletionHandler *This)
83 {
84 aout_sys_t *sys = container_of(This, aout_sys_t, client_locator);
85 return InterlockedDecrement(&sys->refs);
86 }
87
MMDeviceLocator_QueryInterface(IActivateAudioInterfaceCompletionHandler * This,REFIID riid,void ** ppv)88 static STDMETHODIMP MMDeviceLocator_QueryInterface(IActivateAudioInterfaceCompletionHandler *This,
89 REFIID riid, void **ppv)
90 {
91 if( IsEqualIID(riid, &IID_IUnknown) ||
92 IsEqualIID(riid, &IID_IActivateAudioInterfaceCompletionHandler) )
93 {
94 MMDeviceLocator_AddRef(This);
95 *ppv = This;
96 return S_OK;
97 }
98 *ppv = NULL;
99 if( IsEqualIID(riid, &IID_IAgileObject) )
100 {
101 return S_OK;
102 }
103 return ResultFromScode( E_NOINTERFACE );
104 }
105
106 /* MMDeviceLocator IActivateAudioInterfaceCompletionHandler methods */
MMDeviceLocator_ActivateCompleted(IActivateAudioInterfaceCompletionHandler * This,IActivateAudioInterfaceAsyncOperation * operation)107 static HRESULT MMDeviceLocator_ActivateCompleted(IActivateAudioInterfaceCompletionHandler *This,
108 IActivateAudioInterfaceAsyncOperation *operation)
109 {
110 (void)operation;
111 aout_sys_t *sys = container_of(This, aout_sys_t, client_locator);
112 vlc_sem_post( &sys->async_completed );
113 return S_OK;
114 }
115
116 /* MMDeviceLocator vtable */
117 static const struct IActivateAudioInterfaceCompletionHandlerVtbl MMDeviceLocator_vtable =
118 {
119 MMDeviceLocator_QueryInterface,
120 MMDeviceLocator_AddRef,
121 MMDeviceLocator_Release,
122
123 MMDeviceLocator_ActivateCompleted,
124 };
125
WaitForAudioClient(audio_output_t * aout)126 static void WaitForAudioClient(audio_output_t *aout)
127 {
128 aout_sys_t *sys = aout->sys;
129 IActivateAudioInterfaceAsyncOperation* asyncOp = NULL;
130
131 const wchar_t* devId = sys->requested_device ? sys->requested_device : sys->default_device;
132
133 assert(sys->refs == 0);
134 sys->refs = 0;
135 assert(sys->client == NULL);
136 sys->client = NULL;
137 free(sys->acquired_device);
138 sys->acquired_device = NULL;
139 ActivateAudioInterfaceAsync(devId, &IID_IAudioClient, NULL, &sys->client_locator, &asyncOp);
140
141 vlc_sem_wait( &sys->async_completed );
142
143 if (asyncOp)
144 {
145 HRESULT hr;
146 HRESULT hrActivateResult;
147 IUnknown *audioInterface;
148
149 hr = IActivateAudioInterfaceAsyncOperation_GetActivateResult(asyncOp, &hrActivateResult, &audioInterface);
150 IActivateAudioInterfaceAsyncOperation_Release(asyncOp);
151 if (unlikely(FAILED(hr)))
152 msg_Dbg(aout, "Failed to get the activation result. (hr=0x%lX)", hr);
153 else if (FAILED(hrActivateResult))
154 msg_Dbg(aout, "Failed to activate the device. (hr=0x%lX)", hr);
155 else if (unlikely(audioInterface == NULL))
156 msg_Dbg(aout, "Failed to get the device instance.");
157 else
158 {
159 hr = IUnknown_QueryInterface(audioInterface, &IID_IAudioClient, (void**)&sys->client);
160 IUnknown_Release(audioInterface);
161 if (unlikely(FAILED(hr)))
162 msg_Warn(aout, "The received interface is not a IAudioClient. (hr=0x%lX)", hr);
163 else
164 {
165 sys->acquired_device = wcsdup(devId);
166
167 char *report = FromWide(devId);
168 if (likely(report))
169 {
170 aout_DeviceReport(aout, report);
171 free(report);
172 }
173
174 IAudioClient2 *audioClient2;
175 if (SUCCEEDED(IAudioClient_QueryInterface(sys->client, &IID_IAudioClient2, (void**)&audioClient2))
176 && audioClient2)
177 {
178 // "BackgroundCapableMedia" does not work in UWP
179 AudioClientProperties props = (AudioClientProperties) {
180 .cbSize = sizeof(props),
181 .bIsOffload = FALSE,
182 .eCategory = AudioCategory_Movie,
183 .Options = AUDCLNT_STREAMOPTIONS_NONE
184 };
185 if (FAILED(IAudioClient2_SetClientProperties(audioClient2, &props))) {
186 msg_Dbg(aout, "Failed to set audio client properties");
187 }
188 IAudioClient2_Release(audioClient2);
189 }
190 }
191 }
192 }
193 }
194
SetRequestedDevice(audio_output_t * aout,wchar_t * id)195 static bool SetRequestedDevice(audio_output_t *aout, wchar_t *id)
196 {
197 aout_sys_t* sys = aout->sys;
198 if (sys->requested_device != id)
199 {
200 if (sys->requested_device != sys->default_device)
201 free(sys->requested_device);
202 sys->requested_device = id;
203 return true;
204 }
205 return false;
206 }
207
DeviceRequestLocked(audio_output_t * aout)208 static int DeviceRequestLocked(audio_output_t *aout)
209 {
210 aout_sys_t *sys = aout->sys;
211 assert(sys->requested_device);
212
213 WaitForAudioClient(aout);
214
215 if (sys->stream != NULL && sys->client != NULL)
216 /* Request restart of stream with the new device */
217 aout_RestartRequest(aout, AOUT_RESTART_OUTPUT);
218 return (sys->client != NULL) ? 0 : -1;
219 }
220
DeviceRestartLocked(audio_output_t * aout)221 static int DeviceRestartLocked(audio_output_t *aout)
222 {
223 aout_sys_t *sys = aout->sys;
224
225 if (sys->client)
226 {
227 assert(sys->acquired_device);
228 free(sys->acquired_device);
229 sys->acquired_device = NULL;
230 IAudioClient_Release(sys->client);
231 sys->client = NULL;
232 }
233
234 return DeviceRequestLocked(aout);
235 }
236
DeviceSelectLocked(audio_output_t * aout,const char * id)237 static int DeviceSelectLocked(audio_output_t *aout, const char* id)
238 {
239 aout_sys_t *sys = aout->sys;
240 bool changed;
241 if( id == NULL )
242 {
243 changed = SetRequestedDevice(aout, sys->default_device);
244 }
245 else
246 {
247 wchar_t *requested_device = ToWide(id);
248 if (unlikely(requested_device == NULL))
249 return VLC_ENOMEM;
250 changed = SetRequestedDevice(aout, requested_device);
251 }
252 if (!changed)
253 return VLC_EGENERIC;
254 return DeviceRestartLocked(aout);
255 }
256
DeviceSelect(audio_output_t * aout,const char * id)257 static int DeviceSelect(audio_output_t *aout, const char* id)
258 {
259 aout_sys_t *sys = aout->sys;
260 EnterCriticalSection(&sys->lock);
261 int ret = DeviceSelectLocked(aout, id);
262 LeaveCriticalSection(&sys->lock);
263 return ret;
264 }
265
ResetInvalidatedClient(audio_output_t * aout,HRESULT hr)266 static void ResetInvalidatedClient(audio_output_t *aout, HRESULT hr)
267 {
268 /* Select the default device (and restart) on unplug */
269 if (unlikely(hr == AUDCLNT_E_DEVICE_INVALIDATED ||
270 hr == AUDCLNT_E_RESOURCES_INVALIDATED))
271 {
272 // Select the default device (and restart) on unplug
273 DeviceSelect(aout, NULL);
274 }
275 }
276
VolumeSet(audio_output_t * aout,float vol)277 static int VolumeSet(audio_output_t *aout, float vol)
278 {
279 aout_sys_t *sys = aout->sys;
280 if( unlikely( sys->client == NULL ) )
281 return VLC_EGENERIC;
282 HRESULT hr;
283 ISimpleAudioVolume *pc_AudioVolume = NULL;
284
285 float linear_vol = vol * vol * vol; /* ISimpleAudioVolume is tapered linearly. */
286
287 if (linear_vol > 1.f)
288 {
289 sys->gain = linear_vol;
290 linear_vol = 1.f;
291 }
292 else
293 sys->gain = 1.f;
294
295 aout_GainRequest(aout, sys->gain);
296
297 hr = IAudioClient_GetService(sys->client, &IID_ISimpleAudioVolume, &pc_AudioVolume);
298 if (FAILED(hr))
299 {
300 msg_Err(aout, "cannot get volume service (error 0x%lx)", hr);
301 goto done;
302 }
303
304 hr = ISimpleAudioVolume_SetMasterVolume(pc_AudioVolume, linear_vol, NULL);
305 if (FAILED(hr))
306 {
307 msg_Err(aout, "cannot set volume (error 0x%lx)", hr);
308 goto done;
309 }
310
311 aout_VolumeReport(aout, vol);
312
313 done:
314 if (pc_AudioVolume)
315 ISimpleAudioVolume_Release(pc_AudioVolume);
316
317 return SUCCEEDED(hr) ? 0 : -1;
318 }
319
MuteSet(audio_output_t * aout,bool mute)320 static int MuteSet(audio_output_t *aout, bool mute)
321 {
322 aout_sys_t *sys = aout->sys;
323 if( unlikely( sys->client == NULL ) )
324 return VLC_EGENERIC;
325 HRESULT hr;
326 ISimpleAudioVolume *pc_AudioVolume = NULL;
327
328 hr = IAudioClient_GetService(sys->client, &IID_ISimpleAudioVolume, &pc_AudioVolume);
329 if (FAILED(hr))
330 {
331 msg_Err(aout, "cannot get volume service (error 0x%lx)", hr);
332 goto done;
333 }
334
335 hr = ISimpleAudioVolume_SetMute(pc_AudioVolume, mute, NULL);
336 if (FAILED(hr))
337 {
338 msg_Err(aout, "cannot set mute (error 0x%lx)", hr);
339 goto done;
340 }
341
342 float vol;
343 hr = ISimpleAudioVolume_GetMasterVolume(pc_AudioVolume, &vol);
344 if (FAILED(hr))
345 {
346 msg_Err(aout, "cannot get volume (error 0x%lX)", hr);
347 goto done;
348 }
349
350 aout_VolumeReport(aout, cbrtf(vol * sys->gain));
351 aout_MuteReport(aout, mute);
352
353 done:
354 if (pc_AudioVolume)
355 ISimpleAudioVolume_Release(pc_AudioVolume);
356
357 return SUCCEEDED(hr) ? 0 : -1;
358 }
359
TimeGet(audio_output_t * aout,mtime_t * restrict delay)360 static int TimeGet(audio_output_t *aout, mtime_t *restrict delay)
361 {
362 aout_sys_t *sys = aout->sys;
363 if( unlikely( sys->client == NULL ) )
364 return VLC_EGENERIC;
365 HRESULT hr;
366
367 EnterMTA();
368 hr = aout_stream_TimeGet(sys->stream, delay);
369 LeaveMTA();
370
371 return SUCCEEDED(hr) ? 0 : -1;
372 }
373
Play(audio_output_t * aout,block_t * block)374 static void Play(audio_output_t *aout, block_t *block)
375 {
376 aout_sys_t *sys = aout->sys;
377 if( unlikely( sys->client == NULL ) )
378 return;
379
380 EnterMTA();
381 HRESULT hr = aout_stream_Play(sys->stream, block);
382 LeaveMTA();
383
384 ResetInvalidatedClient(aout, hr);
385 }
386
Pause(audio_output_t * aout,bool paused,mtime_t date)387 static void Pause(audio_output_t *aout, bool paused, mtime_t date)
388 {
389 aout_sys_t *sys = aout->sys;
390 if( unlikely( sys->client == NULL ) )
391 return;
392
393 EnterMTA();
394 HRESULT hr = aout_stream_Pause(sys->stream, paused);
395 LeaveMTA();
396
397 (void) date;
398 ResetInvalidatedClient(aout, hr);
399 }
400
Flush(audio_output_t * aout,bool wait)401 static void Flush(audio_output_t *aout, bool wait)
402 {
403 aout_sys_t *sys = aout->sys;
404 if( unlikely( sys->client == NULL ) )
405 return;
406
407 EnterMTA();
408 HRESULT hr = aout_stream_Flush(sys->stream, wait);
409 LeaveMTA();
410
411 ResetInvalidatedClient(aout, hr);
412 }
413
ActivateDevice(void * opaque,REFIID iid,PROPVARIANT * actparms,void ** restrict pv)414 static HRESULT ActivateDevice(void *opaque, REFIID iid, PROPVARIANT *actparms,
415 void **restrict pv)
416 {
417 IAudioClient *client = opaque;
418
419 if (!IsEqualIID(iid, &IID_IAudioClient))
420 return E_NOINTERFACE;
421 if (actparms != NULL || client == NULL )
422 return E_INVALIDARG;
423
424 IAudioClient_AddRef(client); // as would IMMDevice_Activate do
425 *pv = opaque;
426
427 return S_OK;
428 }
429
aout_stream_Start(void * func,va_list ap)430 static int aout_stream_Start(void *func, va_list ap)
431 {
432 aout_stream_start_t start = func;
433 aout_stream_t *s = va_arg(ap, aout_stream_t *);
434 audio_sample_format_t *fmt = va_arg(ap, audio_sample_format_t *);
435 HRESULT *hr = va_arg(ap, HRESULT *);
436
437 *hr = start(s, fmt, &GUID_VLC_AUD_OUT);
438 return SUCCEEDED(*hr) ? VLC_SUCCESS : VLC_EGENERIC;
439 }
440
aout_stream_Stop(void * func,va_list ap)441 static void aout_stream_Stop(void *func, va_list ap)
442 {
443 aout_stream_stop_t stop = func;
444 aout_stream_t *s = va_arg(ap, aout_stream_t *);
445
446 stop(s);
447 }
448
Start(audio_output_t * aout,audio_sample_format_t * restrict fmt)449 static int Start(audio_output_t *aout, audio_sample_format_t *restrict fmt)
450 {
451 aout_sys_t *sys = aout->sys;
452 HRESULT hr;
453
454 aout_stream_t *s = vlc_object_create(aout, sizeof (*s));
455 if (unlikely(s == NULL))
456 return -1;
457
458 // Load the "out stream" for the requested device
459 EnterMTA();
460 EnterCriticalSection(&sys->lock);
461
462 if (sys->requested_device != NULL)
463 {
464 if (sys->acquired_device == NULL || wcscmp(sys->acquired_device, sys->requested_device))
465 {
466 // we have a pending request for a new device
467 DeviceRestartLocked(aout);
468 if (sys->client == NULL)
469 {
470 LeaveCriticalSection(&sys->lock);
471 LeaveMTA();
472 vlc_object_release(s);
473 return -1;
474 }
475 }
476 }
477
478 s->owner.activate = ActivateDevice;
479 for (;;)
480 {
481 s->owner.device = sys->client;
482 sys->module = vlc_module_load(s, "aout stream", NULL, false,
483 aout_stream_Start, s, fmt, &hr);
484
485 int ret = -1;
486 if (hr == AUDCLNT_E_DEVICE_INVALIDATED)
487 {
488 // the requested device is not usable, try the default device
489 ret = DeviceSelectLocked(aout, NULL);
490 }
491 else if (hr == AUDCLNT_E_ALREADY_INITIALIZED)
492 {
493 /* From MSDN: "If the initial call to Initialize fails, subsequent
494 * Initialize calls might fail and return error code
495 * E_ALREADY_INITIALIZED, even though the interface has not been
496 * initialized. If this occurs, release the IAudioClient interface
497 * and obtain a new IAudioClient interface from the MMDevice API
498 * before calling Initialize again."
499 *
500 * Therefore, request to MMThread the same device and try again. */
501
502 ret = DeviceRestartLocked(aout);
503 }
504 if (ret != VLC_SUCCESS)
505 break;
506
507 if (sys->client == NULL || sys->module != NULL)
508 break;
509 }
510
511 LeaveCriticalSection(&sys->lock);
512 LeaveMTA();
513
514 if (sys->module == NULL)
515 {
516 vlc_object_release(s);
517 return -1;
518 }
519
520 if (sys->client)
521 {
522 // the requested device has been used, reset it
523 // we keep the corresponding sys->client until a new request is started
524 SetRequestedDevice(aout, NULL);
525 }
526
527 assert (sys->stream == NULL);
528 sys->stream = s;
529 return 0;
530 }
531
Stop(audio_output_t * aout)532 static void Stop(audio_output_t *aout)
533 {
534 aout_sys_t *sys = aout->sys;
535
536 assert (sys->stream != NULL);
537
538 EnterMTA();
539 vlc_module_unload(sys->stream, sys->module, aout_stream_Stop, sys->stream);
540 LeaveMTA();
541
542 vlc_object_release(sys->stream);
543 sys->stream = NULL;
544 }
545
Open(vlc_object_t * obj)546 static int Open(vlc_object_t *obj)
547 {
548 audio_output_t *aout = (audio_output_t *)obj;
549
550 aout_sys_t *sys = malloc(sizeof (*sys));
551 if (unlikely(sys == NULL))
552 return VLC_ENOMEM;
553
554 if (unlikely(FAILED(StringFromIID(&DEVINTERFACE_AUDIO_RENDER, &sys->default_device))))
555 {
556 msg_Dbg(obj, "Failed to get the default renderer string");
557 free(sys);
558 return VLC_EGENERIC;
559 }
560
561 char *psz_default = FromWide(sys->default_device);
562 if (likely(psz_default != NULL))
563 {
564 aout_HotplugReport(aout, psz_default, _("Default"));
565 free(psz_default);
566 }
567
568 InitializeCriticalSection(&sys->lock);
569
570 vlc_sem_init(&sys->async_completed, 0);
571 sys->refs = 0;
572 sys->requested_device = sys->default_device;
573 sys->acquired_device = NULL;
574 sys->client_locator = (IActivateAudioInterfaceCompletionHandler) { &MMDeviceLocator_vtable };
575 sys->gain = 1.f;
576
577 aout->sys = sys;
578 sys->stream = NULL;
579 sys->client = NULL;
580 aout->start = Start;
581 aout->stop = Stop;
582 aout->time_get = TimeGet;
583 aout->volume_set = VolumeSet;
584 aout->mute_set = MuteSet;
585 aout->play = Play;
586 aout->pause = Pause;
587 aout->flush = Flush;
588 aout->device_select = DeviceSelect;
589 return VLC_SUCCESS;
590 }
591
Close(vlc_object_t * obj)592 static void Close(vlc_object_t *obj)
593 {
594 audio_output_t *aout = (audio_output_t *)obj;
595 aout_sys_t *sys = aout->sys;
596
597 if(sys->client != NULL)
598 IAudioClient_Release(sys->client);
599
600 assert(sys->refs == 0);
601
602 free(sys->acquired_device);
603 if (sys->requested_device != sys->default_device)
604 free(sys->requested_device);
605 CoTaskMemFree(sys->default_device);
606 DeleteCriticalSection(&sys->lock);
607
608 free(sys);
609 }
610
611 vlc_module_begin()
612 set_shortname("winstore")
613 set_description("Windows Store audio output")
614 set_capability("audio output", 0)
615 set_category(CAT_AUDIO)
616 set_subcategory(SUBCAT_AUDIO_AOUT)
617 add_shortcut("wasapi")
618 set_callbacks(Open, Close)
619 vlc_module_end()
620