input: add an input_item_t arg to input_CreateFilename()
[vlc.git] / modules / audio_output / mmdevice.c
blob3aded2b9d3a218f4b545b93359509e19327f876a
1 /*****************************************************************************
2 * mmdevice.c : Windows Multimedia Device API audio output plugin for VLC
3 *****************************************************************************
4 * Copyright (C) 2012-2017 RĂ©mi Denis-Courmont
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.
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.
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 *****************************************************************************/
21 #ifdef HAVE_CONFIG_H
22 # include <config.h>
23 #endif
25 #define INITGUID
26 #define COBJMACROS
27 #define CONST_VTABLE
29 #include <stdlib.h>
30 #include <math.h>
31 #include <assert.h>
32 #include <audiopolicy.h>
33 #include <mmdeviceapi.h>
34 #include <endpointvolume.h>
36 DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd,
37 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14);
39 #include <vlc_common.h>
40 #include <vlc_atomic.h>
41 #include <vlc_plugin.h>
42 #include <vlc_aout.h>
43 #include <vlc_charset.h>
44 #include <vlc_modules.h>
45 #include "audio_output/mmdevice.h"
47 DEFINE_GUID (GUID_VLC_AUD_OUT, 0x4533f59d, 0x59ee, 0x00c6,
48 0xad, 0xb2, 0xc6, 0x8b, 0x50, 0x1a, 0x66, 0x55);
50 static int TryEnterMTA(vlc_object_t *obj)
52 HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
53 if (unlikely(FAILED(hr)))
55 msg_Err (obj, "cannot initialize COM (error 0x%lx)", hr);
56 return -1;
58 return 0;
60 #define TryEnterMTA(o) TryEnterMTA(VLC_OBJECT(o))
62 static void EnterMTA(void)
64 HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
65 if (unlikely(FAILED(hr)))
66 abort();
69 static void LeaveMTA(void)
71 CoUninitialize();
74 static wchar_t default_device[1] = L"";
75 static char default_device_b[1] = "";
77 typedef struct
79 aout_stream_t *stream; /**< Underlying audio output stream */
80 module_t *module;
81 audio_output_t *aout;
82 IMMDeviceEnumerator *it; /**< Device enumerator, NULL when exiting */
83 IMMDevice *dev; /**< Selected output device, NULL if none */
85 struct IMMNotificationClient device_events;
86 struct IAudioSessionEvents session_events;
87 struct IAudioVolumeDuckNotification duck;
89 LONG refs;
90 unsigned ducks;
91 float gain; /**< Current software gain volume */
93 wchar_t *requested_device; /**< Requested device identifier, NULL if none */
94 float requested_volume; /**< Requested volume, negative if none */
95 signed char requested_mute; /**< Requested mute, negative if none */
96 wchar_t *acquired_device; /**< Acquired device identifier, NULL if none */
97 bool request_device_restart;
98 CRITICAL_SECTION lock;
99 CONDITION_VARIABLE work;
100 CONDITION_VARIABLE ready;
101 vlc_thread_t thread; /**< Thread for audio session control */
102 } aout_sys_t;
104 /* NOTE: The Core Audio API documentation totally fails to specify the thread
105 * safety (or lack thereof) of the interfaces. This code takes the most
106 * restrictive assumption: no thread safety. The background thread (MMThread)
107 * only runs at specified times, namely between the device_ready and
108 * device_changed events (effectively a thread synchronization barrier, but
109 * only Windows 8 natively provides such a primitive).
111 * The audio output owner (i.e. the audio output core) is responsible for
112 * serializing callbacks. This code only needs to be concerned with
113 * synchronization between the set of audio output callbacks, MMThread()
114 * and (trivially) the device and session notifications. */
116 static int DeviceSelect(audio_output_t *, const char *);
117 static int vlc_FromHR(audio_output_t *aout, HRESULT hr)
119 /* Select the default device (and restart) on unplug */
120 if (unlikely(hr == AUDCLNT_E_DEVICE_INVALIDATED ||
121 hr == AUDCLNT_E_RESOURCES_INVALIDATED))
122 DeviceSelect(aout, NULL);
123 return SUCCEEDED(hr) ? 0 : -1;
126 /*** VLC audio output callbacks ***/
127 static int TimeGet(audio_output_t *aout, vlc_tick_t *restrict delay)
129 aout_sys_t *sys = aout->sys;
130 HRESULT hr;
132 EnterMTA();
133 hr = aout_stream_TimeGet(sys->stream, delay);
134 LeaveMTA();
136 return SUCCEEDED(hr) ? 0 : -1;
139 static void Play(audio_output_t *aout, block_t *block, vlc_tick_t date)
141 aout_sys_t *sys = aout->sys;
142 HRESULT hr;
144 EnterMTA();
145 hr = aout_stream_Play(sys->stream, block);
146 LeaveMTA();
148 vlc_FromHR(aout, hr);
149 (void) date;
152 static void Pause(audio_output_t *aout, bool paused, vlc_tick_t date)
154 aout_sys_t *sys = aout->sys;
155 HRESULT hr;
157 EnterMTA();
158 hr = aout_stream_Pause(sys->stream, paused);
159 LeaveMTA();
161 vlc_FromHR(aout, hr);
162 (void) date;
165 static void Flush(audio_output_t *aout, bool wait)
167 aout_sys_t *sys = aout->sys;
168 HRESULT hr;
170 EnterMTA();
171 hr = aout_stream_Flush(sys->stream, wait);
172 LeaveMTA();
174 vlc_FromHR(aout, hr);
177 static int VolumeSetLocked(audio_output_t *aout, float vol)
179 aout_sys_t *sys = aout->sys;
180 float gain = 1.f;
182 vol = vol * vol * vol; /* ISimpleAudioVolume is tapered linearly. */
184 if (vol > 1.f)
186 gain = vol;
187 vol = 1.f;
190 aout_GainRequest(aout, gain);
192 sys->gain = gain;
193 sys->requested_volume = vol;
194 return 0;
197 static int VolumeSet(audio_output_t *aout, float vol)
199 aout_sys_t *sys = aout->sys;
201 EnterCriticalSection(&sys->lock);
202 int ret = VolumeSetLocked(aout, vol);
203 WakeConditionVariable(&sys->work);
204 LeaveCriticalSection(&sys->lock);
205 return ret;
208 static int MuteSet(audio_output_t *aout, bool mute)
210 aout_sys_t *sys = aout->sys;
212 EnterCriticalSection(&sys->lock);
213 sys->requested_mute = mute;
214 WakeConditionVariable(&sys->work);
215 LeaveCriticalSection(&sys->lock);
216 return 0;
219 /*** Audio session events ***/
220 static STDMETHODIMP
221 vlc_AudioSessionEvents_QueryInterface(IAudioSessionEvents *this, REFIID riid,
222 void **ppv)
224 if (IsEqualIID(riid, &IID_IUnknown)
225 || IsEqualIID(riid, &IID_IAudioSessionEvents))
227 *ppv = this;
228 IUnknown_AddRef(this);
229 return S_OK;
231 else
233 *ppv = NULL;
234 return E_NOINTERFACE;
238 static STDMETHODIMP_(ULONG)
239 vlc_AudioSessionEvents_AddRef(IAudioSessionEvents *this)
241 aout_sys_t *sys = container_of(this, aout_sys_t, session_events);
242 return InterlockedIncrement(&sys->refs);
245 static STDMETHODIMP_(ULONG)
246 vlc_AudioSessionEvents_Release(IAudioSessionEvents *this)
248 aout_sys_t *sys = container_of(this, aout_sys_t, session_events);
249 return InterlockedDecrement(&sys->refs);
252 static STDMETHODIMP
253 vlc_AudioSessionEvents_OnDisplayNameChanged(IAudioSessionEvents *this,
254 LPCWSTR wname, LPCGUID ctx)
256 aout_sys_t *sys = container_of(this, aout_sys_t, session_events);
257 audio_output_t *aout = sys->aout;
259 msg_Dbg(aout, "display name changed: %ls", wname);
260 (void) ctx;
261 return S_OK;
264 static STDMETHODIMP
265 vlc_AudioSessionEvents_OnIconPathChanged(IAudioSessionEvents *this,
266 LPCWSTR wpath, LPCGUID ctx)
268 aout_sys_t *sys = container_of(this, aout_sys_t, session_events);
269 audio_output_t *aout = sys->aout;
271 msg_Dbg(aout, "icon path changed: %ls", wpath);
272 (void) ctx;
273 return S_OK;
276 static STDMETHODIMP
277 vlc_AudioSessionEvents_OnSimpleVolumeChanged(IAudioSessionEvents *this,
278 float vol, BOOL mute,
279 LPCGUID ctx)
281 aout_sys_t *sys = container_of(this, aout_sys_t, session_events);
282 audio_output_t *aout = sys->aout;
284 msg_Dbg(aout, "simple volume changed: %f, muting %sabled", vol,
285 mute ? "en" : "dis");
286 EnterCriticalSection(&sys->lock);
287 WakeConditionVariable(&sys->work); /* implicit state: vol & mute */
288 LeaveCriticalSection(&sys->lock);
289 (void) ctx;
290 return S_OK;
293 static STDMETHODIMP
294 vlc_AudioSessionEvents_OnChannelVolumeChanged(IAudioSessionEvents *this,
295 DWORD count, float *vols,
296 DWORD changed, LPCGUID ctx)
298 aout_sys_t *sys = container_of(this, aout_sys_t, session_events);
299 audio_output_t *aout = sys->aout;
301 if (changed != (DWORD)-1)
302 msg_Dbg(aout, "channel volume %lu of %lu changed: %f", changed, count,
303 vols[changed]);
304 else
305 msg_Dbg(aout, "%lu channels volume changed", count);
307 (void) ctx;
308 return S_OK;
311 static STDMETHODIMP
312 vlc_AudioSessionEvents_OnGroupingParamChanged(IAudioSessionEvents *this,
313 LPCGUID param, LPCGUID ctx)
316 aout_sys_t *sys = container_of(this, aout_sys_t, session_events);
317 audio_output_t *aout = sys->aout;
319 msg_Dbg(aout, "grouping parameter changed");
320 (void) param;
321 (void) ctx;
322 return S_OK;
325 static STDMETHODIMP
326 vlc_AudioSessionEvents_OnStateChanged(IAudioSessionEvents *this,
327 AudioSessionState state)
329 aout_sys_t *sys = container_of(this, aout_sys_t, session_events);
330 audio_output_t *aout = sys->aout;
332 msg_Dbg(aout, "state changed: %d", state);
333 return S_OK;
336 static STDMETHODIMP
337 vlc_AudioSessionEvents_OnSessionDisconnected(IAudioSessionEvents *this,
338 AudioSessionDisconnectReason reason)
340 aout_sys_t *sys = container_of(this, aout_sys_t, session_events);
341 audio_output_t *aout = sys->aout;
343 switch (reason)
345 case DisconnectReasonDeviceRemoval:
346 msg_Warn(aout, "session disconnected: %s", "device removed");
347 break;
348 case DisconnectReasonServerShutdown:
349 msg_Err(aout, "session disconnected: %s", "service stopped");
350 return S_OK;
351 case DisconnectReasonFormatChanged:
352 msg_Warn(aout, "session disconnected: %s", "format changed");
353 break;
354 case DisconnectReasonSessionLogoff:
355 msg_Err(aout, "session disconnected: %s", "user logged off");
356 return S_OK;
357 case DisconnectReasonSessionDisconnected:
358 msg_Err(aout, "session disconnected: %s", "session disconnected");
359 return S_OK;
360 case DisconnectReasonExclusiveModeOverride:
361 msg_Err(aout, "session disconnected: %s", "stream overriden");
362 return S_OK;
363 default:
364 msg_Warn(aout, "session disconnected: unknown reason %d", reason);
365 return S_OK;
367 /* NOTE: audio decoder thread should get invalidated device and restart */
368 return S_OK;
371 static const struct IAudioSessionEventsVtbl vlc_AudioSessionEvents =
373 vlc_AudioSessionEvents_QueryInterface,
374 vlc_AudioSessionEvents_AddRef,
375 vlc_AudioSessionEvents_Release,
377 vlc_AudioSessionEvents_OnDisplayNameChanged,
378 vlc_AudioSessionEvents_OnIconPathChanged,
379 vlc_AudioSessionEvents_OnSimpleVolumeChanged,
380 vlc_AudioSessionEvents_OnChannelVolumeChanged,
381 vlc_AudioSessionEvents_OnGroupingParamChanged,
382 vlc_AudioSessionEvents_OnStateChanged,
383 vlc_AudioSessionEvents_OnSessionDisconnected,
386 static STDMETHODIMP
387 vlc_AudioVolumeDuckNotification_QueryInterface(
388 IAudioVolumeDuckNotification *this, REFIID riid, void **ppv)
390 if (IsEqualIID(riid, &IID_IUnknown)
391 || IsEqualIID(riid, &IID_IAudioVolumeDuckNotification))
393 *ppv = this;
394 IUnknown_AddRef(this);
395 return S_OK;
397 else
399 *ppv = NULL;
400 return E_NOINTERFACE;
404 static STDMETHODIMP_(ULONG)
405 vlc_AudioVolumeDuckNotification_AddRef(IAudioVolumeDuckNotification *this)
407 aout_sys_t *sys = container_of(this, aout_sys_t, duck);
408 return InterlockedIncrement(&sys->refs);
411 static STDMETHODIMP_(ULONG)
412 vlc_AudioVolumeDuckNotification_Release(IAudioVolumeDuckNotification *this)
414 aout_sys_t *sys = container_of(this, aout_sys_t, duck);
415 return InterlockedDecrement(&sys->refs);
418 static STDMETHODIMP
419 vlc_AudioVolumeDuckNotification_OnVolumeDuckNotification(
420 IAudioVolumeDuckNotification *this, LPCWSTR sid, UINT32 count)
422 aout_sys_t *sys = container_of(this, aout_sys_t, duck);
423 audio_output_t *aout = sys->aout;
425 msg_Dbg(aout, "volume ducked by %ls of %u sessions", sid, count);
426 sys->ducks++;
427 aout_PolicyReport(aout, true);
428 return S_OK;
431 static STDMETHODIMP
432 vlc_AudioVolumeDuckNotification_OnVolumeUnduckNotification(
433 IAudioVolumeDuckNotification *this, LPCWSTR sid)
435 aout_sys_t *sys = container_of(this, aout_sys_t, duck);
436 audio_output_t *aout = sys->aout;
438 msg_Dbg(aout, "volume unducked by %ls", sid);
439 sys->ducks--;
440 aout_PolicyReport(aout, sys->ducks != 0);
441 return S_OK;
444 static const struct IAudioVolumeDuckNotificationVtbl vlc_AudioVolumeDuckNotification =
446 vlc_AudioVolumeDuckNotification_QueryInterface,
447 vlc_AudioVolumeDuckNotification_AddRef,
448 vlc_AudioVolumeDuckNotification_Release,
450 vlc_AudioVolumeDuckNotification_OnVolumeDuckNotification,
451 vlc_AudioVolumeDuckNotification_OnVolumeUnduckNotification,
455 /*** Audio devices ***/
457 /** Gets the user-readable device name */
458 static char *DeviceGetFriendlyName(IMMDevice *dev)
460 IPropertyStore *props;
461 PROPVARIANT v;
462 HRESULT hr;
464 hr = IMMDevice_OpenPropertyStore(dev, STGM_READ, &props);
465 if (FAILED(hr))
466 return NULL;
468 char *name = NULL;
469 PropVariantInit(&v);
470 hr = IPropertyStore_GetValue(props, &PKEY_Device_FriendlyName, &v);
471 if (SUCCEEDED(hr))
473 name = FromWide(v.pwszVal);
474 PropVariantClear(&v);
477 IPropertyStore_Release(props);
479 return name;
482 static int DeviceHotplugReport(audio_output_t *aout, LPCWSTR wid,
483 IMMDevice *dev)
485 char *id = FromWide(wid);
486 if (!id)
487 return VLC_EGENERIC;
489 char *name = DeviceGetFriendlyName(dev);
490 if (name == NULL)
491 name = id;
493 aout_HotplugReport(aout, id, name);
495 free(id);
496 if (id != name)
497 free(name);
498 return VLC_SUCCESS;
501 /** Checks that a device is an output device */
502 static bool DeviceIsRender(IMMDevice *dev)
504 void *pv;
506 if (FAILED(IMMDevice_QueryInterface(dev, &IID_IMMEndpoint, &pv)))
507 return false;
509 IMMEndpoint *ep = pv;
510 EDataFlow flow;
511 HRESULT hr = IMMEndpoint_GetDataFlow(ep, &flow);
513 IMMEndpoint_Release(ep);
514 if (FAILED(hr) || flow != eRender)
515 return false;
517 DWORD pdwState;
518 hr = IMMDevice_GetState(dev, &pdwState);
519 return !FAILED(hr) && pdwState == DEVICE_STATE_ACTIVE;
522 static HRESULT DeviceUpdated(audio_output_t *aout, LPCWSTR wid)
524 aout_sys_t *sys = aout->sys;
525 HRESULT hr;
527 IMMDevice *dev;
528 hr = IMMDeviceEnumerator_GetDevice(sys->it, wid, &dev);
529 if (FAILED(hr))
530 return hr;
532 if (!DeviceIsRender(dev))
534 IMMDevice_Release(dev);
535 return S_OK;
538 DeviceHotplugReport(aout, wid, dev);
539 IMMDevice_Release(dev);
540 return S_OK;
543 static STDMETHODIMP
544 vlc_MMNotificationClient_QueryInterface(IMMNotificationClient *this,
545 REFIID riid, void **ppv)
547 if (IsEqualIID(riid, &IID_IUnknown)
548 || IsEqualIID(riid, &IID_IMMNotificationClient))
550 *ppv = this;
551 IUnknown_AddRef(this);
552 return S_OK;
554 else
556 *ppv = NULL;
557 return E_NOINTERFACE;
561 static STDMETHODIMP_(ULONG)
562 vlc_MMNotificationClient_AddRef(IMMNotificationClient *this)
564 aout_sys_t *sys = container_of(this, aout_sys_t, device_events);
565 return InterlockedIncrement(&sys->refs);
568 static STDMETHODIMP_(ULONG)
569 vlc_MMNotificationClient_Release(IMMNotificationClient *this)
571 aout_sys_t *sys = container_of(this, aout_sys_t, device_events);
572 return InterlockedDecrement(&sys->refs);
575 static STDMETHODIMP
576 vlc_MMNotificationClient_OnDefaultDeviceChange(IMMNotificationClient *this,
577 EDataFlow flow, ERole role,
578 LPCWSTR wid)
580 aout_sys_t *sys = container_of(this, aout_sys_t, device_events);
581 audio_output_t *aout = sys->aout;
583 if (flow != eRender)
584 return S_OK;
585 if (role != eConsole)
586 return S_OK;
588 EnterCriticalSection(&sys->lock);
589 if (sys->acquired_device == NULL || sys->acquired_device == default_device)
591 msg_Dbg(aout, "default device changed: %ls", wid);
592 sys->request_device_restart = true;
593 aout_RestartRequest(aout, AOUT_RESTART_OUTPUT);
595 LeaveCriticalSection(&sys->lock);
597 return S_OK;
600 static STDMETHODIMP
601 vlc_MMNotificationClient_OnDeviceAdded(IMMNotificationClient *this,
602 LPCWSTR wid)
604 aout_sys_t *sys = container_of(this, aout_sys_t, device_events);
605 audio_output_t *aout = sys->aout;
607 msg_Dbg(aout, "device %ls added", wid);
608 return DeviceUpdated(aout, wid);
611 static STDMETHODIMP
612 vlc_MMNotificationClient_OnDeviceRemoved(IMMNotificationClient *this,
613 LPCWSTR wid)
615 aout_sys_t *sys = container_of(this, aout_sys_t, device_events);
616 audio_output_t *aout = sys->aout;
617 char *id = FromWide(wid);
619 msg_Dbg(aout, "device %ls removed", wid);
620 if (unlikely(id == NULL))
621 return E_OUTOFMEMORY;
623 aout_HotplugReport(aout, id, NULL);
624 free(id);
625 return S_OK;
628 static STDMETHODIMP
629 vlc_MMNotificationClient_OnDeviceStateChanged(IMMNotificationClient *this,
630 LPCWSTR wid, DWORD state)
632 aout_sys_t *sys = container_of(this, aout_sys_t, device_events);
633 audio_output_t *aout = sys->aout;
635 switch (state) {
636 case DEVICE_STATE_UNPLUGGED:
637 msg_Dbg(aout, "device %ls state changed: unplugged", wid);
638 break;
639 case DEVICE_STATE_ACTIVE:
640 msg_Dbg(aout, "device %ls state changed: active", wid);
641 return DeviceUpdated(aout, wid);
642 case DEVICE_STATE_DISABLED:
643 msg_Dbg(aout, "device %ls state changed: disabled", wid);
644 break;
645 case DEVICE_STATE_NOTPRESENT:
646 msg_Dbg(aout, "device %ls state changed: not present", wid);
647 break;
648 default:
649 msg_Dbg(aout, "device %ls state changed: unknown: %08lx", wid, state);
650 return E_FAIL;
653 /* Unplugged, disabled or notpresent */
654 char *id = FromWide(wid);
655 if (unlikely(id == NULL))
656 return E_OUTOFMEMORY;
657 aout_HotplugReport(aout, id, NULL);
658 free(id);
660 return S_OK;
663 static STDMETHODIMP
664 vlc_MMNotificationClient_OnPropertyValueChanged(IMMNotificationClient *this,
665 LPCWSTR wid,
666 const PROPERTYKEY key)
668 aout_sys_t *sys = container_of(this, aout_sys_t, device_events);
669 audio_output_t *aout = sys->aout;
671 if (key.pid == PKEY_Device_FriendlyName.pid)
673 msg_Dbg(aout, "device %ls name changed", wid);
674 return DeviceUpdated(aout, wid);
676 return S_OK;
679 static const struct IMMNotificationClientVtbl vlc_MMNotificationClient =
681 vlc_MMNotificationClient_QueryInterface,
682 vlc_MMNotificationClient_AddRef,
683 vlc_MMNotificationClient_Release,
685 vlc_MMNotificationClient_OnDeviceStateChanged,
686 vlc_MMNotificationClient_OnDeviceAdded,
687 vlc_MMNotificationClient_OnDeviceRemoved,
688 vlc_MMNotificationClient_OnDefaultDeviceChange,
689 vlc_MMNotificationClient_OnPropertyValueChanged,
692 static HRESULT DevicesEnum(IMMDeviceEnumerator *it,
693 void (*added_cb)(void *data, LPCWSTR wid, IMMDevice *dev),
694 void *added_cb_data)
696 HRESULT hr;
697 IMMDeviceCollection *devs;
698 assert(added_cb != NULL);
700 hr = IMMDeviceEnumerator_EnumAudioEndpoints(it, eRender,
701 DEVICE_STATE_ACTIVE, &devs);
702 if (FAILED(hr))
703 return hr;
705 UINT count;
706 hr = IMMDeviceCollection_GetCount(devs, &count);
707 if (FAILED(hr))
708 return hr;
710 for (UINT i = 0; i < count; i++)
712 IMMDevice *dev;
714 hr = IMMDeviceCollection_Item(devs, i, &dev);
715 if (FAILED(hr) || !DeviceIsRender(dev))
716 continue;
718 /* Unique device ID */
719 LPWSTR devid;
720 hr = IMMDevice_GetId(dev, &devid);
721 if (FAILED(hr))
723 IMMDevice_Release(dev);
724 continue;
727 added_cb(added_cb_data, devid, dev);
728 IMMDevice_Release(dev);
729 CoTaskMemFree(devid);
731 IMMDeviceCollection_Release(devs);
732 return S_OK;
735 static int DeviceRequestLocked(audio_output_t *aout)
737 aout_sys_t *sys = aout->sys;
738 assert(sys->requested_device);
740 sys->request_device_restart = false;
742 WakeConditionVariable(&sys->work);
743 while (sys->requested_device != NULL)
744 SleepConditionVariableCS(&sys->ready, &sys->lock, INFINITE);
746 if (sys->stream != NULL && sys->dev != NULL)
747 /* Request restart of stream with the new device */
748 aout_RestartRequest(aout, AOUT_RESTART_OUTPUT);
749 return (sys->dev != NULL) ? 0 : -1;
752 static int DeviceSelectLocked(audio_output_t *aout, const char *id)
754 aout_sys_t *sys = aout->sys;
755 assert(sys->requested_device == NULL);
757 if (id != NULL && strcmp(id, default_device_b) != 0)
759 sys->requested_device = ToWide(id);
760 if (unlikely(sys->requested_device == NULL))
761 return -1;
763 else
764 sys->requested_device = default_device;
766 return DeviceRequestLocked(aout);
769 static int DeviceRestartLocked(audio_output_t *aout)
771 aout_sys_t *sys = aout->sys;
772 assert(sys->requested_device == NULL);
773 sys->requested_device = sys->acquired_device ? sys->acquired_device
774 : default_device;
775 return DeviceRequestLocked(aout);
778 static int DeviceSelect(audio_output_t *aout, const char *id)
780 aout_sys_t *sys = aout->sys;
781 EnterCriticalSection(&sys->lock);
782 int ret = DeviceSelectLocked(aout, id);
783 LeaveCriticalSection(&sys->lock);
784 return ret;
787 /*** Initialization / deinitialization **/
788 static wchar_t *var_InheritWide(vlc_object_t *obj, const char *name)
790 char *v8 = var_InheritString(obj, name);
791 if (v8 == NULL)
792 return NULL;
794 wchar_t *v16 = ToWide(v8);
795 free(v8);
796 return v16;
798 #define var_InheritWide(o,n) var_InheritWide(VLC_OBJECT(o),n)
800 /** MMDevice audio output thread.
801 * This thread takes cares of the audio session control. Inconveniently enough,
802 * the audio session control interface must:
803 * - be created and destroyed from the same thread, and
804 * - survive across VLC audio output calls.
805 * The only way to reconcile both requirements is a custom thread.
806 * The thread also ensure that the COM Multi-Thread Apartment is continuously
807 * referenced so that MMDevice objects are not destroyed early.
808 * Furthermore, VolumeSet() and MuteSet() may be called from a thread with a
809 * COM STA, so that it cannot access the COM MTA for audio controls.
811 static HRESULT MMSession(audio_output_t *aout, IMMDeviceEnumerator *it)
813 aout_sys_t *sys = aout->sys;
814 IAudioSessionManager *manager;
815 IAudioSessionControl *control;
816 ISimpleAudioVolume *volume;
817 IAudioEndpointVolume *endpoint;
818 void *pv;
819 HRESULT hr;
821 assert(sys->requested_device != NULL);
822 assert(sys->dev == NULL);
824 /* Yes, it's perfectly valid to request the same device, see Start()
825 * comments. */
826 if (sys->acquired_device != sys->requested_device
827 && sys->acquired_device != default_device)
828 free(sys->acquired_device);
829 if (sys->requested_device != default_device) /* Device selected explicitly */
831 msg_Dbg(aout, "using selected device %ls", sys->requested_device);
832 hr = IMMDeviceEnumerator_GetDevice(it, sys->requested_device, &sys->dev);
833 if (FAILED(hr))
834 msg_Err(aout, "cannot get selected device %ls (error 0x%lx)",
835 sys->requested_device, hr);
836 sys->acquired_device = sys->requested_device;
838 else
839 hr = AUDCLNT_E_DEVICE_INVALIDATED;
841 while (hr == AUDCLNT_E_DEVICE_INVALIDATED)
842 { /* Default device selected by policy and with stream routing.
843 * "Do not use eMultimedia" says MSDN. */
844 msg_Dbg(aout, "using default device");
845 hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(it, eRender,
846 eConsole, &sys->dev);
847 if (FAILED(hr))
849 msg_Err(aout, "cannot get default device (error 0x%lx)", hr);
850 sys->acquired_device = NULL;
852 else
853 sys->acquired_device = default_device;
856 sys->requested_device = NULL;
857 WakeConditionVariable(&sys->ready);
859 if (SUCCEEDED(hr))
860 { /* Report actual device */
861 LPWSTR wdevid;
863 if (sys->acquired_device == default_device)
864 aout_DeviceReport(aout, default_device_b);
865 else
867 hr = IMMDevice_GetId(sys->dev, &wdevid);
868 if (SUCCEEDED(hr))
870 char *id = FromWide(wdevid);
871 CoTaskMemFree(wdevid);
872 if (likely(id != NULL))
874 aout_DeviceReport(aout, id);
875 free(id);
880 else
882 msg_Err(aout, "cannot get device identifier (error 0x%lx)", hr);
883 return hr;
886 /* Create session manager (for controls even w/o active audio client) */
887 hr = IMMDevice_Activate(sys->dev, &IID_IAudioSessionManager,
888 CLSCTX_ALL, NULL, &pv);
889 manager = pv;
890 if (SUCCEEDED(hr))
892 LPCGUID guid = var_GetBool(aout, "volume-save") ? &GUID_VLC_AUD_OUT : NULL;
894 /* Register session control */
895 hr = IAudioSessionManager_GetAudioSessionControl(manager, guid, 0,
896 &control);
897 if (SUCCEEDED(hr))
899 wchar_t *ua = var_InheritWide(aout, "user-agent");
900 IAudioSessionControl_SetDisplayName(control, ua, NULL);
901 free(ua);
903 IAudioSessionControl_RegisterAudioSessionNotification(control,
904 &sys->session_events);
906 else
907 msg_Err(aout, "cannot get session control (error 0x%lx)", hr);
909 hr = IAudioSessionManager_GetSimpleAudioVolume(manager, guid, FALSE,
910 &volume);
911 if (FAILED(hr))
912 msg_Err(aout, "cannot get simple volume (error 0x%lx)", hr);
914 /* Try to get version 2 (Windows 7) of the manager & control */
915 wchar_t *siid = NULL;
917 hr = IAudioSessionManager_QueryInterface(manager,
918 &IID_IAudioSessionControl2, &pv);
919 if (SUCCEEDED(hr))
921 IAudioSessionControl2 *c2 = pv;
923 IAudioSessionControl2_SetDuckingPreference(c2, FALSE);
924 hr = IAudioSessionControl2_GetSessionInstanceIdentifier(c2, &siid);
925 if (FAILED(hr))
926 siid = NULL;
927 IAudioSessionControl2_Release(c2);
929 else
930 msg_Dbg(aout, "version 2 session control unavailable");
932 hr = IAudioSessionManager_QueryInterface(manager,
933 &IID_IAudioSessionManager2, &pv);
934 if (SUCCEEDED(hr))
936 IAudioSessionManager2 *m2 = pv;
938 IAudioSessionManager2_RegisterDuckNotification(m2, siid,
939 &sys->duck);
940 IAudioSessionManager2_Release(m2);
942 else
943 msg_Dbg(aout, "version 2 session management unavailable");
945 CoTaskMemFree(siid);
947 else
949 msg_Err(aout, "cannot activate session manager (error 0x%lx)", hr);
950 control = NULL;
951 volume = NULL;
954 hr = IMMDevice_Activate(sys->dev, &IID_IAudioEndpointVolume,
955 CLSCTX_ALL, NULL, &pv);
956 endpoint = pv;
957 if (SUCCEEDED(hr))
959 float min, max, inc;
961 hr = IAudioEndpointVolume_GetVolumeRange(endpoint, &min, &max, &inc);
962 if (SUCCEEDED(hr))
963 msg_Dbg(aout, "volume from %+f dB to %+f dB with %f dB increments",
964 min, max, inc);
965 else
966 msg_Err(aout, "cannot get volume range (error 0x%lx)", hr);
968 else
969 msg_Err(aout, "cannot activate endpoint volume (error %lx)", hr);
971 /* Main loop (adjust volume as long as device is unchanged) */
972 while (sys->requested_device == NULL)
974 if (volume != NULL)
976 float level;
978 level = sys->requested_volume;
979 if (level >= 0.f)
981 hr = ISimpleAudioVolume_SetMasterVolume(volume, level, NULL);
982 if (FAILED(hr))
983 msg_Err(aout, "cannot set master volume (error 0x%lx)",
984 hr);
986 sys->requested_volume = -1.f;
988 hr = ISimpleAudioVolume_GetMasterVolume(volume, &level);
989 if (SUCCEEDED(hr))
990 aout_VolumeReport(aout, cbrtf(level * sys->gain));
991 else
992 msg_Err(aout, "cannot get master volume (error 0x%lx)", hr);
994 BOOL mute;
996 hr = ISimpleAudioVolume_GetMute(volume, &mute);
997 if (SUCCEEDED(hr))
998 aout_MuteReport(aout, mute != FALSE);
999 else
1000 msg_Err(aout, "cannot get mute (error 0x%lx)", hr);
1002 if (sys->requested_mute >= 0)
1004 mute = sys->requested_mute ? TRUE : FALSE;
1006 hr = ISimpleAudioVolume_SetMute(volume, mute, NULL);
1007 if (FAILED(hr))
1008 msg_Err(aout, "cannot set mute (error 0x%lx)", hr);
1010 sys->requested_mute = -1;
1013 SleepConditionVariableCS(&sys->work, &sys->lock, INFINITE);
1015 LeaveCriticalSection(&sys->lock);
1017 if (endpoint != NULL)
1018 IAudioEndpointVolume_Release(endpoint);
1020 if (manager != NULL)
1021 { /* Deregister callbacks *without* the lock */
1022 hr = IAudioSessionManager_QueryInterface(manager,
1023 &IID_IAudioSessionManager2, &pv);
1024 if (SUCCEEDED(hr))
1026 IAudioSessionManager2 *m2 = pv;
1028 IAudioSessionManager2_UnregisterDuckNotification(m2, &sys->duck);
1029 IAudioSessionManager2_Release(m2);
1032 if (volume != NULL)
1033 ISimpleAudioVolume_Release(volume);
1035 if (control != NULL)
1037 IAudioSessionControl_UnregisterAudioSessionNotification(control,
1038 &sys->session_events);
1039 IAudioSessionControl_Release(control);
1042 IAudioSessionManager_Release(manager);
1045 EnterCriticalSection(&sys->lock);
1046 IMMDevice_Release(sys->dev);
1047 sys->dev = NULL;
1048 return S_OK;
1051 static void MMThread_DevicesEnum_Added(void *data, LPCWSTR wid, IMMDevice *dev)
1053 audio_output_t *aout = data;
1055 DeviceHotplugReport(aout, wid, dev);
1058 static void *MMThread(void *data)
1060 audio_output_t *aout = data;
1061 aout_sys_t *sys = aout->sys;
1062 IMMDeviceEnumerator *it = sys->it;
1064 EnterMTA();
1065 IMMDeviceEnumerator_RegisterEndpointNotificationCallback(it,
1066 &sys->device_events);
1067 HRESULT hr = DevicesEnum(it, MMThread_DevicesEnum_Added, aout);
1068 if (FAILED(hr))
1069 msg_Warn(aout, "cannot enumerate audio endpoints (error 0x%lx)", hr);
1071 EnterCriticalSection(&sys->lock);
1074 if (sys->requested_device == NULL || FAILED(MMSession(aout, it)))
1075 SleepConditionVariableCS(&sys->work, &sys->lock, INFINITE);
1076 while (sys->it != NULL);
1078 LeaveCriticalSection(&sys->lock);
1080 IMMDeviceEnumerator_UnregisterEndpointNotificationCallback(it,
1081 &sys->device_events);
1082 IMMDeviceEnumerator_Release(it);
1083 LeaveMTA();
1084 return NULL;
1088 * Callback for aout_stream_t to create a stream on the device.
1089 * This can instantiate an IAudioClient or IDirectSound(8) object.
1091 static HRESULT ActivateDevice(void *opaque, REFIID iid, PROPVARIANT *actparms,
1092 void **restrict pv)
1094 IMMDevice *dev = opaque;
1095 return IMMDevice_Activate(dev, iid, CLSCTX_ALL, actparms, pv);
1098 static int aout_stream_Start(void *func, va_list ap)
1100 aout_stream_start_t start = func;
1101 aout_stream_t *s = va_arg(ap, aout_stream_t *);
1102 audio_sample_format_t *fmt = va_arg(ap, audio_sample_format_t *);
1103 HRESULT *hr = va_arg(ap, HRESULT *);
1104 LPCGUID sid = var_InheritBool(s, "volume-save") ? &GUID_VLC_AUD_OUT : NULL;
1106 *hr = start(s, fmt, sid);
1107 if (*hr == AUDCLNT_E_DEVICE_INVALIDATED)
1108 return VLC_ETIMEOUT;
1109 return SUCCEEDED(*hr) ? VLC_SUCCESS : VLC_EGENERIC;
1112 static void aout_stream_Stop(void *func, va_list ap)
1114 aout_stream_stop_t stop = func;
1115 aout_stream_t *s = va_arg(ap, aout_stream_t *);
1117 stop(s);
1120 static int Start(audio_output_t *aout, audio_sample_format_t *restrict fmt)
1122 aout_sys_t *sys = aout->sys;
1124 const bool b_spdif = AOUT_FMT_SPDIF(fmt);
1125 const bool b_hdmi = AOUT_FMT_HDMI(fmt);
1126 if (b_spdif || b_hdmi)
1128 switch (var_InheritInteger(aout, "mmdevice-passthrough"))
1130 case MM_PASSTHROUGH_DISABLED:
1131 return -1;
1132 case MM_PASSTHROUGH_ENABLED:
1133 if (b_hdmi)
1134 return -1;
1135 else if (fmt->i_format == VLC_CODEC_DTS)
1136 var_SetBool(aout, "dtshd", false );
1137 /* falltrough */
1138 case MM_PASSTHROUGH_ENABLED_HD:
1139 break;
1143 aout_stream_t *s = vlc_object_create(aout, sizeof (*s));
1144 if (unlikely(s == NULL))
1145 return -1;
1147 s->owner.activate = ActivateDevice;
1149 EnterMTA();
1150 EnterCriticalSection(&sys->lock);
1152 if ((sys->request_device_restart && DeviceRestartLocked(aout) != 0)
1153 || sys->dev == NULL)
1155 /* Error if the device restart failed or if a request previously
1156 * failed. */
1157 LeaveCriticalSection(&sys->lock);
1158 LeaveMTA();
1159 vlc_object_release(s);
1160 return -1;
1163 for (;;)
1165 char *modlist = var_InheritString(aout, "mmdevice-backend");
1166 HRESULT hr;
1167 s->owner.device = sys->dev;
1169 sys->module = vlc_module_load(s, "aout stream", modlist,
1170 false, aout_stream_Start, s, fmt, &hr);
1171 free(modlist);
1173 int ret = -1;
1174 if (hr == AUDCLNT_E_ALREADY_INITIALIZED)
1176 /* From MSDN: "If the initial call to Initialize fails, subsequent
1177 * Initialize calls might fail and return error code
1178 * E_ALREADY_INITIALIZED, even though the interface has not been
1179 * initialized. If this occurs, release the IAudioClient interface
1180 * and obtain a new IAudioClient interface from the MMDevice API
1181 * before calling Initialize again."
1183 * Therefore, request to MMThread the same device and try again. */
1185 ret = DeviceRestartLocked(aout);
1187 else if (hr == AUDCLNT_E_DEVICE_INVALIDATED)
1189 /* The audio endpoint device has been unplugged, request to
1190 * MMThread the default device and try again. */
1192 ret = DeviceSelectLocked(aout, NULL);
1194 if (ret != 0)
1195 break;
1198 if (sys->module != NULL)
1200 IPropertyStore *props;
1201 HRESULT hr = IMMDevice_OpenPropertyStore(sys->dev, STGM_READ, &props);
1202 if (SUCCEEDED(hr))
1204 PROPVARIANT v;
1205 PropVariantInit(&v);
1206 hr = IPropertyStore_GetValue(props, &PKEY_AudioEndpoint_FormFactor, &v);
1207 if (SUCCEEDED(hr))
1209 switch (v.uintVal)
1211 case Headphones:
1212 case Headset:
1213 aout->current_sink_info.headphones = true;
1214 break;
1216 PropVariantClear(&v);
1218 IPropertyStore_Release(props);
1222 LeaveCriticalSection(&sys->lock);
1223 LeaveMTA();
1225 if (sys->module == NULL)
1227 vlc_object_release(s);
1228 return -1;
1231 assert (sys->stream == NULL);
1232 sys->stream = s;
1233 aout_GainRequest(aout, sys->gain);
1234 return 0;
1237 static void Stop(audio_output_t *aout)
1239 aout_sys_t *sys = aout->sys;
1241 assert(sys->stream != NULL);
1243 EnterMTA();
1244 vlc_module_unload(sys->stream, sys->module, aout_stream_Stop, sys->stream);
1245 LeaveMTA();
1247 vlc_object_release(sys->stream);
1248 sys->stream = NULL;
1251 static int Open(vlc_object_t *obj)
1253 audio_output_t *aout = (audio_output_t *)obj;
1255 aout_sys_t *sys = malloc(sizeof (*sys));
1256 if (unlikely(sys == NULL))
1257 return VLC_ENOMEM;
1259 aout->sys = sys;
1260 sys->stream = NULL;
1261 sys->aout = aout;
1262 sys->it = NULL;
1263 sys->dev = NULL;
1264 sys->device_events.lpVtbl = &vlc_MMNotificationClient;
1265 sys->session_events.lpVtbl = &vlc_AudioSessionEvents;
1266 sys->duck.lpVtbl = &vlc_AudioVolumeDuckNotification;
1267 sys->refs = 1;
1268 sys->ducks = 0;
1270 sys->gain = 1.f;
1271 sys->requested_volume = -1.f;
1272 sys->requested_mute = -1;
1273 sys->acquired_device = NULL;
1274 sys->request_device_restart = false;
1276 if (!var_CreateGetBool(aout, "volume-save"))
1277 VolumeSetLocked(aout, var_InheritFloat(aout, "mmdevice-volume"));
1279 InitializeCriticalSection(&sys->lock);
1280 InitializeConditionVariable(&sys->work);
1281 InitializeConditionVariable(&sys->ready);
1283 aout_HotplugReport(aout, default_device_b, _("Default"));
1285 char *saved_device_b = var_InheritString(aout, "mmdevice-audio-device");
1286 if (saved_device_b != NULL && strcmp(saved_device_b, default_device_b) != 0)
1288 sys->requested_device = ToWide(saved_device_b);
1289 free(saved_device_b);
1291 if (unlikely(sys->requested_device == NULL))
1292 goto error;
1294 else
1296 free(saved_device_b);
1297 sys->requested_device = default_device;
1300 /* Initialize MMDevice API */
1301 if (TryEnterMTA(aout))
1302 goto error;
1304 void *pv;
1305 HRESULT hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL,
1306 &IID_IMMDeviceEnumerator, &pv);
1307 if (FAILED(hr))
1309 LeaveMTA();
1310 msg_Dbg(aout, "cannot create device enumerator (error 0x%lx)", hr);
1311 goto error;
1313 sys->it = pv;
1315 if (vlc_clone(&sys->thread, MMThread, aout, VLC_THREAD_PRIORITY_LOW))
1317 IMMDeviceEnumerator_Release(sys->it);
1318 LeaveMTA();
1319 goto error;
1322 EnterCriticalSection(&sys->lock);
1323 while (sys->requested_device != NULL)
1324 SleepConditionVariableCS(&sys->ready, &sys->lock, INFINITE);
1325 LeaveCriticalSection(&sys->lock);
1326 LeaveMTA(); /* Leave MTA after thread has entered MTA */
1328 aout->start = Start;
1329 aout->stop = Stop;
1330 aout->time_get = TimeGet;
1331 aout->play = Play;
1332 aout->pause = Pause;
1333 aout->flush = Flush;
1334 aout->volume_set = VolumeSet;
1335 aout->mute_set = MuteSet;
1336 aout->device_select = DeviceSelect;
1338 return VLC_SUCCESS;
1340 error:
1341 DeleteCriticalSection(&sys->lock);
1342 free(sys);
1343 return VLC_EGENERIC;
1346 static void Close(vlc_object_t *obj)
1348 audio_output_t *aout = (audio_output_t *)obj;
1349 aout_sys_t *sys = aout->sys;
1351 EnterCriticalSection(&sys->lock);
1352 sys->requested_device = default_device; /* break out of MMSession() loop */
1353 sys->it = NULL; /* break out of MMThread() loop */
1354 WakeConditionVariable(&sys->work);
1355 LeaveCriticalSection(&sys->lock);
1357 vlc_join(sys->thread, NULL);
1358 DeleteCriticalSection(&sys->lock);
1360 free(sys);
1363 struct mm_list
1365 size_t count;
1366 char **ids;
1367 char **names;
1370 static void Reload_DevicesEnum_Added(void *data, LPCWSTR wid, IMMDevice *dev)
1372 struct mm_list *list = data;
1374 size_t new_count = list->count + 1;
1375 list->ids = realloc_or_free(list->ids, new_count * sizeof(char *));
1376 list->names = realloc_or_free(list->names, new_count * sizeof(char *));
1377 if (!list->ids || !list->names)
1379 free(list->ids);
1380 return;
1383 char *id = FromWide(wid);
1384 if (!id)
1385 return;
1387 char *name = DeviceGetFriendlyName(dev);
1388 if (!name && !(name = strdup(id)))
1390 free(id);
1391 return;
1393 list->ids[list->count] = id;
1394 list->names[list->count] = name;
1396 list->count = new_count;
1399 static int ReloadAudioDevices(char const *name, char ***values, char ***descs)
1401 (void) name;
1403 bool in_mta = SUCCEEDED(CoInitializeEx(NULL, COINIT_MULTITHREADED));
1405 struct mm_list list = { .count = 0 };
1406 void *it;
1407 HRESULT hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL,
1408 &IID_IMMDeviceEnumerator, &it);
1409 if (FAILED(hr))
1410 goto error;
1412 list.ids = malloc(sizeof (char *));
1413 list.names = malloc(sizeof (char *));
1414 if (!list.ids || !list.names)
1416 free(list.ids);
1417 goto error;
1420 list.ids[0] = strdup("");
1421 list.names[0] = strdup(_("Default"));
1422 if (!list.ids[0] || !list.names[0])
1424 free(list.ids[0]);
1425 free(list.ids);
1426 free(list.names);
1427 goto error;
1429 list.count++;
1431 DevicesEnum(it, Reload_DevicesEnum_Added, &list);
1433 error:
1434 IMMDeviceEnumerator_Release((IMMDeviceEnumerator *)it);
1435 if (in_mta)
1436 CoUninitialize();
1438 if (list.count > 0)
1440 *values = list.ids;
1441 *descs = list.names;
1444 return list.count;
1447 #define MM_PASSTHROUGH_TEXT N_( \
1448 "HDMI/SPDIF audio passthrough")
1449 #define MM_PASSTHROUGH_LONGTEXT N_( \
1450 "Change this value if you have issue with HD codecs when using a HDMI receiver.")
1451 static const int pi_mmdevice_passthrough_values[] = {
1452 MM_PASSTHROUGH_DISABLED,
1453 MM_PASSTHROUGH_ENABLED,
1454 MM_PASSTHROUGH_ENABLED_HD,
1456 static const char *const ppsz_mmdevice_passthrough_texts[] = {
1457 N_("Disabled"),
1458 N_("Enabled (AC3/DTS only)"),
1459 N_("Enabled"),
1462 #define DEVICE_TEXT N_("Output device")
1463 #define DEVICE_LONGTEXT N_("Select your audio output device")
1465 #define VOLUME_TEXT N_("Audio volume")
1466 #define VOLUME_LONGTEXT N_("Audio volume in hundredths of decibels (dB).")
1468 vlc_module_begin()
1469 set_shortname("MMDevice")
1470 set_description(N_("Windows Multimedia Device output"))
1471 set_capability("audio output", 150)
1472 set_category(CAT_AUDIO)
1473 set_subcategory(SUBCAT_AUDIO_AOUT)
1474 set_callbacks(Open, Close)
1475 add_module("mmdevice-backend", "aout stream", "any",
1476 N_("Output back-end"), N_("Audio output back-end interface."))
1477 add_integer( "mmdevice-passthrough", MM_PASSTHROUGH_DEFAULT,
1478 MM_PASSTHROUGH_TEXT, MM_PASSTHROUGH_LONGTEXT, false )
1479 change_integer_list( pi_mmdevice_passthrough_values,
1480 ppsz_mmdevice_passthrough_texts )
1481 add_string("mmdevice-audio-device", NULL, DEVICE_TEXT, DEVICE_LONGTEXT, false)
1482 change_string_cb(ReloadAudioDevices)
1483 add_float("mmdevice-volume", 1.f, VOLUME_TEXT, VOLUME_LONGTEXT, true)
1484 change_float_range( 0.f, 1.25f )
1485 vlc_module_end()