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