1 /*****************************************************************************
2 * wasapi.c : Windows Audio Session API output plugin for VLC
3 *****************************************************************************
4 * Copyright (C) 2012 RĂ©mi Denis-Courmont
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU Lesser General Public License as published by
8 * the Free Software Foundation; either version 2.1 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
19 *****************************************************************************/
31 #include <audioclient.h>
33 #include <vlc_common.h>
35 #include <vlc_plugin.h>
36 #include "audio_output/mmdevice.h"
38 static LARGE_INTEGER freq; /* performance counters frequency */
40 BOOL WINAPI DllMain(HINSTANCE, DWORD, LPVOID); /* avoid warning */
42 BOOL WINAPI DllMain(HINSTANCE dll, DWORD reason, LPVOID reserved)
49 case DLL_PROCESS_ATTACH:
50 if (!QueryPerformanceFrequency(&freq))
57 static UINT64 GetQPC(void)
59 LARGE_INTEGER counter;
61 if (!QueryPerformanceCounter(&counter))
64 lldiv_t d = lldiv(counter.QuadPart, freq.QuadPart);
65 return (d.quot * 10000000) + ((d.rem * 10000000) / freq.QuadPart);
68 typedef struct aout_stream_sys
72 uint8_t chans_table[AOUT_CHAN_MAX];
73 uint8_t chans_to_reorder;
75 vlc_fourcc_t format; /**< Sample format */
76 unsigned rate; /**< Sample rate */
77 unsigned bytes_per_frame;
78 UINT64 written; /**< Frames written to the buffer */
79 UINT32 frames; /**< Total buffer size (frames) */
83 /*** VLC audio output callbacks ***/
84 static HRESULT TimeGet(aout_stream_t *s, mtime_t *restrict delay)
86 aout_stream_sys_t *sys = s->sys;
88 UINT64 pos, qpcpos, freq;
91 hr = IAudioClient_GetService(sys->client, &IID_IAudioClock, &pv);
94 msg_Err(s, "cannot get clock (error 0x%lx)", hr);
98 IAudioClock *clock = pv;
100 hr = IAudioClock_GetPosition(clock, &pos, &qpcpos);
102 hr = IAudioClock_GetFrequency(clock, &freq);
103 IAudioClock_Release(clock);
106 msg_Err(s, "cannot get position (error 0x%lx)", hr);
110 lldiv_t w = lldiv(sys->written, sys->rate);
111 lldiv_t r = lldiv(pos, freq);
113 static_assert((10000000 % CLOCK_FREQ) == 0, "Frequency conversion broken");
115 *delay = ((w.quot - r.quot) * CLOCK_FREQ)
116 + ((w.rem * CLOCK_FREQ) / sys->rate)
117 - ((r.rem * CLOCK_FREQ) / freq)
118 - ((GetQPC() - qpcpos) / (10000000 / CLOCK_FREQ));
123 static HRESULT Play(aout_stream_t *s, block_t *block)
125 aout_stream_sys_t *sys = s->sys;
129 if (sys->chans_to_reorder)
130 aout_ChannelReorder(block->p_buffer, block->i_buffer,
131 sys->chans_to_reorder, sys->chans_table, sys->format);
133 hr = IAudioClient_GetService(sys->client, &IID_IAudioRenderClient, &pv);
136 msg_Err(s, "cannot get render client (error 0x%lx)", hr);
140 IAudioRenderClient *render = pv;
144 hr = IAudioClient_GetCurrentPadding(sys->client, &frames);
147 msg_Err(s, "cannot get current padding (error 0x%lx)", hr);
151 assert(frames <= sys->frames);
152 frames = sys->frames - frames;
153 if (frames > block->i_nb_samples)
154 frames = block->i_nb_samples;
157 hr = IAudioRenderClient_GetBuffer(render, frames, &dst);
160 msg_Err(s, "cannot get buffer (error 0x%lx)", hr);
164 const size_t copy = frames * sys->bytes_per_frame;
166 memcpy(dst, block->p_buffer, copy);
167 hr = IAudioRenderClient_ReleaseBuffer(render, frames, 0);
170 msg_Err(s, "cannot release buffer (error 0x%lx)", hr);
173 IAudioClient_Start(sys->client);
175 block->p_buffer += copy;
176 block->i_buffer -= copy;
177 block->i_nb_samples -= frames;
178 sys->written += frames;
179 if (block->i_nb_samples == 0)
182 /* Out of buffer space, sleep */
183 msleep(sys->frames * (CLOCK_FREQ / 2) / sys->rate);
185 IAudioRenderClient_Release(render);
187 block_Release(block);
192 static HRESULT Pause(aout_stream_t *s, bool paused)
194 aout_stream_sys_t *sys = s->sys;
198 hr = IAudioClient_Stop(sys->client);
200 hr = IAudioClient_Start(sys->client);
202 msg_Warn(s, "cannot %s stream (error 0x%lx)",
203 paused ? "stop" : "start", hr);
207 static HRESULT Flush(aout_stream_t *s)
209 aout_stream_sys_t *sys = s->sys;
212 IAudioClient_Stop(sys->client);
214 hr = IAudioClient_Reset(sys->client);
221 msg_Warn(s, "cannot reset stream (error 0x%lx)", hr);
226 /*** Initialization / deinitialization **/
227 static const uint32_t chans_out[] = {
228 SPEAKER_FRONT_LEFT, SPEAKER_FRONT_RIGHT,
229 SPEAKER_FRONT_CENTER, SPEAKER_LOW_FREQUENCY,
230 SPEAKER_BACK_LEFT, SPEAKER_BACK_RIGHT, SPEAKER_BACK_CENTER,
231 SPEAKER_SIDE_LEFT, SPEAKER_SIDE_RIGHT, 0
233 static const uint32_t chans_in[] = {
234 SPEAKER_FRONT_LEFT, SPEAKER_FRONT_RIGHT,
235 SPEAKER_SIDE_LEFT, SPEAKER_SIDE_RIGHT,
236 SPEAKER_BACK_LEFT, SPEAKER_BACK_RIGHT, SPEAKER_BACK_CENTER,
237 SPEAKER_FRONT_CENTER, SPEAKER_LOW_FREQUENCY, 0
240 static void vlc_ToWave(WAVEFORMATEXTENSIBLE *restrict wf,
241 audio_sample_format_t *restrict audio)
243 switch (audio->i_format)
246 audio->i_format = VLC_CODEC_FL32;
248 wf->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
252 audio->i_format = VLC_CODEC_S16N;
254 wf->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
258 audio->i_format = VLC_CODEC_FL32;
259 wf->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
262 aout_FormatPrepare (audio);
264 wf->Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
265 wf->Format.nChannels = audio->i_channels;
266 wf->Format.nSamplesPerSec = audio->i_rate;
267 wf->Format.nAvgBytesPerSec = audio->i_bytes_per_frame * audio->i_rate;
268 wf->Format.nBlockAlign = audio->i_bytes_per_frame;
269 wf->Format.wBitsPerSample = audio->i_bitspersample;
270 wf->Format.cbSize = sizeof (*wf) - sizeof (wf->Format);
272 wf->Samples.wValidBitsPerSample = audio->i_bitspersample;
274 wf->dwChannelMask = 0;
275 for (unsigned i = 0; pi_vlc_chan_order_wg4[i]; i++)
276 if (audio->i_physical_channels & pi_vlc_chan_order_wg4[i])
277 wf->dwChannelMask |= chans_in[i];
280 static int vlc_FromWave(const WAVEFORMATEX *restrict wf,
281 audio_sample_format_t *restrict audio)
283 audio->i_rate = wf->nSamplesPerSec;
284 audio->i_physical_channels = 0;
286 if (wf->wFormatTag == WAVE_FORMAT_EXTENSIBLE)
288 const WAVEFORMATEXTENSIBLE *wfe = (void *)wf;
290 if (IsEqualIID(&wfe->SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT))
292 switch (wf->wBitsPerSample)
295 audio->i_format = VLC_CODEC_FL64;
298 audio->i_format = VLC_CODEC_FL32;
304 else if (IsEqualIID(&wfe->SubFormat, &KSDATAFORMAT_SUBTYPE_PCM))
306 switch (wf->wBitsPerSample)
309 audio->i_format = VLC_CODEC_S32N;
312 audio->i_format = VLC_CODEC_S16N;
319 if (wfe->Samples.wValidBitsPerSample != wf->wBitsPerSample)
322 for (unsigned i = 0; chans_in[i]; i++)
323 if (wfe->dwChannelMask & chans_in[i])
324 audio->i_physical_channels |= pi_vlc_chan_order_wg4[i];
329 audio->i_original_channels = audio->i_physical_channels;
330 aout_FormatPrepare (audio);
332 if (wf->nChannels != audio->i_channels)
337 static unsigned vlc_CheckWaveOrder (const WAVEFORMATEX *restrict wf,
338 uint8_t *restrict table)
342 if (wf->wFormatTag == WAVE_FORMAT_EXTENSIBLE)
344 const WAVEFORMATEXTENSIBLE *wfe = (void *)wf;
346 mask = wfe->dwChannelMask;
348 return aout_CheckChannelReorder(chans_in, chans_out, mask, table);
351 static HRESULT Start(aout_stream_t *s, audio_sample_format_t *restrict fmt,
354 if (!s->b_force && var_InheritBool(s, "spdif") && AOUT_FMT_SPDIF(fmt))
355 /* Fallback to other plugin until pass-through is implemented */
358 aout_stream_sys_t *sys = malloc(sizeof (*sys));
359 if (unlikely(sys == NULL))
360 return E_OUTOFMEMORY;
364 HRESULT hr = aout_stream_Activate(s, &IID_IAudioClient, NULL, &pv);
367 msg_Err(s, "cannot activate client (error 0x%lx)", hr);
372 /* Configure audio stream */
373 WAVEFORMATEXTENSIBLE wf;
376 vlc_ToWave(&wf, fmt);
377 hr = IAudioClient_IsFormatSupported(sys->client, AUDCLNT_SHAREMODE_SHARED,
381 msg_Err(s, "cannot negotiate audio format (error 0x%lx)", hr);
388 if (vlc_FromWave(pwf, fmt))
391 msg_Err(s, "unsupported audio format");
395 msg_Dbg(s, "modified format");
400 sys->chans_to_reorder = vlc_CheckWaveOrder((hr == S_OK) ? &wf.Format : pwf,
402 sys->format = fmt->i_format;
404 hr = IAudioClient_Initialize(sys->client, AUDCLNT_SHAREMODE_SHARED, 0,
405 AOUT_MAX_PREPARE_TIME * 10, 0,
406 (hr == S_OK) ? &wf.Format : pwf, sid);
410 msg_Err(s, "cannot initialize audio client (error 0x%lx)", hr);
414 hr = IAudioClient_GetBufferSize(sys->client, &sys->frames);
417 msg_Err(s, "cannot get buffer size (error 0x%lx)", hr);
420 msg_Dbg(s, "buffer size : %"PRIu32" frames", sys->frames);
422 REFERENCE_TIME latT, defT, minT;
423 if (SUCCEEDED(IAudioClient_GetStreamLatency(sys->client, &latT))
424 && SUCCEEDED(IAudioClient_GetDevicePeriod(sys->client, &defT, &minT)))
426 msg_Dbg(s, "maximum latency: %"PRIu64"00 ns", latT);
427 msg_Dbg(s, "default period : %"PRIu64"00 ns", defT);
428 msg_Dbg(s, "minimum period : %"PRIu64"00 ns", minT);
431 sys->rate = fmt->i_rate;
432 sys->bytes_per_frame = fmt->i_bytes_per_frame;
435 s->time_get = TimeGet;
441 if (sys->client != NULL)
442 IAudioClient_Release(sys->client);
447 static void Stop(aout_stream_t *s)
449 aout_stream_sys_t *sys = s->sys;
451 IAudioClient_Stop(sys->client); /* should not be needed */
452 IAudioClient_Release(sys->client);
458 set_shortname("WASAPI")
459 set_description(N_("Windows Audio Session API output"))
460 set_capability("aout stream", 50)
461 set_category(CAT_AUDIO)
462 set_subcategory(SUBCAT_AUDIO_AOUT)
463 set_callbacks(Start, Stop)