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 <mmdeviceapi.h>
32 #include <vlc_common.h>
33 #include <vlc_plugin.h>
36 static int Open(vlc_object_t *);
37 static void Close(vlc_object_t *);
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)
52 IAudioRenderClient *render;
54 UINT32 frames; /**< Total buffer size (frames) */
55 HANDLE done; /**< Semaphore for MTA thread */
58 static void Play(audio_output_t *aout, block_t *block)
60 aout_sys_t *sys = aout->sys;
63 CoInitializeEx(NULL, COINIT_MULTITHREADED);
64 if (likely(sys->clock != NULL))
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);
77 hr = IAudioClient_GetCurrentPadding(sys->client, &frames);
80 msg_Err(aout, "cannot get current padding (error 0x%lx)", hr);
84 assert(frames <= sys->frames);
85 frames = sys->frames - frames;
86 if (frames > block->i_nb_samples)
87 frames = block->i_nb_samples;
90 hr = IAudioRenderClient_GetBuffer(sys->render, frames, &dst);
93 msg_Err(aout, "cannot get buffer (error 0x%lx)", hr);
97 const size_t copy = frames * (size_t)aout->format.i_bytes_per_frame;
99 memcpy(dst, block->p_buffer, copy);
100 hr = IAudioRenderClient_ReleaseBuffer(sys->render, frames, 0);
103 msg_Err(aout, "cannot release buffer (error 0x%lx)", hr);
106 IAudioClient_Start(sys->client);
108 block->p_buffer += copy;
109 block->i_buffer -= copy;
110 block->i_nb_samples -= frames;
111 if (block->i_nb_samples == 0)
114 /* Out of buffer space, sleep */
115 msleep(AOUT_MIN_PREPARE_TIME
116 + block->i_nb_samples * CLOCK_FREQ / aout->format.i_rate);
120 block_Release(block);
123 static void Pause(audio_output_t *aout, bool paused, mtime_t date)
125 aout_sys_t *sys = aout->sys;
128 CoInitializeEx(NULL, COINIT_MULTITHREADED);
130 hr = IAudioClient_Stop(sys->client);
132 hr = IAudioClient_Start(sys->client);
134 msg_Warn(aout, "cannot %s stream (error 0x%lx)",
135 paused ? "stop" : "start", hr);
140 static void Flush(audio_output_t *aout, bool wait)
142 aout_sys_t *sys = aout->sys;
146 return; /* Not drain implemented */
148 CoInitializeEx(NULL, COINIT_MULTITHREADED);
149 IAudioClient_Stop(sys->client);
150 hr = IAudioClient_Reset(sys->client);
152 msg_Warn(aout, "cannot reset stream (error 0x%lx)", hr);
156 /*static int VolumeSet(audio_output_t *aout, float vol, bool mute)
158 aout_sys_t *sys = aout->sys;
163 static void vlc_ToWave(WAVEFORMATEXTENSIBLE *restrict wf,
164 audio_sample_format_t *restrict audio)
166 switch (audio->i_format)
171 wf->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
178 wf->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
182 audio->i_format = VLC_CODEC_FL32;
183 audio->i_rate = 48000;
184 wf->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
187 aout_FormatPrepare (audio);
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);
197 wf->Samples.wValidBitsPerSample = audio->i_bitspersample;
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;
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;
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;
223 static int vlc_FromWave(const WAVEFORMATEX *restrict wf,
224 audio_sample_format_t *restrict audio)
226 /* FIXME? different sample format? possible? */
227 audio->i_rate = wf->nSamplesPerSec;
229 if (wf->nChannels != audio->i_channels)
232 aout_FormatPrepare(audio);
236 /* Dummy thread to keep COM MTA alive */
237 static void MTAThread(void *data)
242 hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
243 if (unlikely(FAILED(hr)))
245 WaitForSingleObject(done, INFINITE);
250 static int Open(vlc_object_t *obj)
252 audio_output_t *aout = (audio_output_t *)obj;
255 aout_sys_t *sys = malloc(sizeof (*sys));
256 if (unlikely(sys == NULL))
263 hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
270 /* Select audio device */
271 IMMDeviceEnumerator *devs;
272 hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL,
273 &IID_IMMDeviceEnumerator, (void **)&devs);
276 msg_Dbg(aout, "cannot create device enumerator (error 0x%lx)", hr);
280 /* TODO: support selecting a device from config? */
282 hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(devs, eRender,
284 IMMDeviceEnumerator_Release(devs);
287 msg_Err(aout, "cannot get audio endpoint (error 0x%lx)", hr);
292 hr = IMMDevice_GetId(dev, &str);
295 msg_Dbg(aout, "using device %ls", str);
299 hr = IMMDevice_Activate(dev, &IID_IAudioClient, CLSCTX_ALL, NULL,
300 (void **)&sys->client);
301 IMMDevice_Release(dev);
304 msg_Err(aout, "cannot activate audio client (error 0x%lx)", hr);
308 /* Configure audio stream */
309 audio_sample_format_t format = aout->format;
310 WAVEFORMATEXTENSIBLE wf;
313 vlc_ToWave(&wf, &format);
314 hr = IAudioClient_IsFormatSupported(sys->client, AUDCLNT_SHAREMODE_SHARED,
316 // TODO: deal with (hr == AUDCLNT_E_DEVICE_INVALIDATED) ?
319 msg_Err(aout, "cannot negotiate audio format (error 0x%lx)", hr);
326 if (vlc_FromWave(pwf, &format))
329 msg_Err(aout, "unsupported audio format");
332 msg_Dbg(aout, "modified format");
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);
342 msg_Err(aout, "cannot initialize audio client (error 0x%lx)", hr);
346 hr = IAudioClient_GetBufferSize(sys->client, &sys->frames);
349 msg_Err(aout, "cannot get buffer size (error 0x%lx)", hr);
353 hr = IAudioClient_GetService(sys->client, &IID_IAudioRenderClient,
354 (void **)&sys->render);
357 msg_Err(aout, "cannot get audio render service (error 0x%lx)", hr);
361 hr = IAudioClient_GetService(sys->client, &IID_IAudioClock,
362 (void **)&sys->clock);
364 msg_Warn(aout, "cannot get audio clock (error 0x%lx)", hr);
366 sys->done = CreateSemaphore(NULL, 0, 1, NULL);
367 if (unlikely(sys->done == NULL))
369 /* Note: thread handle released by CRT, ignore it. */
370 if (_beginthread(MTAThread, 0, sys->done) == (uintptr_t)-1)
373 aout->format = format;
375 aout->pf_play = Play;
376 aout->pf_pause = Pause;
377 aout->pf_flush = Flush;
378 aout_VolumeNoneInit (aout);
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);
395 static void Close (vlc_object_t *obj)
397 audio_output_t *aout = (audio_output_t *)obj;
398 aout_sys_t *sys = aout->sys;
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);
407 ReleaseSemaphore(sys->done, 1, NULL); /* MTA thread will exit */