*****************************************************************************
* 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 "audio_output/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 */
-static int TryEnter(void)
+BOOL WINAPI DllMain(HINSTANCE dll, DWORD reason, LPVOID reserved)
{
- HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
- return -!!FAILED(hr);
+ (void) dll;
+ (void) reserved;
+
+ switch (reason)
+ {
+ case DLL_PROCESS_ATTACH:
+ if (!QueryPerformanceFrequency(&freq))
+ return FALSE;
+ break;
+ }
+ return TRUE;
}
-static void Enter(void)
+static UINT64 GetQPC(void)
{
- HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
- if (unlikely(FAILED(hr)))
+ LARGE_INTEGER counter;
+
+ if (!QueryPerformanceCounter(&counter))
abort();
-}
-static void Leave(void)
-{
- CoUninitialize();
+ lldiv_t d = lldiv(counter.QuadPart, freq.QuadPart);
+ return (d.quot * 10000000) + ((d.rem * 10000000) / freq.QuadPart);
}
-struct aout_sys_t
+typedef struct aout_stream_sys
{
IAudioClient *client;
- IAudioRenderClient *render;
- IAudioClock *clock;
- union
- {
- ISimpleAudioVolume *simple;
- } volume;
+
+ 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 ready; /**< Semaphore from MTA thread */
- HANDLE done; /**< Semaphore to MTA thread */
-};
+} aout_stream_sys_t;
+
-static void Play(audio_output_t *aout, block_t *block)
+/*** VLC audio output callbacks ***/
+static HRESULT TimeGet(aout_stream_t *s, mtime_t *restrict delay)
{
- aout_sys_t *sys = aout->sys;
+ aout_stream_sys_t *sys = s->sys;
+ void *pv;
+ UINT64 pos, qpcpos;
HRESULT hr;
- Enter();
- if (likely(sys->clock != NULL))
+ 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);
+
+ if (SUCCEEDED(hr))
{
- UINT64 pos, qpcpos;
+ 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;
+}
- IAudioClock_GetPosition(sys->clock, &pos, &qpcpos);
- qpcpos = (qpcpos + 5) / 10; /* 100ns -> 1µs */
- /* NOTE: this assumes mdate() uses QPC() (which it currently does). */
- aout_TimeReport(aout, qpcpos);
+static HRESULT Play(aout_stream_t *s, block_t *block)
+{
+ aout_stream_sys_t *sys = s->sys;
+ void *pv;
+ HRESULT hr;
+
+ if (sys->chans_to_reorder)
+ aout_ChannelReorder(block->p_buffer, block->i_buffer,
+ sys->chans_to_reorder, sys->chans_table, sys->format);
+
+ 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;
}
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 */
/* Out of buffer space, sleep */
msleep(AOUT_MIN_PREPARE_TIME
- + block->i_nb_samples * CLOCK_FREQ / aout->format.i_rate);
+ + block->i_nb_samples * CLOCK_FREQ / sys->rate);
}
-
- Leave();
+ 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;
- Enter();
if (paused)
hr = IAudioClient_Stop(sys->client);
else
hr = IAudioClient_Start(sys->client);
if (FAILED(hr))
- msg_Warn(aout, "cannot %s stream (error 0x%lx)",
+ msg_Warn(s, "cannot %s stream (error 0x%lx)",
paused ? "stop" : "start", hr);
- Leave();
- (void) date;
+ 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; /* Drain not implemented */
-
- Enter();
IAudioClient_Stop(sys->client);
+
hr = IAudioClient_Reset(sys->client);
if (FAILED(hr))
- msg_Warn(aout, "cannot reset stream (error 0x%lx)", hr);
- Leave();
+ msg_Warn(s, "cannot reset stream (error 0x%lx)", hr);
+ else
+ sys->written = 0;
+ return hr;
}
-static int SimpleVolumeSet(audio_output_t *aout, float vol, bool mute)
-{
- aout_sys_t *sys = aout->sys;
- HRESULT hr;
-
- if (vol > 1.)
- vol = 1.;
-
- Enter();
- /* NOTE: better change volume while muted (if mute is toggled) */
- if (mute)
- {
- hr = ISimpleAudioVolume_SetMute(sys->volume.simple, true, NULL);
- if (FAILED(hr))
- msg_Warn(aout, "cannot mute session (error 0x%lx)", hr);
- }
-
- hr = ISimpleAudioVolume_SetMasterVolume(sys->volume.simple, vol, NULL);
- if (FAILED(hr))
- msg_Warn(aout, "cannot set session volume (error 0x%lx)", hr);
- if (mute)
- {
- hr = ISimpleAudioVolume_SetMute(sys->volume.simple, false, NULL);
- if (FAILED(hr))
- msg_Warn(aout, "cannot unmute session (error 0x%lx)", hr);
- }
- Leave();
- 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)
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:
audio->i_format = VLC_CODEC_FL32;
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 create and release COM interfaces when needed. */
-static void MTAThread(void *data)
+static unsigned vlc_CheckWaveOrder (const WAVEFORMATEX *restrict wf,
+ uint8_t *restrict table)
{
- audio_output_t *aout = data;
- aout_sys_t *sys = aout->sys;
- HRESULT hr;
+ uint32_t mask = 0;
- Enter();
-
- hr = IAudioClient_GetService(sys->client, &IID_IAudioRenderClient,
- (void **)&sys->render);
- if (FAILED(hr))
+ if (wf->wFormatTag == WAVE_FORMAT_EXTENSIBLE)
{
- msg_Err(aout, "cannot get audio render service (error 0x%lx)", hr);
- goto fail;
- }
+ const WAVEFORMATEXTENSIBLE *wfe = (void *)wf;
- hr = IAudioClient_GetService(sys->client, &IID_IAudioClock,
- (void **)&sys->clock);
- if (FAILED(hr))
- msg_Warn(aout, "cannot get audio clock (error 0x%lx)", hr);
-
- /*if (AOUT_FMT_LINEAR(&format) && !exclusive)*/
- {
- hr = IAudioClient_GetService(sys->client, &IID_ISimpleAudioVolume,
- (void **)&sys->volume.simple);
+ mask = wfe->dwChannelMask;
}
-
- /* do nothing until the audio session terminates */
- ReleaseSemaphore(sys->ready, 1, NULL);
- WaitForSingleObject(sys->done, INFINITE);
-
- if (sys->clock != NULL)
- IAudioClock_Release(sys->clock);
- IAudioRenderClient_Release(sys->render);
-fail:
- Leave();
- ReleaseSemaphore(sys->ready, 1, NULL);
+ 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;
-
- if (AOUT_FMT_SPDIF(&aout->format) && !aout->b_force
- && var_InheritBool(aout, "spdif"))
- /* Fallback to other plugin until pass-through is implemented */
- return VLC_EGENERIC;
-
- 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->clock = NULL;
- sys->ready = NULL;
- sys->done = NULL;
- aout->sys = sys;
-
- if (TryEnter())
- {
- free(sys);
- return VLC_EGENERIC;
- }
- /* Get audio device according to policy */
- IMMDeviceEnumerator *devs;
- hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL,
- &IID_IMMDeviceEnumerator, (void **)&devs);
+ void *pv;
+ HRESULT hr = aout_stream_Activate(s, &IID_IAudioClient, NULL, &pv);
if (FAILED(hr))
{
- msg_Dbg(aout, "cannot create device enumerator (error 0x%lx)", hr);
- goto error;
- }
-
- 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);
+ msg_Err(s, "cannot get buffer size (error 0x%lx)", hr);
goto error;
}
- sys->ready = CreateSemaphore(NULL, 0, 1, NULL);
- sys->done = CreateSemaphore(NULL, 0, 1, NULL);
- if (unlikely(sys->ready == NULL || sys->done == NULL))
- goto error;
- /* Note: thread handle released by CRT, ignore it. */
- if (_beginthread(MTAThread, 0, aout) == (uintptr_t)-1)
- goto error;
-
- WaitForSingleObject(sys->ready, INFINITE);
- if (sys->render == NULL)
- goto error;
-
- aout->format = format;
- aout->pf_play = Play;
- aout->pf_pause = Pause;
- aout->pf_flush = Flush;
- /*if (AOUT_FMT_LINEAR(&format) && !exclusive)*/
- aout_VolumeHardInit(aout, SimpleVolumeSet, false);
- Leave();
- 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->ready != NULL)
- CloseHandle(sys->done);
if (sys->client != NULL)
IAudioClient_Release(sys->client);
- Leave();
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;
- Enter();
- ReleaseSemaphore(sys->done, 1, NULL); /* tell MTA thread to finish */
- WaitForSingleObject(sys->ready, INFINITE); /* wait for that ^ */
IAudioClient_Stop(sys->client); /* should not be needed */
IAudioClient_Release(sys->client);
- Leave();
+}
- CloseHandle(sys->done);
- CloseHandle(sys->ready);
- 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);
}