1 /*****************************************************************************
2 * aout.c: Windows DirectX audio output method
3 *****************************************************************************
4 * Copyright (C) 2001 VideoLAN
5 * $Id: directx.c,v 1.3 2002/10/11 10:08:06 gbazin Exp $
7 * Authors: Gildas Bazin <gbazin@netcourrier.com>
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
22 *****************************************************************************/
24 /*****************************************************************************
26 *****************************************************************************/
27 #include <errno.h> /* ENOMEM */
28 #include <fcntl.h> /* open(), O_WRONLY */
29 #include <string.h> /* strerror() */
31 #include <stdlib.h> /* calloc(), malloc(), free() */
35 #include "aout_internal.h"
41 #define FRAME_SIZE 2048 /* The size is in samples, not in bytes */
43 /*****************************************************************************
45 * Defining them here allows us to get rid of the dxguid library during
47 *****************************************************************************/
49 DEFINE_GUID(IID_IDirectSoundNotify, 0xb0210783, 0x89cd, 0x11d0, 0xaf, 0x8, 0x0, 0xa0, 0xc9, 0x25, 0xcd, 0x16);
51 /*****************************************************************************
52 * notification_thread_t: DirectX event thread
53 *****************************************************************************/
54 typedef struct notification_thread_t
58 aout_instance_t * p_aout;
59 DSBPOSITIONNOTIFY p_events[2]; /* play notification events */
60 int i_buffer_size; /* Size in bytes of one frame */
62 } notification_thread_t;
64 /*****************************************************************************
65 * aout_sys_t: directx audio output method descriptor
66 *****************************************************************************
67 * This structure is part of the audio output thread descriptor.
68 * It describes the direct sound specific properties of an audio device.
69 *****************************************************************************/
72 LPDIRECTSOUND p_dsobject; /* main Direct Sound object */
74 LPDIRECTSOUNDBUFFER p_dsbuffer_primary; /* the actual sound card buffer
75 (not used directly) */
77 LPDIRECTSOUNDBUFFER p_dsbuffer; /* the sound buffer we use (direct sound
78 * takes care of mixing all the
79 * secondary buffers into the primary) */
81 LPDIRECTSOUNDNOTIFY p_dsnotify; /* the position notify interface */
83 HINSTANCE hdsound_dll; /* handle of the opened dsound dll */
85 vlc_mutex_t buffer_lock; /* audio buffer lock */
87 notification_thread_t * p_notif; /* DirectSoundThread id */
91 /*****************************************************************************
93 *****************************************************************************/
94 static int OpenAudio ( vlc_object_t * );
95 static void CloseAudio ( vlc_object_t * );
97 static void Play ( aout_instance_t * );
100 static int DirectxCreateSecondaryBuffer ( aout_instance_t * );
101 static void DirectxDestroySecondaryBuffer( aout_instance_t * );
102 static int DirectxInitDSound ( aout_instance_t * );
103 static void DirectSoundThread ( notification_thread_t * );
105 /*****************************************************************************
107 *****************************************************************************/
109 set_description( _("DirectX audio module") );
110 set_capability( "audio output", 100 );
111 add_shortcut( "directx" );
112 set_callbacks( OpenAudio, CloseAudio );
115 /*****************************************************************************
116 * OpenAudio: open the audio device
117 *****************************************************************************
118 * This function opens and setups Direct Sound.
119 *****************************************************************************/
120 static int OpenAudio( vlc_object_t *p_this )
122 aout_instance_t * p_aout = (aout_instance_t *)p_this;
124 DSBUFFERDESC dsbuffer_desc;
126 msg_Dbg( p_aout, "Open" );
128 /* Allocate structure */
129 p_aout->output.p_sys = malloc( sizeof( aout_sys_t ) );
130 if( p_aout->output.p_sys == NULL )
132 msg_Err( p_aout, "out of memory" );
136 /* Initialize some variables */
137 p_aout->output.p_sys->p_dsobject = NULL;
138 p_aout->output.p_sys->p_dsbuffer_primary = NULL;
139 p_aout->output.p_sys->p_dsbuffer = NULL;
140 p_aout->output.p_sys->p_dsnotify = NULL;
141 p_aout->output.p_sys->p_notif = NULL;
142 vlc_mutex_init( p_aout, &p_aout->output.p_sys->buffer_lock );
144 p_aout->output.pf_play = Play;
145 aout_VolumeSoftInit( p_aout );
147 /* Initialise DirectSound */
148 if( DirectxInitDSound( p_aout ) )
150 msg_Err( p_aout, "cannot initialize DirectSound" );
154 /* Obtain (not create) Direct Sound primary buffer */
155 memset( &dsbuffer_desc, 0, sizeof(DSBUFFERDESC) );
156 dsbuffer_desc.dwSize = sizeof(DSBUFFERDESC);
157 dsbuffer_desc.dwFlags = DSBCAPS_PRIMARYBUFFER;
158 msg_Warn( p_aout, "create direct sound primary buffer" );
159 dsresult = IDirectSound_CreateSoundBuffer(p_aout->output.p_sys->p_dsobject,
161 &p_aout->output.p_sys->p_dsbuffer_primary,
163 if( dsresult != DS_OK )
165 msg_Err( p_aout, "cannot create direct sound primary buffer" );
169 /* Now we need to setup DirectSound play notification */
170 p_aout->output.p_sys->p_notif =
171 vlc_object_create( p_aout, sizeof(notification_thread_t) );
172 p_aout->output.p_sys->p_notif->p_aout = p_aout;
174 /* first we need to create the notification events */
175 p_aout->output.p_sys->p_notif->p_events[0].hEventNotify =
176 CreateEvent( NULL, FALSE, FALSE, NULL );
177 p_aout->output.p_sys->p_notif->p_events[1].hEventNotify =
178 CreateEvent( NULL, FALSE, FALSE, NULL );
180 vlc_mutex_lock( &p_aout->output.p_sys->buffer_lock );
182 /* then create a new secondary buffer */
183 if( DirectxCreateSecondaryBuffer( p_aout ) )
185 msg_Err( p_aout, "cannot create buffer" );
186 vlc_mutex_unlock( &p_aout->output.p_sys->buffer_lock );
190 vlc_mutex_unlock( &p_aout->output.p_sys->buffer_lock );
192 /* start playing the buffer */
193 dsresult = IDirectSoundBuffer_Play( p_aout->output.p_sys->p_dsbuffer,
196 DSBPLAY_LOOPING ); /* Flags */
197 if( dsresult == DSERR_BUFFERLOST )
199 IDirectSoundBuffer_Restore( p_aout->output.p_sys->p_dsbuffer );
200 dsresult = IDirectSoundBuffer_Play( p_aout->output.p_sys->p_dsbuffer,
203 DSBPLAY_LOOPING ); /* Flags */
205 if( dsresult != DS_OK )
207 msg_Warn( p_aout, "cannot play buffer" );
210 /* then launch the notification thread */
211 msg_Dbg( p_aout, "creating DirectSoundThread" );
212 if( vlc_thread_create( p_aout->output.p_sys->p_notif,
213 "DirectSound Notification Thread",
214 DirectSoundThread, VLC_THREAD_PRIORITY_OUTPUT, 1 ) )
216 msg_Err( p_aout, "cannot create DirectSoundThread" );
220 vlc_object_attach( p_aout->output.p_sys->p_notif, p_aout );
225 CloseAudio( VLC_OBJECT(p_aout) );
229 /*****************************************************************************
230 * Play: nothing to do
231 *****************************************************************************/
232 static void Play( aout_instance_t *p_aout )
236 /*****************************************************************************
237 * CloseAudio: close the audio device
238 *****************************************************************************/
239 static void CloseAudio( vlc_object_t *p_this )
241 aout_instance_t * p_aout = (aout_instance_t *)p_this;
243 msg_Dbg( p_aout, "Close" );
245 /* kill the position notification thread, if any */
246 if( p_aout->output.p_sys->p_notif )
248 vlc_object_detach( p_aout->output.p_sys->p_notif );
249 if( p_aout->output.p_sys->p_notif->b_thread )
251 p_aout->output.p_sys->p_notif->b_die = 1;
252 vlc_thread_join( p_aout->output.p_sys->p_notif );
254 vlc_object_destroy( p_aout->output.p_sys->p_notif );
257 /* release the secondary buffer */
258 DirectxDestroySecondaryBuffer( p_aout );
260 /* then release the primary buffer */
261 if( p_aout->output.p_sys->p_dsbuffer_primary )
262 IDirectSoundBuffer_Release( p_aout->output.p_sys->p_dsbuffer_primary );
264 /* finally release the DirectSound object */
265 if( p_aout->output.p_sys->p_dsobject )
266 IDirectSound_Release( p_aout->output.p_sys->p_dsobject );
268 /* free DSOUND.DLL */
269 if( p_aout->output.p_sys->hdsound_dll )
270 FreeLibrary( p_aout->output.p_sys->hdsound_dll );
272 /* Close the Output. */
273 if ( p_aout->output.p_sys )
275 free( p_aout->output.p_sys );
276 p_aout->output.p_sys = NULL;
280 /*****************************************************************************
281 * DirectxInitDSound: handle all the gory details of DirectSound initialisation
282 *****************************************************************************/
283 static int DirectxInitDSound( aout_instance_t *p_aout )
285 HRESULT (WINAPI *OurDirectSoundCreate)(LPGUID, LPDIRECTSOUND *, LPUNKNOWN);
287 p_aout->output.p_sys->hdsound_dll = LoadLibrary("DSOUND.DLL");
288 if( p_aout->output.p_sys->hdsound_dll == NULL )
290 msg_Warn( p_aout, "cannot open DSOUND.DLL" );
294 OurDirectSoundCreate = (void *)GetProcAddress(
295 p_aout->output.p_sys->hdsound_dll,
296 "DirectSoundCreate" );
297 if( OurDirectSoundCreate == NULL )
299 msg_Warn( p_aout, "GetProcAddress FAILED" );
303 /* Create the direct sound object */
304 if( OurDirectSoundCreate( NULL, &p_aout->output.p_sys->p_dsobject, NULL )
307 msg_Warn( p_aout, "cannot create a direct sound device" );
311 /* Set DirectSound Cooperative level, ie what control we want over Windows
312 * sound device. In our case, DSSCL_EXCLUSIVE means that we can modify the
313 * settings of the primary buffer, but also that only the sound of our
314 * application will be hearable when it will have the focus.
315 * !!! (this is not really working as intended yet because to set the
316 * cooperative level you need the window handle of your application, and
317 * I don't know of any easy way to get it. Especially since we might play
318 * sound without any video, and so what window handle should we use ???
319 * The hack for now is to use the Desktop window handle - it seems to be
321 if( IDirectSound_SetCooperativeLevel( p_aout->output.p_sys->p_dsobject,
325 msg_Warn( p_aout, "cannot set direct sound cooperative level" );
331 p_aout->output.p_sys->p_dsobject = NULL;
332 if( p_aout->output.p_sys->hdsound_dll )
334 FreeLibrary( p_aout->output.p_sys->hdsound_dll );
335 p_aout->output.p_sys->hdsound_dll = NULL;
341 /*****************************************************************************
342 * DirectxCreateSecondaryBuffer
343 *****************************************************************************
344 * This function creates the buffer we'll use to play audio.
345 * In DirectSound there are two kinds of buffers:
346 * - the primary buffer: which is the actual buffer that the soundcard plays
347 * - the secondary buffer(s): these buffers are the one actually used by
348 * applications and DirectSound takes care of mixing them into the primary.
350 * Once you create a secondary buffer, you cannot change its format anymore so
351 * you have to release the current and create another one.
352 *****************************************************************************/
353 static int DirectxCreateSecondaryBuffer( aout_instance_t *p_aout )
355 WAVEFORMATEX waveformat;
356 DSBUFFERDESC dsbdesc;
359 if( p_aout->output.output.i_channels > 2 )
360 p_aout->output.output.i_channels = 2;
362 /* First set the buffer format */
363 memset(&waveformat, 0, sizeof(WAVEFORMATEX));
364 waveformat.wFormatTag = WAVE_FORMAT_PCM;
365 waveformat.nChannels = p_aout->output.output.i_channels;
366 waveformat.nSamplesPerSec = p_aout->output.output.i_rate;
367 waveformat.wBitsPerSample = 16;
368 waveformat.nBlockAlign = waveformat.wBitsPerSample / 8 *
369 waveformat.nChannels;
370 waveformat.nAvgBytesPerSec = waveformat.nSamplesPerSec *
371 waveformat.nBlockAlign;
373 /* Then fill in the descriptor */
374 memset(&dsbdesc, 0, sizeof(DSBUFFERDESC));
375 dsbdesc.dwSize = sizeof(DSBUFFERDESC);
376 dsbdesc.dwFlags = DSBCAPS_GETCURRENTPOSITION2/* Better position accuracy */
377 | DSBCAPS_CTRLPOSITIONNOTIFY /* We need notification */
378 | DSBCAPS_GLOBALFOCUS; /* Allows background playing */
379 dsbdesc.dwBufferBytes = FRAME_SIZE * 2 /* frames*/ * /* buffer size */
380 sizeof(s16) * p_aout->output.output.i_channels;
381 dsbdesc.lpwfxFormat = &waveformat;
383 if( IDirectSound_CreateSoundBuffer( p_aout->output.p_sys->p_dsobject,
385 &p_aout->output.p_sys->p_dsbuffer,
388 msg_Warn( p_aout, "cannot create direct sound secondary buffer" );
392 /* backup the size of a frame */
393 p_aout->output.p_sys->p_notif->i_buffer_size = FRAME_SIZE * sizeof(s16)
394 * p_aout->output.output.i_channels;
396 memset(&dsbcaps, 0, sizeof(DSBCAPS));
397 dsbcaps.dwSize = sizeof(DSBCAPS);
398 IDirectSoundBuffer_GetCaps( p_aout->output.p_sys->p_dsbuffer, &dsbcaps );
399 msg_Dbg( p_aout, "requested %li bytes buffer and got %li bytes.",
400 2 * p_aout->output.p_sys->p_notif->i_buffer_size,
401 dsbcaps.dwBufferBytes );
403 /* Now the secondary buffer is created, we need to setup its position
405 p_aout->output.p_sys->p_notif->p_events[0].dwOffset = 0;
406 p_aout->output.p_sys->p_notif->p_events[1].dwOffset =
407 p_aout->output.p_sys->p_notif->i_buffer_size;
409 /* Get the IDirectSoundNotify interface */
410 if FAILED( IDirectSoundBuffer_QueryInterface(
411 p_aout->output.p_sys->p_dsbuffer,
412 &IID_IDirectSoundNotify,
413 (LPVOID *)&p_aout->output.p_sys->p_dsnotify ) )
415 msg_Err( p_aout, "cannot get Notify interface" );
419 if FAILED( IDirectSoundNotify_SetNotificationPositions(
420 p_aout->output.p_sys->p_dsnotify, 2,
421 p_aout->output.p_sys->p_notif->p_events ) )
423 msg_Err( p_aout, "cannot set position Notification" );
426 p_aout->output.output.i_format = AOUT_FMT_S16_NE;
427 p_aout->output.i_nb_samples = FRAME_SIZE;
432 if( p_aout->output.p_sys->p_dsbuffer )
434 IDirectSoundBuffer_Release( p_aout->output.p_sys->p_dsbuffer );
435 p_aout->output.p_sys->p_dsbuffer = NULL;
437 if( p_aout->output.p_sys->p_dsnotify )
439 IDirectSoundBuffer_Release( p_aout->output.p_sys->p_dsbuffer );
440 p_aout->output.p_sys->p_dsnotify = NULL;
445 /*****************************************************************************
446 * DirectxCreateSecondaryBuffer
447 *****************************************************************************
448 * This function destroys the secondary buffer.
449 *****************************************************************************/
450 static void DirectxDestroySecondaryBuffer( aout_instance_t *p_aout )
452 /* make sure the buffer isn't playing */
453 if( p_aout->output.p_sys->p_dsbuffer )
454 IDirectSoundBuffer_Stop( p_aout->output.p_sys->p_dsbuffer );
456 if( p_aout->output.p_sys->p_dsnotify )
458 IDirectSoundNotify_Release( p_aout->output.p_sys->p_dsnotify );
459 p_aout->output.p_sys->p_dsnotify = NULL;
462 if( p_aout->output.p_sys->p_dsbuffer )
464 IDirectSoundBuffer_Release( p_aout->output.p_sys->p_dsbuffer );
465 p_aout->output.p_sys->p_dsbuffer = NULL;
469 /*****************************************************************************
470 * DirectSoundThread: this thread will capture play notification events.
471 *****************************************************************************
472 * We use this thread to emulate a callback mechanism. The thread probes for
473 * event notification and fills up the DS secondary buffer when needed.
474 *****************************************************************************/
475 static void DirectSoundThread( notification_thread_t *p_notif )
477 HANDLE notification_events[2];
479 aout_instance_t *p_aout = p_notif->p_aout;
481 notification_events[0] = p_notif->p_events[0].hEventNotify;
482 notification_events[1] = p_notif->p_events[1].hEventNotify;
484 /* Tell the main thread that we are ready */
485 vlc_thread_ready( p_notif );
487 msg_Dbg( p_notif, "DirectSoundThread ready" );
489 while( !p_notif->b_die )
492 void *p_write_position, *p_wrap_around;
493 long l_bytes1, l_bytes2;
494 aout_buffer_t * p_buffer;
496 /* wait for the position notification */
497 i_which_event = WaitForMultipleObjects( 2, notification_events, 0,
498 INFINITE ) - WAIT_OBJECT_0;
500 vlc_mutex_lock( &p_aout->output.p_sys->buffer_lock );
504 vlc_mutex_unlock( &p_aout->output.p_sys->buffer_lock );
508 /* Before copying anything, we have to lock the buffer */
509 dsresult = IDirectSoundBuffer_Lock(
511 p_aout->output.p_sys->p_dsbuffer,
512 /* Offset of lock start */
513 i_which_event ? 0 : p_notif->i_buffer_size,
514 p_notif->i_buffer_size, /* Number of bytes */
515 &p_write_position, /* Address of lock start */
516 &l_bytes1, /* Count of bytes locked before wrap around */
517 &p_wrap_around, /* Buffer adress (if wrap around) */
518 &l_bytes2, /* Count of bytes after wrap around */
520 if( dsresult == DSERR_BUFFERLOST )
522 IDirectSoundBuffer_Restore( p_aout->output.p_sys->p_dsbuffer );
523 dsresult = IDirectSoundBuffer_Lock(
524 p_aout->output.p_sys->p_dsbuffer,
525 i_which_event ? 0 : p_notif->i_buffer_size,
526 p_notif->i_buffer_size,
533 if( dsresult != DS_OK )
535 msg_Warn( p_notif, "cannot lock buffer" );
536 vlc_mutex_unlock( &p_aout->output.p_sys->buffer_lock );
540 /* We also take into account the latency instead of just mdate() */
541 p_buffer = aout_OutputNextBuffer( p_aout,
542 mdate() + 1000000 / p_aout->output.output.i_rate * FRAME_SIZE,
545 /* Now do the actual memcpy into the circular buffer */
546 if ( l_bytes1 != p_notif->i_buffer_size )
547 msg_Err( p_aout, "Wrong buffer size: %d, %d", l_bytes1,
548 p_notif->i_buffer_size );
550 if ( p_buffer != NULL )
552 p_aout->p_vlc->pf_memcpy( p_write_position, p_buffer->p_buffer,
554 aout_BufferFree( p_buffer );
558 memset( p_write_position, 0, l_bytes1 );
561 /* Now the data has been copied, unlock the buffer */
562 IDirectSoundBuffer_Unlock( p_aout->output.p_sys->p_dsbuffer,
563 p_write_position, l_bytes1, p_wrap_around, l_bytes2 );
565 vlc_mutex_unlock( &p_aout->output.p_sys->buffer_lock );
569 /* free the events */
570 CloseHandle( notification_events[0] );
571 CloseHandle( notification_events[1] );
573 msg_Dbg( p_notif, "DirectSoundThread exiting" );