1 /*****************************************************************************
2 * aout.c: Windows DirectX audio output method
3 *****************************************************************************
4 * Copyright (C) 2001 VideoLAN
5 * $Id: aout.c,v 1.4 2002/08/14 00:43:52 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 void E_(CloseAudio) ( vlc_object_t *p_this );
96 /*****************************************************************************
98 *****************************************************************************/
99 static int SetFormat ( aout_instance_t * );
100 static void Play ( aout_instance_t *, aout_buffer_t * );
102 /* local functions */
103 static int DirectxCreateSecondaryBuffer ( aout_instance_t * );
104 static void DirectxDestroySecondaryBuffer( aout_instance_t * );
105 static int DirectxInitDSound ( aout_instance_t * );
106 static void DirectSoundThread ( notification_thread_t * );
108 /*****************************************************************************
109 * OpenAudio: open the audio device
110 *****************************************************************************
111 * This function opens and setups Direct Sound.
112 *****************************************************************************/
113 int E_(OpenAudio) ( vlc_object_t *p_this )
115 aout_instance_t * p_aout = (aout_instance_t *)p_this;
117 DSBUFFERDESC dsbuffer_desc;
119 msg_Dbg( p_aout, "Open" );
121 /* Allocate structure */
122 p_aout->output.p_sys = malloc( sizeof( aout_sys_t ) );
123 if( p_aout->output.p_sys == NULL )
125 msg_Err( p_aout, "out of memory" );
129 /* Initialize some variables */
130 p_aout->output.p_sys->p_dsobject = NULL;
131 p_aout->output.p_sys->p_dsbuffer_primary = NULL;
132 p_aout->output.p_sys->p_dsbuffer = NULL;
133 p_aout->output.p_sys->p_dsnotify = NULL;
134 p_aout->output.p_sys->p_notif = NULL;
135 vlc_mutex_init( p_aout, &p_aout->output.p_sys->buffer_lock );
137 p_aout->output.pf_setformat = SetFormat;
138 p_aout->output.pf_play = Play;
140 /* Initialise DirectSound */
141 if( DirectxInitDSound( p_aout ) )
143 msg_Err( p_aout, "cannot initialize DirectSound" );
147 /* Obtain (not create) Direct Sound primary buffer */
148 memset( &dsbuffer_desc, 0, sizeof(DSBUFFERDESC) );
149 dsbuffer_desc.dwSize = sizeof(DSBUFFERDESC);
150 dsbuffer_desc.dwFlags = DSBCAPS_PRIMARYBUFFER;
151 msg_Warn( p_aout, "create direct sound primary buffer" );
152 dsresult = IDirectSound_CreateSoundBuffer(p_aout->output.p_sys->p_dsobject,
154 &p_aout->output.p_sys->p_dsbuffer_primary,
156 if( dsresult != DS_OK )
158 msg_Err( p_aout, "cannot create direct sound primary buffer" );
163 /* Now we need to setup DirectSound play notification */
164 p_aout->output.p_sys->p_notif =
165 vlc_object_create( p_aout, sizeof(notification_thread_t) );
166 p_aout->output.p_sys->p_notif->p_aout = p_aout;
168 /* first we need to create the notification events */
169 p_aout->output.p_sys->p_notif->p_events[0].hEventNotify =
170 CreateEvent( NULL, FALSE, FALSE, NULL );
171 p_aout->output.p_sys->p_notif->p_events[1].hEventNotify =
172 CreateEvent( NULL, FALSE, FALSE, NULL );
174 /* then launch the notification thread */
175 msg_Dbg( p_aout, "creating DirectSoundThread" );
176 if( vlc_thread_create( p_aout->output.p_sys->p_notif,
177 "DirectSound Notification Thread",
178 DirectSoundThread, 1 ) )
180 msg_Err( p_aout, "cannot create DirectSoundThread" );
184 vlc_object_attach( p_aout->output.p_sys->p_notif, p_aout );
189 E_(CloseAudio)( VLC_OBJECT(p_aout) );
193 /*****************************************************************************
194 * SetFormat: reset the audio device and sets its format
195 *****************************************************************************
196 * This functions set a new audio format.
197 * For this we need to close the current secondary buffer and create another
198 * one with the desired format.
199 *****************************************************************************/
200 static int SetFormat( aout_instance_t *p_aout )
204 msg_Dbg( p_aout, "SetFormat" );
205 vlc_mutex_lock( &p_aout->output.p_sys->buffer_lock );
207 /* first release the current secondary buffer */
208 DirectxDestroySecondaryBuffer( p_aout );
210 /* calculate the frame size in bytes */
211 p_aout->output.p_sys->p_notif->i_buffer_size = FRAME_SIZE * sizeof(s16)
212 * p_aout->output.output.i_channels;
214 /* then create a new secondary buffer */
215 if( DirectxCreateSecondaryBuffer( p_aout ) )
217 msg_Err( p_aout, "cannot create buffer" );
218 vlc_mutex_unlock( &p_aout->output.p_sys->buffer_lock );
222 vlc_mutex_unlock( &p_aout->output.p_sys->buffer_lock );
224 /* start playing the buffer */
225 dsresult = IDirectSoundBuffer_Play( p_aout->output.p_sys->p_dsbuffer,
228 DSBPLAY_LOOPING ); /* Flags */
229 if( dsresult == DSERR_BUFFERLOST )
231 IDirectSoundBuffer_Restore( p_aout->output.p_sys->p_dsbuffer );
232 dsresult = IDirectSoundBuffer_Play( p_aout->output.p_sys->p_dsbuffer,
235 DSBPLAY_LOOPING ); /* Flags */
237 if( dsresult != DS_OK )
239 msg_Warn( p_aout, "cannot play buffer" );
245 /*****************************************************************************
246 * Play: play a sound buffer
247 *****************************************************************************
248 * This doesn't actually play the buffer. This just stores the buffer so it
249 * can be played by the callback thread.
250 *****************************************************************************/
251 static void Play( aout_instance_t *p_aout, aout_buffer_t *p_buffer )
253 aout_FifoPush( p_aout, &p_aout->output.fifo, p_buffer );
256 /*****************************************************************************
257 * CloseAudio: close the audio device
258 *****************************************************************************/
259 void E_(CloseAudio) ( vlc_object_t *p_this )
261 aout_instance_t * p_aout = (aout_instance_t *)p_this;
263 msg_Dbg( p_aout, "Close" );
265 /* kill the position notification thread, if any */
266 if( p_aout->output.p_sys->p_notif )
268 vlc_object_detach( p_aout->output.p_sys->p_notif );
269 if( p_aout->output.p_sys->p_notif->b_thread )
271 p_aout->output.p_sys->p_notif->b_die = 1;
272 vlc_thread_join( p_aout->output.p_sys->p_notif );
274 vlc_object_destroy( p_aout->output.p_sys->p_notif );
277 /* release the secondary buffer */
278 DirectxDestroySecondaryBuffer( p_aout );
280 /* then release the primary buffer */
281 if( p_aout->output.p_sys->p_dsbuffer_primary )
282 IDirectSoundBuffer_Release( p_aout->output.p_sys->p_dsbuffer_primary );
284 /* finally release the DirectSound object */
285 if( p_aout->output.p_sys->p_dsobject )
286 IDirectSound_Release( p_aout->output.p_sys->p_dsobject );
288 /* free DSOUND.DLL */
289 if( p_aout->output.p_sys->hdsound_dll )
290 FreeLibrary( p_aout->output.p_sys->hdsound_dll );
292 /* Close the Output. */
293 if ( p_aout->output.p_sys )
295 free( p_aout->output.p_sys );
296 p_aout->output.p_sys = NULL;
300 /*****************************************************************************
301 * DirectxInitDSound: handle all the gory details of DirectSound initialisation
302 *****************************************************************************/
303 static int DirectxInitDSound( aout_instance_t *p_aout )
305 HRESULT (WINAPI *OurDirectSoundCreate)(LPGUID, LPDIRECTSOUND *, LPUNKNOWN);
307 p_aout->output.p_sys->hdsound_dll = LoadLibrary("DSOUND.DLL");
308 if( p_aout->output.p_sys->hdsound_dll == NULL )
310 msg_Warn( p_aout, "cannot open DSOUND.DLL" );
314 OurDirectSoundCreate = (void *)GetProcAddress(
315 p_aout->output.p_sys->hdsound_dll,
316 "DirectSoundCreate" );
317 if( OurDirectSoundCreate == NULL )
319 msg_Warn( p_aout, "GetProcAddress FAILED" );
323 /* Create the direct sound object */
324 if( OurDirectSoundCreate( NULL, &p_aout->output.p_sys->p_dsobject, NULL )
327 msg_Warn( p_aout, "cannot create a direct sound device" );
331 /* Set DirectSound Cooperative level, ie what control we want over Windows
332 * sound device. In our case, DSSCL_EXCLUSIVE means that we can modify the
333 * settings of the primary buffer, but also that only the sound of our
334 * application will be hearable when it will have the focus.
335 * !!! (this is not really working as intended yet because to set the
336 * cooperative level you need the window handle of your application, and
337 * I don't know of any easy way to get it. Especially since we might play
338 * sound without any video, and so what window handle should we use ???
339 * The hack for now is to use the Desktop window handle - it seems to be
341 if( IDirectSound_SetCooperativeLevel( p_aout->output.p_sys->p_dsobject,
345 msg_Warn( p_aout, "cannot set direct sound cooperative level" );
351 p_aout->output.p_sys->p_dsobject = NULL;
352 if( p_aout->output.p_sys->hdsound_dll )
354 FreeLibrary( p_aout->output.p_sys->hdsound_dll );
355 p_aout->output.p_sys->hdsound_dll = NULL;
361 /*****************************************************************************
362 * DirectxCreateSecondaryBuffer
363 *****************************************************************************
364 * This function creates the buffer we'll use to play audio.
365 * In DirectSound there are two kinds of buffers:
366 * - the primary buffer: which is the actual buffer that the soundcard plays
367 * - the secondary buffer(s): these buffers are the one actually used by
368 * applications and DirectSound takes care of mixing them into the primary.
370 * Once you create a secondary buffer, you cannot change its format anymore so
371 * you have to release the current and create another one.
372 *****************************************************************************/
373 static int DirectxCreateSecondaryBuffer( aout_instance_t *p_aout )
375 WAVEFORMATEX waveformat;
376 DSBUFFERDESC dsbdesc;
379 /* First set the buffer format */
380 memset(&waveformat, 0, sizeof(WAVEFORMATEX));
381 waveformat.wFormatTag = WAVE_FORMAT_PCM;
382 waveformat.nChannels = p_aout->output.output.i_channels;
383 waveformat.nSamplesPerSec = p_aout->output.output.i_rate;
384 waveformat.wBitsPerSample = 16;
385 waveformat.nBlockAlign = waveformat.wBitsPerSample / 8 *
386 waveformat.nChannels;
387 waveformat.nAvgBytesPerSec = waveformat.nSamplesPerSec *
388 waveformat.nBlockAlign;
390 /* Then fill in the descriptor */
391 memset(&dsbdesc, 0, sizeof(DSBUFFERDESC));
392 dsbdesc.dwSize = sizeof(DSBUFFERDESC);
393 dsbdesc.dwFlags = DSBCAPS_GETCURRENTPOSITION2/* Better position accuracy */
394 | DSBCAPS_CTRLPOSITIONNOTIFY /* We need notification */
395 | DSBCAPS_GLOBALFOCUS; /* Allows background playing */
396 dsbdesc.dwBufferBytes = FRAME_SIZE * 2 /* frames*/ * /* buffer size */
397 sizeof(s16) * p_aout->output.output.i_channels;
398 dsbdesc.lpwfxFormat = &waveformat;
400 if( IDirectSound_CreateSoundBuffer( p_aout->output.p_sys->p_dsobject,
402 &p_aout->output.p_sys->p_dsbuffer,
405 msg_Warn( p_aout, "cannot create direct sound secondary buffer" );
409 /* backup the size of the secondary sound buffer */
410 memset(&dsbcaps, 0, sizeof(DSBCAPS));
411 dsbcaps.dwSize = sizeof(DSBCAPS);
412 IDirectSoundBuffer_GetCaps( p_aout->output.p_sys->p_dsbuffer, &dsbcaps );
414 msg_Dbg( p_aout, "DirectxCreateSecondaryBuffer: %li",
415 dsbcaps.dwBufferBytes );
417 /* Now the secondary buffer is created, we need to setup its position
419 p_aout->output.p_sys->p_notif->p_events[0].dwOffset = 0;
420 p_aout->output.p_sys->p_notif->p_events[1].dwOffset =
421 p_aout->output.p_sys->p_notif->i_buffer_size;
423 /* Get the IDirectSoundNotify interface */
424 if FAILED( IDirectSoundBuffer_QueryInterface(
425 p_aout->output.p_sys->p_dsbuffer,
426 &IID_IDirectSoundNotify,
427 (LPVOID *)&p_aout->output.p_sys->p_dsnotify ) )
429 msg_Warn( p_aout, "cannot get Notify interface" );
433 if FAILED( IDirectSoundNotify_SetNotificationPositions(
434 p_aout->output.p_sys->p_dsnotify, 2,
435 p_aout->output.p_sys->p_notif->p_events ) )
437 msg_Warn( p_aout, "cannot set position Notification" );
441 p_aout->output.output.i_format = AOUT_FMT_S16_NE;
442 p_aout->output.i_nb_samples = FRAME_SIZE;
447 if( p_aout->output.p_sys->p_dsbuffer )
449 IDirectSoundBuffer_Release( p_aout->output.p_sys->p_dsbuffer );
450 p_aout->output.p_sys->p_dsbuffer = NULL;
452 if( p_aout->output.p_sys->p_dsnotify )
454 IDirectSoundBuffer_Release( p_aout->output.p_sys->p_dsbuffer );
455 p_aout->output.p_sys->p_dsnotify = NULL;
460 /*****************************************************************************
461 * DirectxCreateSecondaryBuffer
462 *****************************************************************************
463 * This function destroys the secondary buffer.
464 *****************************************************************************/
465 static void DirectxDestroySecondaryBuffer( aout_instance_t *p_aout )
467 /* make sure the buffer isn't playing */
468 if( p_aout->output.p_sys->p_dsbuffer )
469 IDirectSoundBuffer_Stop( p_aout->output.p_sys->p_dsbuffer );
471 if( p_aout->output.p_sys->p_dsnotify )
473 IDirectSoundNotify_Release( p_aout->output.p_sys->p_dsnotify );
474 p_aout->output.p_sys->p_dsnotify = NULL;
477 if( p_aout->output.p_sys->p_dsbuffer )
479 IDirectSoundBuffer_Release( p_aout->output.p_sys->p_dsbuffer );
480 p_aout->output.p_sys->p_dsbuffer = NULL;
484 /*****************************************************************************
485 * DirectSoundThread: this thread will capture play notification events.
486 *****************************************************************************
487 * We use this thread to emulate a callback mechanism. The thread probes for
488 * event notification and fills up the DS secondary buffer when needed.
489 *****************************************************************************/
490 static void DirectSoundThread( notification_thread_t *p_notif )
492 HANDLE notification_events[2];
494 aout_instance_t *p_aout = p_notif->p_aout;
496 notification_events[0] = p_notif->p_events[0].hEventNotify;
497 notification_events[1] = p_notif->p_events[1].hEventNotify;
499 /* Tell the main thread that we are ready */
500 vlc_thread_ready( p_notif );
502 /* this thread must be high-priority */
503 if( !SetThreadPriority( GetCurrentThread(),
504 THREAD_PRIORITY_ABOVE_NORMAL ) )
506 msg_Warn( p_notif, "DirectSoundThread could not raise its priority" );
509 msg_Dbg( p_notif, "DirectSoundThread ready" );
511 while( !p_notif->b_die )
514 void *p_write_position, *p_wrap_around;
515 long l_bytes1, l_bytes2;
516 aout_buffer_t * p_buffer;
518 /* wait for the position notification */
519 i_which_event = WaitForMultipleObjects( 2, notification_events, 0,
520 INFINITE ) - WAIT_OBJECT_0;
522 vlc_mutex_lock( &p_aout->output.p_sys->buffer_lock );
526 vlc_mutex_unlock( &p_aout->output.p_sys->buffer_lock );
530 /* Before copying anything, we have to lock the buffer */
531 dsresult = IDirectSoundBuffer_Lock(
533 p_aout->output.p_sys->p_dsbuffer,
534 /* Offset of lock start */
535 i_which_event ? 0 : p_notif->i_buffer_size,
536 p_notif->i_buffer_size, /* Number of bytes */
537 &p_write_position, /* Address of lock start */
538 &l_bytes1, /* Count of bytes locked before wrap around */
539 &p_wrap_around, /* Buffer adress (if wrap around) */
540 &l_bytes2, /* Count of bytes after wrap around */
542 if( dsresult == DSERR_BUFFERLOST )
544 IDirectSoundBuffer_Restore( p_aout->output.p_sys->p_dsbuffer );
545 dsresult = IDirectSoundBuffer_Lock(
546 p_aout->output.p_sys->p_dsbuffer,
547 i_which_event ? 0 : p_notif->i_buffer_size,
548 p_notif->i_buffer_size,
555 if( dsresult != DS_OK )
557 msg_Warn( p_notif, "cannot lock buffer" );
558 vlc_mutex_unlock( &p_aout->output.p_sys->buffer_lock );
562 /* FIXME : take into account DirectSound latency instead of mdate() */
563 p_buffer = aout_OutputNextBuffer( p_aout, mdate(), 0 );
565 /* Now do the actual memcpy into the circular buffer */
566 if ( l_bytes1 != p_notif->i_buffer_size )
567 msg_Err( p_aout, "Wrong buffer size: %d, %d", l_bytes1,
568 p_notif->i_buffer_size );
570 if ( p_buffer != NULL )
572 p_aout->p_vlc->pf_memcpy( p_write_position, p_buffer->p_buffer,
574 aout_BufferFree( p_buffer );
578 memset( p_write_position, 0, l_bytes1 );
581 /* Now the data has been copied, unlock the buffer */
582 IDirectSoundBuffer_Unlock( p_aout->output.p_sys->p_dsbuffer,
583 p_write_position, l_bytes1, p_wrap_around, l_bytes2 );
585 vlc_mutex_unlock( &p_aout->output.p_sys->buffer_lock );
589 /* free the events */
590 CloseHandle( notification_events[0] );
591 CloseHandle( notification_events[1] );
593 msg_Dbg( p_notif, "DirectSoundThread exiting" );