]> git.sesse.net Git - vlc/blobdiff - modules/audio_output/wasapi.c
vlc.desktop: add missing --started-from-file option (fixes #8839)
[vlc] / modules / audio_output / wasapi.c
index 80bcff6afb92cf04bc478fadce711e7a305edf74..6d715f59e903b111d4483d0a5509910fcdb2e788 100644 (file)
@@ -3,14 +3,14 @@
  *****************************************************************************
  * Copyright (C) 2012 RĂ©mi Denis-Courmont
  *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
  * the Free Software Foundation; either version 2.1 of the License, or
  * (at your option) any later version.
  *
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  * GNU Lesser General Public License for more details.
  *
  * You should have received a copy of the GNU Lesser General Public License
  *****************************************************************************/
 
 #ifdef HAVE_CONFIG_H
-# include "config.h"
+# include <config.h>
 #endif
 
 #define INITGUID
 #define COBJMACROS
+#define CONST_VTABLE
 
+#include <stdlib.h>
 #include <assert.h>
 #include <audioclient.h>
-#include <mmdeviceapi.h>
 
 #include <vlc_common.h>
-#include <vlc_plugin.h>
 #include <vlc_aout.h>
+#include "mmdevice.h"
 
-static int Open(vlc_object_t *);
-static void Close(vlc_object_t *);
+static LARGE_INTEGER freq; /* performance counters frequency */
 
-vlc_module_begin()
-    set_shortname("WASAPI")
-    set_description(N_("Windows Audio Session output") )
-    set_capability("audio output", 150)
-    set_category(CAT_AUDIO)
-    set_subcategory(SUBCAT_AUDIO_AOUT)
-    add_shortcut("was", "audioclient")
-    set_callbacks(Open, Close)
-vlc_module_end()
+BOOL WINAPI DllMain(HINSTANCE, DWORD, LPVOID); /* avoid warning */
 
-struct aout_sys_t
+BOOL WINAPI DllMain(HINSTANCE dll, DWORD reason, LPVOID reserved)
+{
+    (void) dll;
+    (void) reserved;
+
+    switch (reason)
+    {
+        case DLL_PROCESS_ATTACH:
+            if (!QueryPerformanceFrequency(&freq))
+                return FALSE;
+            break;
+    }
+    return TRUE;
+}
+
+static UINT64 GetQPC(void)
+{
+    LARGE_INTEGER counter;
+
+    if (!QueryPerformanceCounter(&counter))
+        abort();
+
+    lldiv_t d = lldiv(counter.QuadPart, freq.QuadPart);
+    return (d.quot * 10000000) + ((d.rem * 10000000) / freq.QuadPart);
+}
+
+typedef struct aout_stream_sys
 {
     IAudioClient *client;
-    IAudioRenderClient *render;
+
+    uint8_t chans_table[AOUT_CHAN_MAX];
+    uint8_t chans_to_reorder;
+
+    vlc_fourcc_t format; /**< Sample format */
+    unsigned rate; /**< Sample rate */
+    unsigned bytes_per_frame;
+    UINT32 written; /**< Frames written to the buffer */
     UINT32 frames; /**< Total buffer size (frames) */
-    HANDLE done; /**< Semaphore for MTA thread */
-};
+} aout_stream_sys_t;
+
+
+/*** VLC audio output callbacks ***/
+static HRESULT TimeGet(aout_stream_t *s, mtime_t *restrict delay)
+{
+    aout_stream_sys_t *sys = s->sys;
+    void *pv;
+    UINT64 pos, qpcpos;
+    HRESULT hr;
+
+    hr = IAudioClient_GetService(sys->client, &IID_IAudioClock, &pv);
+    if (SUCCEEDED(hr))
+    {
+        IAudioClock *clock = pv;
+
+        hr = IAudioClock_GetPosition(clock, &pos, &qpcpos);
+        if (FAILED(hr))
+            msg_Err(s, "cannot get position (error 0x%lx)", hr);
+        IAudioClock_Release(clock);
+    }
+    else
+        msg_Err(s, "cannot get clock (error 0x%lx)", hr);
 
-static void Play(audio_output_t *aout, block_t *block)
+    if (SUCCEEDED(hr))
+    {
+        if (pos != 0)
+        {
+            *delay = ((GetQPC() - qpcpos) / (10000000 / CLOCK_FREQ));
+            static_assert((10000000 % CLOCK_FREQ) == 0,
+                          "Frequency conversion broken");
+        }
+        else
+        {
+            *delay = sys->written * CLOCK_FREQ / sys->rate;
+            msg_Dbg(s, "extrapolating position: still propagating buffers");
+        }
+    }
+    return hr;
+}
+
+static HRESULT Play(aout_stream_t *s, block_t *block)
 {
-    aout_sys_t *sys = aout->sys;
+    aout_stream_sys_t *sys = s->sys;
+    void *pv;
     HRESULT hr;
 
-    CoInitializeEx(NULL, COINIT_MULTITHREADED);
+    if (sys->chans_to_reorder)
+        aout_ChannelReorder(block->p_buffer, block->i_buffer,
+                          sys->chans_to_reorder, sys->chans_table, sys->format);
 
-    while (block->i_nb_samples > 0)
+    hr = IAudioClient_GetService(sys->client, &IID_IAudioRenderClient, &pv);
+    if (FAILED(hr))
+    {
+        msg_Err(s, "cannot get render client (error 0x%lx)", hr);
+        goto out;
+    }
+
+    IAudioRenderClient *render = pv;
+    for (;;)
     {
         UINT32 frames;
         hr = IAudioClient_GetCurrentPadding(sys->client, &frames);
         if (FAILED(hr))
         {
-            msg_Err(aout, "cannot get current padding (error 0x%lx)", hr);
+            msg_Err(s, "cannot get current padding (error 0x%lx)", hr);
             break;
         }
 
@@ -77,309 +151,261 @@ static void Play(audio_output_t *aout, block_t *block)
             frames = block->i_nb_samples;
 
         BYTE *dst;
-        hr = IAudioRenderClient_GetBuffer(sys->render, frames, &dst);
+        hr = IAudioRenderClient_GetBuffer(render, frames, &dst);
         if (FAILED(hr))
         {
-            msg_Err(aout, "cannot get buffer (error 0x%lx)", hr);
+            msg_Err(s, "cannot get buffer (error 0x%lx)", hr);
             break;
         }
 
-        const size_t copy = frames * (size_t)aout->format.i_bytes_per_frame;
+        const size_t copy = frames * sys->bytes_per_frame;
 
         memcpy(dst, block->p_buffer, copy);
-        hr = IAudioRenderClient_ReleaseBuffer(sys->render, frames, 0);
+        hr = IAudioRenderClient_ReleaseBuffer(render, frames, 0);
         if (FAILED(hr))
         {
-            msg_Err(aout, "cannot release buffer (error 0x%lx)", hr);
+            msg_Err(s, "cannot release buffer (error 0x%lx)", hr);
             break;
         }
+        IAudioClient_Start(sys->client);
 
         block->p_buffer += copy;
         block->i_buffer -= copy;
         block->i_nb_samples -= frames;
+        sys->written += frames;
+        if (block->i_nb_samples == 0)
+            break; /* done */
 
-        /* FIXME: implement synchro */
-        IAudioClient_Start(sys->client);
-        Sleep(AOUT_MIN_PREPARE_TIME / 1000);
+        /* Out of buffer space, sleep */
+        msleep(AOUT_MIN_PREPARE_TIME
+             + block->i_nb_samples * CLOCK_FREQ / sys->rate);
     }
-
-    CoUninitialize();
+    IAudioRenderClient_Release(render);
+out:
     block_Release(block);
+
+    return hr;
 }
 
-static void Pause(audio_output_t *aout, bool paused, mtime_t date)
+static HRESULT Pause(aout_stream_t *s, bool paused)
 {
-    aout_sys_t *sys = aout->sys;
+    aout_stream_sys_t *sys = s->sys;
     HRESULT hr;
 
-    if (!paused)
-        return;
-
-    CoInitializeEx(NULL, COINIT_MULTITHREADED);
-    hr = IAudioClient_Stop(sys->client);
+    if (paused)
+        hr = IAudioClient_Stop(sys->client);
+    else
+        hr = IAudioClient_Start(sys->client);
     if (FAILED(hr))
-        msg_Warn(aout, "cannot stop stream (error 0x%lx)", hr);
-    CoUninitialize();
-    (void) date;
+        msg_Warn(s, "cannot %s stream (error 0x%lx)",
+                 paused ? "stop" : "start", hr);
+    return hr;
 }
 
-static void Flush(audio_output_t *aout, bool wait)
+static HRESULT Flush(aout_stream_t *s)
 {
-    aout_sys_t *sys = aout->sys;
+    aout_stream_sys_t *sys = s->sys;
     HRESULT hr;
 
-    if (wait)
-        return; /* Not drain implemented */
-
-    CoInitializeEx(NULL, COINIT_MULTITHREADED);
     IAudioClient_Stop(sys->client);
+
     hr = IAudioClient_Reset(sys->client);
     if (FAILED(hr))
-        msg_Warn(aout, "cannot reset stream (error 0x%lx)", hr);
-    CoUninitialize();
+        msg_Warn(s, "cannot reset stream (error 0x%lx)", hr);
+    else
+        sys->written = 0;
+    return hr;
 }
 
-/*static int VolumeSet(audio_output_t *aout, float vol, bool mute)
-{
-    aout_sys_t *sys = aout->sys;
 
-    return 0;
-}*/
+/*** Initialization / deinitialization **/
+static const uint32_t chans_out[] = {
+    SPEAKER_FRONT_LEFT, SPEAKER_FRONT_RIGHT,
+    SPEAKER_FRONT_CENTER, SPEAKER_LOW_FREQUENCY,
+    SPEAKER_BACK_LEFT, SPEAKER_BACK_RIGHT, SPEAKER_BACK_CENTER,
+    SPEAKER_SIDE_LEFT, SPEAKER_SIDE_RIGHT, 0
+};
+static const uint32_t chans_in[] = {
+    SPEAKER_FRONT_LEFT, SPEAKER_FRONT_RIGHT,
+    SPEAKER_SIDE_LEFT, SPEAKER_SIDE_RIGHT,
+    SPEAKER_BACK_LEFT, SPEAKER_BACK_RIGHT, SPEAKER_BACK_CENTER,
+    SPEAKER_FRONT_CENTER, SPEAKER_LOW_FREQUENCY, 0
+};
 
 static void vlc_ToWave(WAVEFORMATEXTENSIBLE *restrict wf,
                        audio_sample_format_t *restrict audio)
 {
     switch (audio->i_format)
     {
-#if 0
-        case VLC_CODEC_FL32:
         case VLC_CODEC_FL64:
+            audio->i_format = VLC_CODEC_FL32;
+        case VLC_CODEC_FL32:
             wf->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
             break;
 
-        case VLC_CODEC_S8:
+        case VLC_CODEC_U8:
+            audio->i_format = VLC_CODEC_S16N;
         case VLC_CODEC_S16N:
-        case VLC_CODEC_S24N:
-        case VLC_CODEC_S32N:
             wf->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
             break;
-#endif
+
         default:
             audio->i_format = VLC_CODEC_FL32;
-            audio->i_rate = 48000;
             wf->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
             break;
-     }
-     aout_FormatPrepare (audio);
-
-     wf->Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
-     wf->Format.nChannels = audio->i_channels;
-     wf->Format.nSamplesPerSec = audio->i_rate;
-     wf->Format.nAvgBytesPerSec = audio->i_bytes_per_frame * audio->i_rate;
-     wf->Format.nBlockAlign = audio->i_bytes_per_frame;
-     wf->Format.wBitsPerSample = audio->i_bitspersample;
-     wf->Format.cbSize = sizeof (*wf) - sizeof (wf->Format);
-
-     wf->Samples.wValidBitsPerSample = audio->i_bitspersample;
-
-     wf->dwChannelMask = 0;
-     if (audio->i_physical_channels & AOUT_CHAN_LEFT)
-         wf->dwChannelMask |= SPEAKER_FRONT_LEFT;
-     if (audio->i_physical_channels & AOUT_CHAN_RIGHT)
-         wf->dwChannelMask |= SPEAKER_FRONT_RIGHT;
-     if (audio->i_physical_channels & AOUT_CHAN_CENTER)
-         wf->dwChannelMask |= SPEAKER_FRONT_CENTER;
-     if (audio->i_physical_channels & AOUT_CHAN_LFE)
-         wf->dwChannelMask |= SPEAKER_LOW_FREQUENCY;
-     // TODO: reorder
-     if (audio->i_physical_channels & AOUT_CHAN_REARLEFT)
-         wf->dwChannelMask |= SPEAKER_BACK_LEFT;
-     if (audio->i_physical_channels & AOUT_CHAN_REARRIGHT)
-         wf->dwChannelMask |= SPEAKER_BACK_RIGHT;
-     /* ... */
-     if (audio->i_physical_channels & AOUT_CHAN_REARCENTER)
-         wf->dwChannelMask |= SPEAKER_BACK_CENTER;
-     if (audio->i_physical_channels & AOUT_CHAN_MIDDLELEFT)
-         wf->dwChannelMask |= SPEAKER_SIDE_LEFT;
-     if (audio->i_physical_channels & AOUT_CHAN_MIDDLERIGHT)
-         wf->dwChannelMask |= SPEAKER_SIDE_RIGHT;
-     /* ... */
+    }
+    aout_FormatPrepare (audio);
+
+    wf->Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
+    wf->Format.nChannels = audio->i_channels;
+    wf->Format.nSamplesPerSec = audio->i_rate;
+    wf->Format.nAvgBytesPerSec = audio->i_bytes_per_frame * audio->i_rate;
+    wf->Format.nBlockAlign = audio->i_bytes_per_frame;
+    wf->Format.wBitsPerSample = audio->i_bitspersample;
+    wf->Format.cbSize = sizeof (*wf) - sizeof (wf->Format);
+
+    wf->Samples.wValidBitsPerSample = audio->i_bitspersample;
+
+    wf->dwChannelMask = 0;
+    for (unsigned i = 0; pi_vlc_chan_order_wg4[i]; i++)
+        if (audio->i_physical_channels & pi_vlc_chan_order_wg4[i])
+            wf->dwChannelMask |= chans_in[i];
 }
 
 static int vlc_FromWave(const WAVEFORMATEX *restrict wf,
                         audio_sample_format_t *restrict audio)
 {
-    /* FIXME? different sample format? possible? */
     audio->i_rate = wf->nSamplesPerSec;
-    /* FIXME */
+    audio->i_physical_channels = 0;
+
+    if (wf->wFormatTag == WAVE_FORMAT_EXTENSIBLE)
+    {
+        const WAVEFORMATEXTENSIBLE *wfe = (void *)wf;
+
+        for (unsigned i = 0; chans_in[i]; i++)
+            if (wfe->dwChannelMask & chans_in[i])
+                audio->i_physical_channels |= pi_vlc_chan_order_wg4[i];
+    }
+
+    audio->i_original_channels = audio->i_physical_channels;
+    aout_FormatPrepare (audio);
+
     if (wf->nChannels != audio->i_channels)
         return -1;
-
-    aout_FormatPrepare(audio);
     return 0;
 }
 
-/* Dummy thread to keep COM MTA alive */
-static void MTAThread(void *data)
+static unsigned vlc_CheckWaveOrder (const WAVEFORMATEX *restrict wf,
+                                    uint8_t *restrict table)
 {
-    HANDLE done = data;
-    HRESULT hr;
+    uint32_t mask = 0;
 
-    hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
-    if (unlikely(FAILED(hr)))
-        abort();
-    WaitForSingleObject(done, INFINITE);
-    CoUninitialize();
-    CloseHandle(done);
+    if (wf->wFormatTag == WAVE_FORMAT_EXTENSIBLE)
+    {
+        const WAVEFORMATEXTENSIBLE *wfe = (void *)wf;
+
+        mask = wfe->dwChannelMask;
+    }
+    return aout_CheckChannelReorder(chans_in, chans_out, mask, table);
 }
 
-static int Open(vlc_object_t *obj)
+static HRESULT Start(aout_stream_t *s, audio_sample_format_t *restrict fmt,
+                     const GUID *sid)
 {
-    audio_output_t *aout = (audio_output_t *)obj;
-    HRESULT hr;
-
-    aout_sys_t *sys = malloc(sizeof (*sys));
+    aout_stream_sys_t *sys = malloc(sizeof (*sys));
     if (unlikely(sys == NULL))
-        return VLC_ENOMEM;
+        return E_OUTOFMEMORY;
     sys->client = NULL;
-    sys->render = NULL;
-    sys->done = NULL;
 
-    hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
+    void *pv;
+    HRESULT hr = aout_stream_Activate(s, &IID_IAudioClient, NULL, &pv);
     if (FAILED(hr))
     {
-        free(sys);
-        return VLC_EGENERIC;
-    }
-
-    /* Select audio device */
-    IMMDeviceEnumerator *devs;
-    hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL,
-                          &IID_IMMDeviceEnumerator, (void **)&devs);
-    if (FAILED(hr))
-    {
-        msg_Dbg(aout, "cannot create device enumerator (error 0x%lx)", hr);
-        goto error;
-    }
-
-    /* TODO: support selecting a device from config? */
-    IMMDevice *dev;
-    hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(devs, eRender,
-                                                     eConsole, &dev);
-    IMMDeviceEnumerator_Release(devs);
-    if (FAILED(hr))
-    {
-        msg_Err(aout, "cannot get audio endpoint (error 0x%lx)", hr);
-        goto error;
-    }
-
-    LPWSTR str;
-    hr = IMMDevice_GetId(dev, &str);
-    if (SUCCEEDED(hr))
-    {
-        msg_Dbg(aout, "using device %ls", str);
-        CoTaskMemFree(str);
-    }
-
-    hr = IMMDevice_Activate(dev, &IID_IAudioClient, CLSCTX_ALL, NULL,
-                            (void **)&sys->client);
-    IMMDevice_Release(dev);
-    if (FAILED(hr))
-    {
-        msg_Err(aout, "cannot activate audio client (error 0x%lx)", hr);
+        msg_Err(s, "cannot activate client (error 0x%lx)", hr);
         goto error;
     }
+    sys->client = pv;
 
     /* Configure audio stream */
-    audio_sample_format_t format = aout->format;
     WAVEFORMATEXTENSIBLE wf;
     WAVEFORMATEX *pwf;
 
-    vlc_ToWave(&wf, &format);
+    vlc_ToWave(&wf, fmt);
     hr = IAudioClient_IsFormatSupported(sys->client, AUDCLNT_SHAREMODE_SHARED,
                                         &wf.Format, &pwf);
-    // TODO: deal with (hr == AUDCLNT_E_DEVICE_INVALIDATED) ?
     if (FAILED(hr))
     {
-        msg_Err(aout, "cannot negotiate audio format (error 0x%lx)", hr);
+        msg_Err(s, "cannot negotiate audio format (error 0x%lx)", hr);
         goto error;
     }
 
     if (hr == S_FALSE)
     {
         assert(pwf != NULL);
-        if (vlc_FromWave(pwf, &format))
+        if (vlc_FromWave(pwf, fmt))
         {
             CoTaskMemFree(pwf);
-            msg_Err(aout, "unsupported audio format");
+            msg_Err(s, "unsupported audio format");
+            hr = E_INVALIDARG;
             goto error;
         }
-        msg_Dbg(aout, "modified format");
+        msg_Dbg(s, "modified format");
     }
     else
         assert(pwf == NULL);
+
+    sys->chans_to_reorder = vlc_CheckWaveOrder((hr == S_OK) ? &wf.Format : pwf,
+                                               sys->chans_table);
+    sys->format = fmt->i_format;
+
     hr = IAudioClient_Initialize(sys->client, AUDCLNT_SHAREMODE_SHARED, 0,
                                  AOUT_MAX_PREPARE_TIME * 10, 0,
-                                 (hr == S_OK) ? &wf.Format : pwf, NULL);
+                                 (hr == S_OK) ? &wf.Format : pwf, sid);
     CoTaskMemFree(pwf);
     if (FAILED(hr))
     {
-        msg_Err(aout, "cannot initialize audio client (error 0x%lx)", hr);
+        msg_Err(s, "cannot initialize audio client (error 0x%lx)", hr);
         goto error;
     }
 
     hr = IAudioClient_GetBufferSize(sys->client, &sys->frames);
     if (FAILED(hr))
     {
-        msg_Err(aout, "cannot get buffer size (error 0x%lx)", hr);
-        goto error;
-    }
-
-    hr = IAudioClient_GetService(sys->client, &IID_IAudioRenderClient,
-                                 (void **)&sys->render);
-    if (FAILED(hr))
-    {
-        msg_Err(aout, "cannot get audio render service (error 0x%lx)", hr);
+        msg_Err(s, "cannot get buffer size (error 0x%lx)", hr);
         goto error;
     }
 
-    sys->done = CreateSemaphore(NULL, 0, 1, NULL);
-    if (unlikely(sys->done == NULL))
-        goto error;
-    /* Note: thread handle released by CRT, ignore it. */
-    if (_beginthread(MTAThread, 0, sys->done) == (uintptr_t)-1)
-        goto error;
-
-    aout->format = format;
-    aout->sys = sys;
-    aout->pf_play = Play;
-    aout->pf_pause = Pause;
-    aout->pf_flush = Flush;
-    aout_VolumeNoneInit (aout);
-    CoUninitialize();
-    return VLC_SUCCESS;
+    sys->rate = fmt->i_rate;
+    sys->bytes_per_frame = fmt->i_bytes_per_frame;
+    sys->written = 0;
+    s->sys = sys;
+    s->time_get = TimeGet;
+    s->play = Play;
+    s->pause = Pause;
+    s->flush = Flush;
+    return S_OK;
 error:
-    if (sys->done != NULL)
-        CloseHandle(sys->done);
-    if (sys->render != NULL)
-        IAudioRenderClient_Release(sys->render);
     if (sys->client != NULL)
         IAudioClient_Release(sys->client);
-    CoUninitialize();
     free(sys);
-    return VLC_EGENERIC;
+    return hr;
 }
 
-static void Close (vlc_object_t *obj)
+static void Stop(aout_stream_t *s)
 {
-    audio_output_t *aout = (audio_output_t *)obj;
-    aout_sys_t *sys = aout->sys;
+    aout_stream_sys_t *sys = s->sys;
 
-    CoInitializeEx(NULL, COINIT_MULTITHREADED);
-    IAudioRenderClient_Release(sys->render);
+    IAudioClient_Stop(sys->client); /* should not be needed */
     IAudioClient_Release(sys->client);
-    CoUninitialize();
+}
 
-    ReleaseSemaphore(sys->done, 1, NULL); /* MTA thread will exit */
-    free(sys);
+HRESULT aout_stream_Start(aout_stream_t *s,
+                          audio_sample_format_t *restrict fmt, const GUID *sid)
+{
+    return Start(s, fmt, sid);
+}
+
+void aout_stream_Stop(aout_stream_t *s)
+{
+    Stop(s);
 }