[vlc-devel] [PATCH v2 3/4] aout: winstore: select the audio output using ActivateAudioInterfaceAsync

Steve Lhomme robux4 at ycbcr.xyz
Mon Jun 29 12:40:32 CEST 2020


The documentation from the provided link says:

There are some activations that are explicitly safe and therefore don't 
require that this function be called from the main UI thread. These 
explicitly safe activations include:

- Calling ActivateAudioInterfaceAsync with a deviceInterfacePath that 
specifies an audio render device and an riid that specifies the 
IAudioClient interface.

This is our case.

On 2020-06-26 16:20, Jean-Baptiste Kempf wrote:
> 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
> _______________________________________________
> vlc-devel mailing list
> To unsubscribe or modify your subscription options:
> https://mailman.videolan.org/listinfo/vlc-devel
> 


More information about the vlc-devel mailing list