]> git.sesse.net Git - vlc/blob - modules/audio_output/wasapi.c
wasapi: clock synchronization
[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
7  * it 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
28 #include <assert.h>
29 #include <audioclient.h>
30 #include <mmdeviceapi.h>
31
32 #include <vlc_common.h>
33 #include <vlc_plugin.h>
34 #include <vlc_aout.h>
35
36 static int Open(vlc_object_t *);
37 static void Close(vlc_object_t *);
38
39 vlc_module_begin()
40     set_shortname("WASAPI")
41     set_description(N_("Windows Audio Session output") )
42     set_capability("audio output", 150)
43     set_category(CAT_AUDIO)
44     set_subcategory(SUBCAT_AUDIO_AOUT)
45     add_shortcut("was", "audioclient")
46     set_callbacks(Open, Close)
47 vlc_module_end()
48
49 struct aout_sys_t
50 {
51     IAudioClient *client;
52     IAudioRenderClient *render;
53     IAudioClock *clock;
54     UINT32 frames; /**< Total buffer size (frames) */
55     HANDLE done; /**< Semaphore for MTA thread */
56 };
57
58 static void Play(audio_output_t *aout, block_t *block)
59 {
60     aout_sys_t *sys = aout->sys;
61     HRESULT hr;
62
63     CoInitializeEx(NULL, COINIT_MULTITHREADED);
64     if (likely(sys->clock != NULL))
65     {
66         UINT64 pos, qpcpos;
67
68         IAudioClock_GetPosition(sys->clock, &pos, &qpcpos);
69         qpcpos = (qpcpos + 5) / 10; /* 100ns -> 1µs */
70         /* NOTE: this assumes mdate() uses QPC() (which it currently does). */
71         aout_TimeReport(aout, qpcpos);
72     }
73
74     for (;;)
75     {
76         UINT32 frames;
77         hr = IAudioClient_GetCurrentPadding(sys->client, &frames);
78         if (FAILED(hr))
79         {
80             msg_Err(aout, "cannot get current padding (error 0x%lx)", hr);
81             break;
82         }
83
84         assert(frames <= sys->frames);
85         frames = sys->frames - frames;
86         if (frames > block->i_nb_samples)
87             frames = block->i_nb_samples;
88
89         BYTE *dst;
90         hr = IAudioRenderClient_GetBuffer(sys->render, frames, &dst);
91         if (FAILED(hr))
92         {
93             msg_Err(aout, "cannot get buffer (error 0x%lx)", hr);
94             break;
95         }
96
97         const size_t copy = frames * (size_t)aout->format.i_bytes_per_frame;
98
99         memcpy(dst, block->p_buffer, copy);
100         hr = IAudioRenderClient_ReleaseBuffer(sys->render, frames, 0);
101         if (FAILED(hr))
102         {
103             msg_Err(aout, "cannot release buffer (error 0x%lx)", hr);
104             break;
105         }
106         IAudioClient_Start(sys->client);
107
108         block->p_buffer += copy;
109         block->i_buffer -= copy;
110         block->i_nb_samples -= frames;
111         if (block->i_nb_samples == 0)
112             break; /* done */
113
114         /* Out of buffer space, sleep */
115         msleep(AOUT_MIN_PREPARE_TIME
116              + block->i_nb_samples * CLOCK_FREQ / aout->format.i_rate);
117     }
118
119     CoUninitialize();
120     block_Release(block);
121 }
122
123 static void Pause(audio_output_t *aout, bool paused, mtime_t date)
124 {
125     aout_sys_t *sys = aout->sys;
126     HRESULT hr;
127
128     CoInitializeEx(NULL, COINIT_MULTITHREADED);
129     if (paused)
130         hr = IAudioClient_Stop(sys->client);
131     else
132         hr = IAudioClient_Start(sys->client);
133     if (FAILED(hr))
134         msg_Warn(aout, "cannot %s stream (error 0x%lx)",
135                  paused ? "stop" : "start", hr);
136     CoUninitialize();
137     (void) date;
138 }
139
140 static void Flush(audio_output_t *aout, bool wait)
141 {
142     aout_sys_t *sys = aout->sys;
143     HRESULT hr;
144
145     if (wait)
146         return; /* Not drain implemented */
147
148     CoInitializeEx(NULL, COINIT_MULTITHREADED);
149     IAudioClient_Stop(sys->client);
150     hr = IAudioClient_Reset(sys->client);
151     if (FAILED(hr))
152         msg_Warn(aout, "cannot reset stream (error 0x%lx)", hr);
153     CoUninitialize();
154 }
155
156 /*static int VolumeSet(audio_output_t *aout, float vol, bool mute)
157 {
158     aout_sys_t *sys = aout->sys;
159
160     return 0;
161 }*/
162
163 static void vlc_ToWave(WAVEFORMATEXTENSIBLE *restrict wf,
164                        audio_sample_format_t *restrict audio)
165 {
166     switch (audio->i_format)
167     {
168 #if 0
169         case VLC_CODEC_FL32:
170         case VLC_CODEC_FL64:
171             wf->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
172             break;
173
174         case VLC_CODEC_S8:
175         case VLC_CODEC_S16N:
176         case VLC_CODEC_S24N:
177         case VLC_CODEC_S32N:
178             wf->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
179             break;
180 #endif
181         default:
182             audio->i_format = VLC_CODEC_FL32;
183             audio->i_rate = 48000;
184             wf->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
185             break;
186      }
187      aout_FormatPrepare (audio);
188
189      wf->Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
190      wf->Format.nChannels = audio->i_channels;
191      wf->Format.nSamplesPerSec = audio->i_rate;
192      wf->Format.nAvgBytesPerSec = audio->i_bytes_per_frame * audio->i_rate;
193      wf->Format.nBlockAlign = audio->i_bytes_per_frame;
194      wf->Format.wBitsPerSample = audio->i_bitspersample;
195      wf->Format.cbSize = sizeof (*wf) - sizeof (wf->Format);
196
197      wf->Samples.wValidBitsPerSample = audio->i_bitspersample;
198
199      wf->dwChannelMask = 0;
200      if (audio->i_physical_channels & AOUT_CHAN_LEFT)
201          wf->dwChannelMask |= SPEAKER_FRONT_LEFT;
202      if (audio->i_physical_channels & AOUT_CHAN_RIGHT)
203          wf->dwChannelMask |= SPEAKER_FRONT_RIGHT;
204      if (audio->i_physical_channels & AOUT_CHAN_CENTER)
205          wf->dwChannelMask |= SPEAKER_FRONT_CENTER;
206      if (audio->i_physical_channels & AOUT_CHAN_LFE)
207          wf->dwChannelMask |= SPEAKER_LOW_FREQUENCY;
208      // TODO: reorder
209      if (audio->i_physical_channels & AOUT_CHAN_REARLEFT)
210          wf->dwChannelMask |= SPEAKER_BACK_LEFT;
211      if (audio->i_physical_channels & AOUT_CHAN_REARRIGHT)
212          wf->dwChannelMask |= SPEAKER_BACK_RIGHT;
213      /* ... */
214      if (audio->i_physical_channels & AOUT_CHAN_REARCENTER)
215          wf->dwChannelMask |= SPEAKER_BACK_CENTER;
216      if (audio->i_physical_channels & AOUT_CHAN_MIDDLELEFT)
217          wf->dwChannelMask |= SPEAKER_SIDE_LEFT;
218      if (audio->i_physical_channels & AOUT_CHAN_MIDDLERIGHT)
219          wf->dwChannelMask |= SPEAKER_SIDE_RIGHT;
220      /* ... */
221 }
222
223 static int vlc_FromWave(const WAVEFORMATEX *restrict wf,
224                         audio_sample_format_t *restrict audio)
225 {
226     /* FIXME? different sample format? possible? */
227     audio->i_rate = wf->nSamplesPerSec;
228     /* FIXME */
229     if (wf->nChannels != audio->i_channels)
230         return -1;
231
232     aout_FormatPrepare(audio);
233     return 0;
234 }
235
236 /* Dummy thread to keep COM MTA alive */
237 static void MTAThread(void *data)
238 {
239     HANDLE done = data;
240     HRESULT hr;
241
242     hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
243     if (unlikely(FAILED(hr)))
244         abort();
245     WaitForSingleObject(done, INFINITE);
246     CoUninitialize();
247     CloseHandle(done);
248 }
249
250 static int Open(vlc_object_t *obj)
251 {
252     audio_output_t *aout = (audio_output_t *)obj;
253     HRESULT hr;
254
255     aout_sys_t *sys = malloc(sizeof (*sys));
256     if (unlikely(sys == NULL))
257         return VLC_ENOMEM;
258     sys->client = NULL;
259     sys->render = NULL;
260     sys->clock = NULL;
261     sys->done = NULL;
262
263     hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
264     if (FAILED(hr))
265     {
266         free(sys);
267         return VLC_EGENERIC;
268     }
269
270     /* Select audio device */
271     IMMDeviceEnumerator *devs;
272     hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL,
273                           &IID_IMMDeviceEnumerator, (void **)&devs);
274     if (FAILED(hr))
275     {
276         msg_Dbg(aout, "cannot create device enumerator (error 0x%lx)", hr);
277         goto error;
278     }
279
280     /* TODO: support selecting a device from config? */
281     IMMDevice *dev;
282     hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(devs, eRender,
283                                                      eConsole, &dev);
284     IMMDeviceEnumerator_Release(devs);
285     if (FAILED(hr))
286     {
287         msg_Err(aout, "cannot get audio endpoint (error 0x%lx)", hr);
288         goto error;
289     }
290
291     LPWSTR str;
292     hr = IMMDevice_GetId(dev, &str);
293     if (SUCCEEDED(hr))
294     {
295         msg_Dbg(aout, "using device %ls", str);
296         CoTaskMemFree(str);
297     }
298
299     hr = IMMDevice_Activate(dev, &IID_IAudioClient, CLSCTX_ALL, NULL,
300                             (void **)&sys->client);
301     IMMDevice_Release(dev);
302     if (FAILED(hr))
303     {
304         msg_Err(aout, "cannot activate audio client (error 0x%lx)", hr);
305         goto error;
306     }
307
308     /* Configure audio stream */
309     audio_sample_format_t format = aout->format;
310     WAVEFORMATEXTENSIBLE wf;
311     WAVEFORMATEX *pwf;
312
313     vlc_ToWave(&wf, &format);
314     hr = IAudioClient_IsFormatSupported(sys->client, AUDCLNT_SHAREMODE_SHARED,
315                                         &wf.Format, &pwf);
316     // TODO: deal with (hr == AUDCLNT_E_DEVICE_INVALIDATED) ?
317     if (FAILED(hr))
318     {
319         msg_Err(aout, "cannot negotiate audio format (error 0x%lx)", hr);
320         goto error;
321     }
322
323     if (hr == S_FALSE)
324     {
325         assert(pwf != NULL);
326         if (vlc_FromWave(pwf, &format))
327         {
328             CoTaskMemFree(pwf);
329             msg_Err(aout, "unsupported audio format");
330             goto error;
331         }
332         msg_Dbg(aout, "modified format");
333     }
334     else
335         assert(pwf == NULL);
336     hr = IAudioClient_Initialize(sys->client, AUDCLNT_SHAREMODE_SHARED, 0,
337                                  AOUT_MAX_PREPARE_TIME * 10, 0,
338                                  (hr == S_OK) ? &wf.Format : pwf, NULL);
339     CoTaskMemFree(pwf);
340     if (FAILED(hr))
341     {
342         msg_Err(aout, "cannot initialize audio client (error 0x%lx)", hr);
343         goto error;
344     }
345
346     hr = IAudioClient_GetBufferSize(sys->client, &sys->frames);
347     if (FAILED(hr))
348     {
349         msg_Err(aout, "cannot get buffer size (error 0x%lx)", hr);
350         goto error;
351     }
352
353     hr = IAudioClient_GetService(sys->client, &IID_IAudioRenderClient,
354                                  (void **)&sys->render);
355     if (FAILED(hr))
356     {
357         msg_Err(aout, "cannot get audio render service (error 0x%lx)", hr);
358         goto error;
359     }
360
361     hr = IAudioClient_GetService(sys->client, &IID_IAudioClock,
362                                  (void **)&sys->clock);
363     if (FAILED(hr))
364         msg_Warn(aout, "cannot get audio clock (error 0x%lx)", hr);
365
366     sys->done = CreateSemaphore(NULL, 0, 1, NULL);
367     if (unlikely(sys->done == NULL))
368         goto error;
369     /* Note: thread handle released by CRT, ignore it. */
370     if (_beginthread(MTAThread, 0, sys->done) == (uintptr_t)-1)
371         goto error;
372
373     aout->format = format;
374     aout->sys = sys;
375     aout->pf_play = Play;
376     aout->pf_pause = Pause;
377     aout->pf_flush = Flush;
378     aout_VolumeNoneInit (aout);
379     CoUninitialize();
380     return VLC_SUCCESS;
381 error:
382     if (sys->done != NULL)
383         CloseHandle(sys->done);
384     if (sys->clock != NULL)
385         IAudioClock_Release(sys->clock);
386     if (sys->render != NULL)
387         IAudioRenderClient_Release(sys->render);
388     if (sys->client != NULL)
389         IAudioClient_Release(sys->client);
390     CoUninitialize();
391     free(sys);
392     return VLC_EGENERIC;
393 }
394
395 static void Close (vlc_object_t *obj)
396 {
397     audio_output_t *aout = (audio_output_t *)obj;
398     aout_sys_t *sys = aout->sys;
399
400     CoInitializeEx(NULL, COINIT_MULTITHREADED);
401     IAudioClient_Stop(sys->client); /* should not be needed */
402     IAudioClock_Release(sys->clock);
403     IAudioRenderClient_Release(sys->render);
404     IAudioClient_Release(sys->client);
405     CoUninitialize();
406
407     ReleaseSemaphore(sys->done, 1, NULL); /* MTA thread will exit */
408     free(sys);
409 }