1 /*****************************************************************************
2 * wasapi.c : Windows Audio Session API output plugin for VLC
3 *****************************************************************************
4 * Copyright (C) 2012 Rémi Denis-Courmont
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.
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.
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 *****************************************************************************/
29 #include <audioclient.h>
30 #include <audiopolicy.h>
31 #include <mmdeviceapi.h>
33 #include <vlc_common.h>
34 #include <vlc_plugin.h>
36 #include <vlc_charset.h>
38 static int Open(vlc_object_t *);
39 static void Close(vlc_object_t *);
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)
51 static int TryEnter(vlc_object_t *obj)
53 HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
54 if (unlikely(FAILED(hr)))
56 msg_Err (obj, "cannot initialize COM (error 0x%lx)", hr);
61 #define TryEnter(o) TryEnter(VLC_OBJECT(o))
63 static void Enter(void)
65 HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
66 if (unlikely(FAILED(hr)))
70 static void Leave(void)
78 IAudioRenderClient *render;
82 ISimpleAudioVolume *simple;
84 IAudioSessionControl *control;
85 UINT32 frames; /**< Total buffer size (frames) */
86 HANDLE ready; /**< Semaphore from MTA thread */
87 HANDLE done; /**< Semaphore to MTA thread */
90 static void Play(audio_output_t *aout, block_t *block, mtime_t *restrict drift)
92 aout_sys_t *sys = aout->sys;
96 if (likely(sys->clock != NULL))
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;
109 hr = IAudioClient_GetCurrentPadding(sys->client, &frames);
112 msg_Err(aout, "cannot get current padding (error 0x%lx)", hr);
116 assert(frames <= sys->frames);
117 frames = sys->frames - frames;
118 if (frames > block->i_nb_samples)
119 frames = block->i_nb_samples;
122 hr = IAudioRenderClient_GetBuffer(sys->render, frames, &dst);
125 msg_Err(aout, "cannot get buffer (error 0x%lx)", hr);
129 const size_t copy = frames * (size_t)aout->format.i_bytes_per_frame;
131 memcpy(dst, block->p_buffer, copy);
132 hr = IAudioRenderClient_ReleaseBuffer(sys->render, frames, 0);
135 msg_Err(aout, "cannot release buffer (error 0x%lx)", hr);
138 IAudioClient_Start(sys->client);
140 block->p_buffer += copy;
141 block->i_buffer -= copy;
142 block->i_nb_samples -= frames;
143 if (block->i_nb_samples == 0)
146 /* Out of buffer space, sleep */
147 msleep(AOUT_MIN_PREPARE_TIME
148 + block->i_nb_samples * CLOCK_FREQ / aout->format.i_rate);
152 block_Release(block);
155 static void Pause(audio_output_t *aout, bool paused, mtime_t date)
157 aout_sys_t *sys = aout->sys;
162 hr = IAudioClient_Stop(sys->client);
164 hr = IAudioClient_Start(sys->client);
166 msg_Warn(aout, "cannot %s stream (error 0x%lx)",
167 paused ? "stop" : "start", hr);
172 static void Flush(audio_output_t *aout, bool wait)
174 aout_sys_t *sys = aout->sys;
178 return; /* Drain not implemented */
181 IAudioClient_Stop(sys->client);
182 hr = IAudioClient_Reset(sys->client);
184 msg_Warn(aout, "cannot reset stream (error 0x%lx)", hr);
188 static int SimpleVolumeSet(audio_output_t *aout, float vol)
190 aout_sys_t *sys = aout->sys;
195 hr = ISimpleAudioVolume_SetMasterVolume(sys->volume.simple, vol, NULL);
197 msg_Warn(aout, "cannot set session volume (error 0x%lx)", hr);
199 return FAILED(hr) ? -1 : 0;
202 static int SimpleMuteSet(audio_output_t *aout, bool mute)
204 aout_sys_t *sys = aout->sys;
209 hr = ISimpleAudioVolume_SetMute(sys->volume.simple, mute, NULL);
211 msg_Warn(aout, "cannot mute session (error 0x%lx)", hr);
213 return FAILED(hr) ? -1 : 0;
216 static int DeviceChanged(vlc_object_t *obj, const char *varname,
217 vlc_value_t prev, vlc_value_t cur, void *data)
219 aout_ChannelsRestart(obj, varname, prev, cur, data);
221 if (!var_Type (obj, "wasapi-audio-device"))
222 var_Create (obj, "wasapi-audio-device", VLC_VAR_STRING);
223 var_SetString (obj, "wasapi-audio-device", cur.psz_string);
227 static void GetDevices(vlc_object_t *obj, IMMDeviceEnumerator *it)
230 vlc_value_t val, text;
232 text.psz_string = _("Audio Device");
233 var_Change (obj, "audio-device", VLC_VAR_SETTEXT, &text, NULL);
235 IMMDeviceCollection *devs;
236 hr = IMMDeviceEnumerator_EnumAudioEndpoints(it, eRender,
237 DEVICE_STATE_ACTIVE, &devs);
240 msg_Warn (obj, "cannot enumerate audio endpoints (error 0x%lx)", hr);
245 hr = IMMDeviceCollection_GetCount(devs, &n);
252 hr = IMMDeviceCollection_Item(devs, --n, &dev);
256 /* Unique device ID */
258 hr = IMMDevice_GetId(dev, &devid);
261 IMMDevice_Release(dev);
264 val.psz_string = FromWide(devid);
265 CoTaskMemFree(devid);
266 text.psz_string = val.psz_string;
268 /* User-readable device name */
269 IPropertyStore *props;
270 hr = IMMDevice_OpenPropertyStore(dev, STGM_READ, &props);
277 hr = IPropertyStore_GetValue(props, PKEY_Device_FriendlyName, &v);
279 text.psz_string = FromWide(v.pwszVal);
281 PropVariantClear(&v);
282 IPropertyStore_Release(props);
284 IMMDevice_Release(dev);
286 var_Change(obj, "audio-device", VLC_VAR_ADDCHOICE, &val, &text);
287 if (likely(text.psz_string != val.psz_string))
288 free(text.psz_string);
289 free(val.psz_string);
291 IMMDeviceCollection_Release(devs);
295 static void vlc_ToWave(WAVEFORMATEXTENSIBLE *restrict wf,
296 audio_sample_format_t *restrict audio)
298 switch (audio->i_format)
301 audio->i_format = VLC_CODEC_FL32;
303 wf->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
308 audio->i_format = VLC_CODEC_S16N;
310 wf->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
314 audio->i_format = VLC_CODEC_FL32;
315 wf->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
318 aout_FormatPrepare (audio);
320 wf->Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
321 wf->Format.nChannels = audio->i_channels;
322 wf->Format.nSamplesPerSec = audio->i_rate;
323 wf->Format.nAvgBytesPerSec = audio->i_bytes_per_frame * audio->i_rate;
324 wf->Format.nBlockAlign = audio->i_bytes_per_frame;
325 wf->Format.wBitsPerSample = audio->i_bitspersample;
326 wf->Format.cbSize = sizeof (*wf) - sizeof (wf->Format);
328 wf->Samples.wValidBitsPerSample = audio->i_bitspersample;
330 wf->dwChannelMask = 0;
331 if (audio->i_physical_channels & AOUT_CHAN_LEFT)
332 wf->dwChannelMask |= SPEAKER_FRONT_LEFT;
333 if (audio->i_physical_channels & AOUT_CHAN_RIGHT)
334 wf->dwChannelMask |= SPEAKER_FRONT_RIGHT;
335 if (audio->i_physical_channels & AOUT_CHAN_CENTER)
336 wf->dwChannelMask |= SPEAKER_FRONT_CENTER;
337 if (audio->i_physical_channels & AOUT_CHAN_LFE)
338 wf->dwChannelMask |= SPEAKER_LOW_FREQUENCY;
340 if (audio->i_physical_channels & AOUT_CHAN_REARLEFT)
341 wf->dwChannelMask |= SPEAKER_BACK_LEFT;
342 if (audio->i_physical_channels & AOUT_CHAN_REARRIGHT)
343 wf->dwChannelMask |= SPEAKER_BACK_RIGHT;
345 if (audio->i_physical_channels & AOUT_CHAN_REARCENTER)
346 wf->dwChannelMask |= SPEAKER_BACK_CENTER;
347 if (audio->i_physical_channels & AOUT_CHAN_MIDDLELEFT)
348 wf->dwChannelMask |= SPEAKER_SIDE_LEFT;
349 if (audio->i_physical_channels & AOUT_CHAN_MIDDLERIGHT)
350 wf->dwChannelMask |= SPEAKER_SIDE_RIGHT;
354 static int vlc_FromWave(const WAVEFORMATEX *restrict wf,
355 audio_sample_format_t *restrict audio)
357 /* FIXME? different sample format? possible? */
358 audio->i_rate = wf->nSamplesPerSec;
360 if (wf->nChannels != audio->i_channels)
363 aout_FormatPrepare(audio);
367 static wchar_t *var_InheritWide(vlc_object_t *obj, const char *name)
369 char *v8 = var_InheritString(obj, name);
373 wchar_t *v16 = ToWide(v8);
377 #define var_InheritWide(o,n) var_InheritWide(VLC_OBJECT(o),n)
379 static int var_SetWide(vlc_object_t *obj, const char *name, const wchar_t *val)
381 char *str = FromWide(val);
382 if (unlikely(str == NULL))
385 int ret = var_SetString(obj, name, str);
389 #define var_SetWide(o,n,v) var_SetWide(VLC_OBJECT(o),n,v)
391 /* Dummy thread to create and release COM interfaces when needed. */
392 static void MTAThread(void *data)
394 audio_output_t *aout = data;
395 aout_sys_t *sys = aout->sys;
400 hr = IAudioClient_GetService(sys->client, &IID_IAudioRenderClient,
401 (void **)&sys->render);
404 msg_Err(aout, "cannot get audio render service (error 0x%lx)", hr);
408 hr = IAudioClient_GetService(sys->client, &IID_IAudioClock,
409 (void **)&sys->clock);
411 msg_Warn(aout, "cannot get audio clock (error 0x%lx)", hr);
413 /*if (AOUT_FMT_LINEAR(&format) && !exclusive)*/
415 hr = IAudioClient_GetService(sys->client, &IID_ISimpleAudioVolume,
416 (void **)&sys->volume.simple);
419 hr = IAudioClient_GetService(sys->client, &IID_IAudioSessionControl,
420 (void **)&sys->control);
422 msg_Warn(aout, "cannot get audio session control (error 0x%lx)", hr);
425 wchar_t *ua = var_InheritWide(aout, "user-agent");
426 IAudioSessionControl_SetDisplayName(sys->control, ua, NULL);
430 /* do nothing until the audio session terminates */
431 ReleaseSemaphore(sys->ready, 1, NULL);
432 WaitForSingleObject(sys->done, INFINITE);
434 if (sys->control != NULL)
435 IAudioSessionControl_Release(sys->control);
436 /*if (AOUT_FMT_LINEAR(&format) && !exclusive)*/
438 if (sys->volume.simple != NULL)
439 ISimpleAudioVolume_Release(sys->volume.simple);
441 if (sys->clock != NULL)
442 IAudioClock_Release(sys->clock);
443 IAudioRenderClient_Release(sys->render);
446 ReleaseSemaphore(sys->ready, 1, NULL);
449 static int Open(vlc_object_t *obj)
451 audio_output_t *aout = (audio_output_t *)obj;
454 if (AOUT_FMT_SPDIF(&aout->format) && !aout->b_force
455 && var_InheritBool(aout, "spdif"))
456 /* Fallback to other plugin until pass-through is implemented */
459 aout_sys_t *sys = malloc(sizeof (*sys));
460 if (unlikely(sys == NULL))
475 /* Get audio device according to policy */
476 var_Create (aout, "audio-device", VLC_VAR_STRING|VLC_VAR_HASCHOICE);
478 IMMDeviceEnumerator *devs;
479 hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL,
480 &IID_IMMDeviceEnumerator, (void **)&devs);
483 msg_Dbg(aout, "cannot create device enumerator (error 0x%lx)", hr);
487 // Without configuration item, the variable must be created explicitly.
488 var_Create (aout, "wasapi-audio-device", VLC_VAR_STRING);
489 LPWSTR devid = var_InheritWide (aout, "wasapi-audio-device");
490 var_Destroy (aout, "wasapi-audio-device");
492 IMMDevice *dev = NULL;
495 msg_Dbg (aout, "using selected device %ls", devid);
496 hr = IMMDeviceEnumerator_GetDevice (devs, devid, &dev);
498 msg_Warn(aout, "cannot get audio endpoint (error 0x%lx)", hr);
503 msg_Dbg (aout, "using default device");
504 hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(devs, eRender,
508 GetDevices(VLC_OBJECT(aout), devs);
509 IMMDeviceEnumerator_Release(devs);
512 msg_Err(aout, "cannot get audio endpoint (error 0x%lx)", hr);
516 hr = IMMDevice_GetId(dev, &devid);
519 msg_Dbg(aout, "using device %ls", devid);
520 var_SetWide (aout, "audio-device", devid);
521 CoTaskMemFree(devid);
524 hr = IMMDevice_Activate(dev, &IID_IAudioClient, CLSCTX_ALL, NULL,
525 (void **)&sys->client);
526 IMMDevice_Release(dev);
529 msg_Err(aout, "cannot activate audio client (error 0x%lx)", hr);
533 /* Configure audio stream */
534 audio_sample_format_t format = aout->format;
535 WAVEFORMATEXTENSIBLE wf;
538 vlc_ToWave(&wf, &format);
539 hr = IAudioClient_IsFormatSupported(sys->client, AUDCLNT_SHAREMODE_SHARED,
541 // TODO: deal with (hr == AUDCLNT_E_DEVICE_INVALIDATED) ?
544 msg_Err(aout, "cannot negotiate audio format (error 0x%lx)", hr);
551 if (vlc_FromWave(pwf, &format))
554 msg_Err(aout, "unsupported audio format");
557 msg_Dbg(aout, "modified format");
561 hr = IAudioClient_Initialize(sys->client, AUDCLNT_SHAREMODE_SHARED, 0,
562 AOUT_MAX_PREPARE_TIME * 10, 0,
563 (hr == S_OK) ? &wf.Format : pwf, NULL);
567 msg_Err(aout, "cannot initialize audio client (error 0x%lx)", hr);
571 hr = IAudioClient_GetBufferSize(sys->client, &sys->frames);
574 msg_Err(aout, "cannot get buffer size (error 0x%lx)", hr);
578 sys->ready = CreateSemaphore(NULL, 0, 1, NULL);
579 sys->done = CreateSemaphore(NULL, 0, 1, NULL);
580 if (unlikely(sys->ready == NULL || sys->done == NULL))
582 /* Note: thread handle released by CRT, ignore it. */
583 if (_beginthread(MTAThread, 0, aout) == (uintptr_t)-1)
586 WaitForSingleObject(sys->ready, INFINITE);
587 if (sys->render == NULL)
592 aout->format = format;
593 aout->pf_play = Play;
594 aout->pf_pause = Pause;
595 aout->pf_flush = Flush;
596 /*if (AOUT_FMT_LINEAR(&format) && !exclusive)*/
598 aout->volume_set = SimpleVolumeSet;
599 aout->mute_set = SimpleMuteSet;
601 var_AddCallback (aout, "audio-device", DeviceChanged, NULL);
605 if (sys->done != NULL)
606 CloseHandle(sys->done);
607 if (sys->ready != NULL)
608 CloseHandle(sys->done);
609 if (sys->client != NULL)
610 IAudioClient_Release(sys->client);
612 var_Destroy (aout, "audio-device");
617 static void Close (vlc_object_t *obj)
619 audio_output_t *aout = (audio_output_t *)obj;
620 aout_sys_t *sys = aout->sys;
623 ReleaseSemaphore(sys->done, 1, NULL); /* tell MTA thread to finish */
624 WaitForSingleObject(sys->ready, INFINITE); /* wait for that ^ */
625 IAudioClient_Stop(sys->client); /* should not be needed */
626 IAudioClient_Release(sys->client);
629 var_DelCallback (aout, "audio-device", DeviceChanged, NULL);
630 var_Destroy (aout, "audio-device");
632 CloseHandle(sys->done);
633 CloseHandle(sys->ready);