[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