]> git.sesse.net Git - vlc/blob - modules/audio_output/wasapi.c
wasapi: fix Flush()
[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; /* Not drain implemented */
127
128     IAudioClient_Stop(sys->client);
129     hr = IAudioClient_Reset(sys->client);
130     if (FAILED(hr))
131         msg_Warn(aout, "cannot reset stream (error 0x%lx)", hr);
132 }
133
134 /*static int VolumeSet(audio_output_t *aout, float vol, bool mute)
135 {
136     aout_sys_t *sys = aout->sys;
137
138     return 0;
139 }*/
140
141 static void vlc_ToWave(WAVEFORMATEXTENSIBLE *restrict wf,
142                        audio_sample_format_t *restrict audio)
143 {
144     switch (audio->i_format)
145     {
146 #if 0
147         case VLC_CODEC_FL32:
148         case VLC_CODEC_FL64:
149             wf->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
150             break;
151
152         case VLC_CODEC_S8:
153         case VLC_CODEC_S16N:
154         case VLC_CODEC_S24N:
155         case VLC_CODEC_S32N:
156             wf->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
157             break;
158 #endif
159         default:
160             audio->i_format = VLC_CODEC_FL32;
161             audio->i_rate = 48000;
162             wf->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
163             break;
164      }
165      aout_FormatPrepare (audio);
166
167      wf->Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
168      wf->Format.nChannels = audio->i_channels;
169      wf->Format.nSamplesPerSec = audio->i_rate;
170      wf->Format.nAvgBytesPerSec = audio->i_bytes_per_frame * audio->i_rate;
171      wf->Format.nBlockAlign = audio->i_bytes_per_frame;
172      wf->Format.wBitsPerSample = audio->i_bitspersample;
173      wf->Format.cbSize = sizeof (*wf) - sizeof (wf->Format);
174
175      wf->Samples.wValidBitsPerSample = audio->i_bitspersample;
176
177      wf->dwChannelMask = 0;
178      if (audio->i_physical_channels & AOUT_CHAN_LEFT)
179          wf->dwChannelMask |= SPEAKER_FRONT_LEFT;
180      if (audio->i_physical_channels & AOUT_CHAN_RIGHT)
181          wf->dwChannelMask |= SPEAKER_FRONT_RIGHT;
182      if (audio->i_physical_channels & AOUT_CHAN_CENTER)
183          wf->dwChannelMask |= SPEAKER_FRONT_CENTER;
184      if (audio->i_physical_channels & AOUT_CHAN_LFE)
185          wf->dwChannelMask |= SPEAKER_LOW_FREQUENCY;
186      // TODO: reorder
187      if (audio->i_physical_channels & AOUT_CHAN_REARLEFT)
188          wf->dwChannelMask |= SPEAKER_BACK_LEFT;
189      if (audio->i_physical_channels & AOUT_CHAN_REARRIGHT)
190          wf->dwChannelMask |= SPEAKER_BACK_RIGHT;
191      /* ... */
192      if (audio->i_physical_channels & AOUT_CHAN_REARCENTER)
193          wf->dwChannelMask |= SPEAKER_BACK_CENTER;
194      if (audio->i_physical_channels & AOUT_CHAN_MIDDLELEFT)
195          wf->dwChannelMask |= SPEAKER_SIDE_LEFT;
196      if (audio->i_physical_channels & AOUT_CHAN_MIDDLERIGHT)
197          wf->dwChannelMask |= SPEAKER_SIDE_RIGHT;
198      /* ... */
199 }
200
201 static int vlc_FromWave(const WAVEFORMATEX *restrict wf,
202                         audio_sample_format_t *restrict audio)
203 {
204     /* FIXME? different sample format? possible? */
205     audio->i_rate = wf->nSamplesPerSec;
206     /* FIXME */
207     if (wf->nChannels != audio->i_channels)
208         return -1;
209
210     aout_FormatPrepare(audio);
211     return 0;
212 }
213
214 static int Open(vlc_object_t *obj)
215 {
216     audio_output_t *aout = (audio_output_t *)obj;
217     HRESULT hr;
218
219     aout_sys_t *sys = malloc(sizeof (*sys));
220     if (unlikely(sys == NULL))
221         return VLC_ENOMEM;
222
223     aout->sys = sys;
224     sys->client = NULL;
225     sys->render = NULL;
226
227     hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
228     if (FAILED(hr))
229     {
230         free(sys);
231         return VLC_EGENERIC;
232     }
233
234     /* Select audio device */
235     IMMDeviceEnumerator *devs;
236     hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL,
237                           &IID_IMMDeviceEnumerator, (void **)&devs);
238     if (FAILED(hr))
239     {
240         msg_Dbg(aout, "cannot create device enumerator (error 0x%lx)", hr);
241         goto error;
242     }
243
244     /* TODO: support selecting a device from config? */
245     IMMDevice *dev;
246     hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(devs, eRender,
247                                                      eConsole, &dev);
248     IMMDeviceEnumerator_Release(devs);
249     if (FAILED(hr))
250     {
251         msg_Err(aout, "cannot get audio endpoint (error 0x%lx)", hr);
252         goto error;
253     }
254
255     LPWSTR str;
256     hr = IMMDevice_GetId(dev, &str);
257     if (SUCCEEDED(hr))
258     {
259         msg_Dbg(aout, "using device %ls", str);
260         CoTaskMemFree(str);
261     }
262
263     hr = IMMDevice_Activate(dev, &IID_IAudioClient, CLSCTX_ALL, NULL,
264                             (void **)&sys->client);
265     IMMDevice_Release(dev);
266     if (FAILED(hr))
267     {
268         msg_Err(aout, "cannot activate audio client (error 0x%lx)", hr);
269         goto error;
270     }
271
272     /* Configure audio stream */
273     audio_sample_format_t format = aout->format;
274     WAVEFORMATEXTENSIBLE wf;
275     WAVEFORMATEX *pwf;
276
277     vlc_ToWave(&wf, &format);
278     hr = IAudioClient_IsFormatSupported(sys->client, AUDCLNT_SHAREMODE_SHARED,
279                                         &wf.Format, &pwf);
280     // TODO: deal with (hr == AUDCLNT_E_DEVICE_INVALIDATED) ?
281     if (FAILED(hr))
282     {
283         msg_Err(aout, "cannot negotiate audio format (error 0x%lx)", hr);
284         goto error;
285     }
286
287     if (hr == S_FALSE)
288     {
289         assert(pwf != NULL);
290         if (vlc_FromWave(pwf, &format))
291         {
292             CoTaskMemFree(pwf);
293             msg_Err(aout, "unsupported audio format");
294             goto error;
295         }
296         msg_Dbg(aout, "modified format");
297     }
298     else
299         assert(pwf == NULL);
300     hr = IAudioClient_Initialize(sys->client, AUDCLNT_SHAREMODE_SHARED, 0,
301                                  AOUT_MAX_PREPARE_TIME * 10, 0,
302                                  (hr == S_OK) ? &wf.Format : pwf, NULL);
303     CoTaskMemFree(pwf);
304     if (FAILED(hr))
305     {
306         msg_Err(aout, "cannot initialize audio client (error 0x%lx)", hr);
307         goto error;
308     }
309
310     hr = IAudioClient_GetBufferSize(sys->client, &sys->frames);
311     if (FAILED(hr))
312     {
313         msg_Err(aout, "cannot get buffer size (error 0x%lx)", hr);
314         goto error;
315     }
316
317     hr = IAudioClient_GetService(sys->client, &IID_IAudioRenderClient,
318                                  (void **)&sys->render);
319     if (FAILED(hr))
320     {
321         msg_Err(aout, "cannot get audio render service (error 0x%lx)", hr);
322         goto error;
323     }
324
325     aout->format = format;
326     aout->sys = sys;
327     aout->pf_play = Play;
328     aout->pf_pause = Pause;
329     aout->pf_flush = Flush;
330     aout_VolumeNoneInit (aout);
331     return VLC_SUCCESS;
332 error:
333     Close(obj);
334     return VLC_EGENERIC;
335 }
336
337 static void Close (vlc_object_t *obj)
338 {
339     audio_output_t *aout = (audio_output_t *)obj;
340     aout_sys_t *sys = aout->sys;
341
342     if (sys->render != NULL)
343         IAudioRenderClient_Release(sys->render);
344     if (sys->client != NULL)
345         IAudioClient_Release(sys->client);
346     /* FIXME: CoUninitialize(); */
347     free(sys);
348 }