[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