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

Steve Lhomme robux4 at ycbcr.xyz
Wed Jun 24 14:26:57 CEST 2020


See the API https://docs.microsoft.com/en-us/windows/win32/api/mmdeviceapi/nf-mmdeviceapi-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.cpp | 212 +++++++++++++++++++++++++++---
 2 files changed, 196 insertions(+), 18 deletions(-)

diff --git a/modules/audio_output/Makefile.am b/modules/audio_output/Makefile.am
index e1812b9bc91..a72ba649551 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.cpp 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.cpp b/modules/audio_output/winstore.cpp
index 66ce3721dfb..5061600d1cb 100644
--- a/modules/audio_output/winstore.cpp
+++ b/modules/audio_output/winstore.cpp
@@ -24,7 +24,6 @@
 
 #define INITGUID
 
-#include <cassert>
 #include <audiopolicy.h>
 
 #include <vlc_common.h>
@@ -33,6 +32,9 @@
 #include <vlc_modules.h>
 #include "audio_output/mmdevice.h"
 
+#include <audioclient.h>
+#include <mmdeviceapi.h>
+
 #include <wrl.h>
 #include <wrl/client.h>
 using namespace Microsoft::WRL;
@@ -57,8 +59,141 @@ typedef struct
     aout_stream_t *stream; /**< Underlying audio output stream */
     module_t *module;
     IAudioClient *client;
+    wchar_t* default_device; // read once on open
+    wchar_t* requested_device;
 } aout_sys_t;
 
+
+class MMDeviceLocator :
+    public IActivateAudioInterfaceCompletionHandler
+{
+public:
+    MMDeviceLocator();
+    virtual ~MMDeviceLocator();
+
+    IAudioClient* WaitForAudioClient(const wchar_t* devId);
+
+    // IUnknown methods
+    STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
+    STDMETHODIMP_(ULONG) AddRef();
+    STDMETHODIMP_(ULONG) Release();
+
+    // IActivateAudioInterfaceCompletionHandler
+    HRESULT ActivateCompleted(IActivateAudioInterfaceAsyncOperation *operation);
+
+private:
+    IAudioClient*          m_AudioClient;
+    vlc_sem_t              async_completed;
+    LONG                   m_cRef;
+};
+
+MMDeviceLocator::MMDeviceLocator()
+    : m_cRef(0)
+{
+    vlc_sem_init(&async_completed, 0);
+}
+
+MMDeviceLocator::~MMDeviceLocator()
+{
+}
+
+HRESULT MMDeviceLocator::ActivateCompleted(IActivateAudioInterfaceAsyncOperation *operation)
+{
+    HRESULT hr;
+    HRESULT hrActivateResult;
+    ComPtr<IUnknown> audioInterface;
+
+    hr = operation->GetActivateResult(&hrActivateResult, audioInterface.GetAddressOf());
+    if (SUCCEEDED(hr) && SUCCEEDED(hrActivateResult) && audioInterface.Get() != nullptr)
+    {
+        hr = audioInterface->QueryInterface(&m_AudioClient);
+        if (SUCCEEDED(hr))
+        {
+            // "BackgroundCapableMedia" does not work in UWP
+            AudioClientProperties props = AudioClientProperties {
+                .cbSize = sizeof(props),
+                .bIsOffload = FALSE,
+                .eCategory = AudioCategory_Movie,
+                .Options = AUDCLNT_STREAMOPTIONS_NONE
+            };
+            ComPtr<IAudioClient2> audioClient2;
+            if (SUCCEEDED(audioInterface->QueryInterface(audioClient2.GetAddressOf()))
+               && audioClient2.Get())
+            {
+                if (FAILED(audioClient2->SetClientProperties(&props))) {
+                    OutputDebugString(TEXT("Failed to set audio client properties"));
+                }
+            }
+        }
+    }
+    vlc_sem_post( &async_completed );
+    return hr;
+}
+
+
+/* IUnknown methods */
+STDMETHODIMP MMDeviceLocator::QueryInterface(REFIID riid, void **ppv)
+{
+    if( riid == IID_IUnknown ||
+        riid == IID_IActivateAudioInterfaceCompletionHandler )
+    {
+        AddRef();
+        *ppv = this;
+        return S_OK;
+    }
+    if( riid == IID_IAgileObject )
+    {
+        *ppv = nullptr;
+        return S_OK;
+    }
+    *ppv = nullptr;
+    return ResultFromScode( E_NOINTERFACE );
+}
+
+STDMETHODIMP_(ULONG) MMDeviceLocator::AddRef()
+{
+    return InterlockedIncrement(&m_cRef);
+}
+
+STDMETHODIMP_(ULONG) MMDeviceLocator::Release()
+{
+    if( !InterlockedDecrement(&m_cRef) )
+        delete this;
+
+    return m_cRef;
+}
+
+IAudioClient* MMDeviceLocator::WaitForAudioClient(const wchar_t* devId)
+{
+    ComPtr<IActivateAudioInterfaceAsyncOperation> asyncOp;
+
+    m_AudioClient = nullptr;
+    if (FAILED(ActivateAudioInterfaceAsync(devId, __uuidof(IAudioClient), nullptr, this, asyncOp.GetAddressOf())))
+        return nullptr;
+
+    vlc_sem_wait( &async_completed );
+
+    return m_AudioClient;
+}
+
+static IAudioClient* GetAudioClient(const wchar_t* devId)
+{
+    assert(devId != nullptr);
+    ComPtr<MMDeviceLocator> audioReg(new MMDeviceLocator());
+    return audioReg->WaitForAudioClient(devId);
+}
+
+static void SetRequestedDevice(audio_output_t *aout, wchar_t *psz_device)
+{
+    aout_sys_t* sys = reinterpret_cast<aout_sys_t*>(aout->sys);
+    if (sys->requested_device != psz_device)
+    {
+        if (sys->requested_device != sys->default_device)
+            free(sys->requested_device);
+        sys->requested_device = psz_device;
+    }
+}
+
 static void ResetInvalidated(audio_output_t *aout, HRESULT hr)
 {
     /* Select the default device (and restart) on unplug */
@@ -67,6 +202,8 @@ static void ResetInvalidated(audio_output_t *aout, HRESULT hr)
     {
         aout_sys_t* sys = reinterpret_cast<aout_sys_t*>(aout->sys);
         sys->client = nullptr;
+        SetRequestedDevice(aout, sys->default_device);
+        aout_RestartRequest(aout, AOUT_RESTART_OUTPUT);
     }
 }
 
@@ -226,12 +363,36 @@ static int Start(audio_output_t *aout, audio_sample_format_t *restrict fmt)
     if (unlikely(s == nullptr))
         return -1;
 
-    s->owner.device = sys->client;
+    if (sys->requested_device != nullptr)
+    {
+        sys->client = GetAudioClient(sys->requested_device);
+        if (sys->client == nullptr)
+        {
+            vlc_object_delete(&s->obj);
+            return -1;
+        }
+    }
     s->owner.activate = ActivateDevice;
 
     EnterMTA();
-    sys->module = vlc_module_load(vlc_object_logger(&s->obj), "aout stream", nullptr, false,
-                                  aout_stream_Start, s, fmt, &hr);
+
+    for (;;)
+    {
+        hr = S_OK;
+        s->owner.device = sys->client;
+        sys->module = vlc_module_load(vlc_object_logger(&s->obj), "aout stream", nullptr, false,
+                                      aout_stream_Start, s, fmt, &hr);
+        if (hr == AUDCLNT_E_ALREADY_INITIALIZED)
+            sys->client = GetAudioClient(sys->requested_device ? sys->requested_device : sys->default_device);
+        else if (hr == AUDCLNT_E_DEVICE_INVALIDATED)
+            sys->client = GetAudioClient(sys->default_device);
+        else
+            break;
+        if (sys->client == nullptr || sys->module != nullptr)
+            break;
+    }
+    SetRequestedDevice(aout, nullptr);
+
     LeaveMTA();
 
     if (sys->module == nullptr)
@@ -240,6 +401,7 @@ static int Start(audio_output_t *aout, audio_sample_format_t *restrict fmt)
         return -1;
     }
 
+    sys->client->AddRef();
     assert (sys->stream == nullptr);
     sys->stream = s;
     return 0;
@@ -257,21 +419,30 @@ static void Stop(audio_output_t *aout)
 
     vlc_object_delete(&sys->stream->obj);
     sys->stream = nullptr;
+    if(sys->client != nullptr)
+    {
+        sys->client->Release();
+    }
 }
 
 static int DeviceSelect(audio_output_t *aout, const char* psz_device)
 {
+    aout_sys_t *sys = reinterpret_cast<aout_sys_t*>(aout->sys);
     if( psz_device == nullptr )
-        return VLC_EGENERIC;
-    char* psz_end;
-    aout_sys_t* sys = reinterpret_cast<aout_sys_t*>(aout->sys);
-    intptr_t ptr = strtoll( psz_device, &psz_end, 16 );
-    if ( *psz_end != 0 )
-        return VLC_EGENERIC;
-    if (sys->client == reinterpret_cast<IAudioClient*>(ptr))
-        return VLC_SUCCESS;
-    sys->client = reinterpret_cast<IAudioClient*>(ptr);
-    var_SetAddress( vlc_object_parent(aout), "winstore-client", sys->client );
+    {
+        SetRequestedDevice(aout, sys->default_device);
+    }
+    else
+    {
+        size_t len = MultiByteToWideChar(CP_UTF8, 0, psz_device, -1, nullptr, 0);
+        if (len == 0)
+            return VLC_ENOMEM;
+        wchar_t *requested_device = new wchar_t[len];
+        if (unlikely(requested_device == nullptr))
+            return VLC_ENOMEM;
+        MultiByteToWideChar(CP_UTF8, 0, psz_device, -1, requested_device, len);
+        SetRequestedDevice(aout, requested_device);
+    }
     aout_RestartRequest( aout, AOUT_RESTART_OUTPUT );
     return VLC_SUCCESS;
 }
@@ -284,11 +455,17 @@ static int Open(vlc_object_t *obj)
     if (unlikely(sys == nullptr))
         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;
+    }
+
     aout->sys = sys;
     sys->stream = nullptr;
-    sys->client = reinterpret_cast<IAudioClient*>(var_CreateGetAddress( vlc_object_parent(aout), "winstore-client" ));
-    if (sys->client != nullptr)
-        msg_Dbg( aout, "Reusing previous client: %p", sys->client );
+    sys->client = nullptr;
+    sys->requested_device = sys->default_device;
     aout->start = Start;
     aout->stop = Stop;
     aout->time_get = TimeGet;
@@ -306,6 +483,7 @@ static void Close(vlc_object_t *obj)
     audio_output_t *aout = (audio_output_t *)obj;
     aout_sys_t *sys = reinterpret_cast<aout_sys_t*>(aout->sys);
 
+    CoTaskMemFree(sys->default_device);
     free(sys);
 }
 
-- 
2.26.2



More information about the vlc-devel mailing list