]> git.sesse.net Git - vlc/blob - modules/audio_output/wasapi.c
Qt4: fix prototype
[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 <audiopolicy.h>
31 #include <mmdeviceapi.h>
32
33 #include <vlc_common.h>
34 #include <vlc_plugin.h>
35 #include <vlc_aout.h>
36 #include <vlc_charset.h>
37
38 static int Open(vlc_object_t *);
39 static void Close(vlc_object_t *);
40
41 vlc_module_begin()
42     set_shortname("WASAPI")
43     set_description(N_("Windows Audio Session output") )
44     set_capability("audio output", 150)
45     set_category(CAT_AUDIO)
46     set_subcategory(SUBCAT_AUDIO_AOUT)
47     add_shortcut("was", "audioclient")
48     set_callbacks(Open, Close)
49 vlc_module_end()
50
51 static int TryEnter(void)
52 {
53     HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
54     return -!!FAILED(hr);
55 }
56
57 static void Enter(void)
58 {
59     HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
60     if (unlikely(FAILED(hr)))
61         abort();
62 }
63
64 static void Leave(void)
65 {
66     CoUninitialize();
67 }
68
69 struct aout_sys_t
70 {
71     IAudioClient *client;
72     IAudioRenderClient *render;
73     IAudioClock *clock;
74     union
75     {
76         ISimpleAudioVolume *simple;
77     } volume;
78     IAudioSessionControl *control;
79     UINT32 frames; /**< Total buffer size (frames) */
80     HANDLE ready; /**< Semaphore from MTA thread */
81     HANDLE done; /**< Semaphore to MTA thread */
82 };
83
84 static void Play(audio_output_t *aout, block_t *block)
85 {
86     aout_sys_t *sys = aout->sys;
87     HRESULT hr;
88
89     Enter();
90     if (likely(sys->clock != NULL))
91     {
92         UINT64 pos, qpcpos;
93
94         IAudioClock_GetPosition(sys->clock, &pos, &qpcpos);
95         qpcpos = (qpcpos + 5) / 10; /* 100ns -> 1µs */
96         /* NOTE: this assumes mdate() uses QPC() (which it currently does). */
97         aout_TimeReport(aout, qpcpos);
98     }
99
100     for (;;)
101     {
102         UINT32 frames;
103         hr = IAudioClient_GetCurrentPadding(sys->client, &frames);
104         if (FAILED(hr))
105         {
106             msg_Err(aout, "cannot get current padding (error 0x%lx)", hr);
107             break;
108         }
109
110         assert(frames <= sys->frames);
111         frames = sys->frames - frames;
112         if (frames > block->i_nb_samples)
113             frames = block->i_nb_samples;
114
115         BYTE *dst;
116         hr = IAudioRenderClient_GetBuffer(sys->render, frames, &dst);
117         if (FAILED(hr))
118         {
119             msg_Err(aout, "cannot get buffer (error 0x%lx)", hr);
120             break;
121         }
122
123         const size_t copy = frames * (size_t)aout->format.i_bytes_per_frame;
124
125         memcpy(dst, block->p_buffer, copy);
126         hr = IAudioRenderClient_ReleaseBuffer(sys->render, frames, 0);
127         if (FAILED(hr))
128         {
129             msg_Err(aout, "cannot release buffer (error 0x%lx)", hr);
130             break;
131         }
132         IAudioClient_Start(sys->client);
133
134         block->p_buffer += copy;
135         block->i_buffer -= copy;
136         block->i_nb_samples -= frames;
137         if (block->i_nb_samples == 0)
138             break; /* done */
139
140         /* Out of buffer space, sleep */
141         msleep(AOUT_MIN_PREPARE_TIME
142              + block->i_nb_samples * CLOCK_FREQ / aout->format.i_rate);
143     }
144
145     Leave();
146     block_Release(block);
147 }
148
149 static void Pause(audio_output_t *aout, bool paused, mtime_t date)
150 {
151     aout_sys_t *sys = aout->sys;
152     HRESULT hr;
153
154     Enter();
155     if (paused)
156         hr = IAudioClient_Stop(sys->client);
157     else
158         hr = IAudioClient_Start(sys->client);
159     if (FAILED(hr))
160         msg_Warn(aout, "cannot %s stream (error 0x%lx)",
161                  paused ? "stop" : "start", hr);
162     Leave();
163     (void) date;
164 }
165
166 static void Flush(audio_output_t *aout, bool wait)
167 {
168     aout_sys_t *sys = aout->sys;
169     HRESULT hr;
170
171     if (wait)
172         return; /* Drain not implemented */
173
174     Enter();
175     IAudioClient_Stop(sys->client);
176     hr = IAudioClient_Reset(sys->client);
177     if (FAILED(hr))
178         msg_Warn(aout, "cannot reset stream (error 0x%lx)", hr);
179     Leave();
180 }
181
182 static int SimpleVolumeSet(audio_output_t *aout, float vol)
183 {
184     aout_sys_t *sys = aout->sys;
185     HRESULT hr;
186
187     if (vol > 1.)
188         vol = 1.;
189
190     Enter();
191     hr = ISimpleAudioVolume_SetMasterVolume(sys->volume.simple, vol, NULL);
192     if (FAILED(hr))
193         msg_Warn(aout, "cannot set session volume (error 0x%lx)", hr);
194     Leave();
195     return FAILED(hr) ? -1 : 0;
196 }
197
198 static int SimpleMuteSet(audio_output_t *aout, bool mute)
199 {
200     aout_sys_t *sys = aout->sys;
201     HRESULT hr;
202
203     Enter();
204     hr = ISimpleAudioVolume_SetMute(sys->volume.simple, mute, NULL);
205     if (FAILED(hr))
206         msg_Warn(aout, "cannot mute session (error 0x%lx)", hr);
207     Leave();
208     return FAILED(hr) ? -1 : 0;
209 }
210
211 static void vlc_ToWave(WAVEFORMATEXTENSIBLE *restrict wf,
212                        audio_sample_format_t *restrict audio)
213 {
214     switch (audio->i_format)
215     {
216         case VLC_CODEC_FL64:
217             audio->i_format = VLC_CODEC_FL32;
218         case VLC_CODEC_FL32:
219             wf->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
220             break;
221
222         case VLC_CODEC_S8:
223         case VLC_CODEC_U8:
224             audio->i_format = VLC_CODEC_S16N;
225         case VLC_CODEC_S16N:
226             wf->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
227             break;
228
229         default:
230             audio->i_format = VLC_CODEC_FL32;
231             wf->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
232             break;
233      }
234      aout_FormatPrepare (audio);
235
236      wf->Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
237      wf->Format.nChannels = audio->i_channels;
238      wf->Format.nSamplesPerSec = audio->i_rate;
239      wf->Format.nAvgBytesPerSec = audio->i_bytes_per_frame * audio->i_rate;
240      wf->Format.nBlockAlign = audio->i_bytes_per_frame;
241      wf->Format.wBitsPerSample = audio->i_bitspersample;
242      wf->Format.cbSize = sizeof (*wf) - sizeof (wf->Format);
243
244      wf->Samples.wValidBitsPerSample = audio->i_bitspersample;
245
246      wf->dwChannelMask = 0;
247      if (audio->i_physical_channels & AOUT_CHAN_LEFT)
248          wf->dwChannelMask |= SPEAKER_FRONT_LEFT;
249      if (audio->i_physical_channels & AOUT_CHAN_RIGHT)
250          wf->dwChannelMask |= SPEAKER_FRONT_RIGHT;
251      if (audio->i_physical_channels & AOUT_CHAN_CENTER)
252          wf->dwChannelMask |= SPEAKER_FRONT_CENTER;
253      if (audio->i_physical_channels & AOUT_CHAN_LFE)
254          wf->dwChannelMask |= SPEAKER_LOW_FREQUENCY;
255      // TODO: reorder
256      if (audio->i_physical_channels & AOUT_CHAN_REARLEFT)
257          wf->dwChannelMask |= SPEAKER_BACK_LEFT;
258      if (audio->i_physical_channels & AOUT_CHAN_REARRIGHT)
259          wf->dwChannelMask |= SPEAKER_BACK_RIGHT;
260      /* ... */
261      if (audio->i_physical_channels & AOUT_CHAN_REARCENTER)
262          wf->dwChannelMask |= SPEAKER_BACK_CENTER;
263      if (audio->i_physical_channels & AOUT_CHAN_MIDDLELEFT)
264          wf->dwChannelMask |= SPEAKER_SIDE_LEFT;
265      if (audio->i_physical_channels & AOUT_CHAN_MIDDLERIGHT)
266          wf->dwChannelMask |= SPEAKER_SIDE_RIGHT;
267      /* ... */
268 }
269
270 static int vlc_FromWave(const WAVEFORMATEX *restrict wf,
271                         audio_sample_format_t *restrict audio)
272 {
273     /* FIXME? different sample format? possible? */
274     audio->i_rate = wf->nSamplesPerSec;
275     /* FIXME */
276     if (wf->nChannels != audio->i_channels)
277         return -1;
278
279     aout_FormatPrepare(audio);
280     return 0;
281 }
282
283 static wchar_t *var_InheritWide(vlc_object_t *obj, const char *name)
284 {
285     char *v8 = var_InheritString(obj, name);
286     if (v8 == NULL)
287         return NULL;
288
289     wchar_t *v16 = ToWide(v8);
290     free(v8);
291     return v16;
292 }
293 #define var_InheritWide(o,n) var_InheritWide(VLC_OBJECT(o),n)
294
295 /* Dummy thread to create and release COM interfaces when needed. */
296 static void MTAThread(void *data)
297 {
298     audio_output_t *aout = data;
299     aout_sys_t *sys = aout->sys;
300     HRESULT hr;
301
302     Enter();
303
304     hr = IAudioClient_GetService(sys->client, &IID_IAudioRenderClient,
305                                  (void **)&sys->render);
306     if (FAILED(hr))
307     {
308         msg_Err(aout, "cannot get audio render service (error 0x%lx)", hr);
309         goto fail;
310     }
311
312     hr = IAudioClient_GetService(sys->client, &IID_IAudioClock,
313                                  (void **)&sys->clock);
314     if (FAILED(hr))
315         msg_Warn(aout, "cannot get audio clock (error 0x%lx)", hr);
316
317     /*if (AOUT_FMT_LINEAR(&format) && !exclusive)*/
318     {
319         hr = IAudioClient_GetService(sys->client, &IID_ISimpleAudioVolume,
320                                      (void **)&sys->volume.simple);
321     }
322
323     hr = IAudioClient_GetService(sys->client, &IID_IAudioSessionControl,
324                                  (void **)&sys->control);
325     if (FAILED(hr))
326         msg_Warn(aout, "cannot get audio session control (error 0x%lx)", hr);
327     else
328     {
329         wchar_t *ua = var_InheritWide(aout, "user-agent");
330         IAudioSessionControl_SetDisplayName(sys->control, ua, NULL);
331         free(ua);
332     }
333
334     /* do nothing until the audio session terminates */
335     ReleaseSemaphore(sys->ready, 1, NULL);
336     WaitForSingleObject(sys->done, INFINITE);
337
338     if (sys->control != NULL)
339         IAudioSessionControl_Release(sys->control);
340     /*if (AOUT_FMT_LINEAR(&format) && !exclusive)*/
341     {
342         if (sys->volume.simple != NULL)
343             ISimpleAudioVolume_Release(sys->volume.simple);
344     }
345     if (sys->clock != NULL)
346         IAudioClock_Release(sys->clock);
347     IAudioRenderClient_Release(sys->render);
348 fail:
349     Leave();
350     ReleaseSemaphore(sys->ready, 1, NULL);
351 }
352
353 static int Open(vlc_object_t *obj)
354 {
355     audio_output_t *aout = (audio_output_t *)obj;
356     HRESULT hr;
357
358     if (AOUT_FMT_SPDIF(&aout->format) && !aout->b_force
359      && var_InheritBool(aout, "spdif"))
360         /* Fallback to other plugin until pass-through is implemented */
361         return VLC_EGENERIC;
362
363     aout_sys_t *sys = malloc(sizeof (*sys));
364     if (unlikely(sys == NULL))
365         return VLC_ENOMEM;
366     sys->client = NULL;
367     sys->render = NULL;
368     sys->clock = NULL;
369     sys->ready = NULL;
370     sys->done = NULL;
371     aout->sys = sys;
372
373     if (TryEnter())
374     {
375         free(sys);
376         return VLC_EGENERIC;
377     }
378
379     /* Get audio device according to policy */
380     IMMDeviceEnumerator *devs;
381     hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL,
382                           &IID_IMMDeviceEnumerator, (void **)&devs);
383     if (FAILED(hr))
384     {
385         msg_Dbg(aout, "cannot create device enumerator (error 0x%lx)", hr);
386         goto error;
387     }
388
389     IMMDevice *dev;
390     hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(devs, eRender,
391                                                      eConsole, &dev);
392     IMMDeviceEnumerator_Release(devs);
393     if (FAILED(hr))
394     {
395         msg_Err(aout, "cannot get audio endpoint (error 0x%lx)", hr);
396         goto error;
397     }
398
399     LPWSTR str;
400     hr = IMMDevice_GetId(dev, &str);
401     if (SUCCEEDED(hr))
402     {
403         msg_Dbg(aout, "using device %ls", str);
404         CoTaskMemFree(str);
405     }
406
407     hr = IMMDevice_Activate(dev, &IID_IAudioClient, CLSCTX_ALL, NULL,
408                             (void **)&sys->client);
409     IMMDevice_Release(dev);
410     if (FAILED(hr))
411     {
412         msg_Err(aout, "cannot activate audio client (error 0x%lx)", hr);
413         goto error;
414     }
415
416     /* Configure audio stream */
417     audio_sample_format_t format = aout->format;
418     WAVEFORMATEXTENSIBLE wf;
419     WAVEFORMATEX *pwf;
420
421     vlc_ToWave(&wf, &format);
422     hr = IAudioClient_IsFormatSupported(sys->client, AUDCLNT_SHAREMODE_SHARED,
423                                         &wf.Format, &pwf);
424     // TODO: deal with (hr == AUDCLNT_E_DEVICE_INVALIDATED) ?
425     if (FAILED(hr))
426     {
427         msg_Err(aout, "cannot negotiate audio format (error 0x%lx)", hr);
428         goto error;
429     }
430
431     if (hr == S_FALSE)
432     {
433         assert(pwf != NULL);
434         if (vlc_FromWave(pwf, &format))
435         {
436             CoTaskMemFree(pwf);
437             msg_Err(aout, "unsupported audio format");
438             goto error;
439         }
440         msg_Dbg(aout, "modified format");
441     }
442     else
443         assert(pwf == NULL);
444     hr = IAudioClient_Initialize(sys->client, AUDCLNT_SHAREMODE_SHARED, 0,
445                                  AOUT_MAX_PREPARE_TIME * 10, 0,
446                                  (hr == S_OK) ? &wf.Format : pwf, NULL);
447     CoTaskMemFree(pwf);
448     if (FAILED(hr))
449     {
450         msg_Err(aout, "cannot initialize audio client (error 0x%lx)", hr);
451         goto error;
452     }
453
454     hr = IAudioClient_GetBufferSize(sys->client, &sys->frames);
455     if (FAILED(hr))
456     {
457         msg_Err(aout, "cannot get buffer size (error 0x%lx)", hr);
458         goto error;
459     }
460
461     sys->ready = CreateSemaphore(NULL, 0, 1, NULL);
462     sys->done = CreateSemaphore(NULL, 0, 1, NULL);
463     if (unlikely(sys->ready == NULL || sys->done == NULL))
464         goto error;
465     /* Note: thread handle released by CRT, ignore it. */
466     if (_beginthread(MTAThread, 0, aout) == (uintptr_t)-1)
467         goto error;
468
469     WaitForSingleObject(sys->ready, INFINITE);
470     if (sys->render == NULL)
471         goto error;
472
473     aout->format = format;
474     aout->pf_play = Play;
475     aout->pf_pause = Pause;
476     aout->pf_flush = Flush;
477     /*if (AOUT_FMT_LINEAR(&format) && !exclusive)*/
478     {
479         aout->volume_set = SimpleVolumeSet;
480         aout->mute_set = SimpleMuteSet;
481     }
482     Leave();
483     return VLC_SUCCESS;
484 error:
485     if (sys->done != NULL)
486         CloseHandle(sys->done);
487     if (sys->ready != NULL)
488         CloseHandle(sys->done);
489     if (sys->client != NULL)
490         IAudioClient_Release(sys->client);
491     Leave();
492     free(sys);
493     return VLC_EGENERIC;
494 }
495
496 static void Close (vlc_object_t *obj)
497 {
498     audio_output_t *aout = (audio_output_t *)obj;
499     aout_sys_t *sys = aout->sys;
500
501     Enter();
502     ReleaseSemaphore(sys->done, 1, NULL); /* tell MTA thread to finish */
503     WaitForSingleObject(sys->ready, INFINITE); /* wait for that ^ */
504     IAudioClient_Stop(sys->client); /* should not be needed */
505     IAudioClient_Release(sys->client);
506     Leave();
507
508     CloseHandle(sys->done);
509     CloseHandle(sys->ready);
510     free(sys);
511 }