]> git.sesse.net Git - vlc/blob - plugins/directx/aout_directx.c
* DirectX plugin by Gildas Bazin <gbazin@netcourrier.com>.
[vlc] / plugins / directx / aout_directx.c
1 /*****************************************************************************
2  * aout_directx.c: Windows DirectX audio output method
3  *****************************************************************************
4  * Copyright (C) 1999, 2000 VideoLAN
5  * $Id: aout_directx.c,v 1.1 2001/06/02 01:09:03 sam Exp $
6  *
7  * Authors: Gildas Bazin <gbazin@netcourrier.com>
8  *
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.
13  *
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.
18  *
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  *****************************************************************************/
23
24 #define MODULE_NAME directx
25 #include "modules_inner.h"
26
27 /* The most important this 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
31  */
32
33
34 /*****************************************************************************
35  * Preamble
36  *****************************************************************************/
37 #include "defs.h"
38
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() */
45
46 #include "config.h"
47 #include "common.h"                                     /* boolean_t, byte_t */
48 #include "threads.h"
49 #include "mtime.h"
50 #include "tests.h"
51
52 #include "directx.h"
53
54 #include "audio_output.h"                                   /* aout_thread_t */
55
56 #include "intf_msg.h"                        /* intf_DbgMsg(), intf_ErrMsg() */
57 #include "main.h"
58
59 #include "modules.h"
60
61 /*****************************************************************************
62  * aout_sys_t: directx audio output method descriptor
63  *****************************************************************************
64  * This structure is part of the audio output thread descriptor.
65  * It describes the direct sound specific properties of an audio device.
66  *****************************************************************************/
67
68 typedef struct aout_sys_s
69 {
70     LPDIRECTSOUND       p_dsobject;              /* main Direct Sound object */
71
72     LPDIRECTSOUNDBUFFER p_dsbuffer_primary;     /* the actual sound card buffer
73                                                    (not used directly) */
74
75     LPDIRECTSOUNDBUFFER p_dsbuffer;   /* the sound buffer we use (direct sound
76                                        * takes care of mixing all the
77                                        * secondary buffers into the primary) */
78
79     long l_buffer_size;                       /* secondary sound buffer size */
80     long l_write_position;             /* next write position for the buffer */
81
82     boolean_t b_active;
83
84 } aout_sys_t;
85
86 /*****************************************************************************
87  * Local prototypes.
88  *****************************************************************************/
89 static int     aout_Probe       ( probedata_t *p_data );
90 static int     aout_Open        ( aout_thread_t *p_aout );
91 static int     aout_SetFormat   ( aout_thread_t *p_aout );
92 static long    aout_GetBufInfo  ( aout_thread_t *p_aout, long l_buffer_info );
93 static void    aout_Play        ( aout_thread_t *p_aout,
94                                   byte_t *buffer, int i_size );
95 static void    aout_Close       ( aout_thread_t *p_aout );
96
97 /* local function */
98 static int windx_CreateSecondaryBuffer( aout_thread_t *p_aout );
99
100 /*****************************************************************************
101  * Functions exported as capabilities. They are declared as static so that
102  * we don't pollute the namespace too much.
103  *****************************************************************************/
104 void _M( aout_getfunctions )( function_list_t * p_function_list )
105 {
106     p_function_list->pf_probe = aout_Probe;
107     p_function_list->functions.aout.pf_open = aout_Open;
108     p_function_list->functions.aout.pf_setformat = aout_SetFormat;
109     p_function_list->functions.aout.pf_getbufinfo = aout_GetBufInfo;
110     p_function_list->functions.aout.pf_play = aout_Play;
111     p_function_list->functions.aout.pf_close = aout_Close;
112 }
113
114 /*****************************************************************************
115  * aout_Probe: probe the audio device and return a score
116  *****************************************************************************
117  * This function tries to probe for a Direct Sound  device and returns a
118  * score to the plugin manager so that it can select the best plugin.
119  *****************************************************************************/
120 static int aout_Probe( probedata_t *p_data )
121 {
122     /* For now just assume the computer has a sound device */
123     if( TestMethod( AOUT_METHOD_VAR, "directx" ) )
124     {
125         return( 999 );
126     }
127     return( 400 );
128 }
129
130 /*****************************************************************************
131  * aout_Open: open the audio device
132  *****************************************************************************
133  * This function opens and setups Direct Sound.
134  *****************************************************************************/
135 static int aout_Open( aout_thread_t *p_aout )
136 {
137 #if 0
138     HRESULT dsresult;
139     DSBUFFERDESC dsbuffer_desc;
140     WAVEFORMATEX waveformat;
141 #endif
142
143    /* Allocate structure */
144     p_aout->p_sys = malloc( sizeof( aout_sys_t ) );
145
146     if( p_aout->p_sys == NULL )
147     {
148         intf_ErrMsg( "aout error: %s", strerror(ENOMEM) );
149         return( 1 );
150     }
151
152     /* Initialize some variables */
153     p_aout->p_sys->p_dsobject = NULL;
154     p_aout->p_sys->p_dsbuffer_primary = NULL;
155     p_aout->p_sys->p_dsbuffer = NULL;
156
157     p_aout->psz_device = 0;
158     p_aout->i_format   = AOUT_FORMAT_DEFAULT;
159     p_aout->i_channels = 1 + main_GetIntVariable( AOUT_STEREO_VAR,
160                                                   AOUT_STEREO_DEFAULT );
161     p_aout->l_rate     = main_GetIntVariable( AOUT_RATE_VAR,
162                                               AOUT_RATE_DEFAULT );
163
164     /* Create the direct sound object */
165     if( DirectSoundCreate(NULL, &p_aout->p_sys->p_dsobject, NULL) != DS_OK )
166     {
167         intf_WarnMsg( 3, "aout: can't create a direct sound device ");
168         p_aout->p_sys->p_dsobject = NULL;
169         return( 1 );
170     }
171
172     /* Set DirectSound Cooperative level, ie what control we want over Windows
173      * sound device. In our case, DSSCL_EXCLUSIVE means that we can modify the
174      * settings of the primary buffer, but also that only the sound of our
175      * application will be hearable when it will have the focus.
176      * !!! (this is not really working as intended yet because to set the
177      * cooperative level you need the window handle of your application, and
178      * I don't know of any easy way to get it. Especially since we might play
179      * sound without any video, and so what window handle should we use ???
180      * The hack for now is to use the Desktop window handle - it seems to be
181      * working */
182     if( IDirectSound_SetCooperativeLevel(p_aout->p_sys->p_dsobject,
183                                          GetDesktopWindow(),
184                                          DSSCL_EXCLUSIVE) )
185     {
186         intf_WarnMsg( 3, "aout: can't set direct sound cooperative level ");
187     }
188
189 #if 0
190     /* Obtain (not create) Direct Sound primary buffer */
191     memset( &dsbuffer_desc, 0, sizeof(DSBUFFERDESC) );
192     dsbuffer_desc.dwSize = sizeof(DSBUFFERDESC);
193     dsbuffer_desc.dwFlags = DSBCAPS_PRIMARYBUFFER;
194     intf_WarnMsg( 3, "aout: Create direct sound primary buffer ");
195     dsresult = IDirectSound_CreateSoundBuffer(p_aout->p_sys->p_dsobject,
196                                             &dsbuffer_desc,
197                                             &p_aout->p_sys->p_dsbuffer_primary,
198                                             NULL);
199     if( dsresult != DS_OK )
200     {
201         intf_WarnMsg( 3, "aout: can't create direct sound primary buffer ");
202         IDirectSound_Release( p_aout->p_sys->p_dsobject );
203         p_aout->p_sys->p_dsobject = NULL;
204         p_aout->p_sys->p_dsbuffer_primary = NULL;
205         return( 1 );
206     }
207
208     /* Set Direct Sound primary buffer format because the default value set by
209      * Windows is usually not the high quality value */
210     memset(&waveformat, 0, sizeof(WAVEFORMATEX)); 
211     waveformat.wFormatTag = WAVE_FORMAT_PCM; 
212     waveformat.nChannels = 2; 
213     waveformat.nSamplesPerSec = 44100; 
214     waveformat.wBitsPerSample = 16; 
215     waveformat.nBlockAlign = waveformat.wBitsPerSample / 8 *
216                                  waveformat.nChannels;
217     waveformat.nAvgBytesPerSec = waveformat.nSamplesPerSec *
218                                      waveformat.nBlockAlign;
219
220     dsresult = IDirectSoundBuffer_SetFormat(p_aout->p_sys->p_dsbuffer_primary,
221                                             &waveformat);
222     if( dsresult != DS_OK )
223     {
224         intf_WarnMsg( 3, "aout: can't set primary buffer format");
225     }
226
227     /* ensure the primary buffer is playing. We won't actually hear anything
228      * until the secondary buffer is playing */
229     dsresult = IDirectSoundBuffer_Play( p_aout->p_sys->p_dsbuffer_primary,
230                                         0,
231                                         0,
232                                         DSBPLAY_LOOPING);
233     if( dsresult != DS_OK )
234     {
235         intf_WarnMsg( 3, "aout: can't play direct sound primary buffer ");
236         IDirectSound_Release( p_aout->p_sys->p_dsbuffer_primary );
237         IDirectSound_Release( p_aout->p_sys->p_dsobject );
238         p_aout->p_sys->p_dsobject = NULL;
239         p_aout->p_sys->p_dsbuffer_primary = NULL;
240         return( 1 );
241     }
242 #endif
243
244     /* Now create the buffer that we'll actually use: the secondary buffer */
245     if( windx_CreateSecondaryBuffer( p_aout ) )
246     {
247         intf_WarnMsg( 3, "aout: can't create direct sound secondary buffer ");
248 #if 0
249         IDirectSound_Release( p_aout->p_sys->p_dsbuffer_primary );
250 #endif
251         IDirectSound_Release( p_aout->p_sys->p_dsobject );
252         p_aout->p_sys->p_dsobject = NULL;
253         p_aout->p_sys->p_dsbuffer_primary = NULL;
254         p_aout->p_sys->p_dsbuffer = NULL;
255         return( 1 );
256     }
257
258     return( 0 );
259 }
260
261 /*****************************************************************************
262  * aout_SetFormat: reset the audio device and sets its format
263  *****************************************************************************
264  * This functions set a new audio format.
265  * For this we need to close the current secondary buffer and create another
266  * one with the desired format.
267  *****************************************************************************/
268 static int aout_SetFormat( aout_thread_t *p_aout )
269 {
270     HRESULT dsresult;
271
272     /* first release the current secondary buffer */
273     if( p_aout->p_sys->p_dsbuffer != NULL )
274     {
275         IDirectSoundBuffer_Release( p_aout->p_sys->p_dsbuffer );
276         p_aout->p_sys->p_dsbuffer = NULL;
277     }
278
279     /* then create a new secondary buffer */
280     dsresult = windx_CreateSecondaryBuffer( p_aout );    
281     if( dsresult != DS_OK )
282     {
283         intf_WarnMsg( 3, "aout: WinDX aout_SetFormat cannot create buffer");
284         return( 1 );
285     }
286   
287     return( 0 );
288 }
289
290 /*****************************************************************************
291  * aout_GetBufInfo: buffer status query
292  *****************************************************************************
293  * returns the number of bytes in the audio buffer compared to the size of
294  * l_buffer_limit...
295  *****************************************************************************/
296 static long aout_GetBufInfo( aout_thread_t *p_aout, long l_buffer_limit )
297 {
298     long l_play_position, l_notused, l_result;
299     HRESULT dsresult;
300
301     dsresult = IDirectSoundBuffer_GetCurrentPosition(p_aout->p_sys->p_dsbuffer,
302                                                  &l_play_position, &l_notused);
303     if( dsresult == DSERR_BUFFERLOST )
304     {
305         IDirectSoundBuffer_Restore( p_aout->p_sys->p_dsbuffer );
306         dsresult = IDirectSoundBuffer_GetCurrentPosition(
307                                                  p_aout->p_sys->p_dsbuffer,
308                                                  &l_play_position, &l_notused
309                                                         );
310     }
311     if( dsresult != DS_OK )
312     {
313         intf_WarnMsg( 3, "aout: WinDX aout_GetBufInfo cannot get current pos");
314         return( l_buffer_limit );
315     }
316
317     l_result = ((p_aout->p_sys->l_write_position >= l_play_position) ?
318                 (p_aout->p_sys->l_write_position - l_play_position)/2
319                 : (p_aout->p_sys->l_buffer_size - l_play_position
320                     + p_aout->p_sys->l_write_position)/2 );
321
322     intf_WarnMsg( 5, "aout: WinDX aout_GetBufInfo: %li", l_result);
323     return l_result;
324 }
325
326 /*****************************************************************************
327  * aout_Play: play a sound buffer
328  *****************************************************************************
329  * This function writes a buffer of i_length bytes
330  *****************************************************************************/
331 static void aout_Play( aout_thread_t *p_aout, byte_t *buffer, int i_size )
332 {
333     VOID            *p_write_position, *p_start_buffer;
334     long            l_bytes1, l_bytes2;
335     long            l_play_position, l_notused, l_buffer_free_length;
336     HRESULT         dsresult;
337
338     /* We want to copy data to the circular sound buffer, so first we need to
339      * find out were in the buffer we can write our data */
340     dsresult = IDirectSoundBuffer_GetCurrentPosition(p_aout->p_sys->p_dsbuffer,
341                                                      &l_play_position,
342                                                      &l_notused);
343     if( dsresult == DSERR_BUFFERLOST )
344     {
345         IDirectSoundBuffer_Restore( p_aout->p_sys->p_dsbuffer );
346         dsresult = IDirectSoundBuffer_GetCurrentPosition(
347                                                  p_aout->p_sys->p_dsbuffer,
348                                                  &l_play_position, &l_notused
349                                                         );
350     }
351     if( dsresult != DS_OK )
352     {
353         intf_WarnMsg( 3, "aout: WinDX aout_Play can'get buffer position");
354     }
355
356     /* check that we are not overflowing the circular buffer (everything should
357      * be alright but just in case) */
358     l_buffer_free_length =  l_play_position - p_aout->p_sys->l_write_position;
359     if( l_buffer_free_length <= 0 )
360         l_buffer_free_length += p_aout->p_sys->l_buffer_size  ;
361
362     if( i_size > l_buffer_free_length )
363     {
364         intf_WarnMsg( 3, "aout: WinDX aout_Play buffer overflow: size %i, free %i !!!", i_size, l_buffer_free_length);
365         intf_WarnMsg( 3, "aout: WinDX aout_Play buffer overflow: writepos %i, readpos %i !!!", l_play_position, p_aout->p_sys->l_write_position);
366         /*i_size = l_buffer_free_length;*/
367     }
368
369     /* Before copying anything, we have to lock the buffer */
370     dsresult = IDirectSoundBuffer_Lock( p_aout->p_sys->p_dsbuffer,
371                    p_aout->p_sys->l_write_position,  /* Offset of lock start */
372                    i_size,                        /* Number of bytes to lock */
373                    &p_write_position,               /* Address of lock start */
374                    &l_bytes1,    /* Count of bytes locked before wrap around */
375                    &p_start_buffer,        /* Buffer adress (if wrap around) */
376                    &l_bytes2,            /* Count of bytes after wrap around */
377                    0);                                              /* Flags */
378     if( dsresult == DSERR_BUFFERLOST )
379     {
380         IDirectSoundBuffer_Restore( p_aout->p_sys->p_dsbuffer );
381         dsresult = IDirectSoundBuffer_Lock( p_aout->p_sys->p_dsbuffer,
382                                             p_aout->p_sys->l_write_position,
383                                             i_size,
384                                             &p_write_position,
385                                             &l_bytes1,
386                                             &p_start_buffer,
387                                             &l_bytes2,
388                                             0);
389
390     }
391     if( dsresult != DS_OK )
392     {
393         intf_WarnMsg( 3, "aout: WinDX aout_Play can't lock buffer");
394         return;
395     }
396
397     /* Now do the actual memcopy (two memcpy because the buffer is circular) */
398     memcpy( p_write_position, buffer, l_bytes1 );
399     if( p_start_buffer != NULL )
400         memcpy( p_start_buffer, buffer + l_bytes1, l_bytes2 );
401
402     /* Now the data has been copied, unlock the buffer */
403     IDirectSoundBuffer_Unlock( p_aout->p_sys->p_dsbuffer, 
404             p_write_position, l_bytes1, p_start_buffer, l_bytes2 );
405
406     /* Update the write position index of the buffer*/
407     p_aout->p_sys->l_write_position += i_size;
408     p_aout->p_sys->l_write_position %= p_aout->p_sys->l_buffer_size;
409
410     /* The play function has no effect if the buffer is already playing */
411     dsresult = IDirectSoundBuffer_Play( p_aout->p_sys->p_dsbuffer,
412                                         0,                         /* Unused */
413                                         0,                         /* Unused */
414                                         DSBPLAY_LOOPING );          /* Flags */
415     if( dsresult == DSERR_BUFFERLOST )
416     {
417         IDirectSoundBuffer_Restore( p_aout->p_sys->p_dsbuffer );
418         dsresult = IDirectSoundBuffer_Play( p_aout->p_sys->p_dsbuffer,
419                                             0,                     /* Unused */
420                                             0,                     /* Unused */
421                                             DSBPLAY_LOOPING );      /* Flags */
422     }
423     if( dsresult != DS_OK )
424     {
425         intf_WarnMsg( 3, "aout: WinDX aout_Play can't play buffer");
426         return;
427     }
428
429 }
430
431 /*****************************************************************************
432  * aout_Close: close the audio device
433  *****************************************************************************/
434 static void aout_Close( aout_thread_t *p_aout )
435 {
436     /* make sure the buffer isn't playing */
437     if( p_aout->p_sys->p_dsbuffer != NULL )
438     {
439         IDirectSoundBuffer_Stop( p_aout->p_sys->p_dsbuffer );
440     }
441
442     /* first release the secondary buffer */
443     if( p_aout->p_sys->p_dsbuffer != NULL )
444     {
445         IDirectSoundBuffer_Release( p_aout->p_sys->p_dsbuffer );
446         p_aout->p_sys->p_dsbuffer = NULL;
447     }  
448
449     /* then release the primary buffer */
450     if( p_aout->p_sys->p_dsbuffer_primary != NULL )
451     {
452         IDirectSoundBuffer_Release( p_aout->p_sys->p_dsbuffer_primary );
453         p_aout->p_sys->p_dsbuffer_primary = NULL;
454     }  
455
456     /* finally release the DirectSound object */
457     if( p_aout->p_sys->p_dsobject != NULL )
458     {
459         IDirectSound_Release( p_aout->p_sys->p_dsobject );
460         p_aout->p_sys->p_dsobject = NULL;
461     }  
462     
463     /* Close the Output. */
464     if ( p_aout->p_sys != NULL )
465     { 
466         free( p_aout->p_sys );
467         p_aout->p_sys = NULL;
468     }
469 }
470
471 /*****************************************************************************
472  * windx_CreateSecondaryBuffer
473  *****************************************************************************
474  * This function creates the buffer we'll use to play audio.
475  * In DirectSound there are two kinds of buffers:
476  * - the primary buffer: which is the actual buffer that the soundcard plays
477  * - the secondary buffer(s): these buffers are the one actually used by
478  *    applications and DirectSound takes care of mixing them into the primary.
479  *
480  * Once you create a secondary buffer, you cannot change its format anymore so
481  * you have to release the current and create another one.
482  *****************************************************************************/
483 static int windx_CreateSecondaryBuffer( aout_thread_t *p_aout )
484 {
485     WAVEFORMATEX waveformat;
486     DSBUFFERDESC dsbdesc;
487     DSBCAPS      dsbcaps;
488     HRESULT      dsresult;
489
490     /* First set the buffer format */
491     memset(&waveformat, 0, sizeof(WAVEFORMATEX)); 
492     waveformat.wFormatTag = WAVE_FORMAT_PCM; 
493     waveformat.nChannels = p_aout->i_channels; 
494     waveformat.nSamplesPerSec = p_aout->l_rate; 
495     waveformat.wBitsPerSample = 16; 
496     waveformat.nBlockAlign = waveformat.wBitsPerSample / 8 *
497                                  waveformat.nChannels;
498     waveformat.nAvgBytesPerSec = waveformat.nSamplesPerSec *
499                                      waveformat.nBlockAlign;
500
501     /* Then fill in the descriptor */
502     memset(&dsbdesc, 0, sizeof(DSBUFFERDESC)); 
503     dsbdesc.dwSize = sizeof(DSBUFFERDESC); 
504     dsbdesc.dwFlags = DSBCAPS_GETCURRENTPOSITION2/* Better position accuracy */
505                     | DSBCAPS_GLOBALFOCUS;      /* Allows background playing */
506     /*              | DSBCAPS_CTRLPAN
507                     | DSBCAPS_CTRLVOLUME
508                     | DSBCAPS_CTRLFREQUENCY;
509     */
510     dsbdesc.dwBufferBytes = waveformat.nAvgBytesPerSec * 4; /* 4 sec buffer */
511     dsbdesc.lpwfxFormat = &waveformat; 
512  
513     if( IDirectSound_CreateSoundBuffer( p_aout->p_sys->p_dsobject,
514                                         &dsbdesc,
515                                         &p_aout->p_sys->p_dsbuffer,
516                                         NULL) != DS_OK )
517     {
518         intf_WarnMsg( 3, "aout: can't create direct sound secondary buffer ");
519         p_aout->p_sys->p_dsbuffer = NULL;
520         return( 1 );
521     }
522
523     /* backup the size of the secondary sound buffer */
524     memset(&dsbcaps, 0, sizeof(DSBCAPS)); 
525     dsbcaps.dwSize = sizeof(DSBCAPS); 
526     IDirectSoundBuffer_GetCaps( p_aout->p_sys->p_dsbuffer, &dsbcaps  );
527     p_aout->p_sys->l_buffer_size = dsbcaps.dwBufferBytes;
528     p_aout->p_sys->l_write_position = 0;
529     intf_WarnMsg( 3, "aout: WinDX WinDX_CreateSecondaryBuffer: %li",
530                   p_aout->p_sys->l_buffer_size);
531
532     /* make sure the buffer isn't playing */
533     IDirectSoundBuffer_Stop( p_aout->p_sys->p_dsbuffer );
534
535     /* reset play position, just to be sure (and after some tests it seems
536      * indeed necessary */
537     dsresult = IDirectSoundBuffer_SetCurrentPosition(p_aout->p_sys->p_dsbuffer,
538                                                      0 );
539     if( dsresult == DSERR_BUFFERLOST )
540     {
541         IDirectSoundBuffer_Restore( p_aout->p_sys->p_dsbuffer );
542         dsresult = IDirectSoundBuffer_SetCurrentPosition(
543                                                  p_aout->p_sys->p_dsbuffer,
544                                                  0 );
545     }
546     if( dsresult != DS_OK )
547     {
548         intf_WarnMsg( 3, "aout: WinDX CreateSecondary cannot wet current pos");
549     }
550
551     return( 0 );
552 }
553