[vlc-devel] [PATCH v2 3/4] aout: winstore: select the audio output using ActivateAudioInterfaceAsync
Rémi Denis-Courmont
remi at remlab.net
Fri Jun 26 16:07:24 CEST 2020
Hi,
AFAIR, doing all that stuff from a random thread (well, the VLC audio decoder
thread) is not defined; it must be performed on GUI thread.
Hence the winstore-client horror.
Le perjantaina 26. kesäkuuta 2020, 12.34.50 EEST Steve Lhomme a écrit :
> See the API
> https://docs.microsoft.com/en-us/windows/win32/api/mmdeviceapi/nf-mmdevicea
> pi-activateaudiointerfaceasync
>
> It requires a recent mingw-w64 with the added API.
>
> We request the IAudioClient asynchronously and return the found client or
> NULL once the async call as completed.
> The code originates from the vlc-winrt project with some modifications.
>
> Do not rely anymore on the local "winstore-client" variable to cache the
> IAudioClient. A client is queried/used between each Start/Stop calls.
> ---
> modules/audio_output/Makefile.am | 2 +-
> modules/audio_output/winstore.c | 312 ++++++++++++++++++++++++++++---
> 2 files changed, 286 insertions(+), 28 deletions(-)
>
> diff --git a/modules/audio_output/Makefile.am
> b/modules/audio_output/Makefile.am index 7ed509e7346..b64659e6c9e 100644
> --- a/modules/audio_output/Makefile.am
> +++ b/modules/audio_output/Makefile.am
> @@ -62,7 +62,7 @@ endif
> libmmdevice_plugin_la_SOURCES = audio_output/mmdevice.c
> audio_output/mmdevice.h libmmdevice_plugin_la_LIBADD = $(LIBCOM) $(LIBM)
> libwinstore_plugin_la_SOURCES = audio_output/winstore.c
> audio_output/mmdevice.h -libwinstore_plugin_la_LIBADD = $(LIBCOM)
> +libwinstore_plugin_la_LIBADD = $(LIBCOM) -lmmdevapi
> libwasapi_plugin_la_SOURCES = audio_output/wasapi.c
> libwasapi_plugin_la_LIBADD = $(LIBCOM) -lksuser
> if HAVE_WASAPI
> diff --git a/modules/audio_output/winstore.c
> b/modules/audio_output/winstore.c index f108b577ebe..6373e6bfd18 100644
> --- a/modules/audio_output/winstore.c
> +++ b/modules/audio_output/winstore.c
> @@ -24,15 +24,20 @@
>
> #define INITGUID
> #define COBJMACROS
> +#define CONST_VTABLE
>
> #include <audiopolicy.h>
>
> #include <vlc_common.h>
> #include <vlc_plugin.h>
> #include <vlc_aout.h>
> +#include <vlc_charset.h> // ToWide
> #include <vlc_modules.h>
> #include "audio_output/mmdevice.h"
>
> +#include <audioclient.h>
> +#include <mmdeviceapi.h>
> +
> DEFINE_GUID (GUID_VLC_AUD_OUT, 0x4533f59d, 0x59ee, 0x00c6,
> 0xad, 0xb2, 0xc6, 0x8b, 0x50, 0x1a, 0x66, 0x55);
>
> @@ -53,16 +58,210 @@ typedef struct
> aout_stream_t *stream; /**< Underlying audio output stream */
> module_t *module;
> IAudioClient *client;
> + wchar_t* acquired_device;
> + wchar_t* requested_device;
> + wchar_t* default_device; // read once on open
> +
> + // IActivateAudioInterfaceCompletionHandler interface
> + IActivateAudioInterfaceCompletionHandler client_locator;
> + vlc_sem_t async_completed;
> + LONG refs;
> + CRITICAL_SECTION lock;
> } aout_sys_t;
>
> -static void ResetInvalidatedClient(audio_output_t *aout, HRESULT hr)
> +
> +/* MMDeviceLocator IUnknown methods */
> +static STDMETHODIMP_(ULONG)
> MMDeviceLocator_AddRef(IActivateAudioInterfaceCompletionHandler *This) +{
> + aout_sys_t *sys = container_of(This, aout_sys_t, client_locator);
> + return InterlockedIncrement(&sys->refs);
> +}
> +
> +static STDMETHODIMP_(ULONG)
> MMDeviceLocator_Release(IActivateAudioInterfaceCompletionHandler *This) +{
> + aout_sys_t *sys = container_of(This, aout_sys_t, client_locator);
> + return InterlockedDecrement(&sys->refs);
> +}
> +
> +static STDMETHODIMP
> MMDeviceLocator_QueryInterface(IActivateAudioInterfaceCompletionHandler
> *This, + REFIID riid,
> void **ppv) +{
> + if( IsEqualIID(riid, &IID_IUnknown) ||
> + IsEqualIID(riid, &IID_IActivateAudioInterfaceCompletionHandler) )
> + {
> + MMDeviceLocator_AddRef(This);
> + *ppv = This;
> + return S_OK;
> + }
> + *ppv = NULL;
> + if( IsEqualIID(riid, &IID_IAgileObject) )
> + {
> + return S_OK;
> + }
> + return ResultFromScode( E_NOINTERFACE );
> +}
> +
> +/* MMDeviceLocator IActivateAudioInterfaceCompletionHandler methods */
> +static HRESULT
> MMDeviceLocator_ActivateCompleted(IActivateAudioInterfaceCompletionHandler
> *This, +
> IActivateAudioInterfaceAsyncOperation *operation) +{
> + (void)operation;
> + aout_sys_t *sys = container_of(This, aout_sys_t, client_locator);
> + vlc_sem_post( &sys->async_completed );
> + return S_OK;
> +}
> +
> +/* MMDeviceLocator vtable */
> +static const struct IActivateAudioInterfaceCompletionHandlerVtbl
> MMDeviceLocator_vtable = +{
> + MMDeviceLocator_QueryInterface,
> + MMDeviceLocator_AddRef,
> + MMDeviceLocator_Release,
> +
> + MMDeviceLocator_ActivateCompleted,
> +};
> +
> +static void WaitForAudioClient(audio_output_t *aout)
> +{
> + aout_sys_t *sys = aout->sys;
> + IActivateAudioInterfaceAsyncOperation* asyncOp = NULL;
> +
> + const wchar_t* devId = sys->requested_device ? sys->requested_device :
> sys->default_device; +
> + assert(sys->refs == 0);
> + sys->refs = 0;
> + assert(sys->client == NULL);
> + sys->client = NULL;
> + free(sys->acquired_device);
> + sys->acquired_device = NULL;
> + ActivateAudioInterfaceAsync(devId, &IID_IAudioClient, NULL,
> &sys->client_locator, &asyncOp); +
> + vlc_sem_wait( &sys->async_completed );
> +
> + if (asyncOp)
> + {
> + HRESULT hr;
> + HRESULT hrActivateResult;
> + IUnknown *audioInterface;
> +
> + hr =
> IActivateAudioInterfaceAsyncOperation_GetActivateResult(asyncOp,
> &hrActivateResult, &audioInterface); +
> IActivateAudioInterfaceAsyncOperation_Release(asyncOp);
> + if (unlikely(FAILED(hr)))
> + msg_Dbg(aout, "Failed to get the activation result.
> (hr=0x%lX)", hr); + else if (FAILED(hrActivateResult))
> + msg_Dbg(aout, "Failed to activate the device. (hr=0x%lX)", hr);
> + else if (unlikely(audioInterface == NULL))
> + msg_Dbg(aout, "Failed to get the device instance.");
> + else
> + {
> + hr = IUnknown_QueryInterface(audioInterface, &IID_IAudioClient,
> (void**)&sys->client); + IUnknown_Release(audioInterface);
> + if (unlikely(FAILED(hr)))
> + msg_Warn(aout, "The received interface is not a
> IAudioClient. (hr=0x%lX)", hr); + else
> + {
> + sys->acquired_device = wcsdup(devId);
> +
> + IAudioClient2 *audioClient2;
> + if (SUCCEEDED(IAudioClient_QueryInterface(sys->client,
> &IID_IAudioClient2, (void**)&audioClient2)) + &&
> audioClient2)
> + {
> + // "BackgroundCapableMedia" does not work in UWP
> + AudioClientProperties props = (AudioClientProperties) {
> + .cbSize = sizeof(props),
> + .bIsOffload = FALSE,
> + .eCategory = AudioCategory_Movie,
> + .Options = AUDCLNT_STREAMOPTIONS_NONE
> + };
> + if
> (FAILED(IAudioClient2_SetClientProperties(audioClient2, &props))) { +
> msg_Dbg(aout, "Failed to set audio client properties"); +
> }
> + IAudioClient2_Release(audioClient2);
> + }
> + }
> + }
> + }
> +}
> +
> +static bool SetRequestedDevice(audio_output_t *aout, wchar_t *id)
> {
> aout_sys_t* sys = aout->sys;
> + if (sys->requested_device != id)
> + {
> + if (sys->requested_device != sys->default_device)
> + free(sys->requested_device);
> + sys->requested_device = id;
> + return true;
> + }
> + return false;
> +}
> +
> +static int DeviceRequestLocked(audio_output_t *aout)
> +{
> + aout_sys_t *sys = aout->sys;
> + assert(sys->requested_device);
> +
> + WaitForAudioClient(aout);
> +
> + if (sys->stream != NULL && sys->client != NULL)
> + /* Request restart of stream with the new device */
> + aout_RestartRequest(aout, AOUT_RESTART_OUTPUT);
> + return (sys->client != NULL) ? 0 : -1;
> +}
> +
> +static int DeviceRestartLocked(audio_output_t *aout)
> +{
> + aout_sys_t *sys = aout->sys;
> +
> + if (sys->client)
> + {
> + assert(sys->acquired_device);
> + free(sys->acquired_device);
> + sys->acquired_device = NULL;
> + IAudioClient_Release(sys->client);
> + sys->client = NULL;
> + }
> +
> + return DeviceRequestLocked(aout);
> +}
> +
> +static int DeviceSelectLocked(audio_output_t *aout, const char* id)
> +{
> + aout_sys_t *sys = aout->sys;
> + bool changed;
> + if( id == NULL )
> + {
> + changed = SetRequestedDevice(aout, sys->default_device);
> + }
> + else
> + {
> + wchar_t *requested_device = ToWide(id);
> + if (unlikely(requested_device == NULL))
> + return VLC_ENOMEM;
> + changed = SetRequestedDevice(aout, requested_device);
> + }
> + if (!changed)
> + return VLC_EGENERIC;
> + return DeviceRestartLocked(aout);
> +}
> +
> +static int DeviceSelect(audio_output_t *aout, const char* id)
> +{
> + aout_sys_t *sys = aout->sys;
> + EnterCriticalSection(&sys->lock);
> + int ret = DeviceSelectLocked(aout, id);
> + LeaveCriticalSection(&sys->lock);
> + return ret;
> +}
> +
> +static void ResetInvalidatedClient(audio_output_t *aout, HRESULT hr)
> +{
> /* Select the default device (and restart) on unplug */
> if (unlikely(hr == AUDCLNT_E_DEVICE_INVALIDATED ||
> hr == AUDCLNT_E_RESOURCES_INVALIDATED))
> {
> - sys->client = NULL;
> + // Select the default device (and restart) on unplug
> + DeviceSelect(aout, NULL);
> }
> }
>
> @@ -198,7 +397,7 @@ static HRESULT ActivateDevice(void *opaque, REFIID iid,
> PROPVARIANT *actparms, if (actparms != NULL || client == NULL )
> return E_INVALIDARG;
>
> - IAudioClient_AddRef(client);
> + IAudioClient_AddRef(client); // as would IMMDevice_Activate do
> *pv = opaque;
>
> return S_OK;
> @@ -227,12 +426,58 @@ static int Start(audio_output_t *aout,
> audio_sample_format_t *restrict fmt) if (unlikely(s == NULL))
> return -1;
>
> - s->owner.device = sys->client;
> - s->owner.activate = ActivateDevice;
> + if (sys->requested_device != NULL)
> + {
> + if (sys->acquired_device == NULL || wcscmp(sys->acquired_device,
> sys->requested_device)) + {
> + // we have a pending request for a new device
> + DeviceRestartLocked(aout);
> + if (sys->client == NULL)
> + {
> + vlc_object_delete(&s->obj);
> + return -1;
> + }
> + }
> + }
>
> + // Load the "out stream" for the requested device
> EnterMTA();
> - sys->module = vlc_module_load(s, "aout stream", NULL, false,
> - aout_stream_Start, s, fmt, &hr);
> + EnterCriticalSection(&sys->lock);
> +
> + s->owner.activate = ActivateDevice;
> + for (;;)
> + {
> + s->owner.device = sys->client;
> + sys->module = vlc_module_load(s, "aout stream", NULL, false,
> + aout_stream_Start, s, fmt, &hr);
> +
> + int ret = -1;
> + if (hr == AUDCLNT_E_DEVICE_INVALIDATED)
> + {
> + // the requested device is not usable, try the default device
> + ret = DeviceSelectLocked(aout, NULL);
> + }
> + else if (hr == AUDCLNT_E_ALREADY_INITIALIZED)
> + {
> + /* From MSDN: "If the initial call to Initialize fails,
> subsequent + * Initialize calls might fail and return error
> code
> + * E_ALREADY_INITIALIZED, even though the interface has not
> been + * initialized. If this occurs, release the IAudioClient
> interface + * and obtain a new IAudioClient interface from the
> MMDevice API + * before calling Initialize again."
> + *
> + * Therefore, request to MMThread the same device and try
> again. */ +
> + ret = DeviceRestartLocked(aout);
> + }
> + if (ret != VLC_SUCCESS)
> + break;
> +
> + if (sys->client == NULL || sys->module != NULL)
> + break;
> + }
> +
> + LeaveCriticalSection(&sys->lock);
> LeaveMTA();
>
> if (sys->module == NULL)
> @@ -241,6 +486,13 @@ static int Start(audio_output_t *aout,
> audio_sample_format_t *restrict fmt) return -1;
> }
>
> + if (sys->client)
> + {
> + // the requested device has been used, reset it
> + // we keep the corresponding sys->client until a new request is
> started + SetRequestedDevice(aout, NULL);
> + }
> +
> assert (sys->stream == NULL);
> sys->stream = s;
> return 0;
> @@ -260,23 +512,6 @@ static void Stop(audio_output_t *aout)
> sys->stream = NULL;
> }
>
> -static int DeviceSelect(audio_output_t *aout, const char* psz_device)
> -{
> - if( psz_device == NULL )
> - return VLC_EGENERIC;
> - char* psz_end;
> - aout_sys_t* sys = aout->sys;
> - intptr_t ptr = strtoll( psz_device, &psz_end, 16 );
> - if ( *psz_end != 0 )
> - return VLC_EGENERIC;
> - if (sys->client == (IAudioClient*)ptr)
> - return VLC_SUCCESS;
> - sys->client = (IAudioClient*)ptr;
> - var_SetAddress( vlc_object_parent(aout), "winstore-client", sys->client
> ); - aout_RestartRequest( aout, AOUT_RESTART_OUTPUT );
> - return VLC_SUCCESS;
> -}
> -
> static int Open(vlc_object_t *obj)
> {
> audio_output_t *aout = (audio_output_t *)obj;
> @@ -285,14 +520,27 @@ static int Open(vlc_object_t *obj)
> if (unlikely(sys == NULL))
> return VLC_ENOMEM;
>
> + if (unlikely(FAILED(StringFromIID(&DEVINTERFACE_AUDIO_RENDER,
> &sys->default_device)))) + {
> + msg_Dbg(obj, "Failed to get the default renderer string");
> + free(sys);
> + return VLC_EGENERIC;
> + }
> +
> + InitializeCriticalSection(&sys->lock);
> +
> + vlc_sem_init(&sys->async_completed, 0);
> + sys->refs = 0;
> + sys->requested_device = sys->default_device;
> + sys->acquired_device = NULL;
> + sys->client_locator = (IActivateAudioInterfaceCompletionHandler) {
> &MMDeviceLocator_vtable }; +
> if (!var_CreateGetBool(aout, "volume-save"))
> VolumeSet(aout, var_InheritFloat(aout, "mmdevice-volume"));
>
> aout->sys = sys;
> sys->stream = NULL;
> - sys->client = var_CreateGetAddress( vlc_object_parent(aout),
> "winstore-client" ); - if (sys->client != NULL)
> - msg_Dbg( aout, "Reusing previous client: %p", sys->client );
> + sys->client = NULL;
> aout->start = Start;
> aout->stop = Stop;
> aout->time_get = TimeGet;
> @@ -310,6 +558,16 @@ static void Close(vlc_object_t *obj)
> audio_output_t *aout = (audio_output_t *)obj;
> aout_sys_t *sys = aout->sys;
>
> + if(sys->client != NULL)
> + IAudioClient_Release(sys->client);
> +
> + assert(sys->refs == 0);
> +
> + free(sys->acquired_device);
> + free(sys->requested_device);
> + CoTaskMemFree(sys->default_device);
> + DeleteCriticalSection(&sys->lock);
> +
> free(sys);
> }
--
雷米‧德尼-库尔蒙
http://www.remlab.net/
More information about the vlc-devel
mailing list