1 /*****************************************************************************
2 * aout_directx.c: Windows DirectX audio output method
3 *****************************************************************************
4 * Copyright (C) 1999, 2000 VideoLAN
5 * $Id: aout_directx.c,v 1.5 2001/07/12 20:44:52 reno 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() */
42 #include <unistd.h> /* write(), close() */
43 #include <stdio.h> /* "intf_msg.h" */
44 #include <stdlib.h> /* calloc(), malloc(), free() */
47 #include "common.h" /* boolean_t, byte_t */
54 #include "audio_output.h" /* aout_thread_t */
56 #include "intf_msg.h" /* intf_DbgMsg(), intf_ErrMsg() */
60 #include "modules_export.h"
62 /*****************************************************************************
63 * aout_sys_t: directx audio output method descriptor
64 *****************************************************************************
65 * This structure is part of the audio output thread descriptor.
66 * It describes the direct sound specific properties of an audio device.
67 *****************************************************************************/
69 typedef struct aout_sys_s
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 HINSTANCE hdsound_dll; /* handle of the opened dsound dll */
82 long l_buffer_size; /* secondary sound buffer size */
83 long l_write_position; /* next write position for the buffer */
89 /*****************************************************************************
91 *****************************************************************************/
92 static int aout_Probe ( probedata_t *p_data );
93 static int aout_Open ( aout_thread_t *p_aout );
94 static int aout_SetFormat ( aout_thread_t *p_aout );
95 static long aout_GetBufInfo ( aout_thread_t *p_aout, long l_buffer_info );
96 static void aout_Play ( aout_thread_t *p_aout,
97 byte_t *buffer, int i_size );
98 static void aout_Close ( aout_thread_t *p_aout );
100 /* local functions */
101 static int DirectxCreateSecondaryBuffer( aout_thread_t *p_aout );
102 static int DirectxInitDSound( aout_thread_t *p_aout );
104 /*****************************************************************************
105 * Functions exported as capabilities. They are declared as static so that
106 * we don't pollute the namespace too much.
107 *****************************************************************************/
108 void _M( aout_getfunctions )( function_list_t * p_function_list )
110 p_function_list->pf_probe = aout_Probe;
111 p_function_list->functions.aout.pf_open = aout_Open;
112 p_function_list->functions.aout.pf_setformat = aout_SetFormat;
113 p_function_list->functions.aout.pf_getbufinfo = aout_GetBufInfo;
114 p_function_list->functions.aout.pf_play = aout_Play;
115 p_function_list->functions.aout.pf_close = aout_Close;
118 /*****************************************************************************
119 * aout_Probe: probe the audio device and return a score
120 *****************************************************************************
121 * This function tries to probe for a Direct Sound device and returns a
122 * score to the plugin manager so that it can select the best plugin.
123 *****************************************************************************/
124 static int aout_Probe( probedata_t *p_data )
126 /* For now just assume the computer has a sound device */
127 if( TestMethod( AOUT_METHOD_VAR, "directx" ) )
134 /*****************************************************************************
135 * aout_Open: open the audio device
136 *****************************************************************************
137 * This function opens and setups Direct Sound.
138 *****************************************************************************/
139 static int aout_Open( aout_thread_t *p_aout )
142 DSBUFFERDESC dsbuffer_desc;
143 WAVEFORMATEX waveformat;
145 intf_WarnMsg( 3, "aout: DirectX aout_Open ");
147 /* Allocate structure */
148 p_aout->p_sys = malloc( sizeof( aout_sys_t ) );
150 if( p_aout->p_sys == NULL )
152 intf_ErrMsg( "aout error: %s", strerror(ENOMEM) );
156 /* Initialize some variables */
157 p_aout->p_sys->p_dsobject = NULL;
158 p_aout->p_sys->p_dsbuffer_primary = NULL;
159 p_aout->p_sys->p_dsbuffer = NULL;
161 p_aout->psz_device = 0;
162 p_aout->i_format = AOUT_FORMAT_DEFAULT;
163 p_aout->i_channels = 1 + main_GetIntVariable( AOUT_STEREO_VAR,
164 AOUT_STEREO_DEFAULT );
165 p_aout->l_rate = main_GetIntVariable( AOUT_RATE_VAR,
168 /* Initialise DirectSound */
169 if( DirectxInitDSound( p_aout ) )
171 intf_WarnMsg( 3, "aout: can't initialise DirectSound ");
175 /* Obtain (not create) Direct Sound primary buffer */
176 memset( &dsbuffer_desc, 0, sizeof(DSBUFFERDESC) );
177 dsbuffer_desc.dwSize = sizeof(DSBUFFERDESC);
178 dsbuffer_desc.dwFlags = DSBCAPS_PRIMARYBUFFER;
179 intf_WarnMsg( 3, "aout: Create direct sound primary buffer ");
180 dsresult = IDirectSound_CreateSoundBuffer(p_aout->p_sys->p_dsobject,
182 &p_aout->p_sys->p_dsbuffer_primary,
184 if( dsresult != DS_OK )
186 intf_WarnMsg( 3, "aout: can't create direct sound primary buffer ");
187 IDirectSound_Release( p_aout->p_sys->p_dsobject );
188 p_aout->p_sys->p_dsobject = NULL;
189 p_aout->p_sys->p_dsbuffer_primary = NULL;
193 /* Set Direct Sound primary buffer format because the default value set by
194 * Windows is usually not the high quality value */
195 memset(&waveformat, 0, sizeof(WAVEFORMATEX));
196 waveformat.wFormatTag = WAVE_FORMAT_PCM;
197 waveformat.nChannels = 2;
198 waveformat.nSamplesPerSec = 44100;
199 waveformat.wBitsPerSample = 16;
200 waveformat.nBlockAlign = waveformat.wBitsPerSample / 8 *
201 waveformat.nChannels;
202 waveformat.nAvgBytesPerSec = waveformat.nSamplesPerSec *
203 waveformat.nBlockAlign;
205 dsresult = IDirectSoundBuffer_SetFormat(p_aout->p_sys->p_dsbuffer_primary,
207 if( dsresult != DS_OK )
209 intf_WarnMsg( 3, "aout: can't set primary buffer format");
213 /* ensure the primary buffer is playing. We won't actually hear anything
214 * until the secondary buffer is playing */
215 dsresult = IDirectSoundBuffer_Play( p_aout->p_sys->p_dsbuffer_primary,
219 if( dsresult != DS_OK )
221 intf_WarnMsg( 3, "aout: can't play direct sound primary buffer ");
222 IDirectSound_Release( p_aout->p_sys->p_dsbuffer_primary );
223 IDirectSound_Release( p_aout->p_sys->p_dsobject );
224 p_aout->p_sys->p_dsobject = NULL;
225 p_aout->p_sys->p_dsbuffer_primary = NULL;
233 /*****************************************************************************
234 * aout_SetFormat: reset the audio device and sets its format
235 *****************************************************************************
236 * This functions set a new audio format.
237 * For this we need to close the current secondary buffer and create another
238 * one with the desired format.
239 *****************************************************************************/
240 static int aout_SetFormat( aout_thread_t *p_aout )
244 intf_WarnMsg( 3, "aout: DirectX aout_SetFormat ");
246 /* first release the current secondary buffer */
247 if( p_aout->p_sys->p_dsbuffer != NULL )
249 IDirectSoundBuffer_Release( p_aout->p_sys->p_dsbuffer );
250 p_aout->p_sys->p_dsbuffer = NULL;
253 /* then create a new secondary buffer */
254 dsresult = DirectxCreateSecondaryBuffer( p_aout );
255 if( dsresult != DS_OK )
257 intf_WarnMsg( 3, "aout: DirectX aout_SetFormat cannot create buffer");
261 p_aout->i_latency = 0;
266 /*****************************************************************************
267 * aout_GetBufInfo: buffer status query
268 *****************************************************************************
269 * returns the number of bytes in the audio buffer compared to the size of
271 *****************************************************************************/
272 static long aout_GetBufInfo( aout_thread_t *p_aout, long l_buffer_limit )
274 long l_play_position, l_notused, l_result;
277 dsresult = IDirectSoundBuffer_GetCurrentPosition(p_aout->p_sys->p_dsbuffer,
278 &l_play_position, &l_notused);
279 if( dsresult == DSERR_BUFFERLOST )
281 IDirectSoundBuffer_Restore( p_aout->p_sys->p_dsbuffer );
282 dsresult = IDirectSoundBuffer_GetCurrentPosition(
283 p_aout->p_sys->p_dsbuffer,
284 &l_play_position, &l_notused
287 if( dsresult != DS_OK )
289 intf_WarnMsg(3,"aout: DirectX aout_GetBufInfo cannot get current pos");
290 return( l_buffer_limit );
294 /* temporary hack. When you start playing a new file, the play position
295 * doesn't start changing immediatly, even though sound is already
296 * playing from the sound card */
297 if( l_play_position == 0 )
299 intf_WarnMsg( 5, "aout: DirectX aout_GetBufInfo: %li", l_buffer_limit);
300 return( l_buffer_limit );
304 l_result = (p_aout->p_sys->l_write_position >= l_play_position) ?
305 (p_aout->p_sys->l_write_position - l_play_position) /2
306 : (p_aout->p_sys->l_buffer_size - l_play_position
307 + p_aout->p_sys->l_write_position) /2 ;
309 intf_WarnMsg( 5, "aout: DirectX aout_GetBufInfo: %li", l_result);
313 /*****************************************************************************
314 * aout_Play: play a sound buffer
315 *****************************************************************************
316 * This function writes a buffer of i_length bytes
317 *****************************************************************************/
318 static void aout_Play( aout_thread_t *p_aout, byte_t *buffer, int i_size )
320 VOID *p_write_position, *p_start_buffer;
321 long l_bytes1, l_bytes2;
322 long l_play_position, l_notused, l_buffer_free_length;
325 /* We want to copy data to the circular sound buffer, so we first need to
326 * find out were in the buffer we can write our data */
327 dsresult = IDirectSoundBuffer_GetCurrentPosition(p_aout->p_sys->p_dsbuffer,
330 if( dsresult == DSERR_BUFFERLOST )
332 IDirectSoundBuffer_Restore( p_aout->p_sys->p_dsbuffer );
333 dsresult = IDirectSoundBuffer_GetCurrentPosition(
334 p_aout->p_sys->p_dsbuffer,
335 &l_play_position, &l_notused
338 if( dsresult != DS_OK )
340 intf_WarnMsg( 3, "aout: DirectX aout_Play can'get buffer position");
344 /* check that we are not overflowing the circular buffer (everything should
345 * be alright but just in case) */
346 l_buffer_free_length = l_play_position - p_aout->p_sys->l_write_position;
347 if( l_buffer_free_length <= 0 )
348 l_buffer_free_length += p_aout->p_sys->l_buffer_size;
350 if( i_size > l_buffer_free_length )
352 intf_WarnMsg( 3, "aout: DirectX aout_Play buffer overflow: size %i, free %i !!!", i_size, l_buffer_free_length);
353 intf_WarnMsg( 3, "aout: DirectX aout_Play buffer overflow: writepos %i, readpos %i !!!", p_aout->p_sys->l_write_position, l_play_position);
354 /*i_size = l_buffer_free_length;*/
356 /* Update the write pointer */
357 p_aout->p_sys->l_write_position = l_notused;
363 intf_WarnMsg( 4, "aout: DirectX aout_Play buffer: size %i, free %i !!!"
364 , i_size, l_buffer_free_length);
365 intf_WarnMsg( 4, "aout: DirectX aout_Play buffer: writepos %i, readpos %i !!!", p_aout->p_sys->l_write_position, l_play_position);
370 /* Before copying anything, we have to lock the buffer */
371 dsresult = IDirectSoundBuffer_Lock( p_aout->p_sys->p_dsbuffer,
372 p_aout->p_sys->l_write_position, /* Offset of lock start */
373 i_size, /* Number of bytes to lock */
374 &p_write_position, /* Address of lock start */
375 &l_bytes1, /* Count of bytes locked before wrap around */
376 &p_start_buffer, /* Buffer adress (if wrap around) */
377 &l_bytes2, /* Count of bytes after wrap around */
379 if( dsresult == DSERR_BUFFERLOST )
381 IDirectSoundBuffer_Restore( p_aout->p_sys->p_dsbuffer );
382 dsresult = IDirectSoundBuffer_Lock( p_aout->p_sys->p_dsbuffer,
383 p_aout->p_sys->l_write_position,
392 if( dsresult != DS_OK )
394 intf_WarnMsg( 3, "aout: DirectX aout_Play can't lock buffer");
398 /* Now do the actual memcopy (two memcpy because the buffer is circular) */
399 memcpy( p_write_position, buffer, l_bytes1 );
400 if( p_start_buffer != NULL )
402 memcpy( p_start_buffer, buffer + l_bytes1, l_bytes2 );
405 /* Now the data has been copied, unlock the buffer */
406 IDirectSoundBuffer_Unlock( p_aout->p_sys->p_dsbuffer,
407 p_write_position, l_bytes1, p_start_buffer, l_bytes2 );
409 /* Update the write position index of the buffer*/
410 p_aout->p_sys->l_write_position += i_size;
411 p_aout->p_sys->l_write_position %= p_aout->p_sys->l_buffer_size;
413 /* The play function has no effect if the buffer is already playing */
414 dsresult = IDirectSoundBuffer_Play( p_aout->p_sys->p_dsbuffer,
417 DSBPLAY_LOOPING ); /* Flags */
418 if( dsresult == DSERR_BUFFERLOST )
420 IDirectSoundBuffer_Restore( p_aout->p_sys->p_dsbuffer );
421 dsresult = IDirectSoundBuffer_Play( p_aout->p_sys->p_dsbuffer,
424 DSBPLAY_LOOPING ); /* Flags */
426 if( dsresult != DS_OK )
428 intf_WarnMsg( 3, "aout: DirectX aout_Play can't play buffer");
434 /*****************************************************************************
435 * aout_Close: close the audio device
436 *****************************************************************************/
437 static void aout_Close( aout_thread_t *p_aout )
440 intf_WarnMsg( 3, "aout: DirectX aout_Close ");
442 /* make sure the buffer isn't playing */
443 if( p_aout->p_sys->p_dsbuffer != NULL )
445 IDirectSoundBuffer_Stop( p_aout->p_sys->p_dsbuffer );
448 /* first release the secondary buffer */
449 if( p_aout->p_sys->p_dsbuffer != NULL )
451 IDirectSoundBuffer_Release( p_aout->p_sys->p_dsbuffer );
452 p_aout->p_sys->p_dsbuffer = NULL;
455 /* then release the primary buffer */
456 if( p_aout->p_sys->p_dsbuffer_primary != NULL )
458 IDirectSoundBuffer_Release( p_aout->p_sys->p_dsbuffer_primary );
459 p_aout->p_sys->p_dsbuffer_primary = NULL;
462 /* finally release the DirectSound object */
463 if( p_aout->p_sys->p_dsobject != NULL )
465 IDirectSound_Release( p_aout->p_sys->p_dsobject );
466 p_aout->p_sys->p_dsobject = NULL;
469 /* free DSOUND.DLL */
470 if( p_aout->p_sys->hdsound_dll != NULL )
472 FreeLibrary( p_aout->p_sys->hdsound_dll );
473 p_aout->p_sys->hdsound_dll = NULL;
476 /* Close the Output. */
477 if ( p_aout->p_sys != NULL )
479 free( p_aout->p_sys );
480 p_aout->p_sys = NULL;
484 /*****************************************************************************
486 *****************************************************************************
487 *****************************************************************************/
488 static int DirectxInitDSound( aout_thread_t *p_aout )
490 HRESULT (WINAPI *OurDirectSoundCreate)(LPGUID, LPDIRECTSOUND *, LPUNKNOWN);
492 p_aout->p_sys->hdsound_dll = LoadLibrary("DSOUND.DLL");
493 if( p_aout->p_sys->hdsound_dll == NULL )
495 intf_WarnMsg( 3, "aout: can't open DSOUND.DLL ");
499 OurDirectSoundCreate = (void *)GetProcAddress( p_aout->p_sys->hdsound_dll,
500 "DirectSoundCreate" );
502 if( OurDirectSoundCreate == NULL )
504 intf_WarnMsg( 3, "aout: GetProcAddress FAILED ");
505 FreeLibrary( p_aout->p_sys->hdsound_dll );
506 p_aout->p_sys->hdsound_dll = NULL;
510 /* Create the direct sound object */
511 if( OurDirectSoundCreate(NULL, &p_aout->p_sys->p_dsobject, NULL) != DS_OK )
513 intf_WarnMsg( 3, "aout: can't create a direct sound device ");
514 p_aout->p_sys->p_dsobject = NULL;
515 FreeLibrary( p_aout->p_sys->hdsound_dll );
516 p_aout->p_sys->hdsound_dll = NULL;
520 /* Set DirectSound Cooperative level, ie what control we want over Windows
521 * sound device. In our case, DSSCL_EXCLUSIVE means that we can modify the
522 * settings of the primary buffer, but also that only the sound of our
523 * application will be hearable when it will have the focus.
524 * !!! (this is not really working as intended yet because to set the
525 * cooperative level you need the window handle of your application, and
526 * I don't know of any easy way to get it. Especially since we might play
527 * sound without any video, and so what window handle should we use ???
528 * The hack for now is to use the Desktop window handle - it seems to be
530 if( IDirectSound_SetCooperativeLevel(p_aout->p_sys->p_dsobject,
534 intf_WarnMsg( 3, "aout: can't set direct sound cooperative level ");
540 /*****************************************************************************
541 * DirectxCreateSecondaryBuffer
542 *****************************************************************************
543 * This function creates the buffer we'll use to play audio.
544 * In DirectSound there are two kinds of buffers:
545 * - the primary buffer: which is the actual buffer that the soundcard plays
546 * - the secondary buffer(s): these buffers are the one actually used by
547 * applications and DirectSound takes care of mixing them into the primary.
549 * Once you create a secondary buffer, you cannot change its format anymore so
550 * you have to release the current and create another one.
551 *****************************************************************************/
552 static int DirectxCreateSecondaryBuffer( aout_thread_t *p_aout )
554 WAVEFORMATEX waveformat;
555 DSBUFFERDESC dsbdesc;
558 /* First set the buffer format */
559 memset(&waveformat, 0, sizeof(WAVEFORMATEX));
560 waveformat.wFormatTag = WAVE_FORMAT_PCM;
561 waveformat.nChannels = p_aout->i_channels;
562 waveformat.nSamplesPerSec = p_aout->l_rate;
563 waveformat.wBitsPerSample = 16;
564 waveformat.nBlockAlign = waveformat.wBitsPerSample / 8 *
565 waveformat.nChannels;
566 waveformat.nAvgBytesPerSec = waveformat.nSamplesPerSec *
567 waveformat.nBlockAlign;
569 /* Then fill in the descriptor */
570 memset(&dsbdesc, 0, sizeof(DSBUFFERDESC));
571 dsbdesc.dwSize = sizeof(DSBUFFERDESC);
572 dsbdesc.dwFlags = DSBCAPS_GETCURRENTPOSITION2/* Better position accuracy */
573 | DSBCAPS_GLOBALFOCUS; /* Allows background playing */
574 dsbdesc.dwBufferBytes = waveformat.nAvgBytesPerSec * 2; /* 2 sec buffer */
575 dsbdesc.lpwfxFormat = &waveformat;
577 if( IDirectSound_CreateSoundBuffer( p_aout->p_sys->p_dsobject,
579 &p_aout->p_sys->p_dsbuffer,
582 intf_WarnMsg( 3, "aout: can't create direct sound secondary buffer ");
583 p_aout->p_sys->p_dsbuffer = NULL;
587 /* backup the size of the secondary sound buffer */
588 memset(&dsbcaps, 0, sizeof(DSBCAPS));
589 dsbcaps.dwSize = sizeof(DSBCAPS);
590 IDirectSoundBuffer_GetCaps( p_aout->p_sys->p_dsbuffer, &dsbcaps );
591 p_aout->p_sys->l_buffer_size = dsbcaps.dwBufferBytes;
592 p_aout->p_sys->l_write_position = 0;
593 intf_WarnMsg( 3, "aout: DirectX DirectxCreateSecondaryBuffer: %li",
594 p_aout->p_sys->l_buffer_size);