]> git.sesse.net Git - vlc/blob - modules/audio_output/wasapi.c
wasapi: fix S/PDIF check
[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
33 #include <vlc_common.h>
34 #include <vlc_aout.h>
35 #include <vlc_plugin.h>
36 #include "audio_output/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 typedef struct aout_stream_sys
69 {
70     IAudioClient *client;
71
72     uint8_t chans_table[AOUT_CHAN_MAX];
73     uint8_t chans_to_reorder;
74
75     vlc_fourcc_t format; /**< Sample format */
76     unsigned rate; /**< Sample rate */
77     unsigned bytes_per_frame;
78     UINT32 written; /**< Frames written to the buffer */
79     UINT32 frames; /**< Total buffer size (frames) */
80 } aout_stream_sys_t;
81
82
83 /*** VLC audio output callbacks ***/
84 static HRESULT TimeGet(aout_stream_t *s, mtime_t *restrict delay)
85 {
86     aout_stream_sys_t *sys = s->sys;
87     void *pv;
88     UINT64 pos, qpcpos;
89     HRESULT hr;
90
91     hr = IAudioClient_GetService(sys->client, &IID_IAudioClock, &pv);
92     if (SUCCEEDED(hr))
93     {
94         IAudioClock *clock = pv;
95
96         hr = IAudioClock_GetPosition(clock, &pos, &qpcpos);
97         if (FAILED(hr))
98             msg_Err(s, "cannot get position (error 0x%lx)", hr);
99         IAudioClock_Release(clock);
100     }
101     else
102         msg_Err(s, "cannot get clock (error 0x%lx)", hr);
103
104     if (SUCCEEDED(hr))
105     {
106         if (pos != 0)
107         {
108             *delay = ((GetQPC() - qpcpos) / (10000000 / CLOCK_FREQ));
109             static_assert((10000000 % CLOCK_FREQ) == 0,
110                           "Frequency conversion broken");
111         }
112         else
113         {
114             *delay = sys->written * CLOCK_FREQ / sys->rate;
115             msg_Dbg(s, "extrapolating position: still propagating buffers");
116         }
117     }
118     return hr;
119 }
120
121 static HRESULT Play(aout_stream_t *s, block_t *block)
122 {
123     aout_stream_sys_t *sys = s->sys;
124     void *pv;
125     HRESULT hr;
126
127     if (sys->chans_to_reorder)
128         aout_ChannelReorder(block->p_buffer, block->i_buffer,
129                           sys->chans_to_reorder, sys->chans_table, sys->format);
130
131     hr = IAudioClient_GetService(sys->client, &IID_IAudioRenderClient, &pv);
132     if (FAILED(hr))
133     {
134         msg_Err(s, "cannot get render client (error 0x%lx)", hr);
135         goto out;
136     }
137
138     IAudioRenderClient *render = pv;
139     for (;;)
140     {
141         UINT32 frames;
142         hr = IAudioClient_GetCurrentPadding(sys->client, &frames);
143         if (FAILED(hr))
144         {
145             msg_Err(s, "cannot get current padding (error 0x%lx)", hr);
146             break;
147         }
148
149         assert(frames <= sys->frames);
150         frames = sys->frames - frames;
151         if (frames > block->i_nb_samples)
152             frames = block->i_nb_samples;
153
154         BYTE *dst;
155         hr = IAudioRenderClient_GetBuffer(render, frames, &dst);
156         if (FAILED(hr))
157         {
158             msg_Err(s, "cannot get buffer (error 0x%lx)", hr);
159             break;
160         }
161
162         const size_t copy = frames * sys->bytes_per_frame;
163
164         memcpy(dst, block->p_buffer, copy);
165         hr = IAudioRenderClient_ReleaseBuffer(render, frames, 0);
166         if (FAILED(hr))
167         {
168             msg_Err(s, "cannot release buffer (error 0x%lx)", hr);
169             break;
170         }
171         IAudioClient_Start(sys->client);
172
173         block->p_buffer += copy;
174         block->i_buffer -= copy;
175         block->i_nb_samples -= frames;
176         sys->written += frames;
177         if (block->i_nb_samples == 0)
178             break; /* done */
179
180         /* Out of buffer space, sleep */
181         msleep(AOUT_MIN_PREPARE_TIME
182              + block->i_nb_samples * CLOCK_FREQ / sys->rate);
183     }
184     IAudioRenderClient_Release(render);
185 out:
186     block_Release(block);
187
188     return hr;
189 }
190
191 static HRESULT Pause(aout_stream_t *s, bool paused)
192 {
193     aout_stream_sys_t *sys = s->sys;
194     HRESULT hr;
195
196     if (paused)
197         hr = IAudioClient_Stop(sys->client);
198     else
199         hr = IAudioClient_Start(sys->client);
200     if (FAILED(hr))
201         msg_Warn(s, "cannot %s stream (error 0x%lx)",
202                  paused ? "stop" : "start", hr);
203     return hr;
204 }
205
206 static HRESULT Flush(aout_stream_t *s)
207 {
208     aout_stream_sys_t *sys = s->sys;
209     HRESULT hr;
210
211     IAudioClient_Stop(sys->client);
212
213     hr = IAudioClient_Reset(sys->client);
214     if (FAILED(hr))
215         msg_Warn(s, "cannot reset stream (error 0x%lx)", hr);
216     else
217         sys->written = 0;
218     return hr;
219 }
220
221
222 /*** Initialization / deinitialization **/
223 static const uint32_t chans_out[] = {
224     SPEAKER_FRONT_LEFT, SPEAKER_FRONT_RIGHT,
225     SPEAKER_FRONT_CENTER, SPEAKER_LOW_FREQUENCY,
226     SPEAKER_BACK_LEFT, SPEAKER_BACK_RIGHT, SPEAKER_BACK_CENTER,
227     SPEAKER_SIDE_LEFT, SPEAKER_SIDE_RIGHT, 0
228 };
229 static const uint32_t chans_in[] = {
230     SPEAKER_FRONT_LEFT, SPEAKER_FRONT_RIGHT,
231     SPEAKER_SIDE_LEFT, SPEAKER_SIDE_RIGHT,
232     SPEAKER_BACK_LEFT, SPEAKER_BACK_RIGHT, SPEAKER_BACK_CENTER,
233     SPEAKER_FRONT_CENTER, SPEAKER_LOW_FREQUENCY, 0
234 };
235
236 static void vlc_ToWave(WAVEFORMATEXTENSIBLE *restrict wf,
237                        audio_sample_format_t *restrict audio)
238 {
239     switch (audio->i_format)
240     {
241         case VLC_CODEC_FL64:
242             audio->i_format = VLC_CODEC_FL32;
243         case VLC_CODEC_FL32:
244             wf->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
245             break;
246
247         case VLC_CODEC_U8:
248             audio->i_format = VLC_CODEC_S16N;
249         case VLC_CODEC_S16N:
250             wf->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
251             break;
252
253         default:
254             audio->i_format = VLC_CODEC_FL32;
255             wf->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
256             break;
257     }
258     aout_FormatPrepare (audio);
259
260     wf->Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
261     wf->Format.nChannels = audio->i_channels;
262     wf->Format.nSamplesPerSec = audio->i_rate;
263     wf->Format.nAvgBytesPerSec = audio->i_bytes_per_frame * audio->i_rate;
264     wf->Format.nBlockAlign = audio->i_bytes_per_frame;
265     wf->Format.wBitsPerSample = audio->i_bitspersample;
266     wf->Format.cbSize = sizeof (*wf) - sizeof (wf->Format);
267
268     wf->Samples.wValidBitsPerSample = audio->i_bitspersample;
269
270     wf->dwChannelMask = 0;
271     for (unsigned i = 0; pi_vlc_chan_order_wg4[i]; i++)
272         if (audio->i_physical_channels & pi_vlc_chan_order_wg4[i])
273             wf->dwChannelMask |= chans_in[i];
274 }
275
276 static int vlc_FromWave(const WAVEFORMATEX *restrict wf,
277                         audio_sample_format_t *restrict audio)
278 {
279     audio->i_rate = wf->nSamplesPerSec;
280     audio->i_physical_channels = 0;
281
282     if (wf->wFormatTag == WAVE_FORMAT_EXTENSIBLE)
283     {
284         const WAVEFORMATEXTENSIBLE *wfe = (void *)wf;
285
286         for (unsigned i = 0; chans_in[i]; i++)
287             if (wfe->dwChannelMask & chans_in[i])
288                 audio->i_physical_channels |= pi_vlc_chan_order_wg4[i];
289     }
290
291     audio->i_original_channels = audio->i_physical_channels;
292     aout_FormatPrepare (audio);
293
294     if (wf->nChannels != audio->i_channels)
295         return -1;
296     return 0;
297 }
298
299 static unsigned vlc_CheckWaveOrder (const WAVEFORMATEX *restrict wf,
300                                     uint8_t *restrict table)
301 {
302     uint32_t mask = 0;
303
304     if (wf->wFormatTag == WAVE_FORMAT_EXTENSIBLE)
305     {
306         const WAVEFORMATEXTENSIBLE *wfe = (void *)wf;
307
308         mask = wfe->dwChannelMask;
309     }
310     return aout_CheckChannelReorder(chans_in, chans_out, mask, table);
311 }
312
313 static HRESULT Start(aout_stream_t *s, audio_sample_format_t *restrict fmt,
314                      const GUID *sid)
315 {
316     if (!s->b_force && var_InheritBool(s, "spdif") && AOUT_FMT_SPDIF(fmt))
317         /* Fallback to other plugin until pass-through is implemented */
318         return E_NOTIMPL;
319
320     aout_stream_sys_t *sys = malloc(sizeof (*sys));
321     if (unlikely(sys == NULL))
322         return E_OUTOFMEMORY;
323     sys->client = NULL;
324
325     void *pv;
326     HRESULT hr = aout_stream_Activate(s, &IID_IAudioClient, NULL, &pv);
327     if (FAILED(hr))
328     {
329         msg_Err(s, "cannot activate client (error 0x%lx)", hr);
330         goto error;
331     }
332     sys->client = pv;
333
334     /* Configure audio stream */
335     WAVEFORMATEXTENSIBLE wf;
336     WAVEFORMATEX *pwf;
337
338     vlc_ToWave(&wf, fmt);
339     hr = IAudioClient_IsFormatSupported(sys->client, AUDCLNT_SHAREMODE_SHARED,
340                                         &wf.Format, &pwf);
341     if (FAILED(hr))
342     {
343         msg_Err(s, "cannot negotiate audio format (error 0x%lx)", hr);
344         goto error;
345     }
346
347     if (hr == S_FALSE)
348     {
349         assert(pwf != NULL);
350         if (vlc_FromWave(pwf, fmt))
351         {
352             CoTaskMemFree(pwf);
353             msg_Err(s, "unsupported audio format");
354             hr = E_INVALIDARG;
355             goto error;
356         }
357         msg_Dbg(s, "modified format");
358     }
359     else
360         assert(pwf == NULL);
361
362     sys->chans_to_reorder = vlc_CheckWaveOrder((hr == S_OK) ? &wf.Format : pwf,
363                                                sys->chans_table);
364     sys->format = fmt->i_format;
365
366     hr = IAudioClient_Initialize(sys->client, AUDCLNT_SHAREMODE_SHARED, 0,
367                                  AOUT_MAX_PREPARE_TIME * 10, 0,
368                                  (hr == S_OK) ? &wf.Format : pwf, sid);
369     CoTaskMemFree(pwf);
370     if (FAILED(hr))
371     {
372         msg_Err(s, "cannot initialize audio client (error 0x%lx)", hr);
373         goto error;
374     }
375
376     hr = IAudioClient_GetBufferSize(sys->client, &sys->frames);
377     if (FAILED(hr))
378     {
379         msg_Err(s, "cannot get buffer size (error 0x%lx)", hr);
380         goto error;
381     }
382
383     sys->rate = fmt->i_rate;
384     sys->bytes_per_frame = fmt->i_bytes_per_frame;
385     sys->written = 0;
386     s->sys = sys;
387     s->time_get = TimeGet;
388     s->play = Play;
389     s->pause = Pause;
390     s->flush = Flush;
391     return S_OK;
392 error:
393     if (sys->client != NULL)
394         IAudioClient_Release(sys->client);
395     free(sys);
396     return hr;
397 }
398
399 static void Stop(aout_stream_t *s)
400 {
401     aout_stream_sys_t *sys = s->sys;
402
403     IAudioClient_Stop(sys->client); /* should not be needed */
404     IAudioClient_Release(sys->client);
405 }
406
407 vlc_module_begin()
408     set_shortname("WASAPI")
409     set_description(N_("Windows Audio Session API output"))
410     set_capability("aout stream", /*50*/0)
411     set_category(CAT_AUDIO)
412     set_subcategory(SUBCAT_AUDIO_AOUT)
413     set_callbacks(Start, Stop)
414 vlc_module_end()