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(void)
53 HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
57 static void Enter(void)
59 HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
60 if (unlikely(FAILED(hr)))
64 static void Leave(void)
72 IAudioRenderClient *render;
76 ISimpleAudioVolume *simple;
78 IAudioSessionControl *control;
79 UINT32 frames; /**< Total buffer size (frames) */
80 HANDLE ready; /**< Semaphore from MTA thread */
81 HANDLE done; /**< Semaphore to MTA thread */
84 static void Play(audio_output_t *aout, block_t *block)
86 aout_sys_t *sys = aout->sys;
90 if (likely(sys->clock != NULL))
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);
103 hr = IAudioClient_GetCurrentPadding(sys->client, &frames);
106 msg_Err(aout, "cannot get current padding (error 0x%lx)", hr);
110 assert(frames <= sys->frames);
111 frames = sys->frames - frames;
112 if (frames > block->i_nb_samples)
113 frames = block->i_nb_samples;
116 hr = IAudioRenderClient_GetBuffer(sys->render, frames, &dst);
119 msg_Err(aout, "cannot get buffer (error 0x%lx)", hr);
123 const size_t copy = frames * (size_t)aout->format.i_bytes_per_frame;
125 memcpy(dst, block->p_buffer, copy);
126 hr = IAudioRenderClient_ReleaseBuffer(sys->render, frames, 0);
129 msg_Err(aout, "cannot release buffer (error 0x%lx)", hr);
132 IAudioClient_Start(sys->client);
134 block->p_buffer += copy;
135 block->i_buffer -= copy;
136 block->i_nb_samples -= frames;
137 if (block->i_nb_samples == 0)
140 /* Out of buffer space, sleep */
141 msleep(AOUT_MIN_PREPARE_TIME
142 + block->i_nb_samples * CLOCK_FREQ / aout->format.i_rate);
146 block_Release(block);
149 static void Pause(audio_output_t *aout, bool paused, mtime_t date)
151 aout_sys_t *sys = aout->sys;
156 hr = IAudioClient_Stop(sys->client);
158 hr = IAudioClient_Start(sys->client);
160 msg_Warn(aout, "cannot %s stream (error 0x%lx)",
161 paused ? "stop" : "start", hr);
166 static void Flush(audio_output_t *aout, bool wait)
168 aout_sys_t *sys = aout->sys;
172 return; /* Drain not implemented */
175 IAudioClient_Stop(sys->client);
176 hr = IAudioClient_Reset(sys->client);
178 msg_Warn(aout, "cannot reset stream (error 0x%lx)", hr);
182 static int SimpleVolumeSet(audio_output_t *aout, float vol)
184 aout_sys_t *sys = aout->sys;
191 hr = ISimpleAudioVolume_SetMasterVolume(sys->volume.simple, vol, NULL);
193 msg_Warn(aout, "cannot set session volume (error 0x%lx)", hr);
195 return FAILED(hr) ? -1 : 0;
198 static int SimpleMuteSet(audio_output_t *aout, bool mute)
200 aout_sys_t *sys = aout->sys;
204 hr = ISimpleAudioVolume_SetMute(sys->volume.simple, mute, NULL);
206 msg_Warn(aout, "cannot mute session (error 0x%lx)", hr);
208 return FAILED(hr) ? -1 : 0;
211 static void vlc_ToWave(WAVEFORMATEXTENSIBLE *restrict wf,
212 audio_sample_format_t *restrict audio)
214 switch (audio->i_format)
217 audio->i_format = VLC_CODEC_FL32;
219 wf->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
224 audio->i_format = VLC_CODEC_S16N;
226 wf->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
230 audio->i_format = VLC_CODEC_FL32;
231 wf->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
234 aout_FormatPrepare (audio);
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);
244 wf->Samples.wValidBitsPerSample = audio->i_bitspersample;
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;
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;
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;
270 static int vlc_FromWave(const WAVEFORMATEX *restrict wf,
271 audio_sample_format_t *restrict audio)
273 /* FIXME? different sample format? possible? */
274 audio->i_rate = wf->nSamplesPerSec;
276 if (wf->nChannels != audio->i_channels)
279 aout_FormatPrepare(audio);
283 static wchar_t *var_InheritWide(vlc_object_t *obj, const char *name)
285 char *v8 = var_InheritString(obj, name);
289 wchar_t *v16 = ToWide(v8);
293 #define var_InheritWide(o,n) var_InheritWide(VLC_OBJECT(o),n)
295 /* Dummy thread to create and release COM interfaces when needed. */
296 static void MTAThread(void *data)
298 audio_output_t *aout = data;
299 aout_sys_t *sys = aout->sys;
304 hr = IAudioClient_GetService(sys->client, &IID_IAudioRenderClient,
305 (void **)&sys->render);
308 msg_Err(aout, "cannot get audio render service (error 0x%lx)", hr);
312 hr = IAudioClient_GetService(sys->client, &IID_IAudioClock,
313 (void **)&sys->clock);
315 msg_Warn(aout, "cannot get audio clock (error 0x%lx)", hr);
317 /*if (AOUT_FMT_LINEAR(&format) && !exclusive)*/
319 hr = IAudioClient_GetService(sys->client, &IID_ISimpleAudioVolume,
320 (void **)&sys->volume.simple);
323 hr = IAudioClient_GetService(sys->client, &IID_IAudioSessionControl,
324 (void **)&sys->control);
326 msg_Warn(aout, "cannot get audio session control (error 0x%lx)", hr);
329 wchar_t *ua = var_InheritWide(aout, "user-agent");
330 IAudioSessionControl_SetDisplayName(sys->control, ua, NULL);
334 /* do nothing until the audio session terminates */
335 ReleaseSemaphore(sys->ready, 1, NULL);
336 WaitForSingleObject(sys->done, INFINITE);
338 if (sys->control != NULL)
339 IAudioSessionControl_Release(sys->control);
340 /*if (AOUT_FMT_LINEAR(&format) && !exclusive)*/
342 if (sys->volume.simple != NULL)
343 ISimpleAudioVolume_Release(sys->volume.simple);
345 if (sys->clock != NULL)
346 IAudioClock_Release(sys->clock);
347 IAudioRenderClient_Release(sys->render);
350 ReleaseSemaphore(sys->ready, 1, NULL);
353 static int Open(vlc_object_t *obj)
355 audio_output_t *aout = (audio_output_t *)obj;
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 */
363 aout_sys_t *sys = malloc(sizeof (*sys));
364 if (unlikely(sys == NULL))
379 /* Get audio device according to policy */
380 IMMDeviceEnumerator *devs;
381 hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL,
382 &IID_IMMDeviceEnumerator, (void **)&devs);
385 msg_Dbg(aout, "cannot create device enumerator (error 0x%lx)", hr);
390 hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(devs, eRender,
392 IMMDeviceEnumerator_Release(devs);
395 msg_Err(aout, "cannot get audio endpoint (error 0x%lx)", hr);
400 hr = IMMDevice_GetId(dev, &str);
403 msg_Dbg(aout, "using device %ls", str);
407 hr = IMMDevice_Activate(dev, &IID_IAudioClient, CLSCTX_ALL, NULL,
408 (void **)&sys->client);
409 IMMDevice_Release(dev);
412 msg_Err(aout, "cannot activate audio client (error 0x%lx)", hr);
416 /* Configure audio stream */
417 audio_sample_format_t format = aout->format;
418 WAVEFORMATEXTENSIBLE wf;
421 vlc_ToWave(&wf, &format);
422 hr = IAudioClient_IsFormatSupported(sys->client, AUDCLNT_SHAREMODE_SHARED,
424 // TODO: deal with (hr == AUDCLNT_E_DEVICE_INVALIDATED) ?
427 msg_Err(aout, "cannot negotiate audio format (error 0x%lx)", hr);
434 if (vlc_FromWave(pwf, &format))
437 msg_Err(aout, "unsupported audio format");
440 msg_Dbg(aout, "modified format");
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);
450 msg_Err(aout, "cannot initialize audio client (error 0x%lx)", hr);
454 hr = IAudioClient_GetBufferSize(sys->client, &sys->frames);
457 msg_Err(aout, "cannot get buffer size (error 0x%lx)", hr);
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))
465 /* Note: thread handle released by CRT, ignore it. */
466 if (_beginthread(MTAThread, 0, aout) == (uintptr_t)-1)
469 WaitForSingleObject(sys->ready, INFINITE);
470 if (sys->render == NULL)
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)*/
479 aout->volume_set = SimpleVolumeSet;
480 aout->mute_set = SimpleMuteSet;
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);
496 static void Close (vlc_object_t *obj)
498 audio_output_t *aout = (audio_output_t *)obj;
499 aout_sys_t *sys = aout->sys;
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);
508 CloseHandle(sys->done);
509 CloseHandle(sys->ready);