]> git.sesse.net Git - vlc/blob - modules/audio_output/wasapi.c
e03ea25a8387354d65a296883be41b9c3a2f6ebf
[vlc] / modules / audio_output / wasapi.c
1 /*****************************************************************************
2  * wasapi.c : Windows Audio Session API output plugin for VLC
3  *****************************************************************************
4  * Copyright (C) 2012 RĂ©mi Denis-Courmont
5  *
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.
10  *
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.
15  *
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  *****************************************************************************/
20
21 #ifdef HAVE_CONFIG_H
22 # include <config.h>
23 #endif
24
25 #define INITGUID
26 #define COBJMACROS
27 #define CONST_VTABLE
28
29 #include <stdlib.h>
30 #include <assert.h>
31 #include <audioclient.h>
32 #include <mmdeviceapi.h>
33
34 #include <vlc_common.h>
35 #include <vlc_aout.h>
36 #include "mmdevice.h"
37
38 static LARGE_INTEGER freq; /* performance counters frequency */
39
40 BOOL WINAPI DllMain(HINSTANCE, DWORD, LPVOID); /* avoid warning */
41
42 BOOL WINAPI DllMain(HINSTANCE dll, DWORD reason, LPVOID reserved)
43 {
44     (void) dll;
45     (void) reserved;
46
47     switch (reason)
48     {
49         case DLL_PROCESS_ATTACH:
50             if (!QueryPerformanceFrequency(&freq))
51                 return FALSE;
52             break;
53     }
54     return TRUE;
55 }
56
57 static UINT64 GetQPC(void)
58 {
59     LARGE_INTEGER counter;
60
61     if (!QueryPerformanceCounter(&counter))
62         abort();
63
64     lldiv_t d = lldiv(counter.QuadPart, freq.QuadPart);
65     return (d.quot * 10000000) + ((d.rem * 10000000) / freq.QuadPart);
66 }
67
68 static void Enter(void)
69 {
70     HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
71     if (unlikely(FAILED(hr)))
72         abort();
73 }
74
75 static void Leave(void)
76 {
77     CoUninitialize();
78 }
79
80 typedef struct aout_stream_sys
81 {
82     IAudioClient *client;
83
84     uint8_t chans_table[AOUT_CHAN_MAX];
85     uint8_t chans_to_reorder;
86
87     uint8_t bits; /**< Bits per sample */
88     unsigned rate; /**< Sample rate */
89     unsigned bytes_per_frame;
90     UINT32 written; /**< Frames written to the buffer */
91     UINT32 frames; /**< Total buffer size (frames) */
92 } aout_stream_sys_t;
93
94
95 /*** VLC audio output callbacks ***/
96 static HRESULT TimeGet(aout_stream_t *s, mtime_t *restrict delay)
97 {
98     aout_stream_sys_t *sys = s->sys;
99     void *pv;
100     UINT64 pos, qpcpos;
101     HRESULT hr;
102
103     hr = IAudioClient_GetService(sys->client, &IID_IAudioClock, &pv);
104     if (SUCCEEDED(hr))
105     {
106         IAudioClock *clock = pv;
107
108         hr = IAudioClock_GetPosition(clock, &pos, &qpcpos);
109         if (FAILED(hr))
110             msg_Err(s, "cannot get position (error 0x%lx)", hr);
111         IAudioClock_Release(clock);
112     }
113     else
114         msg_Err(s, "cannot get clock (error 0x%lx)", hr);
115
116     if (SUCCEEDED(hr))
117     {
118         if (pos != 0)
119         {
120             *delay = ((GetQPC() - qpcpos) / (10000000 / CLOCK_FREQ));
121             static_assert((10000000 % CLOCK_FREQ) == 0,
122                           "Frequency conversion broken");
123         }
124         else
125         {
126             *delay = sys->written * CLOCK_FREQ / sys->rate;
127             msg_Dbg(s, "extrapolating position: still propagating buffers");
128         }
129     }
130     return hr;
131 }
132
133 static HRESULT Play(aout_stream_t *s, block_t *block)
134 {
135     aout_stream_sys_t *sys = s->sys;
136     void *pv;
137     HRESULT hr;
138
139     if (sys->chans_to_reorder)
140         aout_ChannelReorder(block->p_buffer, block->i_buffer,
141                           sys->chans_to_reorder, sys->chans_table, sys->bits);
142
143     hr = IAudioClient_GetService(sys->client, &IID_IAudioRenderClient, &pv);
144     if (FAILED(hr))
145     {
146         msg_Err(s, "cannot get render client (error 0x%lx)", hr);
147         goto out;
148     }
149
150     IAudioRenderClient *render = pv;
151     for (;;)
152     {
153         UINT32 frames;
154         hr = IAudioClient_GetCurrentPadding(sys->client, &frames);
155         if (FAILED(hr))
156         {
157             msg_Err(s, "cannot get current padding (error 0x%lx)", hr);
158             break;
159         }
160
161         assert(frames <= sys->frames);
162         frames = sys->frames - frames;
163         if (frames > block->i_nb_samples)
164             frames = block->i_nb_samples;
165
166         BYTE *dst;
167         hr = IAudioRenderClient_GetBuffer(render, frames, &dst);
168         if (FAILED(hr))
169         {
170             msg_Err(s, "cannot get buffer (error 0x%lx)", hr);
171             break;
172         }
173
174         const size_t copy = frames * sys->bytes_per_frame;
175
176         memcpy(dst, block->p_buffer, copy);
177         hr = IAudioRenderClient_ReleaseBuffer(render, frames, 0);
178         if (FAILED(hr))
179         {
180             msg_Err(s, "cannot release buffer (error 0x%lx)", hr);
181             break;
182         }
183         IAudioClient_Start(sys->client);
184
185         block->p_buffer += copy;
186         block->i_buffer -= copy;
187         block->i_nb_samples -= frames;
188         sys->written += frames;
189         if (block->i_nb_samples == 0)
190             break; /* done */
191
192         /* Out of buffer space, sleep */
193         msleep(AOUT_MIN_PREPARE_TIME
194              + block->i_nb_samples * CLOCK_FREQ / sys->rate);
195     }
196     IAudioRenderClient_Release(render);
197 out:
198     block_Release(block);
199
200     return hr;
201 }
202
203 static HRESULT Pause(aout_stream_t *s, bool paused)
204 {
205     aout_stream_sys_t *sys = s->sys;
206     HRESULT hr;
207
208     if (paused)
209         hr = IAudioClient_Stop(sys->client);
210     else
211         hr = IAudioClient_Start(sys->client);
212     if (FAILED(hr))
213         msg_Warn(s, "cannot %s stream (error 0x%lx)",
214                  paused ? "stop" : "start", hr);
215     return hr;
216 }
217
218 static HRESULT Flush(aout_stream_t *s)
219 {
220     aout_stream_sys_t *sys = s->sys;
221     HRESULT hr;
222
223     IAudioClient_Stop(sys->client);
224
225     hr = IAudioClient_Reset(sys->client);
226     if (FAILED(hr))
227         msg_Warn(s, "cannot reset stream (error 0x%lx)", hr);
228     else
229         sys->written = 0;
230     return hr;
231 }
232
233
234 /*** Initialization / deinitialization **/
235 static const uint32_t chans_out[] = {
236     SPEAKER_FRONT_LEFT, SPEAKER_FRONT_RIGHT,
237     SPEAKER_FRONT_CENTER, SPEAKER_LOW_FREQUENCY,
238     SPEAKER_BACK_LEFT, SPEAKER_BACK_RIGHT, SPEAKER_BACK_CENTER,
239     SPEAKER_SIDE_LEFT, SPEAKER_SIDE_RIGHT, 0
240 };
241 static const uint32_t chans_in[] = {
242     SPEAKER_FRONT_LEFT, SPEAKER_FRONT_RIGHT,
243     SPEAKER_SIDE_LEFT, SPEAKER_SIDE_RIGHT,
244     SPEAKER_BACK_LEFT, SPEAKER_BACK_RIGHT, SPEAKER_BACK_CENTER,
245     SPEAKER_FRONT_CENTER, SPEAKER_LOW_FREQUENCY, 0
246 };
247
248 static void vlc_ToWave(WAVEFORMATEXTENSIBLE *restrict wf,
249                        audio_sample_format_t *restrict audio)
250 {
251     switch (audio->i_format)
252     {
253         case VLC_CODEC_FL64:
254             audio->i_format = VLC_CODEC_FL32;
255         case VLC_CODEC_FL32:
256             wf->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
257             break;
258
259         case VLC_CODEC_S8:
260         case VLC_CODEC_U8:
261             audio->i_format = VLC_CODEC_S16N;
262         case VLC_CODEC_S16N:
263             wf->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
264             break;
265
266         default:
267             audio->i_format = VLC_CODEC_FL32;
268             wf->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
269             break;
270     }
271     aout_FormatPrepare (audio);
272
273     wf->Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
274     wf->Format.nChannels = audio->i_channels;
275     wf->Format.nSamplesPerSec = audio->i_rate;
276     wf->Format.nAvgBytesPerSec = audio->i_bytes_per_frame * audio->i_rate;
277     wf->Format.nBlockAlign = audio->i_bytes_per_frame;
278     wf->Format.wBitsPerSample = audio->i_bitspersample;
279     wf->Format.cbSize = sizeof (*wf) - sizeof (wf->Format);
280
281     wf->Samples.wValidBitsPerSample = audio->i_bitspersample;
282
283     wf->dwChannelMask = 0;
284     for (unsigned i = 0; pi_vlc_chan_order_wg4[i]; i++)
285         if (audio->i_physical_channels & pi_vlc_chan_order_wg4[i])
286             wf->dwChannelMask |= chans_in[i];
287 }
288
289 static int vlc_FromWave(const WAVEFORMATEX *restrict wf,
290                         audio_sample_format_t *restrict audio)
291 {
292     audio->i_rate = wf->nSamplesPerSec;
293     audio->i_physical_channels = 0;
294
295     if (wf->wFormatTag == WAVE_FORMAT_EXTENSIBLE)
296     {
297         const WAVEFORMATEXTENSIBLE *wfe = (void *)wf;
298
299         for (unsigned i = 0; chans_in[i]; i++)
300             if (wfe->dwChannelMask & chans_in[i])
301                 audio->i_physical_channels |= pi_vlc_chan_order_wg4[i];
302     }
303
304     audio->i_original_channels = audio->i_physical_channels;
305     aout_FormatPrepare (audio);
306
307     if (wf->nChannels != audio->i_channels)
308         return -1;
309     return 0;
310 }
311
312 static unsigned vlc_CheckWaveOrder (const WAVEFORMATEX *restrict wf,
313                                     uint8_t *restrict table)
314 {
315     uint32_t mask = 0;
316
317     if (wf->wFormatTag == WAVE_FORMAT_EXTENSIBLE)
318     {
319         const WAVEFORMATEXTENSIBLE *wfe = (void *)wf;
320
321         mask = wfe->dwChannelMask;
322     }
323     return aout_CheckChannelReorder(chans_in, chans_out, mask, table);
324 }
325
326 static HRESULT Start(aout_stream_t *s, audio_sample_format_t *restrict fmt,
327                      IMMDevice *dev, const GUID *sid)
328 {
329     aout_stream_sys_t *sys = malloc(sizeof (*sys));
330     if (unlikely(sys == NULL))
331         return E_OUTOFMEMORY;
332     sys->client = NULL;
333
334     void *pv;
335     HRESULT hr;
336
337     Enter();
338     hr = IMMDevice_Activate(dev, &IID_IAudioClient, CLSCTX_ALL, NULL, &pv);
339     Leave();
340     if (FAILED(hr))
341     {
342         msg_Err(s, "cannot activate client (error 0x%lx)", hr);
343         goto error;
344     }
345     sys->client = pv;
346
347     /* Configure audio stream */
348     WAVEFORMATEXTENSIBLE wf;
349     WAVEFORMATEX *pwf;
350
351     vlc_ToWave(&wf, fmt);
352     hr = IAudioClient_IsFormatSupported(sys->client, AUDCLNT_SHAREMODE_SHARED,
353                                         &wf.Format, &pwf);
354     if (FAILED(hr))
355     {
356         msg_Err(s, "cannot negotiate audio format (error 0x%lx)", hr);
357         goto error;
358     }
359
360     if (hr == S_FALSE)
361     {
362         assert(pwf != NULL);
363         if (vlc_FromWave(pwf, fmt))
364         {
365             CoTaskMemFree(pwf);
366             msg_Err(s, "unsupported audio format");
367             hr = E_INVALIDARG;
368             goto error;
369         }
370         msg_Dbg(s, "modified format");
371     }
372     else
373         assert(pwf == NULL);
374
375     sys->chans_to_reorder = vlc_CheckWaveOrder((hr == S_OK) ? &wf.Format : pwf,
376                                                sys->chans_table);
377     sys->bits = fmt->i_bitspersample;
378
379     hr = IAudioClient_Initialize(sys->client, AUDCLNT_SHAREMODE_SHARED, 0,
380                                  AOUT_MAX_PREPARE_TIME * 10, 0,
381                                  (hr == S_OK) ? &wf.Format : pwf, sid);
382     CoTaskMemFree(pwf);
383     if (FAILED(hr))
384     {
385         msg_Err(s, "cannot initialize audio client (error 0x%lx)", hr);
386         goto error;
387     }
388
389     hr = IAudioClient_GetBufferSize(sys->client, &sys->frames);
390     if (FAILED(hr))
391     {
392         msg_Err(s, "cannot get buffer size (error 0x%lx)", hr);
393         goto error;
394     }
395
396     sys->rate = fmt->i_rate;
397     sys->bytes_per_frame = fmt->i_bytes_per_frame;
398     sys->written = 0;
399     s->sys = sys;
400     s->time_get = TimeGet;
401     s->play = Play;
402     s->pause = Pause;
403     s->flush = Flush;
404     return S_OK;
405 error:
406     if (sys->client != NULL)
407         IAudioClient_Release(sys->client);
408     free(sys);
409     return hr;
410 }
411
412 static void Stop(aout_stream_t *s)
413 {
414     aout_stream_sys_t *sys = s->sys;
415
416     IAudioClient_Stop(sys->client); /* should not be needed */
417     IAudioClient_Release(sys->client);
418 }
419
420 #undef aout_stream_Start
421 aout_stream_t *aout_stream_Start(vlc_object_t *parent,
422                                  audio_sample_format_t *restrict fmt,
423                                  IMMDevice *dev, const GUID *sid)
424 {
425     aout_stream_t *s = vlc_object_create(parent, sizeof (*s));
426     if (unlikely(s == NULL))
427         return NULL;
428
429     HRESULT hr = Start(s, fmt, dev, sid);
430     if (FAILED(hr))
431     {
432         vlc_object_release(s);
433         s = NULL;
434     }
435     return s;
436 }
437
438 void aout_stream_Stop(aout_stream_t *s)
439 {
440     Stop(s);
441     vlc_object_release(s);
442 }