1 /*****************************************************************************
2 * aout.c: Windows DirectX audio output method
3 *****************************************************************************
4 * Copyright (C) 2001 VideoLAN
5 * $Id: directx.c,v 1.1 2002/10/05 17:29:50 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"
40 #define FRAME_SIZE 2048 /* The size is in samples, not in bytes */
42 /*****************************************************************************
44 * Defining them here allows us to get rid of the dxguid library during
46 *****************************************************************************/
48 DEFINE_GUID(IID_IDirectSoundNotify, 0xb0210783, 0x89cd, 0x11d0, 0xaf, 0x8, 0x0, 0xa0, 0xc9, 0x25, 0xcd, 0x16);
50 /*****************************************************************************
51 * notification_thread_t: DirectX event thread
52 *****************************************************************************/
53 typedef struct notification_thread_t
57 aout_instance_t * p_aout;
58 DSBPOSITIONNOTIFY p_events[2]; /* play notification events */
59 int i_buffer_size; /* Size in bytes of one frame */
61 } notification_thread_t;
63 /*****************************************************************************
64 * aout_sys_t: directx audio output method descriptor
65 *****************************************************************************
66 * This structure is part of the audio output thread descriptor.
67 * It describes the direct sound specific properties of an audio device.
68 *****************************************************************************/
71 LPDIRECTSOUND p_dsobject; /* main Direct Sound object */
73 LPDIRECTSOUNDBUFFER p_dsbuffer_primary; /* the actual sound card buffer
74 (not used directly) */
76 LPDIRECTSOUNDBUFFER p_dsbuffer; /* the sound buffer we use (direct sound
77 * takes care of mixing all the
78 * secondary buffers into the primary) */
80 LPDIRECTSOUNDNOTIFY p_dsnotify; /* the position notify interface */
82 HINSTANCE hdsound_dll; /* handle of the opened dsound dll */
84 vlc_mutex_t buffer_lock; /* audio buffer lock */
86 notification_thread_t * p_notif; /* DirectSoundThread id */
90 /*****************************************************************************
92 *****************************************************************************/
93 static int OpenAudio ( vlc_object_t * );
94 static void CloseAudio ( vlc_object_t * );
96 static void Play ( aout_instance_t * );
99 static int DirectxCreateSecondaryBuffer ( aout_instance_t * );
100 static void DirectxDestroySecondaryBuffer( aout_instance_t * );
101 static int DirectxInitDSound ( aout_instance_t * );
102 static void DirectSoundThread ( notification_thread_t * );
104 /*****************************************************************************
106 *****************************************************************************/
108 set_description( _("DirectX audio module") );
109 set_capability( "audio output", 100 );
110 add_shortcut( "directx" );
111 set_callbacks( OpenAudio, CloseAudio );
114 /*****************************************************************************
115 * OpenAudio: open the audio device
116 *****************************************************************************
117 * This function opens and setups Direct Sound.
118 *****************************************************************************/
119 static int OpenAudio( vlc_object_t *p_this )
121 aout_instance_t * p_aout = (aout_instance_t *)p_this;
123 DSBUFFERDESC dsbuffer_desc;
125 msg_Dbg( p_aout, "Open" );
127 /* Allocate structure */
128 p_aout->output.p_sys = malloc( sizeof( aout_sys_t ) );
129 if( p_aout->output.p_sys == NULL )
131 msg_Err( p_aout, "out of memory" );
135 /* Initialize some variables */
136 p_aout->output.p_sys->p_dsobject = NULL;
137 p_aout->output.p_sys->p_dsbuffer_primary = NULL;
138 p_aout->output.p_sys->p_dsbuffer = NULL;
139 p_aout->output.p_sys->p_dsnotify = NULL;
140 p_aout->output.p_sys->p_notif = NULL;
141 vlc_mutex_init( p_aout, &p_aout->output.p_sys->buffer_lock );
143 p_aout->output.pf_play = Play;
144 aout_VolumeSoftInit( p_aout );
146 /* Initialise DirectSound */
147 if( DirectxInitDSound( p_aout ) )
149 msg_Err( p_aout, "cannot initialize DirectSound" );
153 /* Obtain (not create) Direct Sound primary buffer */
154 memset( &dsbuffer_desc, 0, sizeof(DSBUFFERDESC) );
155 dsbuffer_desc.dwSize = sizeof(DSBUFFERDESC);
156 dsbuffer_desc.dwFlags = DSBCAPS_PRIMARYBUFFER;
157 msg_Warn( p_aout, "create direct sound primary buffer" );
158 dsresult = IDirectSound_CreateSoundBuffer(p_aout->output.p_sys->p_dsobject,
160 &p_aout->output.p_sys->p_dsbuffer_primary,
162 if( dsresult != DS_OK )
164 msg_Err( p_aout, "cannot create direct sound primary buffer" );
168 /* Now we need to setup DirectSound play notification */
169 p_aout->output.p_sys->p_notif =
170 vlc_object_create( p_aout, sizeof(notification_thread_t) );
171 p_aout->output.p_sys->p_notif->p_aout = p_aout;
173 /* first we need to create the notification events */
174 p_aout->output.p_sys->p_notif->p_events[0].hEventNotify =
175 CreateEvent( NULL, FALSE, FALSE, NULL );
176 p_aout->output.p_sys->p_notif->p_events[1].hEventNotify =
177 CreateEvent( NULL, FALSE, FALSE, NULL );
179 vlc_mutex_lock( &p_aout->output.p_sys->buffer_lock );
181 /* first release the current secondary buffer */
182 DirectxDestroySecondaryBuffer( p_aout );
184 /* calculate the frame size in bytes */
185 p_aout->output.p_sys->p_notif->i_buffer_size = FRAME_SIZE * sizeof(s16)
186 * p_aout->output.output.i_channels;
188 /* then create a new secondary buffer */
189 if( DirectxCreateSecondaryBuffer( p_aout ) )
191 msg_Err( p_aout, "cannot create buffer" );
192 vlc_mutex_unlock( &p_aout->output.p_sys->buffer_lock );
196 vlc_mutex_unlock( &p_aout->output.p_sys->buffer_lock );
198 /* start playing the buffer */
199 dsresult = IDirectSoundBuffer_Play( p_aout->output.p_sys->p_dsbuffer,
202 DSBPLAY_LOOPING ); /* Flags */
203 if( dsresult == DSERR_BUFFERLOST )
205 IDirectSoundBuffer_Restore( p_aout->output.p_sys->p_dsbuffer );
206 dsresult = IDirectSoundBuffer_Play( p_aout->output.p_sys->p_dsbuffer,
209 DSBPLAY_LOOPING ); /* Flags */
211 if( dsresult != DS_OK )
213 msg_Warn( p_aout, "cannot play buffer" );
216 /* then launch the notification thread */
217 msg_Dbg( p_aout, "creating DirectSoundThread" );
218 if( vlc_thread_create( p_aout->output.p_sys->p_notif,
219 "DirectSound Notification Thread",
220 DirectSoundThread, VLC_THREAD_PRIORITY_OUTPUT, 1 ) )
222 msg_Err( p_aout, "cannot create DirectSoundThread" );
226 vlc_object_attach( p_aout->output.p_sys->p_notif, p_aout );
231 CloseAudio( VLC_OBJECT(p_aout) );
235 /*****************************************************************************
236 * Play: nothing to do
237 *****************************************************************************/
238 static void Play( aout_instance_t *p_aout )
242 /*****************************************************************************
243 * CloseAudio: close the audio device
244 *****************************************************************************/
245 static void CloseAudio( vlc_object_t *p_this )
247 aout_instance_t * p_aout = (aout_instance_t *)p_this;
249 msg_Dbg( p_aout, "Close" );
251 /* kill the position notification thread, if any */
252 if( p_aout->output.p_sys->p_notif )
254 vlc_object_detach( p_aout->output.p_sys->p_notif );
255 if( p_aout->output.p_sys->p_notif->b_thread )
257 p_aout->output.p_sys->p_notif->b_die = 1;
258 vlc_thread_join( p_aout->output.p_sys->p_notif );
260 vlc_object_destroy( p_aout->output.p_sys->p_notif );
263 /* release the secondary buffer */
264 DirectxDestroySecondaryBuffer( p_aout );
266 /* then release the primary buffer */
267 if( p_aout->output.p_sys->p_dsbuffer_primary )
268 IDirectSoundBuffer_Release( p_aout->output.p_sys->p_dsbuffer_primary );
270 /* finally release the DirectSound object */
271 if( p_aout->output.p_sys->p_dsobject )
272 IDirectSound_Release( p_aout->output.p_sys->p_dsobject );
274 /* free DSOUND.DLL */
275 if( p_aout->output.p_sys->hdsound_dll )
276 FreeLibrary( p_aout->output.p_sys->hdsound_dll );
278 /* Close the Output. */
279 if ( p_aout->output.p_sys )
281 free( p_aout->output.p_sys );
282 p_aout->output.p_sys = NULL;
286 /*****************************************************************************
287 * DirectxInitDSound: handle all the gory details of DirectSound initialisation
288 *****************************************************************************/
289 static int DirectxInitDSound( aout_instance_t *p_aout )
291 HRESULT (WINAPI *OurDirectSoundCreate)(LPGUID, LPDIRECTSOUND *, LPUNKNOWN);
293 p_aout->output.p_sys->hdsound_dll = LoadLibrary("DSOUND.DLL");
294 if( p_aout->output.p_sys->hdsound_dll == NULL )
296 msg_Warn( p_aout, "cannot open DSOUND.DLL" );
300 OurDirectSoundCreate = (void *)GetProcAddress(
301 p_aout->output.p_sys->hdsound_dll,
302 "DirectSoundCreate" );
303 if( OurDirectSoundCreate == NULL )
305 msg_Warn( p_aout, "GetProcAddress FAILED" );
309 /* Create the direct sound object */
310 if( OurDirectSoundCreate( NULL, &p_aout->output.p_sys->p_dsobject, NULL )
313 msg_Warn( p_aout, "cannot create a direct sound device" );
317 /* Set DirectSound Cooperative level, ie what control we want over Windows
318 * sound device. In our case, DSSCL_EXCLUSIVE means that we can modify the
319 * settings of the primary buffer, but also that only the sound of our
320 * application will be hearable when it will have the focus.
321 * !!! (this is not really working as intended yet because to set the
322 * cooperative level you need the window handle of your application, and
323 * I don't know of any easy way to get it. Especially since we might play
324 * sound without any video, and so what window handle should we use ???
325 * The hack for now is to use the Desktop window handle - it seems to be
327 if( IDirectSound_SetCooperativeLevel( p_aout->output.p_sys->p_dsobject,
331 msg_Warn( p_aout, "cannot set direct sound cooperative level" );
337 p_aout->output.p_sys->p_dsobject = NULL;
338 if( p_aout->output.p_sys->hdsound_dll )
340 FreeLibrary( p_aout->output.p_sys->hdsound_dll );
341 p_aout->output.p_sys->hdsound_dll = NULL;
347 /*****************************************************************************
348 * DirectxCreateSecondaryBuffer
349 *****************************************************************************
350 * This function creates the buffer we'll use to play audio.
351 * In DirectSound there are two kinds of buffers:
352 * - the primary buffer: which is the actual buffer that the soundcard plays
353 * - the secondary buffer(s): these buffers are the one actually used by
354 * applications and DirectSound takes care of mixing them into the primary.
356 * Once you create a secondary buffer, you cannot change its format anymore so
357 * you have to release the current and create another one.
358 *****************************************************************************/
359 static int DirectxCreateSecondaryBuffer( aout_instance_t *p_aout )
361 WAVEFORMATEX waveformat;
362 DSBUFFERDESC dsbdesc;
365 /* First set the buffer format */
366 memset(&waveformat, 0, sizeof(WAVEFORMATEX));
367 waveformat.wFormatTag = WAVE_FORMAT_PCM;
368 waveformat.nChannels = p_aout->output.output.i_channels;
369 waveformat.nSamplesPerSec = p_aout->output.output.i_rate;
370 waveformat.wBitsPerSample = 16;
371 waveformat.nBlockAlign = waveformat.wBitsPerSample / 8 *
372 waveformat.nChannels;
373 waveformat.nAvgBytesPerSec = waveformat.nSamplesPerSec *
374 waveformat.nBlockAlign;
376 /* Then fill in the descriptor */
377 memset(&dsbdesc, 0, sizeof(DSBUFFERDESC));
378 dsbdesc.dwSize = sizeof(DSBUFFERDESC);
379 dsbdesc.dwFlags = DSBCAPS_GETCURRENTPOSITION2/* Better position accuracy */
380 | DSBCAPS_CTRLPOSITIONNOTIFY /* We need notification */
381 | DSBCAPS_GLOBALFOCUS; /* Allows background playing */
382 dsbdesc.dwBufferBytes = FRAME_SIZE * 2 /* frames*/ * /* buffer size */
383 sizeof(s16) * p_aout->output.output.i_channels;
384 dsbdesc.lpwfxFormat = &waveformat;
386 if( IDirectSound_CreateSoundBuffer( p_aout->output.p_sys->p_dsobject,
388 &p_aout->output.p_sys->p_dsbuffer,
391 msg_Warn( p_aout, "cannot create direct sound secondary buffer" );
395 /* backup the size of the secondary sound buffer */
396 memset(&dsbcaps, 0, sizeof(DSBCAPS));
397 dsbcaps.dwSize = sizeof(DSBCAPS);
398 IDirectSoundBuffer_GetCaps( p_aout->output.p_sys->p_dsbuffer, &dsbcaps );
400 msg_Dbg( p_aout, "DirectxCreateSecondaryBuffer: %li",
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_Warn( 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_Warn( p_aout, "cannot set position Notification" );
427 p_aout->output.output.i_format = AOUT_FMT_S16_NE;
428 p_aout->output.i_nb_samples = FRAME_SIZE;
433 if( p_aout->output.p_sys->p_dsbuffer )
435 IDirectSoundBuffer_Release( p_aout->output.p_sys->p_dsbuffer );
436 p_aout->output.p_sys->p_dsbuffer = NULL;
438 if( p_aout->output.p_sys->p_dsnotify )
440 IDirectSoundBuffer_Release( p_aout->output.p_sys->p_dsbuffer );
441 p_aout->output.p_sys->p_dsnotify = NULL;
446 /*****************************************************************************
447 * DirectxCreateSecondaryBuffer
448 *****************************************************************************
449 * This function destroys the secondary buffer.
450 *****************************************************************************/
451 static void DirectxDestroySecondaryBuffer( aout_instance_t *p_aout )
453 /* make sure the buffer isn't playing */
454 if( p_aout->output.p_sys->p_dsbuffer )
455 IDirectSoundBuffer_Stop( p_aout->output.p_sys->p_dsbuffer );
457 if( p_aout->output.p_sys->p_dsnotify )
459 IDirectSoundNotify_Release( p_aout->output.p_sys->p_dsnotify );
460 p_aout->output.p_sys->p_dsnotify = NULL;
463 if( p_aout->output.p_sys->p_dsbuffer )
465 IDirectSoundBuffer_Release( p_aout->output.p_sys->p_dsbuffer );
466 p_aout->output.p_sys->p_dsbuffer = NULL;
470 /*****************************************************************************
471 * DirectSoundThread: this thread will capture play notification events.
472 *****************************************************************************
473 * We use this thread to emulate a callback mechanism. The thread probes for
474 * event notification and fills up the DS secondary buffer when needed.
475 *****************************************************************************/
476 static void DirectSoundThread( notification_thread_t *p_notif )
478 HANDLE notification_events[2];
480 aout_instance_t *p_aout = p_notif->p_aout;
482 notification_events[0] = p_notif->p_events[0].hEventNotify;
483 notification_events[1] = p_notif->p_events[1].hEventNotify;
485 /* Tell the main thread that we are ready */
486 vlc_thread_ready( p_notif );
488 /* this thread must be high-priority */
489 if( !SetThreadPriority( GetCurrentThread(),
490 THREAD_PRIORITY_ABOVE_NORMAL ) )
492 msg_Warn( p_notif, "DirectSoundThread could not raise its priority" );
495 msg_Dbg( p_notif, "DirectSoundThread ready" );
497 while( !p_notif->b_die )
500 void *p_write_position, *p_wrap_around;
501 long l_bytes1, l_bytes2;
502 aout_buffer_t * p_buffer;
504 /* wait for the position notification */
505 i_which_event = WaitForMultipleObjects( 2, notification_events, 0,
506 INFINITE ) - WAIT_OBJECT_0;
508 vlc_mutex_lock( &p_aout->output.p_sys->buffer_lock );
512 vlc_mutex_unlock( &p_aout->output.p_sys->buffer_lock );
516 /* Before copying anything, we have to lock the buffer */
517 dsresult = IDirectSoundBuffer_Lock(
519 p_aout->output.p_sys->p_dsbuffer,
520 /* Offset of lock start */
521 i_which_event ? 0 : p_notif->i_buffer_size,
522 p_notif->i_buffer_size, /* Number of bytes */
523 &p_write_position, /* Address of lock start */
524 &l_bytes1, /* Count of bytes locked before wrap around */
525 &p_wrap_around, /* Buffer adress (if wrap around) */
526 &l_bytes2, /* Count of bytes after wrap around */
528 if( dsresult == DSERR_BUFFERLOST )
530 IDirectSoundBuffer_Restore( p_aout->output.p_sys->p_dsbuffer );
531 dsresult = IDirectSoundBuffer_Lock(
532 p_aout->output.p_sys->p_dsbuffer,
533 i_which_event ? 0 : p_notif->i_buffer_size,
534 p_notif->i_buffer_size,
541 if( dsresult != DS_OK )
543 msg_Warn( p_notif, "cannot lock buffer" );
544 vlc_mutex_unlock( &p_aout->output.p_sys->buffer_lock );
548 /* FIXME : take into account DirectSound latency instead of mdate() */
549 p_buffer = aout_OutputNextBuffer( p_aout, mdate(), VLC_FALSE );
551 /* Now do the actual memcpy into the circular buffer */
552 if ( l_bytes1 != p_notif->i_buffer_size )
553 msg_Err( p_aout, "Wrong buffer size: %d, %d", l_bytes1,
554 p_notif->i_buffer_size );
556 if ( p_buffer != NULL )
558 p_aout->p_vlc->pf_memcpy( p_write_position, p_buffer->p_buffer,
560 aout_BufferFree( p_buffer );
564 memset( p_write_position, 0, l_bytes1 );
567 /* Now the data has been copied, unlock the buffer */
568 IDirectSoundBuffer_Unlock( p_aout->output.p_sys->p_dsbuffer,
569 p_write_position, l_bytes1, p_wrap_around, l_bytes2 );
571 vlc_mutex_unlock( &p_aout->output.p_sys->buffer_lock );
575 /* free the events */
576 CloseHandle( notification_events[0] );
577 CloseHandle( notification_events[1] );
579 msg_Dbg( p_notif, "DirectSoundThread exiting" );