]> git.sesse.net Git - vlc/blob - modules/audio_output/wasapi.c
wasapi: first stab of audio output using the Windows Audio Session API
[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 <mmdeviceapi.h>
31
32 #include <vlc_common.h>
33 #include <vlc_plugin.h>
34 #include <vlc_aout.h>
35
36 static int Open(vlc_object_t *);
37 static void Close(vlc_object_t *);
38
39 vlc_module_begin()
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)
47 vlc_module_end()
48
49 struct aout_sys_t
50 {
51     IAudioClient *client;
52     IAudioRenderClient *render;
53     UINT32 frames;
54 };
55
56 static void Play(audio_output_t *aout, block_t *block)
57 {
58     aout_sys_t *sys = aout->sys;
59     HRESULT hr;
60
61     while (block->i_nb_samples > 0)
62     {
63         UINT32 frames;
64         hr = IAudioClient_GetCurrentPadding(sys->client, &frames);
65         if (FAILED(hr))
66         {
67             msg_Err(aout, "cannot get current padding (error 0x%lx)", hr);
68             break;
69         }
70
71         assert(frames <= sys->frames);
72         frames = sys->frames - frames;
73         if (frames > block->i_nb_samples)
74             frames = block->i_nb_samples;
75
76         BYTE *dst;
77         hr = IAudioRenderClient_GetBuffer(sys->render, frames, &dst);
78         if (FAILED(hr))
79         {
80             msg_Err(aout, "cannot get buffer (error 0x%lx)", hr);
81             break;
82         }
83
84         const size_t copy = frames * (size_t)aout->format.i_bytes_per_frame;
85
86         memcpy(dst, block->p_buffer, copy);
87         hr = IAudioRenderClient_ReleaseBuffer(sys->render, frames, 0);
88         if (FAILED(hr))
89         {
90             msg_Err(aout, "cannot release buffer (error 0x%lx)", hr);
91             break;
92         }
93
94         block->p_buffer += copy;
95         block->i_buffer -= copy;
96         block->i_nb_samples -= frames;
97
98         /* FIXME: implement synchro */
99         IAudioClient_Start(sys->client);
100         Sleep(AOUT_MIN_PREPARE_TIME / 1000);
101     }
102
103     block_Release(block);
104 }
105
106 static void Pause(audio_output_t *aout, bool paused, mtime_t date)
107 {
108     aout_sys_t *sys = aout->sys;
109     HRESULT hr;
110
111     if (!paused)
112         return;
113
114     hr = IAudioClient_Stop(sys->client);
115     if (FAILED(hr))
116         msg_Warn(aout, "cannot stop stream (error 0x%lx)", hr);
117     (void) date;
118 }
119
120 static void Flush(audio_output_t *aout, bool wait)
121 {
122     aout_sys_t *sys = aout->sys;
123     HRESULT hr;
124
125     if (wait)
126         return;
127
128     hr = IAudioClient_Reset(sys->client);
129     if (FAILED(hr))
130         msg_Warn(aout, "cannot reset stream (error 0x%lx)", hr);
131 }
132
133 /*static int VolumeSet(audio_output_t *aout, float vol, bool mute)
134 {
135     aout_sys_t *sys = aout->sys;
136
137     return 0;
138 }*/
139
140 static void vlc_ToWave(WAVEFORMATEXTENSIBLE *restrict wf,
141                        audio_sample_format_t *restrict audio)
142 {
143     switch (audio->i_format)
144     {
145 #if 0
146         case VLC_CODEC_FL32:
147         case VLC_CODEC_FL64:
148             wf->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
149             break;
150
151         case VLC_CODEC_S8:
152         case VLC_CODEC_S16N:
153         case VLC_CODEC_S24N:
154         case VLC_CODEC_S32N:
155             wf->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
156             break;
157 #endif
158         default:
159             audio->i_format = VLC_CODEC_FL32;
160             audio->i_rate = 48000;
161             wf->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
162             break;
163      }
164      aout_FormatPrepare (audio);
165
166      wf->Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
167      wf->Format.nChannels = audio->i_channels;
168      wf->Format.nSamplesPerSec = audio->i_rate;
169      wf->Format.nAvgBytesPerSec = audio->i_bytes_per_frame * audio->i_rate;
170      wf->Format.nBlockAlign = audio->i_bytes_per_frame;
171      wf->Format.wBitsPerSample = audio->i_bitspersample;
172      wf->Format.cbSize = sizeof (*wf) - sizeof (wf->Format);
173
174      wf->Samples.wValidBitsPerSample = audio->i_bitspersample;
175
176      wf->dwChannelMask = 0;
177      if (audio->i_physical_channels & AOUT_CHAN_LEFT)
178          wf->dwChannelMask |= SPEAKER_FRONT_LEFT;
179      if (audio->i_physical_channels & AOUT_CHAN_RIGHT)
180          wf->dwChannelMask |= SPEAKER_FRONT_RIGHT;
181      if (audio->i_physical_channels & AOUT_CHAN_CENTER)
182          wf->dwChannelMask |= SPEAKER_FRONT_CENTER;
183      if (audio->i_physical_channels & AOUT_CHAN_LFE)
184          wf->dwChannelMask |= SPEAKER_LOW_FREQUENCY;
185      // TODO: reorder
186      if (audio->i_physical_channels & AOUT_CHAN_REARLEFT)
187          wf->dwChannelMask |= SPEAKER_BACK_LEFT;
188      if (audio->i_physical_channels & AOUT_CHAN_REARRIGHT)
189          wf->dwChannelMask |= SPEAKER_BACK_RIGHT;
190      /* ... */
191      if (audio->i_physical_channels & AOUT_CHAN_REARCENTER)
192          wf->dwChannelMask |= SPEAKER_BACK_CENTER;
193      if (audio->i_physical_channels & AOUT_CHAN_MIDDLELEFT)
194          wf->dwChannelMask |= SPEAKER_SIDE_LEFT;
195      if (audio->i_physical_channels & AOUT_CHAN_MIDDLERIGHT)
196          wf->dwChannelMask |= SPEAKER_SIDE_RIGHT;
197      /* ... */
198 }
199
200 static int vlc_FromWave(const WAVEFORMATEX *restrict wf,
201                         audio_sample_format_t *restrict audio)
202 {
203     /* FIXME? different sample format? possible? */
204     audio->i_rate = wf->nSamplesPerSec;
205     /* FIXME */
206     if (wf->nChannels != audio->i_channels)
207         return -1;
208
209     aout_FormatPrepare(audio);
210     return 0;
211 }
212
213 static int Open(vlc_object_t *obj)
214 {
215     audio_output_t *aout = (audio_output_t *)obj;
216     HRESULT hr;
217
218     aout_sys_t *sys = malloc(sizeof (*sys));
219     if (unlikely(sys == NULL))
220         return VLC_ENOMEM;
221
222     aout->sys = sys;
223     sys->client = NULL;
224     sys->render = NULL;
225
226     hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
227     if (FAILED(hr))
228     {
229         free(sys);
230         return VLC_EGENERIC;
231     }
232
233     /* Select audio device */
234     IMMDeviceEnumerator *devs;
235     hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL,
236                           &IID_IMMDeviceEnumerator, (void **)&devs);
237     if (FAILED(hr))
238     {
239         msg_Dbg(aout, "cannot create device enumerator (error 0x%lx)", hr);
240         goto error;
241     }
242
243     /* TODO: support selecting a device from config? */
244     IMMDevice *dev;
245     hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(devs, eRender,
246                                                      eConsole, &dev);
247     IMMDeviceEnumerator_Release(devs);
248     if (FAILED(hr))
249     {
250         msg_Err(aout, "cannot get audio endpoint (error 0x%lx)", hr);
251         goto error;
252     }
253
254     LPWSTR str;
255     hr = IMMDevice_GetId(dev, &str);
256     if (SUCCEEDED(hr))
257     {
258         msg_Dbg(aout, "using device %ls", str);
259         CoTaskMemFree(str);
260     }
261
262     hr = IMMDevice_Activate(dev, &IID_IAudioClient, CLSCTX_ALL, NULL,
263                             (void **)&sys->client);
264     IMMDevice_Release(dev);
265     if (FAILED(hr))
266     {
267         msg_Err(aout, "cannot activate audio client (error 0x%lx)", hr);
268         goto error;
269     }
270
271     /* Configure audio stream */
272     audio_sample_format_t format = aout->format;
273     WAVEFORMATEXTENSIBLE wf;
274     WAVEFORMATEX *pwf;
275
276     vlc_ToWave(&wf, &format);
277     hr = IAudioClient_IsFormatSupported(sys->client, AUDCLNT_SHAREMODE_SHARED,
278                                         &wf.Format, &pwf);
279     // TODO: deal with (hr == AUDCLNT_E_DEVICE_INVALIDATED) ?
280     if (FAILED(hr))
281     {
282         msg_Err(aout, "cannot negotiate audio format (error 0x%lx)", hr);
283         goto error;
284     }
285
286     if (hr == S_FALSE)
287     {
288         assert(pwf != NULL);
289         if (vlc_FromWave(pwf, &format))
290         {
291             CoTaskMemFree(pwf);
292             msg_Err(aout, "unsupported audio format");
293             goto error;
294         }
295         msg_Dbg(aout, "modified format");
296     }
297     else
298         assert(pwf == NULL);
299     hr = IAudioClient_Initialize(sys->client, AUDCLNT_SHAREMODE_SHARED, 0,
300                                  AOUT_MAX_PREPARE_TIME * 10, 0,
301                                  (hr == S_OK) ? &wf.Format : pwf, NULL);
302     CoTaskMemFree(pwf);
303     if (FAILED(hr))
304     {
305         msg_Err(aout, "cannot initialize audio client (error 0x%lx)", hr);
306         goto error;
307     }
308
309     hr = IAudioClient_GetBufferSize(sys->client, &sys->frames);
310     if (FAILED(hr))
311     {
312         msg_Err(aout, "cannot get buffer size (error 0x%lx)", hr);
313         goto error;
314     }
315
316     hr = IAudioClient_GetService(sys->client, &IID_IAudioRenderClient,
317                                  (void **)&sys->render);
318     if (FAILED(hr))
319     {
320         msg_Err(aout, "cannot get audio render service (error 0x%lx)", hr);
321         goto error;
322     }
323
324     aout->format = format;
325     aout->sys = sys;
326     aout->pf_play = Play;
327     aout->pf_pause = Pause;
328     aout->pf_flush = Flush;
329     aout_VolumeNoneInit (aout);
330     return VLC_SUCCESS;
331 error:
332     Close(obj);
333     return VLC_EGENERIC;
334 }
335
336 static void Close (vlc_object_t *obj)
337 {
338     audio_output_t *aout = (audio_output_t *)obj;
339     aout_sys_t *sys = aout->sys;
340
341     if (sys->render != NULL)
342         IAudioRenderClient_Release(sys->render);
343     if (sys->client != NULL)
344         IAudioClient_Release(sys->client);
345     /* FIXME: CoUninitialize(); */
346     free(sys);
347 }