1 /*****************************************************************************
2 * aout_directx.c: Windows DirectX audio output method
3 *****************************************************************************
4 * Copyright (C) 1999, 2000 VideoLAN
5 * $Id: aout_directx.c,v 1.7 2001/07/30 00:53:04 sam 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 #define MODULE_NAME directx
25 #include "modules_inner.h"
27 /* The most important thing to do for now is to fix the audio bug we've got
28 * on startup: the audio will start later than the video (sometimes) and
29 * is trying to catching up with it.
30 * At first sight it seems to be a scheduling problem
34 /*****************************************************************************
36 *****************************************************************************/
39 #include <errno.h> /* ENOMEM */
40 #include <fcntl.h> /* open(), O_WRONLY */
41 #include <string.h> /* strerror() */
44 # include <unistd.h> /* write(), close() */
47 #include <stdio.h> /* "intf_msg.h" */
48 #include <stdlib.h> /* calloc(), malloc(), free() */
51 #include "common.h" /* boolean_t, byte_t */
56 #if defined( _MSC_VER )
62 #include "audio_output.h" /* aout_thread_t */
64 #include "intf_msg.h" /* intf_DbgMsg(), intf_ErrMsg() */
68 #include "modules_export.h"
70 /*****************************************************************************
71 * aout_sys_t: directx audio output method descriptor
72 *****************************************************************************
73 * This structure is part of the audio output thread descriptor.
74 * It describes the direct sound specific properties of an audio device.
75 *****************************************************************************/
77 typedef struct aout_sys_s
79 LPDIRECTSOUND p_dsobject; /* main Direct Sound object */
81 LPDIRECTSOUNDBUFFER p_dsbuffer_primary; /* the actual sound card buffer
82 (not used directly) */
84 LPDIRECTSOUNDBUFFER p_dsbuffer; /* the sound buffer we use (direct sound
85 * takes care of mixing all the
86 * secondary buffers into the primary) */
88 HINSTANCE hdsound_dll; /* handle of the opened dsound dll */
90 long l_buffer_size; /* secondary sound buffer size */
91 long l_write_position; /* next write position for the buffer */
97 /*****************************************************************************
99 *****************************************************************************/
100 static int aout_Probe ( probedata_t *p_data );
101 static int aout_Open ( aout_thread_t *p_aout );
102 static int aout_SetFormat ( aout_thread_t *p_aout );
103 static long aout_GetBufInfo ( aout_thread_t *p_aout, long l_buffer_info );
104 static void aout_Play ( aout_thread_t *p_aout,
105 byte_t *buffer, int i_size );
106 static void aout_Close ( aout_thread_t *p_aout );
108 /* local functions */
109 static int DirectxCreateSecondaryBuffer( aout_thread_t *p_aout );
110 static int DirectxInitDSound( aout_thread_t *p_aout );
112 /*****************************************************************************
113 * Functions exported as capabilities. They are declared as static so that
114 * we don't pollute the namespace too much.
115 *****************************************************************************/
116 void _M( aout_getfunctions )( function_list_t * p_function_list )
118 p_function_list->pf_probe = aout_Probe;
119 p_function_list->functions.aout.pf_open = aout_Open;
120 p_function_list->functions.aout.pf_setformat = aout_SetFormat;
121 p_function_list->functions.aout.pf_getbufinfo = aout_GetBufInfo;
122 p_function_list->functions.aout.pf_play = aout_Play;
123 p_function_list->functions.aout.pf_close = aout_Close;
126 /*****************************************************************************
127 * aout_Probe: probe the audio device and return a score
128 *****************************************************************************
129 * This function tries to probe for a Direct Sound device and returns a
130 * score to the plugin manager so that it can select the best plugin.
131 *****************************************************************************/
132 static int aout_Probe( probedata_t *p_data )
134 /* For now just assume the computer has a sound device */
135 if( TestMethod( AOUT_METHOD_VAR, "directx" ) )
142 /*****************************************************************************
143 * aout_Open: open the audio device
144 *****************************************************************************
145 * This function opens and setups Direct Sound.
146 *****************************************************************************/
147 static int aout_Open( aout_thread_t *p_aout )
150 DSBUFFERDESC dsbuffer_desc;
151 WAVEFORMATEX waveformat;
153 intf_WarnMsg( 3, "aout: DirectX aout_Open ");
155 /* Allocate structure */
156 p_aout->p_sys = malloc( sizeof( aout_sys_t ) );
158 if( p_aout->p_sys == NULL )
160 intf_ErrMsg( "aout error: %s", strerror(ENOMEM) );
164 /* Initialize some variables */
165 p_aout->p_sys->p_dsobject = NULL;
166 p_aout->p_sys->p_dsbuffer_primary = NULL;
167 p_aout->p_sys->p_dsbuffer = NULL;
169 p_aout->psz_device = 0;
170 p_aout->i_format = AOUT_FORMAT_DEFAULT;
171 p_aout->i_channels = 1 + main_GetIntVariable( AOUT_STEREO_VAR,
172 AOUT_STEREO_DEFAULT );
173 p_aout->l_rate = main_GetIntVariable( AOUT_RATE_VAR,
176 /* Initialise DirectSound */
177 if( DirectxInitDSound( p_aout ) )
179 intf_WarnMsg( 3, "aout: can't initialise DirectSound ");
183 /* Obtain (not create) Direct Sound primary buffer */
184 memset( &dsbuffer_desc, 0, sizeof(DSBUFFERDESC) );
185 dsbuffer_desc.dwSize = sizeof(DSBUFFERDESC);
186 dsbuffer_desc.dwFlags = DSBCAPS_PRIMARYBUFFER;
187 intf_WarnMsg( 3, "aout: Create direct sound primary buffer ");
188 dsresult = IDirectSound_CreateSoundBuffer(p_aout->p_sys->p_dsobject,
190 &p_aout->p_sys->p_dsbuffer_primary,
192 if( dsresult != DS_OK )
194 intf_WarnMsg( 3, "aout: can't create direct sound primary buffer ");
195 IDirectSound_Release( p_aout->p_sys->p_dsobject );
196 p_aout->p_sys->p_dsobject = NULL;
197 p_aout->p_sys->p_dsbuffer_primary = NULL;
201 /* Set Direct Sound primary buffer format because the default value set by
202 * Windows is usually not the high quality value */
203 memset(&waveformat, 0, sizeof(WAVEFORMATEX));
204 waveformat.wFormatTag = WAVE_FORMAT_PCM;
205 waveformat.nChannels = 2;
206 waveformat.nSamplesPerSec = 44100;
207 waveformat.wBitsPerSample = 16;
208 waveformat.nBlockAlign = waveformat.wBitsPerSample / 8 *
209 waveformat.nChannels;
210 waveformat.nAvgBytesPerSec = waveformat.nSamplesPerSec *
211 waveformat.nBlockAlign;
213 dsresult = IDirectSoundBuffer_SetFormat(p_aout->p_sys->p_dsbuffer_primary,
215 if( dsresult != DS_OK )
217 intf_WarnMsg( 3, "aout: can't set primary buffer format");
221 /* ensure the primary buffer is playing. We won't actually hear anything
222 * until the secondary buffer is playing */
223 dsresult = IDirectSoundBuffer_Play( p_aout->p_sys->p_dsbuffer_primary,
227 if( dsresult != DS_OK )
229 intf_WarnMsg( 3, "aout: can't play direct sound primary buffer ");
230 IDirectSound_Release( p_aout->p_sys->p_dsbuffer_primary );
231 IDirectSound_Release( p_aout->p_sys->p_dsobject );
232 p_aout->p_sys->p_dsobject = NULL;
233 p_aout->p_sys->p_dsbuffer_primary = NULL;
241 /*****************************************************************************
242 * aout_SetFormat: reset the audio device and sets its format
243 *****************************************************************************
244 * This functions set a new audio format.
245 * For this we need to close the current secondary buffer and create another
246 * one with the desired format.
247 *****************************************************************************/
248 static int aout_SetFormat( aout_thread_t *p_aout )
252 intf_WarnMsg( 3, "aout: DirectX aout_SetFormat ");
254 /* first release the current secondary buffer */
255 if( p_aout->p_sys->p_dsbuffer != NULL )
257 IDirectSoundBuffer_Release( p_aout->p_sys->p_dsbuffer );
258 p_aout->p_sys->p_dsbuffer = NULL;
261 /* then create a new secondary buffer */
262 dsresult = DirectxCreateSecondaryBuffer( p_aout );
263 if( dsresult != DS_OK )
265 intf_WarnMsg( 3, "aout: DirectX aout_SetFormat cannot create buffer");
269 p_aout->i_latency = 0;
274 /*****************************************************************************
275 * aout_GetBufInfo: buffer status query
276 *****************************************************************************
277 * returns the number of bytes in the audio buffer compared to the size of
279 *****************************************************************************/
280 static long aout_GetBufInfo( aout_thread_t *p_aout, long l_buffer_limit )
282 long l_play_position, l_notused, l_result;
285 dsresult = IDirectSoundBuffer_GetCurrentPosition(p_aout->p_sys->p_dsbuffer,
286 &l_play_position, &l_notused);
287 if( dsresult == DSERR_BUFFERLOST )
289 IDirectSoundBuffer_Restore( p_aout->p_sys->p_dsbuffer );
290 dsresult = IDirectSoundBuffer_GetCurrentPosition(
291 p_aout->p_sys->p_dsbuffer,
292 &l_play_position, &l_notused
295 if( dsresult != DS_OK )
297 intf_WarnMsg(3,"aout: DirectX aout_GetBufInfo cannot get current pos");
298 return( l_buffer_limit );
302 /* temporary hack. When you start playing a new file, the play position
303 * doesn't start changing immediatly, even though sound is already
304 * playing from the sound card */
305 if( l_play_position == 0 )
307 intf_WarnMsg( 5, "aout: DirectX aout_GetBufInfo: %li", l_buffer_limit);
308 return( l_buffer_limit );
312 l_result = (p_aout->p_sys->l_write_position >= l_play_position) ?
313 (p_aout->p_sys->l_write_position - l_play_position) /2
314 : (p_aout->p_sys->l_buffer_size - l_play_position
315 + p_aout->p_sys->l_write_position) /2 ;
318 intf_WarnMsg( 5, "aout: DirectX aout_GetBufInfo: %li", l_result);
323 /*****************************************************************************
324 * aout_Play: play a sound buffer
325 *****************************************************************************
326 * This function writes a buffer of i_length bytes
327 *****************************************************************************/
328 static void aout_Play( aout_thread_t *p_aout, byte_t *buffer, int i_size )
330 VOID *p_write_position, *p_start_buffer;
331 long l_bytes1, l_bytes2;
332 long l_play_position, l_notused, l_buffer_free_length;
335 /* We want to copy data to the circular sound buffer, so we first need to
336 * find out were in the buffer we can write our data */
337 dsresult = IDirectSoundBuffer_GetCurrentPosition(p_aout->p_sys->p_dsbuffer,
340 if( dsresult == DSERR_BUFFERLOST )
342 IDirectSoundBuffer_Restore( p_aout->p_sys->p_dsbuffer );
343 dsresult = IDirectSoundBuffer_GetCurrentPosition(
344 p_aout->p_sys->p_dsbuffer,
345 &l_play_position, &l_notused
348 if( dsresult != DS_OK )
350 intf_WarnMsg( 3, "aout: DirectX aout_Play can'get buffer position");
354 /* check that we are not overflowing the circular buffer (everything should
355 * be alright but just in case) */
356 l_buffer_free_length = l_play_position - p_aout->p_sys->l_write_position;
357 if( l_buffer_free_length <= 0 )
358 l_buffer_free_length += p_aout->p_sys->l_buffer_size;
360 if( i_size > l_buffer_free_length )
362 intf_WarnMsg( 3, "aout: DirectX aout_Play buffer overflow: size %i, free %i !!!", i_size, l_buffer_free_length);
363 intf_WarnMsg( 3, "aout: DirectX aout_Play buffer overflow: writepos %i, readpos %i !!!", p_aout->p_sys->l_write_position, l_play_position);
364 /*i_size = l_buffer_free_length;*/
366 /* Update the write pointer */
367 p_aout->p_sys->l_write_position = l_notused;
373 intf_WarnMsg( 4, "aout: DirectX aout_Play buffer: size %i, free %i !!!"
374 , i_size, l_buffer_free_length);
375 intf_WarnMsg( 4, "aout: DirectX aout_Play buffer: writepos %i, readpos %i !!!", p_aout->p_sys->l_write_position, l_play_position);
380 /* Before copying anything, we have to lock the buffer */
381 dsresult = IDirectSoundBuffer_Lock( p_aout->p_sys->p_dsbuffer,
382 p_aout->p_sys->l_write_position, /* Offset of lock start */
383 i_size, /* Number of bytes to lock */
384 &p_write_position, /* Address of lock start */
385 &l_bytes1, /* Count of bytes locked before wrap around */
386 &p_start_buffer, /* Buffer adress (if wrap around) */
387 &l_bytes2, /* Count of bytes after wrap around */
389 if( dsresult == DSERR_BUFFERLOST )
391 IDirectSoundBuffer_Restore( p_aout->p_sys->p_dsbuffer );
392 dsresult = IDirectSoundBuffer_Lock( p_aout->p_sys->p_dsbuffer,
393 p_aout->p_sys->l_write_position,
402 if( dsresult != DS_OK )
404 intf_WarnMsg( 3, "aout: DirectX aout_Play can't lock buffer");
408 /* Now do the actual memcopy (two memcpy because the buffer is circular) */
409 memcpy( p_write_position, buffer, l_bytes1 );
410 if( p_start_buffer != NULL )
412 memcpy( p_start_buffer, buffer + l_bytes1, l_bytes2 );
415 /* Now the data has been copied, unlock the buffer */
416 IDirectSoundBuffer_Unlock( p_aout->p_sys->p_dsbuffer,
417 p_write_position, l_bytes1, p_start_buffer, l_bytes2 );
419 /* Update the write position index of the buffer*/
420 p_aout->p_sys->l_write_position += i_size;
421 p_aout->p_sys->l_write_position %= p_aout->p_sys->l_buffer_size;
423 /* The play function has no effect if the buffer is already playing */
424 dsresult = IDirectSoundBuffer_Play( p_aout->p_sys->p_dsbuffer,
427 DSBPLAY_LOOPING ); /* Flags */
428 if( dsresult == DSERR_BUFFERLOST )
430 IDirectSoundBuffer_Restore( p_aout->p_sys->p_dsbuffer );
431 dsresult = IDirectSoundBuffer_Play( p_aout->p_sys->p_dsbuffer,
434 DSBPLAY_LOOPING ); /* Flags */
436 if( dsresult != DS_OK )
438 intf_WarnMsg( 3, "aout: DirectX aout_Play can't play buffer");
444 /*****************************************************************************
445 * aout_Close: close the audio device
446 *****************************************************************************/
447 static void aout_Close( aout_thread_t *p_aout )
450 intf_WarnMsg( 3, "aout: DirectX aout_Close ");
452 /* make sure the buffer isn't playing */
453 if( p_aout->p_sys->p_dsbuffer != NULL )
455 IDirectSoundBuffer_Stop( p_aout->p_sys->p_dsbuffer );
458 /* first release the secondary buffer */
459 if( p_aout->p_sys->p_dsbuffer != NULL )
461 IDirectSoundBuffer_Release( p_aout->p_sys->p_dsbuffer );
462 p_aout->p_sys->p_dsbuffer = NULL;
465 /* then release the primary buffer */
466 if( p_aout->p_sys->p_dsbuffer_primary != NULL )
468 IDirectSoundBuffer_Release( p_aout->p_sys->p_dsbuffer_primary );
469 p_aout->p_sys->p_dsbuffer_primary = NULL;
472 /* finally release the DirectSound object */
473 if( p_aout->p_sys->p_dsobject != NULL )
475 IDirectSound_Release( p_aout->p_sys->p_dsobject );
476 p_aout->p_sys->p_dsobject = NULL;
479 /* free DSOUND.DLL */
480 if( p_aout->p_sys->hdsound_dll != NULL )
482 FreeLibrary( p_aout->p_sys->hdsound_dll );
483 p_aout->p_sys->hdsound_dll = NULL;
486 /* Close the Output. */
487 if ( p_aout->p_sys != NULL )
489 free( p_aout->p_sys );
490 p_aout->p_sys = NULL;
494 /*****************************************************************************
496 *****************************************************************************
497 *****************************************************************************/
498 static int DirectxInitDSound( aout_thread_t *p_aout )
500 HRESULT (WINAPI *OurDirectSoundCreate)(LPGUID, LPDIRECTSOUND *, LPUNKNOWN);
502 p_aout->p_sys->hdsound_dll = LoadLibrary("DSOUND.DLL");
503 if( p_aout->p_sys->hdsound_dll == NULL )
505 intf_WarnMsg( 3, "aout: can't open DSOUND.DLL ");
509 OurDirectSoundCreate = (void *)GetProcAddress( p_aout->p_sys->hdsound_dll,
510 "DirectSoundCreate" );
512 if( OurDirectSoundCreate == NULL )
514 intf_WarnMsg( 3, "aout: GetProcAddress FAILED ");
515 FreeLibrary( p_aout->p_sys->hdsound_dll );
516 p_aout->p_sys->hdsound_dll = NULL;
520 /* Create the direct sound object */
521 if( OurDirectSoundCreate(NULL, &p_aout->p_sys->p_dsobject, NULL) != DS_OK )
523 intf_WarnMsg( 3, "aout: can't create a direct sound device ");
524 p_aout->p_sys->p_dsobject = NULL;
525 FreeLibrary( p_aout->p_sys->hdsound_dll );
526 p_aout->p_sys->hdsound_dll = NULL;
530 /* Set DirectSound Cooperative level, ie what control we want over Windows
531 * sound device. In our case, DSSCL_EXCLUSIVE means that we can modify the
532 * settings of the primary buffer, but also that only the sound of our
533 * application will be hearable when it will have the focus.
534 * !!! (this is not really working as intended yet because to set the
535 * cooperative level you need the window handle of your application, and
536 * I don't know of any easy way to get it. Especially since we might play
537 * sound without any video, and so what window handle should we use ???
538 * The hack for now is to use the Desktop window handle - it seems to be
540 if( IDirectSound_SetCooperativeLevel(p_aout->p_sys->p_dsobject,
544 intf_WarnMsg( 3, "aout: can't set direct sound cooperative level ");
550 /*****************************************************************************
551 * DirectxCreateSecondaryBuffer
552 *****************************************************************************
553 * This function creates the buffer we'll use to play audio.
554 * In DirectSound there are two kinds of buffers:
555 * - the primary buffer: which is the actual buffer that the soundcard plays
556 * - the secondary buffer(s): these buffers are the one actually used by
557 * applications and DirectSound takes care of mixing them into the primary.
559 * Once you create a secondary buffer, you cannot change its format anymore so
560 * you have to release the current and create another one.
561 *****************************************************************************/
562 static int DirectxCreateSecondaryBuffer( aout_thread_t *p_aout )
564 WAVEFORMATEX waveformat;
565 DSBUFFERDESC dsbdesc;
568 /* First set the buffer format */
569 memset(&waveformat, 0, sizeof(WAVEFORMATEX));
570 waveformat.wFormatTag = WAVE_FORMAT_PCM;
571 waveformat.nChannels = p_aout->i_channels;
572 waveformat.nSamplesPerSec = p_aout->l_rate;
573 waveformat.wBitsPerSample = 16;
574 waveformat.nBlockAlign = waveformat.wBitsPerSample / 8 *
575 waveformat.nChannels;
576 waveformat.nAvgBytesPerSec = waveformat.nSamplesPerSec *
577 waveformat.nBlockAlign;
579 /* Then fill in the descriptor */
580 memset(&dsbdesc, 0, sizeof(DSBUFFERDESC));
581 dsbdesc.dwSize = sizeof(DSBUFFERDESC);
582 dsbdesc.dwFlags = DSBCAPS_GETCURRENTPOSITION2/* Better position accuracy */
583 | DSBCAPS_GLOBALFOCUS; /* Allows background playing */
584 dsbdesc.dwBufferBytes = waveformat.nAvgBytesPerSec * 2; /* 2 sec buffer */
585 dsbdesc.lpwfxFormat = &waveformat;
587 if( IDirectSound_CreateSoundBuffer( p_aout->p_sys->p_dsobject,
589 &p_aout->p_sys->p_dsbuffer,
592 intf_WarnMsg( 3, "aout: can't create direct sound secondary buffer ");
593 p_aout->p_sys->p_dsbuffer = NULL;
597 /* backup the size of the secondary sound buffer */
598 memset(&dsbcaps, 0, sizeof(DSBCAPS));
599 dsbcaps.dwSize = sizeof(DSBCAPS);
600 IDirectSoundBuffer_GetCaps( p_aout->p_sys->p_dsbuffer, &dsbcaps );
601 p_aout->p_sys->l_buffer_size = dsbcaps.dwBufferBytes;
602 p_aout->p_sys->l_write_position = 0;
603 intf_WarnMsg( 3, "aout: DirectX DirectxCreateSecondaryBuffer: %li",
604 p_aout->p_sys->l_buffer_size);