[vlc-devel] [PATCH v2 3/4] aout: winstore: select the audio output using ActivateAudioInterfaceAsync
Jean-Baptiste Kempf
jb at videolan.org
Fri Jun 26 16:20:50 CEST 2020
Hello,
This was a documentation error from MS. It was fixed since...
On Fri, 26 Jun 2020, at 16:07, Rémi Denis-Courmont wrote:
> 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/
>
>
>
> _______________________________________________
> vlc-devel mailing list
> To unsubscribe or modify your subscription options:
> https://mailman.videolan.org/listinfo/vlc-devel
--
Jean-Baptiste Kempf - President
+33 672 704 734
More information about the vlc-devel
mailing list