#include <vlc_plugin.h>
#include <vlc_aout.h>
#include <vlc_charset.h>
-#include "mmdevice.h"
+#include "audio_output/mmdevice.h"
DEFINE_GUID (GUID_VLC_AUD_OUT, 0x4533f59d, 0x59ee, 0x00c6,
0xad, 0xb2, 0xc6, 0x8b, 0x50, 0x1a, 0x66, 0x55);
audio_output_t *aout;
aout_stream_t *stream; /**< Underlying audio output stream */
+ IMMDevice *dev; /**< Selected output device, NULL if none */
+
+ ISimpleAudioVolume *volume; /**< Volume setter */
+
+#if !VLC_WINSTORE_APP
IMMDeviceEnumerator *it; /**< Device enumerator, NULL when exiting */
/*TODO: IMMNotificationClient*/
- IMMDevice *dev; /**< Selected output device, NULL if none */
IAudioSessionManager *manager; /**< Session for the output device */
struct IAudioSessionEvents session_events;
- ISimpleAudioVolume *volume; /**< Volume setter */
LONG refs;
HANDLE device_changed; /**< Event to reset thread */
HANDLE device_ready; /**< Event when thread is reset */
vlc_thread_t thread; /**< Thread for audio session control */
+#endif
};
/* NOTE: The Core Audio API documentation totally fails to specify the thread
* safety (or lack thereof) of the interfaces. This code takes the most
- * restrictive assumption, no thread safety: The background thread (MMThread)
+ * restrictive assumption: no thread safety. The background thread (MMThread)
* only runs at specified times, namely between the device_ready and
* device_changed events (effectively, a thread barrier but only Windows 8
* provides thread barriers natively).
{
/* Restart on unplug */
if (unlikely(hr == AUDCLNT_E_DEVICE_INVALIDATED))
- var_TriggerCallback(aout, "audio-device");
+ aout_RestartRequest(aout, AOUT_RESTART_OUTPUT);
return SUCCEEDED(hr) ? 0 : -1;
}
return FAILED(hr) ? -1 : 0;
}
-/*** Audio devices ***/
-static int DeviceChanged(vlc_object_t *obj, const char *varname,
- vlc_value_t prev, vlc_value_t cur, void *data)
-{
- /* FIXME: This does not work. sys->dev, ->manager and ->stream must be
- * recreated. Those pointers are protected by the aout lock, which
- * serializes accesses to the audio_output_t. Unfortunately,
- * aout lock cannot be taken from a variable callback.
- * Solution: add device_change callback to audio_output_t. */
- aout_ChannelsRestart(obj, varname, prev, cur, data);
- return VLC_SUCCESS;
-}
-
-static void GetDevices(vlc_object_t *obj, IMMDeviceEnumerator *it)
-{
- HRESULT hr;
- vlc_value_t val, text;
-
- var_Create (obj, "audio-device", VLC_VAR_STRING | VLC_VAR_HASCHOICE);
- text.psz_string = _("Audio Device");
- var_Change (obj, "audio-device", VLC_VAR_SETTEXT, &text, NULL);
-
- /* TODO: implement IMMNotificationClient for hotplug devices */
- IMMDeviceCollection *devs;
- hr = IMMDeviceEnumerator_EnumAudioEndpoints(it, eRender,
- DEVICE_STATE_ACTIVE, &devs);
- if (FAILED(hr))
- {
- msg_Warn (obj, "cannot enumerate audio endpoints (error 0x%lx)", hr);
- return;
- }
-
- UINT n;
- hr = IMMDeviceCollection_GetCount(devs, &n);
- if (FAILED(hr))
- {
- msg_Warn (obj, "cannot count audio endpoints (error 0x%lx)", hr);
- n = 0;
- }
- else
- msg_Dbg(obj, "Available Windows Audio devices:");
-
- while (n > 0)
- {
- IMMDevice *dev;
-
- hr = IMMDeviceCollection_Item(devs, --n, &dev);
- if (FAILED(hr))
- continue;
-
- /* Unique device ID */
- LPWSTR devid;
- hr = IMMDevice_GetId(dev, &devid);
- if (FAILED(hr))
- {
- IMMDevice_Release(dev);
- continue;
- }
- val.psz_string = FromWide(devid);
- CoTaskMemFree(devid);
- text.psz_string = val.psz_string;
-
- /* User-readable device name */
- IPropertyStore *props;
- hr = IMMDevice_OpenPropertyStore(dev, STGM_READ, &props);
- if (SUCCEEDED(hr))
- {
- PROPVARIANT v;
-
- PropVariantInit(&v);
- hr = IPropertyStore_GetValue(props, &PKEY_Device_FriendlyName, &v);
- if (SUCCEEDED(hr))
- text.psz_string = FromWide(v.pwszVal);
- PropVariantClear(&v);
- IPropertyStore_Release(props);
- }
- IMMDevice_Release(dev);
-
- msg_Dbg(obj, "%s (%s)", val.psz_string, text.psz_string);
- var_Change(obj, "audio-device", VLC_VAR_ADDCHOICE, &val, &text);
- if (likely(text.psz_string != val.psz_string))
- free(text.psz_string);
- free(val.psz_string);
- }
- IMMDeviceCollection_Release(devs);
-}
-
+#if !VLC_WINSTORE_APP
/*** Audio session events ***/
static inline aout_sys_t *vlc_AudioSessionEvents_sys(IAudioSessionEvents *this)
{
msg_Warn(aout, "session disconnected: unknown reason %d", reason);
return S_OK;
}
- var_TriggerCallback(aout, "audio-device");
+ /* NOTE: audio decoder thread should get invalidated device and restart */
return S_OK;
}
return NULL;
}
+/*** Audio devices ***/
+static int DevicesEnum(audio_output_t *aout)
+{
+ aout_sys_t *sys = aout->sys;
+ HRESULT hr;
+ IMMDeviceCollection *devs;
+
+ hr = IMMDeviceEnumerator_EnumAudioEndpoints(sys->it, eRender,
+ DEVICE_STATE_ACTIVE, &devs);
+ if (FAILED(hr))
+ {
+ msg_Warn(aout, "cannot enumerate audio endpoints (error 0x%lx)", hr);
+ return -1;
+ }
+
+ UINT count;
+ hr = IMMDeviceCollection_GetCount(devs, &count);
+ if (FAILED(hr))
+ {
+ msg_Warn(aout, "cannot count audio endpoints (error 0x%lx)", hr);
+ count = 0;
+ }
+
+ unsigned n = 0;
+
+ for (UINT i = 0; i < count; i++)
+ {
+ IMMDevice *dev;
+ char *id, *name = NULL;
+
+ hr = IMMDeviceCollection_Item(devs, i, &dev);
+ if (FAILED(hr))
+ continue;
+
+ /* Unique device ID */
+ LPWSTR devid;
+ hr = IMMDevice_GetId(dev, &devid);
+ if (FAILED(hr))
+ {
+ IMMDevice_Release(dev);
+ continue;
+ }
+ id = FromWide(devid);
+ CoTaskMemFree(devid);
+
+ /* User-readable device name */
+ IPropertyStore *props;
+ hr = IMMDevice_OpenPropertyStore(dev, STGM_READ, &props);
+ if (SUCCEEDED(hr))
+ {
+ PROPVARIANT v;
+
+ PropVariantInit(&v);
+ hr = IPropertyStore_GetValue(props, &PKEY_Device_FriendlyName, &v);
+ if (SUCCEEDED(hr))
+ name = FromWide(v.pwszVal);
+ PropVariantClear(&v);
+ IPropertyStore_Release(props);
+ }
+ IMMDevice_Release(dev);
+
+ aout_HotplugReport(aout, id, (name != NULL) ? name : id);
+ free(name);
+ free(id);
+ n++;
+ }
+ IMMDeviceCollection_Release(devs);
+ return n;
+}
+
/**
* Opens the selected audio output device.
*/
static HRESULT OpenDevice(audio_output_t *aout, const char *devid)
{
aout_sys_t *sys = aout->sys;
- HRESULT hr;
-
assert(sys->dev == NULL);
+ HRESULT hr;
if (devid != NULL) /* Device selected explicitly */
{
msg_Dbg(aout, "using selected device %s", devid);
hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(sys->it, eRender,
eConsole, &sys->dev);
}
+ assert(sys->manager == NULL);
+ if (FAILED(hr))
+ {
+ msg_Err(aout, "cannot get device (error 0x%lx)", hr);
+ goto out;
+ }
/* Create session manager (for controls even w/o active audio client) */
+ void *pv;
+ hr = IMMDevice_Activate(sys->dev, &IID_IAudioSessionManager,
+ CLSCTX_ALL, NULL, &pv);
if (FAILED(hr))
- msg_Err(aout, "cannot get device (error 0x%lx)", hr);
+ msg_Err(aout, "cannot activate session manager (error 0x%lx)", hr);
else
+ sys->manager = pv;
+
+ /* Report actual device */
+ LPWSTR wdevid;
+ hr = IMMDevice_GetId(sys->dev, &wdevid);
+ if (SUCCEEDED(hr))
{
- void *pv;
- hr = IMMDevice_Activate(sys->dev, &IID_IAudioSessionManager,
- CLSCTX_ALL, NULL, &pv);
- if (FAILED(hr))
- msg_Err(aout, "cannot activate session manager (error 0x%lx)", hr);
- else
- sys->manager = pv;
+ char *id = FromWide(wdevid);
+ CoTaskMemFree(wdevid);
+ if (likely(id != NULL))
+ {
+ aout_DeviceReport(aout, id);
+ free(id);
+ }
}
-
+out:
SetEvent(sys->device_changed);
WaitForSingleObject(sys->device_ready, INFINITE);
return hr;
aout_sys_t *sys = aout->sys;
assert(sys->dev != NULL);
-
if (sys->manager != NULL)
{
IAudioSessionManager_Release(sys->manager);
void **restrict pv)
{
IMMDevice *dev = opaque;
-
return IMMDevice_Activate(dev, iid, CLSCTX_ALL, actparms, pv);
}
+#else /* VLC_WINSTORE_APP */
+
+static HRESULT OpenDevice(audio_output_t *aout, const char *devid)
+{
+ aout_sys_t *sys = aout->sys;
+ assert(sys->dev == NULL);
+
+ (void)devid;
+ assert(!devid);
+ sys->dev = var_InheritAddress(aout, "mmdevice-audioclient");
+ return S_OK;
+}
+
+static void CloseDevice(audio_output_t *aout)
+{
+ aout_sys_t *sys = aout->sys;
+
+ assert(sys->dev != NULL);
+ free(sys->dev);
+ sys->dev = NULL;
+}
+
+static HRESULT ActivateDevice(void *opaque, REFIID iid, PROPVARIANT *actparms,
+ void **restrict pv)
+{
+ IMMDevice *dev = opaque;
+ (void)iid; (void)actparms;
+ *pv = dev;
+ return S_OK;
+}
+#endif /* !VLC_WINSTORE_APP */
+
+
+static int DeviceSelect(audio_output_t *aout, const char *id)
+{
+ aout_sys_t *sys = aout->sys;
+ HRESULT hr;
+
+ if (TryEnterMTA(aout))
+ return -1;
+
+ if (sys->dev != NULL)
+ CloseDevice(aout);
+
+ hr = OpenDevice(aout, id);
+ while (hr == AUDCLNT_E_DEVICE_INVALIDATED)
+ hr = OpenDevice(aout, NULL); /* Fallback to default device */
+ LeaveMTA();
+
+ if (sys->stream != NULL)
+ /* Request restart of stream with the new device */
+ aout_RestartRequest(aout, AOUT_RESTART_OUTPUT);
+ return FAILED(hr) ? -1 : 0;
+}
+
static int Start(audio_output_t *aout, audio_sample_format_t *restrict fmt)
{
aout_sys_t *sys = aout->sys;
HRESULT hr;
assert (sys->stream == NULL);
- if (sys->dev == NULL)
+ /* Open the default device if required (to deal with restarts) */
+ if (sys->dev == NULL && FAILED(DeviceSelect(aout, NULL)))
return -1;
aout_stream_t *s = vlc_object_create(aout, sizeof (*s));
static int Open(vlc_object_t *obj)
{
audio_output_t *aout = (audio_output_t *)obj;
- void *pv;
- HRESULT hr;
if (!aout->b_force && var_InheritBool(aout, "spdif"))
/* Fallback to other plugin until pass-through is implemented */
aout->sys = sys;
sys->aout = aout;
sys->stream = NULL;
- sys->it = NULL;
sys->dev = NULL;
+#if !VLC_WINSTORE_APP
+ sys->it = NULL;
sys->manager = NULL;
sys->session_events.lpVtbl = &vlc_AudioSessionEvents;
sys->refs = 1;
if (TryEnterMTA(aout))
goto error;
- hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL,
+ void *pv;
+ HRESULT hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL,
&IID_IMMDeviceEnumerator, &pv);
if (FAILED(hr))
{
goto error;
}
sys->it = pv;
- GetDevices(obj, sys->it);
-
if (vlc_clone(&sys->thread, MMThread, aout, VLC_THREAD_PRIORITY_LOW))
goto error;
WaitForSingleObject(sys->device_ready, INFINITE);
+#endif
- /* Get a device to start with */
- do
- hr = OpenDevice(aout, NULL);
- while (hr == AUDCLNT_E_DEVICE_INVALIDATED);
+ DeviceSelect(aout, NULL); /* Get a device to start with */
LeaveMTA(); /* leave MTA after thread has entered MTA */
aout->start = Start;
aout->flush = Flush;
aout->volume_set = VolumeSet;
aout->mute_set = MuteSet;
- var_AddCallback (aout, "audio-device", DeviceChanged, NULL);
+#if VLC_WINSTORE_APP
+ aout->device_select = NULL;
+ return VLC_SUCCESS;
+#else
+ aout->device_select = DeviceSelect;
+ DevicesEnum(aout);
return VLC_SUCCESS;
error:
CloseHandle(sys->device_changed);
free(sys);
return VLC_EGENERIC;
+#endif
}
static void Close(vlc_object_t *obj)
audio_output_t *aout = (audio_output_t *)obj;
aout_sys_t *sys = aout->sys;
- var_DelCallback (aout, "audio-device", DeviceChanged, NULL);
- var_Destroy (aout, "audio-device");
-
+#if !VLC_WINSTORE_APP
EnterMTA(); /* enter MTA before thread leaves MTA */
if (sys->dev != NULL)
CloseDevice(aout);
SetEvent(sys->device_changed);
vlc_join(sys->thread, NULL);
+
LeaveMTA();
CloseHandle(sys->device_ready);
CloseHandle(sys->device_changed);
+#else
+ if (sys->dev != NULL)
+ CloseDevice(aout);
+#endif
free(sys);
}
vlc_module_begin()
set_shortname("MMDevice")
set_description(N_("Windows Multimedia Device output"))
- set_capability("audio output", 150)
+ set_capability("audio output", /*150*/0)
+#if VLC_WINSTORE_APP
+ /* Pointer to the activated AudioClient* */
+ add_integer("mmdevice-audioclient", 0x0, NULL, NULL, true);
+#endif
set_category(CAT_AUDIO)
set_subcategory(SUBCAT_AUDIO_AOUT)
add_shortcut("wasapi")