1 /*
2 * GPAC - Multimedia Framework C SDK
3 *
4 * Authors: Jean Le Feuvre
5 * Copyright (c) Telecom ParisTech 2000-2012
6 * All rights reserved
7 *
8 * This file is part of GPAC / DirectX audio and video render module
9 *
10 * GPAC is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU Lesser General Public License as published by
12 * the Free Software Foundation; either version 2, or (at your option)
13 * any later version.
14 *
15 * GPAC is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU Lesser General Public License for more details.
19 *
20 * You should have received a copy of the GNU Lesser General Public
21 * License along with this library; see the file COPYING. If not, write to
22 * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
23 *
24 */
25
26
27 #include "dx_hw.h"
28
29 #if (DIRECTSOUND_VERSION >= 0x0800)
30 #define USE_WAVE_EX
31 #endif
32
33 #ifdef USE_WAVE_EX
34 #include <ks.h>
35 #include <ksmedia.h>
36 const static GUID GPAC_KSDATAFORMAT_SUBTYPE_PCM = {0x00000001,0x0000,0x0010,
37 {0x80,0x00,0x00,0xaa,0x00,0x38,0x9b,0x71}
38 };
39 #endif
40
41
42 typedef HRESULT (WINAPI *DIRECTSOUNDCREATEPROC)(LPCGUID pcGuidDevice, LPDIRECTSOUND *ppDS, LPUNKNOWN pUnkOuter);
43
44 /*
45 DirectSound audio output
46 */
47 #define MAX_NUM_BUFFERS 20
48
49 typedef struct
50 {
51 Bool force_config;
52 u32 cfg_num_buffers, cfg_duration;
53
54 HWND hWnd;
55 LPDIRECTSOUND pDS;
56 WAVEFORMATEX format;
57 IDirectSoundBuffer *pOutput;
58
59 u32 buffer_size, num_audio_buffer, total_audio_buffer_ms;
60
61 /*notifs*/
62 Bool use_notif;
63 u32 frame_state[MAX_NUM_BUFFERS];
64 DSBPOSITIONNOTIFY notif_events[MAX_NUM_BUFFERS];
65 HANDLE events[MAX_NUM_BUFFERS];
66
67 HMODULE hDSoundLib;
68 DIRECTSOUNDCREATEPROC DirectSoundCreate;
69 } DSContext;
70
71 #define DSCONTEXT() DSContext *ctx = (DSContext *)dr->opaque;
72
73 void DS_WriteAudio(GF_AudioOutput *dr);
74 void DS_WriteAudio_Notifs(GF_AudioOutput *dr);
75
DS_Setup(GF_AudioOutput * dr,void * os_handle,u32 num_buffers,u32 total_duration)76 static GF_Err DS_Setup(GF_AudioOutput *dr, void *os_handle, u32 num_buffers, u32 total_duration)
77 {
78 HRESULT hr;
79
80 DSCONTEXT();
81 ctx->hWnd = (HWND) os_handle;
82 /*check if we have created a HWND (this requires that video is handled by the DX module*/
83 if (!ctx->hWnd) ctx->hWnd = DD_GetGlobalHWND();
84 /*too bad, use desktop as window*/
85 if (!ctx->hWnd) ctx->hWnd = GetDesktopWindow();
86
87 ctx->force_config = (num_buffers && total_duration) ? GF_TRUE : GF_FALSE;
88 ctx->cfg_num_buffers = num_buffers;
89 ctx->cfg_duration = total_duration;
90 if (ctx->cfg_num_buffers <= 1) ctx->cfg_num_buffers = 2;
91
92 if ( FAILED( hr = ctx->DirectSoundCreate( NULL, &ctx->pDS, NULL ) ) ) return GF_IO_ERR;
93
94 if( FAILED( hr = ctx->pDS->lpVtbl->SetCooperativeLevel(ctx->pDS, ctx->hWnd, DSSCL_EXCLUSIVE) ) ) {
95 SAFE_DS_RELEASE( ctx->pDS );
96 return GF_IO_ERR;
97 }
98 return GF_OK;
99 }
100
101
DS_ResetBuffer(DSContext * ctx)102 void DS_ResetBuffer(DSContext *ctx)
103 {
104 VOID *pLock = NULL;
105 DWORD size;
106
107 if( FAILED(ctx->pOutput->lpVtbl->Lock(ctx->pOutput, 0, ctx->buffer_size*ctx->num_audio_buffer, &pLock, &size, NULL, NULL, 0 ) ) )
108 return;
109 memset(pLock, 0, (size_t) size);
110 ctx->pOutput->lpVtbl->Unlock(ctx->pOutput, pLock, size, NULL, 0L);
111 }
112
DS_ReleaseBuffer(GF_AudioOutput * dr)113 void DS_ReleaseBuffer(GF_AudioOutput *dr)
114 {
115 u32 i;
116 DSCONTEXT();
117
118 /*stop playing and notif proc*/
119 if (ctx->pOutput) ctx->pOutput->lpVtbl->Stop(ctx->pOutput);
120 SAFE_DS_RELEASE(ctx->pOutput);
121
122 /*use notif, shutdown notifier and event watcher*/
123 if (ctx->use_notif) {
124 for (i=0; i<ctx->num_audio_buffer; i++) CloseHandle(ctx->events[i]);
125 }
126 ctx->use_notif = GF_FALSE;
127 }
128
DS_Shutdown(GF_AudioOutput * dr)129 static void DS_Shutdown(GF_AudioOutput *dr)
130 {
131 DSCONTEXT();
132 DS_ReleaseBuffer(dr);
133 SAFE_DS_RELEASE(ctx->pDS );
134 }
135
136 /*we assume what was asked is what we got*/
DS_Configure(GF_AudioOutput * dr,u32 * SampleRate,u32 * NbChannels,u32 * audioFormat,u64 channel_cfg)137 static GF_Err DS_Configure(GF_AudioOutput *dr, u32 *SampleRate, u32 *NbChannels, u32 *audioFormat, u64 channel_cfg)
138 {
139 u32 i;
140 HRESULT hr;
141 DSBUFFERDESC dsbBufferDesc;
142 IDirectSoundNotify *pNotify;
143 #ifdef USE_WAVE_EX
144 WAVEFORMATEXTENSIBLE format_ex;
145 #endif
146 DSCONTEXT();
147
148 DS_ReleaseBuffer(dr);
149 //only support for PCM 8/16/24/32 packet mode
150 switch (*audioFormat) {
151 case GF_AUDIO_FMT_U8:
152 case GF_AUDIO_FMT_S16:
153 case GF_AUDIO_FMT_S24:
154 case GF_AUDIO_FMT_S32:
155 break;
156 default:
157 //otherwise force PCM16
158 *audioFormat = GF_AUDIO_FMT_S16;
159 break;
160 }
161 ctx->format.nChannels = *NbChannels;
162 ctx->format.wBitsPerSample = gf_audio_fmt_bit_depth( *audioFormat);
163 ctx->format.nSamplesPerSec = *SampleRate;
164 ctx->format.cbSize = sizeof (WAVEFORMATEX);
165 ctx->format.wFormatTag = WAVE_FORMAT_PCM;
166 ctx->format.nBlockAlign = ctx->format.nChannels * ctx->format.wBitsPerSample / 8;
167 ctx->format.nAvgBytesPerSec = ctx->format.nSamplesPerSec * ctx->format.nBlockAlign;
168
169 if (!ctx->format.nBlockAlign)
170 return GF_BAD_PARAM;
171
172 if (!ctx->force_config) {
173 ctx->buffer_size = ctx->format.nBlockAlign * 1024;
174 ctx->num_audio_buffer = 2;
175 } else {
176 ctx->num_audio_buffer = ctx->cfg_num_buffers;
177 ctx->buffer_size = (ctx->format.nAvgBytesPerSec * ctx->cfg_duration) / (1000 * ctx->cfg_num_buffers);
178 }
179
180 /*make sure we're aligned*/
181 while (ctx->buffer_size % ctx->format.nBlockAlign) ctx->buffer_size++;
182
183 ctx->use_notif = GF_TRUE;
184 if (gf_opts_get_bool("core", "ds-disable-notif"))
185 ctx->use_notif = GF_FALSE;
186
187 memset(&dsbBufferDesc, 0, sizeof(DSBUFFERDESC));
188 dsbBufferDesc.dwSize = sizeof (DSBUFFERDESC);
189 dsbBufferDesc.dwBufferBytes = ctx->buffer_size * ctx->num_audio_buffer;
190 dsbBufferDesc.lpwfxFormat = &ctx->format;
191 dsbBufferDesc.dwFlags = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_GLOBALFOCUS | DSBCAPS_CTRLVOLUME;
192 if (ctx->use_notif) dsbBufferDesc.dwFlags |= DSBCAPS_CTRLPOSITIONNOTIFY;
193
194 #ifdef USE_WAVE_EX
195 if (channel_cfg && ctx->format.nChannels>2) {
196 memset(&format_ex, 0, sizeof(WAVEFORMATEXTENSIBLE));
197 format_ex.Format = ctx->format;
198 format_ex.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE);
199 format_ex.SubFormat = GPAC_KSDATAFORMAT_SUBTYPE_PCM;
200 format_ex.Samples.wValidBitsPerSample = gf_audio_fmt_bit_depth(*audioFormat);
201
202 format_ex.dwChannelMask = 0;
203 if (channel_cfg & GF_AUDIO_CH_FRONT_LEFT) format_ex.dwChannelMask |= SPEAKER_FRONT_LEFT;
204 if (channel_cfg & GF_AUDIO_CH_FRONT_RIGHT) format_ex.dwChannelMask |= SPEAKER_FRONT_RIGHT;
205 if (channel_cfg & GF_AUDIO_CH_FRONT_CENTER) format_ex.dwChannelMask |= SPEAKER_FRONT_CENTER;
206 if (channel_cfg & GF_AUDIO_CH_LFE) format_ex.dwChannelMask |= SPEAKER_LOW_FREQUENCY;
207 if (channel_cfg & GF_AUDIO_CH_SURROUND_LEFT) format_ex.dwChannelMask |= SPEAKER_BACK_LEFT;
208 if (channel_cfg & GF_AUDIO_CH_SURROUND_RIGHT) format_ex.dwChannelMask |= SPEAKER_BACK_RIGHT;
209 if (channel_cfg & GF_AUDIO_CH_REAR_CENTER) format_ex.dwChannelMask |= SPEAKER_BACK_CENTER;
210 if (channel_cfg & GF_AUDIO_CH_SIDE_SURROUND_LEFT) format_ex.dwChannelMask |= SPEAKER_SIDE_LEFT;
211 if (channel_cfg & GF_AUDIO_CH_SIDE_SURROUND_RIGHT) format_ex.dwChannelMask |= SPEAKER_SIDE_RIGHT;
212
213 dsbBufferDesc.lpwfxFormat = (WAVEFORMATEX *) &format_ex;
214 }
215 #endif
216
217
218 hr = ctx->pDS->lpVtbl->CreateSoundBuffer(ctx->pDS, &dsbBufferDesc, &ctx->pOutput, NULL );
219 if (FAILED(hr)) {
220 retry:
221 if (ctx->use_notif) gf_opts_set_key("core", "ds-disable-notif", "yes");
222 ctx->use_notif = GF_FALSE;
223 dsbBufferDesc.dwFlags = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_GLOBALFOCUS;
224 hr = ctx->pDS->lpVtbl->CreateSoundBuffer(ctx->pDS, &dsbBufferDesc, &ctx->pOutput, NULL );
225 if (FAILED(hr)) {
226 if (ctx->format.nChannels>2) {
227 GF_LOG(GF_LOG_ERROR, GF_LOG_AUDIO, ("[DirectSound] failed to configure output for %d channels (error %08x) - falling back to stereo\n", *NbChannels, hr));
228 *NbChannels = 2;
229 return DS_Configure(dr, SampleRate, NbChannels, audioFormat, 0);
230 }
231 return GF_IO_ERR;
232 }
233 }
234
235 for (i=0; i<ctx->num_audio_buffer; i++) ctx->frame_state[i] = 0;
236
237 if (ctx->use_notif) {
238 hr = ctx->pOutput->lpVtbl->QueryInterface(ctx->pOutput, &IID_IDirectSoundNotify , (void **)&pNotify);
239 if (hr == S_OK) {
240 /*setup the notification positions*/
241 for (i=0; i<ctx->num_audio_buffer; i++) {
242 ctx->events[i] = CreateEvent( NULL, FALSE, FALSE, NULL );
243 ctx->notif_events[i].hEventNotify = ctx->events[i];
244 ctx->notif_events[i].dwOffset = ctx->buffer_size * i;
245 }
246
247 /*Tell DirectSound when to notify us*/
248 hr = pNotify->lpVtbl->SetNotificationPositions(pNotify, ctx->num_audio_buffer, ctx->notif_events);
249
250 if (hr != S_OK) {
251 pNotify->lpVtbl->Release(pNotify);
252 for (i=0; i<ctx->num_audio_buffer; i++) CloseHandle(ctx->events[i]);
253 SAFE_DS_RELEASE(ctx->pOutput);
254 goto retry;
255 }
256
257 pNotify->lpVtbl->Release(pNotify);
258 } else {
259 ctx->use_notif = GF_FALSE;
260 }
261 }
262 if (ctx->use_notif) {
263 dr->WriteAudio = DS_WriteAudio_Notifs;
264 } else {
265 dr->WriteAudio = DS_WriteAudio;
266 }
267
268 ctx->total_audio_buffer_ms = 1000 * ctx->buffer_size * ctx->num_audio_buffer / ctx->format.nAvgBytesPerSec;
269
270 /*reset*/
271 DS_ResetBuffer(ctx);
272 /*play*/
273 ctx->pOutput->lpVtbl->Play(ctx->pOutput, 0, 0, DSBPLAY_LOOPING);
274 return GF_OK;
275 }
276
DS_RestoreBuffer(LPDIRECTSOUNDBUFFER pDSBuffer)277 static Bool DS_RestoreBuffer(LPDIRECTSOUNDBUFFER pDSBuffer)
278 {
279 DWORD dwStatus;
280 pDSBuffer->lpVtbl->GetStatus(pDSBuffer, &dwStatus );
281 if( dwStatus & DSBSTATUS_BUFFERLOST ) {
282 GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DirectSound] buffer lost\n"));
283 pDSBuffer->lpVtbl->Restore(pDSBuffer);
284 pDSBuffer->lpVtbl->GetStatus(pDSBuffer, &dwStatus);
285 if( dwStatus & DSBSTATUS_BUFFERLOST ) return GF_TRUE;
286 }
287 return GF_FALSE;
288 }
289
290
291
DS_FillBuffer(GF_AudioOutput * dr,u32 buffer)292 void DS_FillBuffer(GF_AudioOutput *dr, u32 buffer)
293 {
294 HRESULT hr;
295 VOID *pLock;
296 u32 pos;
297 DWORD size;
298 DSCONTEXT();
299
300 /*restoring*/
301 if (DS_RestoreBuffer(ctx->pOutput)) {
302 GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DirectSound] restoring sound buffer\n"));
303 return;
304 }
305
306 /*lock and fill from current pos*/
307 pos = buffer * ctx->buffer_size;
308 pLock = NULL;
309 if( FAILED( hr = ctx->pOutput->lpVtbl->Lock(ctx->pOutput, pos, ctx->buffer_size,
310 &pLock, &size, NULL, NULL, 0L ) ) ) {
311 GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DirectSound] Error locking sound buffer\n"));
312 return;
313 }
314
315 assert(size == ctx->buffer_size);
316
317 dr->FillBuffer(dr->audio_renderer, pLock, size);
318
319 /*update current pos*/
320 if( FAILED( hr = ctx->pOutput->lpVtbl->Unlock(ctx->pOutput, pLock, size, NULL, 0)) ) {
321 GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DirectSound] Error unlocking sound buffer\n"));
322 }
323 ctx->frame_state[buffer] = 1;
324 }
325
326
DS_WriteAudio_Notifs(GF_AudioOutput * dr)327 void DS_WriteAudio_Notifs(GF_AudioOutput *dr)
328 {
329 s32 i, inframe, nextframe;
330 DSCONTEXT();
331
332 inframe = WaitForMultipleObjects(ctx->num_audio_buffer, ctx->events, 0, 1000);
333 if (inframe==WAIT_TIMEOUT) return;
334 inframe -= WAIT_OBJECT_0;
335 /*reset state*/
336 ctx->frame_state[ (inframe + ctx->num_audio_buffer - 1) % ctx->num_audio_buffer] = 0;
337
338 nextframe = (inframe + 1) % ctx->num_audio_buffer;
339 for (i=nextframe; (i % ctx->num_audio_buffer) != (u32) inframe; i++) {
340 u32 buf = i % ctx->num_audio_buffer;
341 if (ctx->frame_state[buf]) continue;
342 DS_FillBuffer(dr, buf);
343 }
344 }
345
DS_WriteAudio(GF_AudioOutput * dr)346 void DS_WriteAudio(GF_AudioOutput *dr)
347 {
348 u32 retry;
349 DWORD in_play, cur_play;
350 DSCONTEXT();
351 if (!ctx->pOutput) return;
352
353 /*wait for end of current play buffer*/
354 if (ctx->pOutput->lpVtbl->GetCurrentPosition(ctx->pOutput, &in_play, NULL) != DS_OK ) {
355 GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DirectSound] error getting sound buffer positions\n"));
356 return;
357 }
358 in_play = (in_play / ctx->buffer_size);
359 retry = 6;
360 while (retry) {
361 if (ctx->pOutput->lpVtbl->GetCurrentPosition(ctx->pOutput, &cur_play, NULL) != DS_OK ) {
362 GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DirectSound] error getting sound buffer positions\n"));
363 return;
364 }
365 cur_play = (cur_play / ctx->buffer_size);
366 if (cur_play == in_play) {
367 gf_sleep(20);
368 retry--;
369 } else {
370 /**/
371 ctx->frame_state[in_play] = 0;
372 DS_FillBuffer(dr, in_play);
373 return;
374 }
375 }
376 }
377
DS_QueryOutputSampleRate(GF_AudioOutput * dr,u32 * desired_samplerate,u32 * NbChannels,u32 * nbBitsPerSample)378 static GF_Err DS_QueryOutputSampleRate(GF_AudioOutput *dr, u32 *desired_samplerate, u32 *NbChannels, u32 *nbBitsPerSample)
379 {
380 /*all sample rates supported for now ... */
381 return GF_OK;
382 }
383
DS_Play(GF_AudioOutput * dr,u32 PlayType)384 static void DS_Play(GF_AudioOutput *dr, u32 PlayType)
385 {
386 DSCONTEXT();
387 switch (PlayType) {
388 case 0:
389 ctx->pOutput->lpVtbl->Stop(ctx->pOutput);
390 break;
391 case 2:
392 DS_ResetBuffer(ctx);
393 case 1:
394 ctx->pOutput->lpVtbl->Play(ctx->pOutput, 0, 0, DSBPLAY_LOOPING);
395 break;
396 }
397 }
398
DS_SetVolume(GF_AudioOutput * dr,u32 Volume)399 static void DS_SetVolume(GF_AudioOutput *dr, u32 Volume)
400 {
401 LONG Vol;
402 DSCONTEXT();
403 if (!ctx->pOutput) return;
404 if (Volume > 100) Vol = DSBVOLUME_MAX;
405 else if(Volume == 0) Vol = DSBVOLUME_MIN;
406 else Vol = DSBVOLUME_MIN/2 + Volume * (DSBVOLUME_MAX-DSBVOLUME_MIN/2) / 100;
407 ctx->pOutput->lpVtbl->SetVolume(ctx->pOutput, Vol);
408 }
409
DS_SetPan(GF_AudioOutput * dr,u32 Pan)410 static void DS_SetPan(GF_AudioOutput *dr, u32 Pan)
411 {
412 LONG dspan;
413 DSCONTEXT();
414 if (!ctx->pOutput) return;
415
416 if (Pan > 100) Pan = 100;
417 if (Pan > 50) {
418 dspan = DSBPAN_RIGHT * (Pan - 50) / 50;
419 } else if (Pan < 50) {
420 dspan = DSBPAN_LEFT * (50 - Pan) / 50;
421 } else {
422 dspan = 0;
423 }
424 ctx->pOutput->lpVtbl->SetPan(ctx->pOutput, dspan);
425 }
426
427
DS_SetPriority(GF_AudioOutput * dr,u32 Priority)428 static void DS_SetPriority(GF_AudioOutput *dr, u32 Priority)
429 {
430 }
431
DS_GetAudioDelay(GF_AudioOutput * dr)432 static u32 DS_GetAudioDelay(GF_AudioOutput *dr)
433 {
434 DSCONTEXT();
435 return ctx->total_audio_buffer_ms;
436 }
437
DS_GetTotalBufferTime(GF_AudioOutput * dr)438 static u32 DS_GetTotalBufferTime(GF_AudioOutput *dr)
439 {
440 DSCONTEXT();
441 return ctx->total_audio_buffer_ms;
442 }
443
NewAudioOutput()444 void *NewAudioOutput()
445 {
446 HRESULT hr;
447 DSContext *ctx;
448 GF_AudioOutput *driv;
449
450 if( FAILED( hr = CoInitialize(NULL) ) ) return NULL;
451
452
453 ctx = (DSContext*)gf_malloc(sizeof(DSContext));
454 memset(ctx, 0, sizeof(DSContext));
455 #ifdef UNICODE
456 ctx->hDSoundLib = LoadLibrary(L"dsound.dll");
457 #else
458 ctx->hDSoundLib = LoadLibrary("dsound.dll");
459 #endif
460 if (ctx->hDSoundLib) {
461 ctx->DirectSoundCreate = (DIRECTSOUNDCREATEPROC) GetProcAddress(ctx->hDSoundLib, "DirectSoundCreate");
462 }
463 if (!ctx->DirectSoundCreate) {
464 if (ctx->hDSoundLib) FreeLibrary(ctx->hDSoundLib);
465 gf_free(ctx);
466 return NULL;
467 }
468
469
470 driv = (GF_AudioOutput*)gf_malloc(sizeof(GF_AudioOutput));
471 memset(driv, 0, sizeof(GF_AudioOutput));
472 GF_REGISTER_MODULE_INTERFACE(driv, GF_AUDIO_OUTPUT_INTERFACE, "DirectSound Audio Output", "gpac distribution");
473
474 driv->opaque = ctx;
475
476 driv->Setup = DS_Setup;
477 driv->Shutdown = DS_Shutdown;
478 driv->Configure = DS_Configure;
479 driv->SetVolume = DS_SetVolume;
480 driv->SetPan = DS_SetPan;
481 driv->Play = DS_Play;
482 driv->SetPriority = DS_SetPriority;
483 driv->GetAudioDelay = DS_GetAudioDelay;
484 driv->GetTotalBufferTime = DS_GetTotalBufferTime;
485 driv->WriteAudio = DS_WriteAudio;
486 driv->QueryOutputSampleRate = DS_QueryOutputSampleRate;
487 /*never threaded*/
488 driv->SelfThreaded = GF_FALSE;
489 return driv;
490 }
491
DeleteDxAudioOutput(void * ifce)492 void DeleteDxAudioOutput(void *ifce)
493 {
494 GF_AudioOutput *dr = (GF_AudioOutput *)ifce;
495 DSCONTEXT();
496
497 if (ctx->hDSoundLib)
498 FreeLibrary(ctx->hDSoundLib);
499 gf_free(ctx);
500 gf_free(ifce);
501 CoUninitialize();
502 }
503
504