1 /*****************************************************************************
2 * aout.c: Windows DirectX audio output method
3 *****************************************************************************
4 * Copyright (C) 2001 VideoLAN
5 * $Id: directx.c,v 1.2 2002/10/06 19:28:28 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 /* first release the current secondary buffer */
183 DirectxDestroySecondaryBuffer( p_aout );
185 /* calculate the frame size in bytes */
186 p_aout->output.p_sys->p_notif->i_buffer_size = FRAME_SIZE * sizeof(s16)
187 * p_aout->output.output.i_channels;
189 /* then create a new secondary buffer */
190 if( DirectxCreateSecondaryBuffer( p_aout ) )
192 msg_Err( p_aout, "cannot create buffer" );
193 vlc_mutex_unlock( &p_aout->output.p_sys->buffer_lock );
197 vlc_mutex_unlock( &p_aout->output.p_sys->buffer_lock );
199 /* start playing the buffer */
200 dsresult = IDirectSoundBuffer_Play( p_aout->output.p_sys->p_dsbuffer,
203 DSBPLAY_LOOPING ); /* Flags */
204 if( dsresult == DSERR_BUFFERLOST )
206 IDirectSoundBuffer_Restore( p_aout->output.p_sys->p_dsbuffer );
207 dsresult = IDirectSoundBuffer_Play( p_aout->output.p_sys->p_dsbuffer,
210 DSBPLAY_LOOPING ); /* Flags */
212 if( dsresult != DS_OK )
214 msg_Warn( p_aout, "cannot play buffer" );
217 /* then launch the notification thread */
218 msg_Dbg( p_aout, "creating DirectSoundThread" );
219 if( vlc_thread_create( p_aout->output.p_sys->p_notif,
220 "DirectSound Notification Thread",
221 DirectSoundThread, VLC_THREAD_PRIORITY_OUTPUT, 1 ) )
223 msg_Err( p_aout, "cannot create DirectSoundThread" );
227 vlc_object_attach( p_aout->output.p_sys->p_notif, p_aout );
232 CloseAudio( VLC_OBJECT(p_aout) );
236 /*****************************************************************************
237 * Play: nothing to do
238 *****************************************************************************/
239 static void Play( aout_instance_t *p_aout )
243 /*****************************************************************************
244 * CloseAudio: close the audio device
245 *****************************************************************************/
246 static void CloseAudio( vlc_object_t *p_this )
248 aout_instance_t * p_aout = (aout_instance_t *)p_this;
250 msg_Dbg( p_aout, "Close" );
252 /* kill the position notification thread, if any */
253 if( p_aout->output.p_sys->p_notif )
255 vlc_object_detach( p_aout->output.p_sys->p_notif );
256 if( p_aout->output.p_sys->p_notif->b_thread )
258 p_aout->output.p_sys->p_notif->b_die = 1;
259 vlc_thread_join( p_aout->output.p_sys->p_notif );
261 vlc_object_destroy( p_aout->output.p_sys->p_notif );
264 /* release the secondary buffer */
265 DirectxDestroySecondaryBuffer( p_aout );
267 /* then release the primary buffer */
268 if( p_aout->output.p_sys->p_dsbuffer_primary )
269 IDirectSoundBuffer_Release( p_aout->output.p_sys->p_dsbuffer_primary );
271 /* finally release the DirectSound object */
272 if( p_aout->output.p_sys->p_dsobject )
273 IDirectSound_Release( p_aout->output.p_sys->p_dsobject );
275 /* free DSOUND.DLL */
276 if( p_aout->output.p_sys->hdsound_dll )
277 FreeLibrary( p_aout->output.p_sys->hdsound_dll );
279 /* Close the Output. */
280 if ( p_aout->output.p_sys )
282 free( p_aout->output.p_sys );
283 p_aout->output.p_sys = NULL;
287 /*****************************************************************************
288 * DirectxInitDSound: handle all the gory details of DirectSound initialisation
289 *****************************************************************************/
290 static int DirectxInitDSound( aout_instance_t *p_aout )
292 HRESULT (WINAPI *OurDirectSoundCreate)(LPGUID, LPDIRECTSOUND *, LPUNKNOWN);
294 p_aout->output.p_sys->hdsound_dll = LoadLibrary("DSOUND.DLL");
295 if( p_aout->output.p_sys->hdsound_dll == NULL )
297 msg_Warn( p_aout, "cannot open DSOUND.DLL" );
301 OurDirectSoundCreate = (void *)GetProcAddress(
302 p_aout->output.p_sys->hdsound_dll,
303 "DirectSoundCreate" );
304 if( OurDirectSoundCreate == NULL )
306 msg_Warn( p_aout, "GetProcAddress FAILED" );
310 /* Create the direct sound object */
311 if( OurDirectSoundCreate( NULL, &p_aout->output.p_sys->p_dsobject, NULL )
314 msg_Warn( p_aout, "cannot create a direct sound device" );
318 /* Set DirectSound Cooperative level, ie what control we want over Windows
319 * sound device. In our case, DSSCL_EXCLUSIVE means that we can modify the
320 * settings of the primary buffer, but also that only the sound of our
321 * application will be hearable when it will have the focus.
322 * !!! (this is not really working as intended yet because to set the
323 * cooperative level you need the window handle of your application, and
324 * I don't know of any easy way to get it. Especially since we might play
325 * sound without any video, and so what window handle should we use ???
326 * The hack for now is to use the Desktop window handle - it seems to be
328 if( IDirectSound_SetCooperativeLevel( p_aout->output.p_sys->p_dsobject,
332 msg_Warn( p_aout, "cannot set direct sound cooperative level" );
338 p_aout->output.p_sys->p_dsobject = NULL;
339 if( p_aout->output.p_sys->hdsound_dll )
341 FreeLibrary( p_aout->output.p_sys->hdsound_dll );
342 p_aout->output.p_sys->hdsound_dll = NULL;
348 /*****************************************************************************
349 * DirectxCreateSecondaryBuffer
350 *****************************************************************************
351 * This function creates the buffer we'll use to play audio.
352 * In DirectSound there are two kinds of buffers:
353 * - the primary buffer: which is the actual buffer that the soundcard plays
354 * - the secondary buffer(s): these buffers are the one actually used by
355 * applications and DirectSound takes care of mixing them into the primary.
357 * Once you create a secondary buffer, you cannot change its format anymore so
358 * you have to release the current and create another one.
359 *****************************************************************************/
360 static int DirectxCreateSecondaryBuffer( aout_instance_t *p_aout )
362 WAVEFORMATEX waveformat;
363 DSBUFFERDESC dsbdesc;
366 /* First set the buffer format */
367 memset(&waveformat, 0, sizeof(WAVEFORMATEX));
368 waveformat.wFormatTag = WAVE_FORMAT_PCM;
369 waveformat.nChannels = p_aout->output.output.i_channels;
370 waveformat.nSamplesPerSec = p_aout->output.output.i_rate;
371 waveformat.wBitsPerSample = 16;
372 waveformat.nBlockAlign = waveformat.wBitsPerSample / 8 *
373 waveformat.nChannels;
374 waveformat.nAvgBytesPerSec = waveformat.nSamplesPerSec *
375 waveformat.nBlockAlign;
377 /* Then fill in the descriptor */
378 memset(&dsbdesc, 0, sizeof(DSBUFFERDESC));
379 dsbdesc.dwSize = sizeof(DSBUFFERDESC);
380 dsbdesc.dwFlags = DSBCAPS_GETCURRENTPOSITION2/* Better position accuracy */
381 | DSBCAPS_CTRLPOSITIONNOTIFY /* We need notification */
382 | DSBCAPS_GLOBALFOCUS; /* Allows background playing */
383 dsbdesc.dwBufferBytes = FRAME_SIZE * 2 /* frames*/ * /* buffer size */
384 sizeof(s16) * p_aout->output.output.i_channels;
385 dsbdesc.lpwfxFormat = &waveformat;
387 if( IDirectSound_CreateSoundBuffer( p_aout->output.p_sys->p_dsobject,
389 &p_aout->output.p_sys->p_dsbuffer,
392 msg_Warn( p_aout, "cannot create direct sound secondary buffer" );
396 /* backup the size of the secondary sound buffer */
397 memset(&dsbcaps, 0, sizeof(DSBCAPS));
398 dsbcaps.dwSize = sizeof(DSBCAPS);
399 IDirectSoundBuffer_GetCaps( p_aout->output.p_sys->p_dsbuffer, &dsbcaps );
401 msg_Dbg( p_aout, "DirectxCreateSecondaryBuffer: %li",
402 dsbcaps.dwBufferBytes );
404 /* Now the secondary buffer is created, we need to setup its position
406 p_aout->output.p_sys->p_notif->p_events[0].dwOffset = 0;
407 p_aout->output.p_sys->p_notif->p_events[1].dwOffset =
408 p_aout->output.p_sys->p_notif->i_buffer_size;
410 /* Get the IDirectSoundNotify interface */
411 if FAILED( IDirectSoundBuffer_QueryInterface(
412 p_aout->output.p_sys->p_dsbuffer,
413 &IID_IDirectSoundNotify,
414 (LPVOID *)&p_aout->output.p_sys->p_dsnotify ) )
416 msg_Warn( p_aout, "cannot get Notify interface" );
420 if FAILED( IDirectSoundNotify_SetNotificationPositions(
421 p_aout->output.p_sys->p_dsnotify, 2,
422 p_aout->output.p_sys->p_notif->p_events ) )
424 msg_Warn( p_aout, "cannot set position Notification" );
428 p_aout->output.output.i_format = AOUT_FMT_S16_NE;
429 p_aout->output.i_nb_samples = FRAME_SIZE;
434 if( p_aout->output.p_sys->p_dsbuffer )
436 IDirectSoundBuffer_Release( p_aout->output.p_sys->p_dsbuffer );
437 p_aout->output.p_sys->p_dsbuffer = NULL;
439 if( p_aout->output.p_sys->p_dsnotify )
441 IDirectSoundBuffer_Release( p_aout->output.p_sys->p_dsbuffer );
442 p_aout->output.p_sys->p_dsnotify = NULL;
447 /*****************************************************************************
448 * DirectxCreateSecondaryBuffer
449 *****************************************************************************
450 * This function destroys the secondary buffer.
451 *****************************************************************************/
452 static void DirectxDestroySecondaryBuffer( aout_instance_t *p_aout )
454 /* make sure the buffer isn't playing */
455 if( p_aout->output.p_sys->p_dsbuffer )
456 IDirectSoundBuffer_Stop( p_aout->output.p_sys->p_dsbuffer );
458 if( p_aout->output.p_sys->p_dsnotify )
460 IDirectSoundNotify_Release( p_aout->output.p_sys->p_dsnotify );
461 p_aout->output.p_sys->p_dsnotify = NULL;
464 if( p_aout->output.p_sys->p_dsbuffer )
466 IDirectSoundBuffer_Release( p_aout->output.p_sys->p_dsbuffer );
467 p_aout->output.p_sys->p_dsbuffer = NULL;
471 /*****************************************************************************
472 * DirectSoundThread: this thread will capture play notification events.
473 *****************************************************************************
474 * We use this thread to emulate a callback mechanism. The thread probes for
475 * event notification and fills up the DS secondary buffer when needed.
476 *****************************************************************************/
477 static void DirectSoundThread( notification_thread_t *p_notif )
479 HANDLE notification_events[2];
481 aout_instance_t *p_aout = p_notif->p_aout;
483 notification_events[0] = p_notif->p_events[0].hEventNotify;
484 notification_events[1] = p_notif->p_events[1].hEventNotify;
486 /* Tell the main thread that we are ready */
487 vlc_thread_ready( p_notif );
489 /* this thread must be high-priority */
490 if( !SetThreadPriority( GetCurrentThread(),
491 THREAD_PRIORITY_ABOVE_NORMAL ) )
493 msg_Warn( p_notif, "DirectSoundThread could not raise its priority" );
496 msg_Dbg( p_notif, "DirectSoundThread ready" );
498 while( !p_notif->b_die )
501 void *p_write_position, *p_wrap_around;
502 long l_bytes1, l_bytes2;
503 aout_buffer_t * p_buffer;
505 /* wait for the position notification */
506 i_which_event = WaitForMultipleObjects( 2, notification_events, 0,
507 INFINITE ) - WAIT_OBJECT_0;
509 vlc_mutex_lock( &p_aout->output.p_sys->buffer_lock );
513 vlc_mutex_unlock( &p_aout->output.p_sys->buffer_lock );
517 /* Before copying anything, we have to lock the buffer */
518 dsresult = IDirectSoundBuffer_Lock(
520 p_aout->output.p_sys->p_dsbuffer,
521 /* Offset of lock start */
522 i_which_event ? 0 : p_notif->i_buffer_size,
523 p_notif->i_buffer_size, /* Number of bytes */
524 &p_write_position, /* Address of lock start */
525 &l_bytes1, /* Count of bytes locked before wrap around */
526 &p_wrap_around, /* Buffer adress (if wrap around) */
527 &l_bytes2, /* Count of bytes after wrap around */
529 if( dsresult == DSERR_BUFFERLOST )
531 IDirectSoundBuffer_Restore( p_aout->output.p_sys->p_dsbuffer );
532 dsresult = IDirectSoundBuffer_Lock(
533 p_aout->output.p_sys->p_dsbuffer,
534 i_which_event ? 0 : p_notif->i_buffer_size,
535 p_notif->i_buffer_size,
542 if( dsresult != DS_OK )
544 msg_Warn( p_notif, "cannot lock buffer" );
545 vlc_mutex_unlock( &p_aout->output.p_sys->buffer_lock );
549 /* FIXME : take into account DirectSound latency instead of mdate() */
550 p_buffer = aout_OutputNextBuffer( p_aout, mdate(), VLC_FALSE );
552 /* Now do the actual memcpy into the circular buffer */
553 if ( l_bytes1 != p_notif->i_buffer_size )
554 msg_Err( p_aout, "Wrong buffer size: %d, %d", l_bytes1,
555 p_notif->i_buffer_size );
557 if ( p_buffer != NULL )
559 p_aout->p_vlc->pf_memcpy( p_write_position, p_buffer->p_buffer,
561 aout_BufferFree( p_buffer );
565 memset( p_write_position, 0, l_bytes1 );
568 /* Now the data has been copied, unlock the buffer */
569 IDirectSoundBuffer_Unlock( p_aout->output.p_sys->p_dsbuffer,
570 p_write_position, l_bytes1, p_wrap_around, l_bytes2 );
572 vlc_mutex_unlock( &p_aout->output.p_sys->buffer_lock );
576 /* free the events */
577 CloseHandle( notification_events[0] );
578 CloseHandle( notification_events[1] );
580 msg_Dbg( p_notif, "DirectSoundThread exiting" );