1 /*****************************************************************************
2 * aout.c: Windows DirectX audio output method
3 *****************************************************************************
4 * Copyright (C) 2001 VideoLAN
5 * $Id: aout.c,v 1.10 2002/09/18 21:21:24 massiot 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 *****************************************************************************/
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 void Play ( aout_instance_t * );
97 static int DirectxCreateSecondaryBuffer ( aout_instance_t * );
98 static void DirectxDestroySecondaryBuffer( aout_instance_t * );
99 static int DirectxInitDSound ( aout_instance_t * );
100 static void DirectSoundThread ( notification_thread_t * );
102 /*****************************************************************************
103 * OpenAudio: open the audio device
104 *****************************************************************************
105 * This function opens and setups Direct Sound.
106 *****************************************************************************/
107 int E_(OpenAudio) ( vlc_object_t *p_this )
109 aout_instance_t * p_aout = (aout_instance_t *)p_this;
111 DSBUFFERDESC dsbuffer_desc;
113 msg_Dbg( p_aout, "Open" );
115 /* Allocate structure */
116 p_aout->output.p_sys = malloc( sizeof( aout_sys_t ) );
117 if( p_aout->output.p_sys == NULL )
119 msg_Err( p_aout, "out of memory" );
123 /* Initialize some variables */
124 p_aout->output.p_sys->p_dsobject = NULL;
125 p_aout->output.p_sys->p_dsbuffer_primary = NULL;
126 p_aout->output.p_sys->p_dsbuffer = NULL;
127 p_aout->output.p_sys->p_dsnotify = NULL;
128 p_aout->output.p_sys->p_notif = NULL;
129 vlc_mutex_init( p_aout, &p_aout->output.p_sys->buffer_lock );
131 p_aout->output.pf_play = Play;
132 aout_VolumeSoftInit( p_aout );
134 /* Initialise DirectSound */
135 if( DirectxInitDSound( p_aout ) )
137 msg_Err( p_aout, "cannot initialize DirectSound" );
141 /* Obtain (not create) Direct Sound primary buffer */
142 memset( &dsbuffer_desc, 0, sizeof(DSBUFFERDESC) );
143 dsbuffer_desc.dwSize = sizeof(DSBUFFERDESC);
144 dsbuffer_desc.dwFlags = DSBCAPS_PRIMARYBUFFER;
145 msg_Warn( p_aout, "create direct sound primary buffer" );
146 dsresult = IDirectSound_CreateSoundBuffer(p_aout->output.p_sys->p_dsobject,
148 &p_aout->output.p_sys->p_dsbuffer_primary,
150 if( dsresult != DS_OK )
152 msg_Err( p_aout, "cannot create direct sound primary buffer" );
156 /* Now we need to setup DirectSound play notification */
157 p_aout->output.p_sys->p_notif =
158 vlc_object_create( p_aout, sizeof(notification_thread_t) );
159 p_aout->output.p_sys->p_notif->p_aout = p_aout;
161 /* first we need to create the notification events */
162 p_aout->output.p_sys->p_notif->p_events[0].hEventNotify =
163 CreateEvent( NULL, FALSE, FALSE, NULL );
164 p_aout->output.p_sys->p_notif->p_events[1].hEventNotify =
165 CreateEvent( NULL, FALSE, FALSE, NULL );
167 vlc_mutex_lock( &p_aout->output.p_sys->buffer_lock );
169 /* first release the current secondary buffer */
170 DirectxDestroySecondaryBuffer( p_aout );
172 /* calculate the frame size in bytes */
173 p_aout->output.p_sys->p_notif->i_buffer_size = FRAME_SIZE * sizeof(s16)
174 * p_aout->output.output.i_channels;
176 /* then create a new secondary buffer */
177 if( DirectxCreateSecondaryBuffer( p_aout ) )
179 msg_Err( p_aout, "cannot create buffer" );
180 vlc_mutex_unlock( &p_aout->output.p_sys->buffer_lock );
184 vlc_mutex_unlock( &p_aout->output.p_sys->buffer_lock );
186 /* start playing the buffer */
187 dsresult = IDirectSoundBuffer_Play( p_aout->output.p_sys->p_dsbuffer,
190 DSBPLAY_LOOPING ); /* Flags */
191 if( dsresult == DSERR_BUFFERLOST )
193 IDirectSoundBuffer_Restore( p_aout->output.p_sys->p_dsbuffer );
194 dsresult = IDirectSoundBuffer_Play( p_aout->output.p_sys->p_dsbuffer,
197 DSBPLAY_LOOPING ); /* Flags */
199 if( dsresult != DS_OK )
201 msg_Warn( p_aout, "cannot play buffer" );
204 /* then launch the notification thread */
205 msg_Dbg( p_aout, "creating DirectSoundThread" );
206 if( vlc_thread_create( p_aout->output.p_sys->p_notif,
207 "DirectSound Notification Thread",
208 DirectSoundThread, VLC_THREAD_PRIORITY_OUTPUT, 1 ) )
210 msg_Err( p_aout, "cannot create DirectSoundThread" );
214 vlc_object_attach( p_aout->output.p_sys->p_notif, p_aout );
219 E_(CloseAudio)( VLC_OBJECT(p_aout) );
223 /*****************************************************************************
224 * Play: nothing to do
225 *****************************************************************************/
226 static void Play( aout_instance_t *p_aout )
230 /*****************************************************************************
231 * CloseAudio: close the audio device
232 *****************************************************************************/
233 void E_(CloseAudio) ( vlc_object_t *p_this )
235 aout_instance_t * p_aout = (aout_instance_t *)p_this;
237 msg_Dbg( p_aout, "Close" );
239 /* kill the position notification thread, if any */
240 if( p_aout->output.p_sys->p_notif )
242 vlc_object_detach( p_aout->output.p_sys->p_notif );
243 if( p_aout->output.p_sys->p_notif->b_thread )
245 p_aout->output.p_sys->p_notif->b_die = 1;
246 vlc_thread_join( p_aout->output.p_sys->p_notif );
248 vlc_object_destroy( p_aout->output.p_sys->p_notif );
251 /* release the secondary buffer */
252 DirectxDestroySecondaryBuffer( p_aout );
254 /* then release the primary buffer */
255 if( p_aout->output.p_sys->p_dsbuffer_primary )
256 IDirectSoundBuffer_Release( p_aout->output.p_sys->p_dsbuffer_primary );
258 /* finally release the DirectSound object */
259 if( p_aout->output.p_sys->p_dsobject )
260 IDirectSound_Release( p_aout->output.p_sys->p_dsobject );
262 /* free DSOUND.DLL */
263 if( p_aout->output.p_sys->hdsound_dll )
264 FreeLibrary( p_aout->output.p_sys->hdsound_dll );
266 /* Close the Output. */
267 if ( p_aout->output.p_sys )
269 free( p_aout->output.p_sys );
270 p_aout->output.p_sys = NULL;
274 /*****************************************************************************
275 * DirectxInitDSound: handle all the gory details of DirectSound initialisation
276 *****************************************************************************/
277 static int DirectxInitDSound( aout_instance_t *p_aout )
279 HRESULT (WINAPI *OurDirectSoundCreate)(LPGUID, LPDIRECTSOUND *, LPUNKNOWN);
281 p_aout->output.p_sys->hdsound_dll = LoadLibrary("DSOUND.DLL");
282 if( p_aout->output.p_sys->hdsound_dll == NULL )
284 msg_Warn( p_aout, "cannot open DSOUND.DLL" );
288 OurDirectSoundCreate = (void *)GetProcAddress(
289 p_aout->output.p_sys->hdsound_dll,
290 "DirectSoundCreate" );
291 if( OurDirectSoundCreate == NULL )
293 msg_Warn( p_aout, "GetProcAddress FAILED" );
297 /* Create the direct sound object */
298 if( OurDirectSoundCreate( NULL, &p_aout->output.p_sys->p_dsobject, NULL )
301 msg_Warn( p_aout, "cannot create a direct sound device" );
305 /* Set DirectSound Cooperative level, ie what control we want over Windows
306 * sound device. In our case, DSSCL_EXCLUSIVE means that we can modify the
307 * settings of the primary buffer, but also that only the sound of our
308 * application will be hearable when it will have the focus.
309 * !!! (this is not really working as intended yet because to set the
310 * cooperative level you need the window handle of your application, and
311 * I don't know of any easy way to get it. Especially since we might play
312 * sound without any video, and so what window handle should we use ???
313 * The hack for now is to use the Desktop window handle - it seems to be
315 if( IDirectSound_SetCooperativeLevel( p_aout->output.p_sys->p_dsobject,
319 msg_Warn( p_aout, "cannot set direct sound cooperative level" );
325 p_aout->output.p_sys->p_dsobject = NULL;
326 if( p_aout->output.p_sys->hdsound_dll )
328 FreeLibrary( p_aout->output.p_sys->hdsound_dll );
329 p_aout->output.p_sys->hdsound_dll = NULL;
335 /*****************************************************************************
336 * DirectxCreateSecondaryBuffer
337 *****************************************************************************
338 * This function creates the buffer we'll use to play audio.
339 * In DirectSound there are two kinds of buffers:
340 * - the primary buffer: which is the actual buffer that the soundcard plays
341 * - the secondary buffer(s): these buffers are the one actually used by
342 * applications and DirectSound takes care of mixing them into the primary.
344 * Once you create a secondary buffer, you cannot change its format anymore so
345 * you have to release the current and create another one.
346 *****************************************************************************/
347 static int DirectxCreateSecondaryBuffer( aout_instance_t *p_aout )
349 WAVEFORMATEX waveformat;
350 DSBUFFERDESC dsbdesc;
353 /* First set the buffer format */
354 memset(&waveformat, 0, sizeof(WAVEFORMATEX));
355 waveformat.wFormatTag = WAVE_FORMAT_PCM;
356 waveformat.nChannels = p_aout->output.output.i_channels;
357 waveformat.nSamplesPerSec = p_aout->output.output.i_rate;
358 waveformat.wBitsPerSample = 16;
359 waveformat.nBlockAlign = waveformat.wBitsPerSample / 8 *
360 waveformat.nChannels;
361 waveformat.nAvgBytesPerSec = waveformat.nSamplesPerSec *
362 waveformat.nBlockAlign;
364 /* Then fill in the descriptor */
365 memset(&dsbdesc, 0, sizeof(DSBUFFERDESC));
366 dsbdesc.dwSize = sizeof(DSBUFFERDESC);
367 dsbdesc.dwFlags = DSBCAPS_GETCURRENTPOSITION2/* Better position accuracy */
368 | DSBCAPS_CTRLPOSITIONNOTIFY /* We need notification */
369 | DSBCAPS_GLOBALFOCUS; /* Allows background playing */
370 dsbdesc.dwBufferBytes = FRAME_SIZE * 2 /* frames*/ * /* buffer size */
371 sizeof(s16) * p_aout->output.output.i_channels;
372 dsbdesc.lpwfxFormat = &waveformat;
374 if( IDirectSound_CreateSoundBuffer( p_aout->output.p_sys->p_dsobject,
376 &p_aout->output.p_sys->p_dsbuffer,
379 msg_Warn( p_aout, "cannot create direct sound secondary buffer" );
383 /* backup the size of the secondary sound buffer */
384 memset(&dsbcaps, 0, sizeof(DSBCAPS));
385 dsbcaps.dwSize = sizeof(DSBCAPS);
386 IDirectSoundBuffer_GetCaps( p_aout->output.p_sys->p_dsbuffer, &dsbcaps );
388 msg_Dbg( p_aout, "DirectxCreateSecondaryBuffer: %li",
389 dsbcaps.dwBufferBytes );
391 /* Now the secondary buffer is created, we need to setup its position
393 p_aout->output.p_sys->p_notif->p_events[0].dwOffset = 0;
394 p_aout->output.p_sys->p_notif->p_events[1].dwOffset =
395 p_aout->output.p_sys->p_notif->i_buffer_size;
397 /* Get the IDirectSoundNotify interface */
398 if FAILED( IDirectSoundBuffer_QueryInterface(
399 p_aout->output.p_sys->p_dsbuffer,
400 &IID_IDirectSoundNotify,
401 (LPVOID *)&p_aout->output.p_sys->p_dsnotify ) )
403 msg_Warn( p_aout, "cannot get Notify interface" );
407 if FAILED( IDirectSoundNotify_SetNotificationPositions(
408 p_aout->output.p_sys->p_dsnotify, 2,
409 p_aout->output.p_sys->p_notif->p_events ) )
411 msg_Warn( p_aout, "cannot set position Notification" );
415 p_aout->output.output.i_format = AOUT_FMT_S16_NE;
416 p_aout->output.i_nb_samples = FRAME_SIZE;
421 if( p_aout->output.p_sys->p_dsbuffer )
423 IDirectSoundBuffer_Release( p_aout->output.p_sys->p_dsbuffer );
424 p_aout->output.p_sys->p_dsbuffer = NULL;
426 if( p_aout->output.p_sys->p_dsnotify )
428 IDirectSoundBuffer_Release( p_aout->output.p_sys->p_dsbuffer );
429 p_aout->output.p_sys->p_dsnotify = NULL;
434 /*****************************************************************************
435 * DirectxCreateSecondaryBuffer
436 *****************************************************************************
437 * This function destroys the secondary buffer.
438 *****************************************************************************/
439 static void DirectxDestroySecondaryBuffer( aout_instance_t *p_aout )
441 /* make sure the buffer isn't playing */
442 if( p_aout->output.p_sys->p_dsbuffer )
443 IDirectSoundBuffer_Stop( p_aout->output.p_sys->p_dsbuffer );
445 if( p_aout->output.p_sys->p_dsnotify )
447 IDirectSoundNotify_Release( p_aout->output.p_sys->p_dsnotify );
448 p_aout->output.p_sys->p_dsnotify = NULL;
451 if( p_aout->output.p_sys->p_dsbuffer )
453 IDirectSoundBuffer_Release( p_aout->output.p_sys->p_dsbuffer );
454 p_aout->output.p_sys->p_dsbuffer = NULL;
458 /*****************************************************************************
459 * DirectSoundThread: this thread will capture play notification events.
460 *****************************************************************************
461 * We use this thread to emulate a callback mechanism. The thread probes for
462 * event notification and fills up the DS secondary buffer when needed.
463 *****************************************************************************/
464 static void DirectSoundThread( notification_thread_t *p_notif )
466 HANDLE notification_events[2];
468 aout_instance_t *p_aout = p_notif->p_aout;
470 notification_events[0] = p_notif->p_events[0].hEventNotify;
471 notification_events[1] = p_notif->p_events[1].hEventNotify;
473 /* Tell the main thread that we are ready */
474 vlc_thread_ready( p_notif );
476 /* this thread must be high-priority */
477 if( !SetThreadPriority( GetCurrentThread(),
478 THREAD_PRIORITY_ABOVE_NORMAL ) )
480 msg_Warn( p_notif, "DirectSoundThread could not raise its priority" );
483 msg_Dbg( p_notif, "DirectSoundThread ready" );
485 while( !p_notif->b_die )
488 void *p_write_position, *p_wrap_around;
489 long l_bytes1, l_bytes2;
490 aout_buffer_t * p_buffer;
492 /* wait for the position notification */
493 i_which_event = WaitForMultipleObjects( 2, notification_events, 0,
494 INFINITE ) - WAIT_OBJECT_0;
496 vlc_mutex_lock( &p_aout->output.p_sys->buffer_lock );
500 vlc_mutex_unlock( &p_aout->output.p_sys->buffer_lock );
504 /* Before copying anything, we have to lock the buffer */
505 dsresult = IDirectSoundBuffer_Lock(
507 p_aout->output.p_sys->p_dsbuffer,
508 /* Offset of lock start */
509 i_which_event ? 0 : p_notif->i_buffer_size,
510 p_notif->i_buffer_size, /* Number of bytes */
511 &p_write_position, /* Address of lock start */
512 &l_bytes1, /* Count of bytes locked before wrap around */
513 &p_wrap_around, /* Buffer adress (if wrap around) */
514 &l_bytes2, /* Count of bytes after wrap around */
516 if( dsresult == DSERR_BUFFERLOST )
518 IDirectSoundBuffer_Restore( p_aout->output.p_sys->p_dsbuffer );
519 dsresult = IDirectSoundBuffer_Lock(
520 p_aout->output.p_sys->p_dsbuffer,
521 i_which_event ? 0 : p_notif->i_buffer_size,
522 p_notif->i_buffer_size,
529 if( dsresult != DS_OK )
531 msg_Warn( p_notif, "cannot lock buffer" );
532 vlc_mutex_unlock( &p_aout->output.p_sys->buffer_lock );
536 /* FIXME : take into account DirectSound latency instead of mdate() */
537 p_buffer = aout_OutputNextBuffer( p_aout, mdate(), VLC_FALSE );
539 /* Now do the actual memcpy into the circular buffer */
540 if ( l_bytes1 != p_notif->i_buffer_size )
541 msg_Err( p_aout, "Wrong buffer size: %d, %d", l_bytes1,
542 p_notif->i_buffer_size );
544 if ( p_buffer != NULL )
546 p_aout->p_vlc->pf_memcpy( p_write_position, p_buffer->p_buffer,
548 aout_BufferFree( p_buffer );
552 memset( p_write_position, 0, l_bytes1 );
555 /* Now the data has been copied, unlock the buffer */
556 IDirectSoundBuffer_Unlock( p_aout->output.p_sys->p_dsbuffer,
557 p_write_position, l_bytes1, p_wrap_around, l_bytes2 );
559 vlc_mutex_unlock( &p_aout->output.p_sys->buffer_lock );
563 /* free the events */
564 CloseHandle( notification_events[0] );
565 CloseHandle( notification_events[1] );
567 msg_Dbg( p_notif, "DirectSoundThread exiting" );