]> git.sesse.net Git - vlc/blob - modules/audio_output/wasapi.c
11ec2125fb32630ca424c8e3acdd07b7ffcdd5ec
[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(vlc_object_t *obj)
52 {
53     HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
54     if (unlikely(FAILED(hr)))
55     {
56         msg_Err (obj, "cannot initialize COM (error 0x%lx)", hr);
57         return -1;
58     }
59     return 0;
60 }
61 #define TryEnter(o) TryEnter(VLC_OBJECT(o))
62
63 static void Enter(void)
64 {
65     HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
66     if (unlikely(FAILED(hr)))
67         abort();
68 }
69
70 static void Leave(void)
71 {
72     CoUninitialize();
73 }
74
75 struct aout_sys_t
76 {
77     IAudioClient *client;
78     IAudioRenderClient *render;
79     IAudioClock *clock;
80     union
81     {
82         ISimpleAudioVolume *simple;
83     } volume;
84     IAudioSessionControl *control;
85     UINT32 frames; /**< Total buffer size (frames) */
86     HANDLE ready; /**< Semaphore from MTA thread */
87     HANDLE done; /**< Semaphore to MTA thread */
88 };
89
90 static void Play(audio_output_t *aout, block_t *block, mtime_t *restrict drift)
91 {
92     aout_sys_t *sys = aout->sys;
93     HRESULT hr;
94
95     Enter();
96     if (likely(sys->clock != NULL))
97     {
98         UINT64 pos, qpcpos;
99
100         IAudioClock_GetPosition(sys->clock, &pos, &qpcpos);
101         qpcpos = (qpcpos + 5) / 10; /* 100ns -> 1µs */
102         /* NOTE: this assumes mdate() uses QPC() (which it currently does). */
103         *drift = mdate() - qpcpos;
104     }
105
106     for (;;)
107     {
108         UINT32 frames;
109         hr = IAudioClient_GetCurrentPadding(sys->client, &frames);
110         if (FAILED(hr))
111         {
112             msg_Err(aout, "cannot get current padding (error 0x%lx)", hr);
113             break;
114         }
115
116         assert(frames <= sys->frames);
117         frames = sys->frames - frames;
118         if (frames > block->i_nb_samples)
119             frames = block->i_nb_samples;
120
121         BYTE *dst;
122         hr = IAudioRenderClient_GetBuffer(sys->render, frames, &dst);
123         if (FAILED(hr))
124         {
125             msg_Err(aout, "cannot get buffer (error 0x%lx)", hr);
126             break;
127         }
128
129         const size_t copy = frames * (size_t)aout->format.i_bytes_per_frame;
130
131         memcpy(dst, block->p_buffer, copy);
132         hr = IAudioRenderClient_ReleaseBuffer(sys->render, frames, 0);
133         if (FAILED(hr))
134         {
135             msg_Err(aout, "cannot release buffer (error 0x%lx)", hr);
136             break;
137         }
138         IAudioClient_Start(sys->client);
139
140         block->p_buffer += copy;
141         block->i_buffer -= copy;
142         block->i_nb_samples -= frames;
143         if (block->i_nb_samples == 0)
144             break; /* done */
145
146         /* Out of buffer space, sleep */
147         msleep(AOUT_MIN_PREPARE_TIME
148              + block->i_nb_samples * CLOCK_FREQ / aout->format.i_rate);
149     }
150
151     Leave();
152     block_Release(block);
153 }
154
155 static void Pause(audio_output_t *aout, bool paused, mtime_t date)
156 {
157     aout_sys_t *sys = aout->sys;
158     HRESULT hr;
159
160     Enter();
161     if (paused)
162         hr = IAudioClient_Stop(sys->client);
163     else
164         hr = IAudioClient_Start(sys->client);
165     if (FAILED(hr))
166         msg_Warn(aout, "cannot %s stream (error 0x%lx)",
167                  paused ? "stop" : "start", hr);
168     Leave();
169     (void) date;
170 }
171
172 static void Flush(audio_output_t *aout, bool wait)
173 {
174     aout_sys_t *sys = aout->sys;
175     HRESULT hr;
176
177     if (wait)
178         return; /* Drain not implemented */
179
180     Enter();
181     IAudioClient_Stop(sys->client);
182     hr = IAudioClient_Reset(sys->client);
183     if (FAILED(hr))
184         msg_Warn(aout, "cannot reset stream (error 0x%lx)", hr);
185     Leave();
186 }
187
188 static int SimpleVolumeSet(audio_output_t *aout, float vol)
189 {
190     aout_sys_t *sys = aout->sys;
191     HRESULT hr;
192
193     if (vol > 1.)
194         vol = 1.;
195
196     Enter();
197     hr = ISimpleAudioVolume_SetMasterVolume(sys->volume.simple, vol, NULL);
198     if (FAILED(hr))
199         msg_Warn(aout, "cannot set session volume (error 0x%lx)", hr);
200     Leave();
201     return FAILED(hr) ? -1 : 0;
202 }
203
204 static int SimpleMuteSet(audio_output_t *aout, bool mute)
205 {
206     aout_sys_t *sys = aout->sys;
207     HRESULT hr;
208
209     Enter();
210     hr = ISimpleAudioVolume_SetMute(sys->volume.simple, mute, NULL);
211     if (FAILED(hr))
212         msg_Warn(aout, "cannot mute session (error 0x%lx)", hr);
213     Leave();
214     return FAILED(hr) ? -1 : 0;
215 }
216
217 static int DeviceChanged(vlc_object_t *obj, const char *varname,
218                          vlc_value_t prev, vlc_value_t cur, void *data)
219 {
220     aout_ChannelsRestart(obj, varname, prev, cur, data);
221
222     if (!var_Type (obj, "wasapi-audio-device"))
223         var_Create (obj, "wasapi-audio-device", VLC_VAR_STRING);
224     var_SetString (obj, "wasapi-audio-device", cur.psz_string);
225     return VLC_SUCCESS;
226 }
227
228 static void GetDevices(vlc_object_t *obj, IMMDeviceEnumerator *it)
229 {
230     HRESULT hr;
231     vlc_value_t val, text;
232
233     text.psz_string = _("Audio Device");
234     var_Change (obj, "audio-device", VLC_VAR_SETTEXT, &text, NULL);
235
236     IMMDeviceCollection *devs;
237     hr = IMMDeviceEnumerator_EnumAudioEndpoints(it, eRender,
238                                                 DEVICE_STATE_ACTIVE, &devs);
239     if (FAILED(hr))
240     {
241         msg_Warn (obj, "cannot enumerate audio endpoints (error 0x%lx)", hr);
242         return;
243     }
244
245     UINT n;
246     hr = IMMDeviceCollection_GetCount(devs, &n);
247     if (FAILED(hr))
248         n = 0;
249     while (n > 0)
250     {
251         IMMDevice *dev;
252
253         hr = IMMDeviceCollection_Item(devs, --n, &dev);
254         if (FAILED(hr))
255             continue;
256
257         /* Unique device ID */
258         LPWSTR devid;
259         hr = IMMDevice_GetId(dev, &devid);
260         if (FAILED(hr))
261         {
262             IMMDevice_Release(dev);
263             continue;
264         }
265         val.psz_string = FromWide(devid);
266         CoTaskMemFree(devid);
267         text.psz_string = val.psz_string;
268
269         /* User-readable device name */
270         IPropertyStore *props;
271         hr = IMMDevice_OpenPropertyStore(dev, STGM_READ, &props);
272         if (SUCCEEDED(hr))
273         {
274             PROPVARIANT v;
275
276             PropVariantInit(&v);
277 #ifdef FIXED
278             hr = IPropertyStore_GetValue(props, PKEY_Device_FriendlyName, &v);
279             if (SUCCEEDED(hr))
280                 text.psz_string = FromWide(v.pwszVal);
281 #endif
282             PropVariantClear(&v);
283             IPropertyStore_Release(props);
284         }
285         IMMDevice_Release(dev);
286
287         var_Change(obj, "audio-device", VLC_VAR_ADDCHOICE, &val, &text);
288         if (likely(text.psz_string != val.psz_string))
289             free(text.psz_string);
290         free(val.psz_string);
291     }
292     IMMDeviceCollection_Release(devs);
293 }
294
295
296 static void vlc_ToWave(WAVEFORMATEXTENSIBLE *restrict wf,
297                        audio_sample_format_t *restrict audio)
298 {
299     switch (audio->i_format)
300     {
301         case VLC_CODEC_FL64:
302             audio->i_format = VLC_CODEC_FL32;
303         case VLC_CODEC_FL32:
304             wf->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
305             break;
306
307         case VLC_CODEC_S8:
308         case VLC_CODEC_U8:
309             audio->i_format = VLC_CODEC_S16N;
310         case VLC_CODEC_S16N:
311             wf->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
312             break;
313
314         default:
315             audio->i_format = VLC_CODEC_FL32;
316             wf->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
317             break;
318      }
319      aout_FormatPrepare (audio);
320
321      wf->Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
322      wf->Format.nChannels = audio->i_channels;
323      wf->Format.nSamplesPerSec = audio->i_rate;
324      wf->Format.nAvgBytesPerSec = audio->i_bytes_per_frame * audio->i_rate;
325      wf->Format.nBlockAlign = audio->i_bytes_per_frame;
326      wf->Format.wBitsPerSample = audio->i_bitspersample;
327      wf->Format.cbSize = sizeof (*wf) - sizeof (wf->Format);
328
329      wf->Samples.wValidBitsPerSample = audio->i_bitspersample;
330
331      wf->dwChannelMask = 0;
332      if (audio->i_physical_channels & AOUT_CHAN_LEFT)
333          wf->dwChannelMask |= SPEAKER_FRONT_LEFT;
334      if (audio->i_physical_channels & AOUT_CHAN_RIGHT)
335          wf->dwChannelMask |= SPEAKER_FRONT_RIGHT;
336      if (audio->i_physical_channels & AOUT_CHAN_CENTER)
337          wf->dwChannelMask |= SPEAKER_FRONT_CENTER;
338      if (audio->i_physical_channels & AOUT_CHAN_LFE)
339          wf->dwChannelMask |= SPEAKER_LOW_FREQUENCY;
340      // TODO: reorder
341      if (audio->i_physical_channels & AOUT_CHAN_REARLEFT)
342          wf->dwChannelMask |= SPEAKER_BACK_LEFT;
343      if (audio->i_physical_channels & AOUT_CHAN_REARRIGHT)
344          wf->dwChannelMask |= SPEAKER_BACK_RIGHT;
345      /* ... */
346      if (audio->i_physical_channels & AOUT_CHAN_REARCENTER)
347          wf->dwChannelMask |= SPEAKER_BACK_CENTER;
348      if (audio->i_physical_channels & AOUT_CHAN_MIDDLELEFT)
349          wf->dwChannelMask |= SPEAKER_SIDE_LEFT;
350      if (audio->i_physical_channels & AOUT_CHAN_MIDDLERIGHT)
351          wf->dwChannelMask |= SPEAKER_SIDE_RIGHT;
352      /* ... */
353 }
354
355 static int vlc_FromWave(const WAVEFORMATEX *restrict wf,
356                         audio_sample_format_t *restrict audio)
357 {
358     /* FIXME? different sample format? possible? */
359     audio->i_rate = wf->nSamplesPerSec;
360     /* FIXME */
361     if (wf->nChannels != audio->i_channels)
362         return -1;
363
364     aout_FormatPrepare(audio);
365     return 0;
366 }
367
368 static wchar_t *var_InheritWide(vlc_object_t *obj, const char *name)
369 {
370     char *v8 = var_InheritString(obj, name);
371     if (v8 == NULL)
372         return NULL;
373
374     wchar_t *v16 = ToWide(v8);
375     free(v8);
376     return v16;
377 }
378 #define var_InheritWide(o,n) var_InheritWide(VLC_OBJECT(o),n)
379
380 static int var_SetWide(vlc_object_t *obj, const char *name, const wchar_t *val)
381 {
382     char *str = FromWide(val);
383     if (unlikely(str == NULL))
384         return VLC_ENOMEM;
385
386     int ret = var_SetString(obj, name, str);
387     free(str);
388     return ret;
389 }
390 #define var_SetWide(o,n,v) var_SetWide(VLC_OBJECT(o),n,v)
391
392 /* Dummy thread to create and release COM interfaces when needed. */
393 static void MTAThread(void *data)
394 {
395     audio_output_t *aout = data;
396     aout_sys_t *sys = aout->sys;
397     HRESULT hr;
398
399     Enter();
400
401     hr = IAudioClient_GetService(sys->client, &IID_IAudioRenderClient,
402                                  (void **)&sys->render);
403     if (FAILED(hr))
404     {
405         msg_Err(aout, "cannot get audio render service (error 0x%lx)", hr);
406         goto fail;
407     }
408
409     hr = IAudioClient_GetService(sys->client, &IID_IAudioClock,
410                                  (void **)&sys->clock);
411     if (FAILED(hr))
412         msg_Warn(aout, "cannot get audio clock (error 0x%lx)", hr);
413
414     /*if (AOUT_FMT_LINEAR(&format) && !exclusive)*/
415     {
416         hr = IAudioClient_GetService(sys->client, &IID_ISimpleAudioVolume,
417                                      (void **)&sys->volume.simple);
418     }
419
420     hr = IAudioClient_GetService(sys->client, &IID_IAudioSessionControl,
421                                  (void **)&sys->control);
422     if (FAILED(hr))
423         msg_Warn(aout, "cannot get audio session control (error 0x%lx)", hr);
424     else
425     {
426         wchar_t *ua = var_InheritWide(aout, "user-agent");
427         IAudioSessionControl_SetDisplayName(sys->control, ua, NULL);
428         free(ua);
429     }
430
431     /* do nothing until the audio session terminates */
432     ReleaseSemaphore(sys->ready, 1, NULL);
433     WaitForSingleObject(sys->done, INFINITE);
434
435     if (sys->control != NULL)
436         IAudioSessionControl_Release(sys->control);
437     /*if (AOUT_FMT_LINEAR(&format) && !exclusive)*/
438     {
439         if (sys->volume.simple != NULL)
440             ISimpleAudioVolume_Release(sys->volume.simple);
441     }
442     if (sys->clock != NULL)
443         IAudioClock_Release(sys->clock);
444     IAudioRenderClient_Release(sys->render);
445 fail:
446     Leave();
447     ReleaseSemaphore(sys->ready, 1, NULL);
448 }
449
450 static int Open(vlc_object_t *obj)
451 {
452     audio_output_t *aout = (audio_output_t *)obj;
453     HRESULT hr;
454
455     if (AOUT_FMT_SPDIF(&aout->format) && !aout->b_force
456      && var_InheritBool(aout, "spdif"))
457         /* Fallback to other plugin until pass-through is implemented */
458         return VLC_EGENERIC;
459
460     aout_sys_t *sys = malloc(sizeof (*sys));
461     if (unlikely(sys == NULL))
462         return VLC_ENOMEM;
463     sys->client = NULL;
464     sys->render = NULL;
465     sys->clock = NULL;
466     sys->ready = NULL;
467     sys->done = NULL;
468     aout->sys = sys;
469
470     if (TryEnter(aout))
471     {
472         free(sys);
473         return VLC_EGENERIC;
474     }
475
476     /* Get audio device according to policy */
477     var_Create (aout, "audio-device", VLC_VAR_STRING|VLC_VAR_HASCHOICE);
478
479     IMMDeviceEnumerator *devs;
480     hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL,
481                           &IID_IMMDeviceEnumerator, (void **)&devs);
482     if (FAILED(hr))
483     {
484         msg_Dbg(aout, "cannot create device enumerator (error 0x%lx)", hr);
485         goto error;
486     }
487
488     // Without configuration item, the variable must be created explicitly.
489     var_Create (aout, "wasapi-audio-device", VLC_VAR_STRING);
490     LPWSTR devid = var_InheritWide (aout, "wasapi-audio-device");
491     var_Destroy (aout, "wasapi-audio-device");
492
493     IMMDevice *dev = NULL;
494     if (devid != NULL)
495     {
496         msg_Dbg (aout, "using selected device %ls", devid);
497         hr = IMMDeviceEnumerator_GetDevice (devs, devid, &dev);
498         if (FAILED(hr))
499             msg_Warn(aout, "cannot get audio endpoint (error 0x%lx)", hr);
500         free (devid);
501     }
502     if (dev == NULL)
503     {
504         msg_Dbg (aout, "using default device");
505         hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(devs, eRender,
506                                                          eConsole, &dev);
507     }
508
509     GetDevices(VLC_OBJECT(aout), devs);
510     IMMDeviceEnumerator_Release(devs);
511     if (FAILED(hr))
512     {
513         msg_Err(aout, "cannot get audio endpoint (error 0x%lx)", hr);
514         goto error;
515     }
516
517     hr = IMMDevice_GetId(dev, &devid);
518     if (SUCCEEDED(hr))
519     {
520         msg_Dbg(aout, "using device %ls", devid);
521         var_SetWide (aout, "audio-device", devid);
522         CoTaskMemFree(devid);
523     }
524
525     hr = IMMDevice_Activate(dev, &IID_IAudioClient, CLSCTX_ALL, NULL,
526                             (void **)&sys->client);
527     IMMDevice_Release(dev);
528     if (FAILED(hr))
529     {
530         msg_Err(aout, "cannot activate audio client (error 0x%lx)", hr);
531         goto error;
532     }
533
534     /* Configure audio stream */
535     audio_sample_format_t format = aout->format;
536     WAVEFORMATEXTENSIBLE wf;
537     WAVEFORMATEX *pwf;
538
539     vlc_ToWave(&wf, &format);
540     hr = IAudioClient_IsFormatSupported(sys->client, AUDCLNT_SHAREMODE_SHARED,
541                                         &wf.Format, &pwf);
542     // TODO: deal with (hr == AUDCLNT_E_DEVICE_INVALIDATED) ?
543     if (FAILED(hr))
544     {
545         msg_Err(aout, "cannot negotiate audio format (error 0x%lx)", hr);
546         goto error;
547     }
548
549     if (hr == S_FALSE)
550     {
551         assert(pwf != NULL);
552         if (vlc_FromWave(pwf, &format))
553         {
554             CoTaskMemFree(pwf);
555             msg_Err(aout, "unsupported audio format");
556             goto error;
557         }
558         msg_Dbg(aout, "modified format");
559     }
560     else
561         assert(pwf == NULL);
562     hr = IAudioClient_Initialize(sys->client, AUDCLNT_SHAREMODE_SHARED, 0,
563                                  AOUT_MAX_PREPARE_TIME * 10, 0,
564                                  (hr == S_OK) ? &wf.Format : pwf, NULL);
565     CoTaskMemFree(pwf);
566     if (FAILED(hr))
567     {
568         msg_Err(aout, "cannot initialize audio client (error 0x%lx)", hr);
569         goto error;
570     }
571
572     hr = IAudioClient_GetBufferSize(sys->client, &sys->frames);
573     if (FAILED(hr))
574     {
575         msg_Err(aout, "cannot get buffer size (error 0x%lx)", hr);
576         goto error;
577     }
578
579     sys->ready = CreateSemaphore(NULL, 0, 1, NULL);
580     sys->done = CreateSemaphore(NULL, 0, 1, NULL);
581     if (unlikely(sys->ready == NULL || sys->done == NULL))
582         goto error;
583     /* Note: thread handle released by CRT, ignore it. */
584     if (_beginthread(MTAThread, 0, aout) == (uintptr_t)-1)
585         goto error;
586
587     WaitForSingleObject(sys->ready, INFINITE);
588     if (sys->render == NULL)
589         goto error;
590
591     Leave();
592
593     aout->format = format;
594     aout->pf_play = Play;
595     aout->pf_pause = Pause;
596     aout->pf_flush = Flush;
597     /*if (AOUT_FMT_LINEAR(&format) && !exclusive)*/
598     {
599         aout->volume_set = SimpleVolumeSet;
600         aout->mute_set = SimpleMuteSet;
601     }
602     var_AddCallback (aout, "audio-device", DeviceChanged, NULL);
603
604     return VLC_SUCCESS;
605 error:
606     if (sys->done != NULL)
607         CloseHandle(sys->done);
608     if (sys->ready != NULL)
609         CloseHandle(sys->done);
610     if (sys->client != NULL)
611         IAudioClient_Release(sys->client);
612     Leave();
613     var_Destroy (aout, "audio-device");
614     free(sys);
615     return VLC_EGENERIC;
616 }
617
618 static void Close (vlc_object_t *obj)
619 {
620     audio_output_t *aout = (audio_output_t *)obj;
621     aout_sys_t *sys = aout->sys;
622
623     Enter();
624     ReleaseSemaphore(sys->done, 1, NULL); /* tell MTA thread to finish */
625     WaitForSingleObject(sys->ready, INFINITE); /* wait for that ^ */
626     IAudioClient_Stop(sys->client); /* should not be needed */
627     IAudioClient_Release(sys->client);
628     Leave();
629
630     var_DelCallback (aout, "audio-device", DeviceChanged, NULL);
631     var_Destroy (aout, "audio-device");
632
633     CloseHandle(sys->done);
634     CloseHandle(sys->ready);
635     free(sys);
636 }