]> git.sesse.net Git - vlc/blob - modules/audio_output/wasapi.c
direct3d11: catch texture mapping errors
[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     UINT64 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, freq;
89     HRESULT hr;
90
91     hr = IAudioClient_GetService(sys->client, &IID_IAudioClock, &pv);
92     if (FAILED(hr))
93     {
94         msg_Err(s, "cannot get clock (error 0x%lx)", hr);
95         return hr;
96     }
97
98     IAudioClock *clock = pv;
99
100     hr = IAudioClock_GetPosition(clock, &pos, &qpcpos);
101     if (SUCCEEDED(hr))
102         hr = IAudioClock_GetFrequency(clock, &freq);
103     IAudioClock_Release(clock);
104     if (FAILED(hr))
105     {
106         msg_Err(s, "cannot get position (error 0x%lx)", hr);
107         return hr;
108     }
109
110     lldiv_t w = lldiv(sys->written, sys->rate);
111     lldiv_t r = lldiv(pos, freq);
112
113     static_assert((10000000 % CLOCK_FREQ) == 0, "Frequency conversion broken");
114
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));
119
120     return hr;
121 }
122
123 static HRESULT Play(aout_stream_t *s, block_t *block)
124 {
125     aout_stream_sys_t *sys = s->sys;
126     void *pv;
127     HRESULT hr;
128
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);
132
133     hr = IAudioClient_GetService(sys->client, &IID_IAudioRenderClient, &pv);
134     if (FAILED(hr))
135     {
136         msg_Err(s, "cannot get render client (error 0x%lx)", hr);
137         goto out;
138     }
139
140     IAudioRenderClient *render = pv;
141     for (;;)
142     {
143         UINT32 frames;
144         hr = IAudioClient_GetCurrentPadding(sys->client, &frames);
145         if (FAILED(hr))
146         {
147             msg_Err(s, "cannot get current padding (error 0x%lx)", hr);
148             break;
149         }
150
151         assert(frames <= sys->frames);
152         frames = sys->frames - frames;
153         if (frames > block->i_nb_samples)
154             frames = block->i_nb_samples;
155
156         BYTE *dst;
157         hr = IAudioRenderClient_GetBuffer(render, frames, &dst);
158         if (FAILED(hr))
159         {
160             msg_Err(s, "cannot get buffer (error 0x%lx)", hr);
161             break;
162         }
163
164         const size_t copy = frames * sys->bytes_per_frame;
165
166         memcpy(dst, block->p_buffer, copy);
167         hr = IAudioRenderClient_ReleaseBuffer(render, frames, 0);
168         if (FAILED(hr))
169         {
170             msg_Err(s, "cannot release buffer (error 0x%lx)", hr);
171             break;
172         }
173         IAudioClient_Start(sys->client);
174
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)
180             break; /* done */
181
182         /* Out of buffer space, sleep */
183         msleep(sys->frames * (CLOCK_FREQ / 2) / sys->rate);
184     }
185     IAudioRenderClient_Release(render);
186 out:
187     block_Release(block);
188
189     return hr;
190 }
191
192 static HRESULT Pause(aout_stream_t *s, bool paused)
193 {
194     aout_stream_sys_t *sys = s->sys;
195     HRESULT hr;
196
197     if (paused)
198         hr = IAudioClient_Stop(sys->client);
199     else
200         hr = IAudioClient_Start(sys->client);
201     if (FAILED(hr))
202         msg_Warn(s, "cannot %s stream (error 0x%lx)",
203                  paused ? "stop" : "start", hr);
204     return hr;
205 }
206
207 static HRESULT Flush(aout_stream_t *s)
208 {
209     aout_stream_sys_t *sys = s->sys;
210     HRESULT hr;
211
212     IAudioClient_Stop(sys->client);
213
214     hr = IAudioClient_Reset(sys->client);
215     if (SUCCEEDED(hr))
216     {
217         msg_Dbg(s, "reset");
218         sys->written = 0;
219     }
220     else
221         msg_Warn(s, "cannot reset stream (error 0x%lx)", hr);
222     return hr;
223 }
224
225
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
232 };
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
238 };
239
240 static void vlc_ToWave(WAVEFORMATEXTENSIBLE *restrict wf,
241                        audio_sample_format_t *restrict audio)
242 {
243     switch (audio->i_format)
244     {
245         case VLC_CODEC_FL64:
246             audio->i_format = VLC_CODEC_FL32;
247         case VLC_CODEC_FL32:
248             wf->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
249             break;
250
251         case VLC_CODEC_U8:
252             audio->i_format = VLC_CODEC_S16N;
253         case VLC_CODEC_S16N:
254             wf->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
255             break;
256
257         default:
258             audio->i_format = VLC_CODEC_FL32;
259             wf->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
260             break;
261     }
262     aout_FormatPrepare (audio);
263
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);
271
272     wf->Samples.wValidBitsPerSample = audio->i_bitspersample;
273
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];
278 }
279
280 static int vlc_FromWave(const WAVEFORMATEX *restrict wf,
281                         audio_sample_format_t *restrict audio)
282 {
283     audio->i_rate = wf->nSamplesPerSec;
284     audio->i_physical_channels = 0;
285
286     if (wf->wFormatTag == WAVE_FORMAT_EXTENSIBLE)
287     {
288         const WAVEFORMATEXTENSIBLE *wfe = (void *)wf;
289
290         if (IsEqualIID(&wfe->SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT))
291         {
292             switch (wf->wBitsPerSample)
293             {
294                 case 64:
295                     audio->i_format = VLC_CODEC_FL64;
296                     break;
297                 case 32:
298                     audio->i_format = VLC_CODEC_FL32;
299                     break;
300                 default:
301                     return -1;
302             }
303         }
304         else if (IsEqualIID(&wfe->SubFormat, &KSDATAFORMAT_SUBTYPE_PCM))
305         {
306             switch (wf->wBitsPerSample)
307             {
308                 case 32:
309                     audio->i_format = VLC_CODEC_S32N;
310                     break;
311                 case 16:
312                     audio->i_format = VLC_CODEC_S16N;
313                     break;
314                 default:
315                     return -1;
316             }
317         }
318
319         if (wfe->Samples.wValidBitsPerSample != wf->wBitsPerSample)
320             return -1;
321
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];
325     }
326     else
327         return -1;
328
329     audio->i_original_channels = audio->i_physical_channels;
330     aout_FormatPrepare (audio);
331
332     if (wf->nChannels != audio->i_channels)
333         return -1;
334     return 0;
335 }
336
337 static unsigned vlc_CheckWaveOrder (const WAVEFORMATEX *restrict wf,
338                                     uint8_t *restrict table)
339 {
340     uint32_t mask = 0;
341
342     if (wf->wFormatTag == WAVE_FORMAT_EXTENSIBLE)
343     {
344         const WAVEFORMATEXTENSIBLE *wfe = (void *)wf;
345
346         mask = wfe->dwChannelMask;
347     }
348     return aout_CheckChannelReorder(chans_in, chans_out, mask, table);
349 }
350
351 static HRESULT Start(aout_stream_t *s, audio_sample_format_t *restrict fmt,
352                      const GUID *sid)
353 {
354     if (!s->b_force && var_InheritBool(s, "spdif") && AOUT_FMT_SPDIF(fmt))
355         /* Fallback to other plugin until pass-through is implemented */
356         return E_NOTIMPL;
357
358     aout_stream_sys_t *sys = malloc(sizeof (*sys));
359     if (unlikely(sys == NULL))
360         return E_OUTOFMEMORY;
361     sys->client = NULL;
362
363     void *pv;
364     HRESULT hr = aout_stream_Activate(s, &IID_IAudioClient, NULL, &pv);
365     if (FAILED(hr))
366     {
367         msg_Err(s, "cannot activate client (error 0x%lx)", hr);
368         goto error;
369     }
370     sys->client = pv;
371
372     /* Configure audio stream */
373     WAVEFORMATEXTENSIBLE wf;
374     WAVEFORMATEX *pwf;
375
376     vlc_ToWave(&wf, fmt);
377     hr = IAudioClient_IsFormatSupported(sys->client, AUDCLNT_SHAREMODE_SHARED,
378                                         &wf.Format, &pwf);
379     if (FAILED(hr))
380     {
381         msg_Err(s, "cannot negotiate audio format (error 0x%lx)", hr);
382         goto error;
383     }
384
385     if (hr == S_FALSE)
386     {
387         assert(pwf != NULL);
388         if (vlc_FromWave(pwf, fmt))
389         {
390             CoTaskMemFree(pwf);
391             msg_Err(s, "unsupported audio format");
392             hr = E_INVALIDARG;
393             goto error;
394         }
395         msg_Dbg(s, "modified format");
396     }
397     else
398         assert(pwf == NULL);
399
400     sys->chans_to_reorder = vlc_CheckWaveOrder((hr == S_OK) ? &wf.Format : pwf,
401                                                sys->chans_table);
402     sys->format = fmt->i_format;
403
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);
407     CoTaskMemFree(pwf);
408     if (FAILED(hr))
409     {
410         msg_Err(s, "cannot initialize audio client (error 0x%lx)", hr);
411         goto error;
412     }
413
414     hr = IAudioClient_GetBufferSize(sys->client, &sys->frames);
415     if (FAILED(hr))
416     {
417         msg_Err(s, "cannot get buffer size (error 0x%lx)", hr);
418         goto error;
419     }
420     msg_Dbg(s, "buffer size    : %"PRIu32" frames", sys->frames);
421
422     REFERENCE_TIME latT, defT, minT;
423     if (SUCCEEDED(IAudioClient_GetStreamLatency(sys->client, &latT))
424      && SUCCEEDED(IAudioClient_GetDevicePeriod(sys->client, &defT, &minT)))
425     {
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);
429     }
430
431     sys->rate = fmt->i_rate;
432     sys->bytes_per_frame = fmt->i_bytes_per_frame;
433     sys->written = 0;
434     s->sys = sys;
435     s->time_get = TimeGet;
436     s->play = Play;
437     s->pause = Pause;
438     s->flush = Flush;
439     return S_OK;
440 error:
441     if (sys->client != NULL)
442         IAudioClient_Release(sys->client);
443     free(sys);
444     return hr;
445 }
446
447 static void Stop(aout_stream_t *s)
448 {
449     aout_stream_sys_t *sys = s->sys;
450
451     IAudioClient_Stop(sys->client); /* should not be needed */
452     IAudioClient_Release(sys->client);
453
454     free(sys);
455 }
456
457 vlc_module_begin()
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)
464 vlc_module_end()