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