X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=modules%2Faudio_output%2Fwasapi.c;h=73e8160256c29bf6683f5dcb320464adaf8a3a4a;hb=d3de0ffc472ddaf38fe6ba20f05071742ad8d86b;hp=273571ce4e7449f92a15a38b15e5d95729ce8a42;hpb=350e328a95aba120c493aa00b4804724181d4ba3;p=vlc diff --git a/modules/audio_output/wasapi.c b/modules/audio_output/wasapi.c index 273571ce4e..73e8160256 100644 --- a/modules/audio_output/wasapi.c +++ b/modules/audio_output/wasapi.c @@ -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 @@ -19,109 +19,132 @@ *****************************************************************************/ #ifdef HAVE_CONFIG_H -# include "config.h" +# include #endif #define INITGUID #define COBJMACROS #define CONST_VTABLE +#include #include #include -#include -#include #include -#include #include -#include - -DEFINE_GUID (GUID_VLC_AUD_OUT, 0x4533f59d, 0x59ee, 0x00c6, - 0xad, 0xb2, 0xc6, 0x8b, 0x50, 0x1a, 0x66, 0x55); +#include +#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(vlc_object_t *obj) +BOOL WINAPI DllMain(HINSTANCE dll, DWORD reason, LPVOID reserved) { - HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); - if (unlikely(FAILED(hr))) + (void) dll; + (void) reserved; + + switch (reason) { - msg_Err (obj, "cannot initialize COM (error 0x%lx)", hr); - return -1; + case DLL_PROCESS_ATTACH: + if (!QueryPerformanceFrequency(&freq)) + return FALSE; + break; } - return 0; + return TRUE; } -#define TryEnter(o) TryEnter(VLC_OBJECT(o)) -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 { - audio_output_t *aout; IAudioClient *client; - IAudioRenderClient *render; - IAudioClock *clock; - IAudioSessionControl *control; - struct IAudioSessionEvents events; - LONG refs; + 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; + UINT64 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; /*** VLC audio output callbacks ***/ -static void Play(audio_output_t *aout, block_t *block, mtime_t *restrict drift) +static HRESULT TimeGet(aout_stream_t *s, mtime_t *restrict delay) { - aout_sys_t *sys = aout->sys; - HRESULT hr = S_OK; + aout_stream_sys_t *sys = s->sys; + void *pv; + UINT64 pos, qpcpos, freq; + HRESULT hr; - Enter(); - if (likely(sys->clock != NULL)) + hr = IAudioClient_GetService(sys->client, &IID_IAudioClock, &pv); + if (FAILED(hr)) { - UINT64 pos, qpcpos; + msg_Err(s, "cannot get clock (error 0x%lx)", hr); + return hr; + } - /* NOTE: this assumes mdate() uses QPC() (which it currently does). */ - hr = IAudioClock_GetPosition(sys->clock, &pos, &qpcpos); - if (SUCCEEDED(hr)) - { - qpcpos = (qpcpos + 5) / 10; /* 100ns -> 1µs */ - *drift = mdate() - qpcpos; - } - else - msg_Warn(aout, "cannot get position (error 0x%lx)", hr); + IAudioClock *clock = pv; + + hr = IAudioClock_GetPosition(clock, &pos, &qpcpos); + if (SUCCEEDED(hr)) + hr = IAudioClock_GetFrequency(clock, &freq); + IAudioClock_Release(clock); + if (FAILED(hr)) + { + msg_Err(s, "cannot get position (error 0x%lx)", hr); + return hr; + } + + lldiv_t w = lldiv(sys->written, sys->rate); + lldiv_t r = lldiv(pos, freq); + + static_assert((10000000 % CLOCK_FREQ) == 0, "Frequency conversion broken"); + + *delay = ((w.quot - r.quot) * CLOCK_FREQ) + + ((w.rem * CLOCK_FREQ) / sys->rate) + - ((r.rem * CLOCK_FREQ) / freq) + - ((GetQPC() - qpcpos) / (10000000 / CLOCK_FREQ)); + + return hr; +} + +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; } @@ -131,20 +154,20 @@ static void Play(audio_output_t *aout, block_t *block, mtime_t *restrict drift) 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); @@ -152,328 +175,68 @@ static void Play(audio_output_t *aout, block_t *block, mtime_t *restrict drift) 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); + msleep(sys->frames * (CLOCK_FREQ / 2) / sys->rate); } - - Leave(); + IAudioRenderClient_Release(render); +out: block_Release(block); - /* Restart on unplug */ - if (unlikely(hr == AUDCLNT_E_DEVICE_INVALIDATED)) - var_TriggerCallback(aout, "audio-device"); + 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(); -} - -static int SimpleVolumeSet(audio_output_t *aout, float vol) -{ - ISimpleAudioVolume *simple; - HRESULT hr; - - if (TryEnter(aout)) - return -1; - hr = IAudioClient_GetService(aout->sys->client, &IID_ISimpleAudioVolume, - (void **)&simple); - if (SUCCEEDED(hr)) - { - hr = ISimpleAudioVolume_SetMasterVolume(simple, vol, NULL); - ISimpleAudioVolume_Release(simple); - } - Leave(); - - if (FAILED(hr)) - { - msg_Err(aout, "cannot set volume (error 0x%lx)", hr); - return -1; - } - return 0; -} -static int SimpleMuteSet(audio_output_t *aout, bool mute) -{ - ISimpleAudioVolume *simple; - HRESULT hr; - - if (TryEnter(aout)) - return -1; - hr = IAudioClient_GetService(aout->sys->client, &IID_ISimpleAudioVolume, - (void **)&simple); + hr = IAudioClient_Reset(sys->client); if (SUCCEEDED(hr)) { - hr = ISimpleAudioVolume_SetMute(simple, mute, NULL); - ISimpleAudioVolume_Release(simple); - } - Leave(); - - if (FAILED(hr)) - { - msg_Err(aout, "cannot set mute (error 0x%lx)", hr); - return -1; - } - return 0; -} - - -/*** Audio devices ***/ -static int DeviceChanged(vlc_object_t *obj, const char *varname, - vlc_value_t prev, vlc_value_t cur, void *data) -{ - aout_ChannelsRestart(obj, varname, prev, cur, data); - - if (!var_Type (obj, "wasapi-audio-device")) - var_Create (obj, "wasapi-audio-device", VLC_VAR_STRING); - var_SetString (obj, "wasapi-audio-device", cur.psz_string); - return VLC_SUCCESS; -} - -static void GetDevices(vlc_object_t *obj, IMMDeviceEnumerator *it) -{ - HRESULT hr; - vlc_value_t val, text; - - text.psz_string = _("Audio Device"); - var_Change (obj, "audio-device", VLC_VAR_SETTEXT, &text, NULL); - - IMMDeviceCollection *devs; - hr = IMMDeviceEnumerator_EnumAudioEndpoints(it, eRender, - DEVICE_STATE_ACTIVE, &devs); - if (FAILED(hr)) - { - msg_Warn (obj, "cannot enumerate audio endpoints (error 0x%lx)", hr); - return; - } - - UINT n; - hr = IMMDeviceCollection_GetCount(devs, &n); - if (FAILED(hr)) - n = 0; - while (n > 0) - { - IMMDevice *dev; - - hr = IMMDeviceCollection_Item(devs, --n, &dev); - if (FAILED(hr)) - continue; - - /* Unique device ID */ - LPWSTR devid; - hr = IMMDevice_GetId(dev, &devid); - if (FAILED(hr)) - { - IMMDevice_Release(dev); - continue; - } - val.psz_string = FromWide(devid); - CoTaskMemFree(devid); - text.psz_string = val.psz_string; - - /* User-readable device name */ - IPropertyStore *props; - hr = IMMDevice_OpenPropertyStore(dev, STGM_READ, &props); - if (SUCCEEDED(hr)) - { - PROPVARIANT v; - - PropVariantInit(&v); -#ifdef FIXED - hr = IPropertyStore_GetValue(props, PKEY_Device_FriendlyName, &v); - if (SUCCEEDED(hr)) - text.psz_string = FromWide(v.pwszVal); -#endif - PropVariantClear(&v); - IPropertyStore_Release(props); - } - IMMDevice_Release(dev); - - var_Change(obj, "audio-device", VLC_VAR_ADDCHOICE, &val, &text); - if (likely(text.psz_string != val.psz_string)) - free(text.psz_string); - free(val.psz_string); - } - IMMDeviceCollection_Release(devs); -} - - -/*** Audio session events ***/ -static inline aout_sys_t *vlc_AudioSessionEvents_sys(IAudioSessionEvents *this) -{ - return (aout_sys_t *)(((char *)this) - offsetof(aout_sys_t, events)); -} - -static STDMETHODIMP -vlc_AudioSessionEvents_QueryInterface(IAudioSessionEvents *this, REFIID riid, - void **ppv) -{ - if (IsEqualIID(riid, &IID_IUnknown) - || IsEqualIID(riid, &IID_IAudioSessionEvents)) - { - *ppv = this; - IUnknown_AddRef(this); - return S_OK; + msg_Dbg(s, "reset"); + sys->written = 0; } else - { - *ppv = NULL; - return E_NOINTERFACE; - } -} - -static STDMETHODIMP_(ULONG) -vlc_AudioSessionEvents_AddRef(IAudioSessionEvents *this) -{ - aout_sys_t *sys = vlc_AudioSessionEvents_sys(this); - return InterlockedIncrement(&sys->refs); -} - -static STDMETHODIMP_(ULONG) -vlc_AudioSessionEvents_Release(IAudioSessionEvents *this) -{ - aout_sys_t *sys = vlc_AudioSessionEvents_sys(this); - return InterlockedDecrement(&sys->refs); + msg_Warn(s, "cannot reset stream (error 0x%lx)", hr); + return hr; } -static STDMETHODIMP -vlc_AudioSessionEvents_OnDisplayNameChanged(IAudioSessionEvents *this, - LPCWSTR wname, LPCGUID ctx) -{ - aout_sys_t *sys = vlc_AudioSessionEvents_sys(this); - audio_output_t *aout = sys->aout; - - msg_Dbg(aout, "display name changed: %ls", wname); - (void) ctx; - return S_OK; -} - -static STDMETHODIMP -vlc_AudioSessionEvents_OnIconPathChanged(IAudioSessionEvents *this, - LPCWSTR wpath, LPCGUID ctx) -{ - aout_sys_t *sys = vlc_AudioSessionEvents_sys(this); - audio_output_t *aout = sys->aout; - - msg_Dbg(aout, "icon path changed: %ls", wpath); - (void) ctx; - return S_OK; -} - -static STDMETHODIMP -vlc_AudioSessionEvents_OnSimpleVolumeChanged(IAudioSessionEvents *this, float vol, - WINBOOL mute, LPCGUID ctx) -{ - aout_sys_t *sys = vlc_AudioSessionEvents_sys(this); - audio_output_t *aout = sys->aout; - - msg_Dbg(aout, "simple volume changed: %f, muting %sabled", vol, - mute ? "en" : "dis"); - aout_VolumeReport(aout, vol); - aout_MuteReport(aout, mute == TRUE); - (void) ctx; - return S_OK; -} - -static STDMETHODIMP -vlc_AudioSessionEvents_OnChannelVolumeChanged(IAudioSessionEvents *this, - DWORD count, float *vols, - DWORD changed, LPCGUID ctx) -{ - aout_sys_t *sys = vlc_AudioSessionEvents_sys(this); - audio_output_t *aout = sys->aout; - - msg_Dbg(aout, "channel volume %lu of %lu changed: %f", changed, count, - vols[changed]); - (void) ctx; - return S_OK; -} - -static STDMETHODIMP -vlc_AudioSessionEvents_OnGroupingParamChanged(IAudioSessionEvents *this, - LPCGUID param, LPCGUID ctx) - -{ - aout_sys_t *sys = vlc_AudioSessionEvents_sys(this); - audio_output_t *aout = sys->aout; - - msg_Dbg(aout, "grouping parameter changed"); - (void) param; - (void) ctx; - return S_OK; -} - -static STDMETHODIMP -vlc_AudioSessionEvents_OnStateChanged(IAudioSessionEvents *this, - AudioSessionState state) -{ - aout_sys_t *sys = vlc_AudioSessionEvents_sys(this); - audio_output_t *aout = sys->aout; - - msg_Dbg(aout, "state changed: %d", state); - return S_OK; -} -static STDMETHODIMP -vlc_AudioSessionEvents_OnSessionDisconnected(IAudioSessionEvents *this, - AudioSessionDisconnectReason reason) -{ - aout_sys_t *sys = vlc_AudioSessionEvents_sys(this); - audio_output_t *aout = sys->aout; - - msg_Dbg(aout, "session disconnected: reason %d", reason); - return S_OK; -} - -static const struct IAudioSessionEventsVtbl vlc_AudioSessionEvents = -{ - vlc_AudioSessionEvents_QueryInterface, - vlc_AudioSessionEvents_AddRef, - vlc_AudioSessionEvents_Release, - - vlc_AudioSessionEvents_OnDisplayNameChanged, - vlc_AudioSessionEvents_OnIconPathChanged, - vlc_AudioSessionEvents_OnSimpleVolumeChanged, - vlc_AudioSessionEvents_OnChannelVolumeChanged, - vlc_AudioSessionEvents_OnGroupingParamChanged, - vlc_AudioSessionEvents_OnStateChanged, - vlc_AudioSessionEvents_OnSessionDisconnected, +/*** 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 }; - -/*** Initialization / deinitialization **/ static void vlc_ToWave(WAVEFORMATEXTENSIBLE *restrict wf, audio_sample_format_t *restrict audio) { @@ -485,7 +248,6 @@ static void vlc_ToWave(WAVEFORMATEXTENSIBLE *restrict wf, 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: @@ -496,325 +258,205 @@ static void vlc_ToWave(WAVEFORMATEXTENSIBLE *restrict wf, 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 */ - if (wf->nChannels != audio->i_channels) - return -1; + audio->i_physical_channels = 0; - aout_FormatPrepare(audio); - return 0; -} + if (wf->wFormatTag == WAVE_FORMAT_EXTENSIBLE) + { + const WAVEFORMATEXTENSIBLE *wfe = (void *)wf; -static wchar_t *var_InheritWide(vlc_object_t *obj, const char *name) -{ - char *v8 = var_InheritString(obj, name); - if (v8 == NULL) - return NULL; + if (IsEqualIID(&wfe->SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)) + { + switch (wf->wBitsPerSample) + { + case 64: + audio->i_format = VLC_CODEC_FL64; + break; + case 32: + audio->i_format = VLC_CODEC_FL32; + break; + default: + return -1; + } + } + else if (IsEqualIID(&wfe->SubFormat, &KSDATAFORMAT_SUBTYPE_PCM)) + { + switch (wf->wBitsPerSample) + { + case 32: + audio->i_format = VLC_CODEC_S32N; + break; + case 16: + audio->i_format = VLC_CODEC_S16N; + break; + default: + return -1; + } + } - wchar_t *v16 = ToWide(v8); - free(v8); - return v16; -} -#define var_InheritWide(o,n) var_InheritWide(VLC_OBJECT(o),n) + if (wfe->Samples.wValidBitsPerSample != wf->wBitsPerSample) + return -1; -static int var_SetWide(vlc_object_t *obj, const char *name, const wchar_t *val) -{ - char *str = FromWide(val); - if (unlikely(str == NULL)) - return VLC_ENOMEM; + for (unsigned i = 0; chans_in[i]; i++) + if (wfe->dwChannelMask & chans_in[i]) + audio->i_physical_channels |= pi_vlc_chan_order_wg4[i]; + } + else + return -1; - int ret = var_SetString(obj, name, str); - free(str); - return ret; + audio->i_original_channels = audio->i_physical_channels; + aout_FormatPrepare (audio); + + if (wf->nChannels != audio->i_channels) + return -1; + return 0; } -#define var_SetWide(o,n,v) var_SetWide(VLC_OBJECT(o),n,v) -/* 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; - } - - hr = IAudioClient_GetService(sys->client, &IID_IAudioClock, - (void **)&sys->clock); - if (FAILED(hr)) - msg_Warn(aout, "cannot get audio clock (error 0x%lx)", hr); + const WAVEFORMATEXTENSIBLE *wfe = (void *)wf; - hr = IAudioClient_GetService(sys->client, &IID_IAudioSessionControl, - (void **)&sys->control); - if (FAILED(hr)) - msg_Warn(aout, "cannot get audio session control (error 0x%lx)", hr); - else - { - wchar_t *ua = var_InheritWide(aout, "user-agent"); - IAudioSessionControl_SetDisplayName(sys->control, ua, NULL); - free(ua); + mask = wfe->dwChannelMask; } - - /* do nothing until the audio session terminates */ - ReleaseSemaphore(sys->ready, 1, NULL); - WaitForSingleObject(sys->done, INFINITE); - - if (sys->control != NULL) - IAudioSessionControl_Release(sys->control); - 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")) + if (!s->b_force && var_InheritBool(s, "spdif") && AOUT_FMT_SPDIF(fmt)) /* Fallback to other plugin until pass-through is implemented */ - return VLC_EGENERIC; + return E_NOTIMPL; - aout_sys_t *sys = malloc(sizeof (*sys)); + aout_stream_sys_t *sys = malloc(sizeof (*sys)); if (unlikely(sys == NULL)) - return VLC_ENOMEM; - sys->aout = aout; + return E_OUTOFMEMORY; sys->client = NULL; - sys->render = NULL; - sys->clock = NULL; - sys->events.lpVtbl = &vlc_AudioSessionEvents; - sys->refs = 1; - sys->ready = NULL; - sys->done = NULL; - aout->sys = sys; - - if (TryEnter(aout)) - { - free(sys); - return VLC_EGENERIC; - } -retry: - /* Get audio device according to policy */ - var_Create (aout, "audio-device", VLC_VAR_STRING|VLC_VAR_HASCHOICE); - 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; - } - - // Without configuration item, the variable must be created explicitly. - var_Create (aout, "wasapi-audio-device", VLC_VAR_STRING); - LPWSTR devid = var_InheritWide (aout, "wasapi-audio-device"); - var_Destroy (aout, "wasapi-audio-device"); - - IMMDevice *dev = NULL; - if (devid != NULL) - { - msg_Dbg (aout, "using selected device %ls", devid); - hr = IMMDeviceEnumerator_GetDevice (devs, devid, &dev); - if (FAILED(hr)) - msg_Warn(aout, "cannot get audio endpoint (error 0x%lx)", hr); - free (devid); - } - if (dev == NULL) - { - msg_Dbg (aout, "using default device"); - hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(devs, eRender, - eConsole, &dev); - } - - GetDevices(VLC_OBJECT(aout), devs); - IMMDeviceEnumerator_Release(devs); - if (FAILED(hr)) - { - msg_Err(aout, "cannot get audio endpoint (error 0x%lx)", hr); - goto error; - } - - hr = IMMDevice_GetId(dev, &devid); - if (SUCCEEDED(hr)) - { - msg_Dbg(aout, "using device %ls", devid); - var_SetWide (aout, "audio-device", devid); - CoTaskMemFree(devid); - } - - 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); 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, - &GUID_VLC_AUD_OUT); + (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; } + msg_Dbg(s, "buffer size : %"PRIu32" frames", sys->frames); - 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; - - Leave(); - - aout->format = format; - aout->pf_play = Play; - aout->pf_pause = Pause; - aout->pf_flush = Flush; - /*if (AOUT_FMT_LINEAR(&format) && !exclusive)*/ + REFERENCE_TIME latT, defT, minT; + if (SUCCEEDED(IAudioClient_GetStreamLatency(sys->client, &latT)) + && SUCCEEDED(IAudioClient_GetDevicePeriod(sys->client, &defT, &minT))) { - aout->volume_set = SimpleVolumeSet; - aout->mute_set = SimpleMuteSet; + msg_Dbg(s, "maximum latency: %"PRIu64"00 ns", latT); + msg_Dbg(s, "default period : %"PRIu64"00 ns", defT); + msg_Dbg(s, "minimum period : %"PRIu64"00 ns", minT); } - if (likely(sys->control != NULL)) - IAudioSessionControl_RegisterAudioSessionNotification(sys->control, - &sys->events); - var_AddCallback (aout, "audio-device", DeviceChanged, NULL); - 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); - var_Destroy(aout, "audio-device"); - if (hr == AUDCLNT_E_DEVICE_INVALIDATED) - { - msg_Warn(aout, "device invalidated, retrying"); - goto retry; - } - 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; - - Enter(); - if (likely(sys->control != NULL)) - IAudioSessionControl_UnregisterAudioSessionNotification(sys->control, - &sys->events); - ReleaseSemaphore(sys->done, 1, NULL); /* tell MTA thread to finish */ - WaitForSingleObject(sys->ready, INFINITE); /* wait for that ^ */ + aout_stream_sys_t *sys = s->sys; + IAudioClient_Stop(sys->client); /* should not be needed */ IAudioClient_Release(sys->client); - Leave(); - - var_DelCallback (aout, "audio-device", DeviceChanged, NULL); - var_Destroy (aout, "audio-device"); - - CloseHandle(sys->done); - CloseHandle(sys->ready); - free(sys); } + +vlc_module_begin() + set_shortname("WASAPI") + set_description(N_("Windows Audio Session API output")) + set_capability("aout stream", 50) + set_category(CAT_AUDIO) + set_subcategory(SUBCAT_AUDIO_AOUT) + set_callbacks(Start, Stop) +vlc_module_end()