]> git.sesse.net Git - vlc/blob - modules/video_output/directx/aout.c
532a6baa660d81c84d119061ecdb01d548992610
[vlc] / modules / video_output / directx / aout.c
1 /*****************************************************************************
2  * aout.c: Windows DirectX audio output method
3  *****************************************************************************
4  * Copyright (C) 2001 VideoLAN
5  * $Id: aout.c,v 1.9 2002/08/30 23:27:06 massiot 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 /*****************************************************************************
25  * Preamble
26  *****************************************************************************/
27 #include <errno.h>                                                 /* ENOMEM */
28 #include <fcntl.h>                                       /* open(), O_WRONLY */
29 #include <string.h>                                            /* strerror() */
30
31 #include <stdlib.h>                            /* calloc(), malloc(), free() */
32
33 #include <vlc/vlc.h>
34 #include <vlc/aout.h>
35 #include "aout_internal.h"
36
37 #include <mmsystem.h>
38 #include <dsound.h>
39
40 #define FRAME_SIZE 2048              /* The size is in samples, not in bytes */
41
42 /*****************************************************************************
43  * DirectSound GUIDs.
44  * Defining them here allows us to get rid of the dxguid library during
45  * the linking stage.
46  *****************************************************************************/
47 #include <initguid.h>
48 DEFINE_GUID(IID_IDirectSoundNotify, 0xb0210783, 0x89cd, 0x11d0, 0xaf, 0x8, 0x0, 0xa0, 0xc9, 0x25, 0xcd, 0x16);
49
50 /*****************************************************************************
51  * notification_thread_t: DirectX event thread
52  *****************************************************************************/
53 typedef struct notification_thread_t
54 {
55     VLC_COMMON_MEMBERS
56
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 */
60
61 } notification_thread_t;
62
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  *****************************************************************************/
69
70 struct aout_sys_t
71 {
72     LPDIRECTSOUND       p_dsobject;              /* main Direct Sound object */
73
74     LPDIRECTSOUNDBUFFER p_dsbuffer_primary;     /* the actual sound card buffer
75                                                    (not used directly) */
76
77     LPDIRECTSOUNDBUFFER p_dsbuffer;   /* the sound buffer we use (direct sound
78                                        * takes care of mixing all the
79                                        * secondary buffers into the primary) */
80
81     LPDIRECTSOUNDNOTIFY p_dsnotify;         /* the position notify interface */
82
83     HINSTANCE           hdsound_dll;      /* handle of the opened dsound dll */
84
85     vlc_mutex_t buffer_lock;                            /* audio buffer lock */
86
87     notification_thread_t * p_notif;                 /* DirectSoundThread id */
88
89 };
90
91 /*****************************************************************************
92  * Local prototypes.
93  *****************************************************************************/
94 static void Play      ( aout_instance_t * );
95
96 /* local functions */
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 * );
101
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 )
108 {
109     aout_instance_t * p_aout = (aout_instance_t *)p_this;
110     HRESULT dsresult;
111     DSBUFFERDESC dsbuffer_desc;
112
113     msg_Dbg( p_aout, "Open" );
114
115    /* Allocate structure */
116     p_aout->output.p_sys = malloc( sizeof( aout_sys_t ) );
117     if( p_aout->output.p_sys == NULL )
118     {
119         msg_Err( p_aout, "out of memory" );
120         return 1;
121     }
122
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 );
130
131     p_aout->output.pf_play = Play;
132
133     /* Initialise DirectSound */
134     if( DirectxInitDSound( p_aout ) )
135     {
136         msg_Err( p_aout, "cannot initialize DirectSound" );
137         goto error;
138     }
139
140     /* Obtain (not create) Direct Sound primary buffer */
141     memset( &dsbuffer_desc, 0, sizeof(DSBUFFERDESC) );
142     dsbuffer_desc.dwSize = sizeof(DSBUFFERDESC);
143     dsbuffer_desc.dwFlags = DSBCAPS_PRIMARYBUFFER;
144     msg_Warn( p_aout, "create direct sound primary buffer" );
145     dsresult = IDirectSound_CreateSoundBuffer(p_aout->output.p_sys->p_dsobject,
146                                      &dsbuffer_desc,
147                                      &p_aout->output.p_sys->p_dsbuffer_primary,
148                                      NULL);
149     if( dsresult != DS_OK )
150     {
151         msg_Err( p_aout, "cannot create direct sound primary buffer" );
152         goto error;
153     }
154
155     /* Now we need to setup DirectSound play notification */
156     p_aout->output.p_sys->p_notif =
157         vlc_object_create( p_aout, sizeof(notification_thread_t) );
158     p_aout->output.p_sys->p_notif->p_aout = p_aout;
159
160     /* first we need to create the notification events */
161     p_aout->output.p_sys->p_notif->p_events[0].hEventNotify =
162         CreateEvent( NULL, FALSE, FALSE, NULL );
163     p_aout->output.p_sys->p_notif->p_events[1].hEventNotify =
164         CreateEvent( NULL, FALSE, FALSE, NULL );
165
166     vlc_mutex_lock( &p_aout->output.p_sys->buffer_lock );
167
168     /* first release the current secondary buffer */
169     DirectxDestroySecondaryBuffer( p_aout );
170
171     /* calculate the frame size in bytes */
172     p_aout->output.p_sys->p_notif->i_buffer_size = FRAME_SIZE * sizeof(s16)
173                                             * p_aout->output.output.i_channels;
174
175     /* then create a new secondary buffer */
176     if( DirectxCreateSecondaryBuffer( p_aout ) )
177     {
178         msg_Err( p_aout, "cannot create buffer" );
179         vlc_mutex_unlock( &p_aout->output.p_sys->buffer_lock );
180         return 1;
181     }
182
183     vlc_mutex_unlock( &p_aout->output.p_sys->buffer_lock );
184
185     /* start playing the buffer */
186     dsresult = IDirectSoundBuffer_Play( p_aout->output.p_sys->p_dsbuffer,
187                                         0,                         /* Unused */
188                                         0,                         /* Unused */
189                                         DSBPLAY_LOOPING );          /* Flags */
190     if( dsresult == DSERR_BUFFERLOST )
191     {
192         IDirectSoundBuffer_Restore( p_aout->output.p_sys->p_dsbuffer );
193         dsresult = IDirectSoundBuffer_Play( p_aout->output.p_sys->p_dsbuffer,
194                                             0,                     /* Unused */
195                                             0,                     /* Unused */
196                                             DSBPLAY_LOOPING );      /* Flags */
197     }
198     if( dsresult != DS_OK )
199     {
200         msg_Warn( p_aout, "cannot play buffer" );
201     }
202
203     /* then launch the notification thread */
204     msg_Dbg( p_aout, "creating DirectSoundThread" );
205     if( vlc_thread_create( p_aout->output.p_sys->p_notif,
206                            "DirectSound Notification Thread",
207                            DirectSoundThread, VLC_THREAD_PRIORITY_OUTPUT, 1 ) )
208     {
209         msg_Err( p_aout, "cannot create DirectSoundThread" );
210         goto error;
211     }
212
213     vlc_object_attach( p_aout->output.p_sys->p_notif, p_aout );
214
215     return 0;
216
217  error:
218     E_(CloseAudio)( VLC_OBJECT(p_aout) );
219     return 1;
220 }
221
222 /*****************************************************************************
223  * Play: nothing to do
224  *****************************************************************************/
225 static void Play( aout_instance_t *p_aout )
226 {
227 }
228
229 /*****************************************************************************
230  * CloseAudio: close the audio device
231  *****************************************************************************/
232 void E_(CloseAudio) ( vlc_object_t *p_this )
233 {
234     aout_instance_t * p_aout = (aout_instance_t *)p_this;
235
236     msg_Dbg( p_aout, "Close" );
237
238     /* kill the position notification thread, if any */
239     if( p_aout->output.p_sys->p_notif )
240     {
241         vlc_object_detach( p_aout->output.p_sys->p_notif );
242         if( p_aout->output.p_sys->p_notif->b_thread )
243         {
244             p_aout->output.p_sys->p_notif->b_die = 1;
245             vlc_thread_join( p_aout->output.p_sys->p_notif );
246         }
247         vlc_object_destroy( p_aout->output.p_sys->p_notif );
248     }
249
250     /* release the secondary buffer */
251     DirectxDestroySecondaryBuffer( p_aout );
252
253     /* then release the primary buffer */
254     if( p_aout->output.p_sys->p_dsbuffer_primary )
255         IDirectSoundBuffer_Release( p_aout->output.p_sys->p_dsbuffer_primary );
256
257     /* finally release the DirectSound object */
258     if( p_aout->output.p_sys->p_dsobject )
259         IDirectSound_Release( p_aout->output.p_sys->p_dsobject );
260     
261     /* free DSOUND.DLL */
262     if( p_aout->output.p_sys->hdsound_dll )
263        FreeLibrary( p_aout->output.p_sys->hdsound_dll );
264
265     /* Close the Output. */
266     if ( p_aout->output.p_sys  )
267     { 
268         free( p_aout->output.p_sys );
269         p_aout->output.p_sys = NULL;
270     }
271 }
272
273 /*****************************************************************************
274  * DirectxInitDSound: handle all the gory details of DirectSound initialisation
275  *****************************************************************************/
276 static int DirectxInitDSound( aout_instance_t *p_aout )
277 {
278     HRESULT (WINAPI *OurDirectSoundCreate)(LPGUID, LPDIRECTSOUND *, LPUNKNOWN);
279
280     p_aout->output.p_sys->hdsound_dll = LoadLibrary("DSOUND.DLL");
281     if( p_aout->output.p_sys->hdsound_dll == NULL )
282     {
283         msg_Warn( p_aout, "cannot open DSOUND.DLL" );
284         goto error;
285     }
286
287     OurDirectSoundCreate = (void *)GetProcAddress(
288                                            p_aout->output.p_sys->hdsound_dll,
289                                            "DirectSoundCreate" );
290     if( OurDirectSoundCreate == NULL )
291     {
292         msg_Warn( p_aout, "GetProcAddress FAILED" );
293         goto error;
294     }
295
296     /* Create the direct sound object */
297     if( OurDirectSoundCreate( NULL, &p_aout->output.p_sys->p_dsobject, NULL )
298         != DS_OK )
299     {
300         msg_Warn( p_aout, "cannot create a direct sound device" );
301         goto error;
302     }
303
304     /* Set DirectSound Cooperative level, ie what control we want over Windows
305      * sound device. In our case, DSSCL_EXCLUSIVE means that we can modify the
306      * settings of the primary buffer, but also that only the sound of our
307      * application will be hearable when it will have the focus.
308      * !!! (this is not really working as intended yet because to set the
309      * cooperative level you need the window handle of your application, and
310      * I don't know of any easy way to get it. Especially since we might play
311      * sound without any video, and so what window handle should we use ???
312      * The hack for now is to use the Desktop window handle - it seems to be
313      * working */
314     if( IDirectSound_SetCooperativeLevel( p_aout->output.p_sys->p_dsobject,
315                                           GetDesktopWindow(),
316                                           DSSCL_EXCLUSIVE) )
317     {
318         msg_Warn( p_aout, "cannot set direct sound cooperative level" );
319     }
320
321     return 0;
322
323  error:
324     p_aout->output.p_sys->p_dsobject = NULL;
325     if( p_aout->output.p_sys->hdsound_dll )
326     {
327         FreeLibrary( p_aout->output.p_sys->hdsound_dll );
328         p_aout->output.p_sys->hdsound_dll = NULL;
329     }
330     return 1;
331
332 }
333
334 /*****************************************************************************
335  * DirectxCreateSecondaryBuffer
336  *****************************************************************************
337  * This function creates the buffer we'll use to play audio.
338  * In DirectSound there are two kinds of buffers:
339  * - the primary buffer: which is the actual buffer that the soundcard plays
340  * - the secondary buffer(s): these buffers are the one actually used by
341  *    applications and DirectSound takes care of mixing them into the primary.
342  *
343  * Once you create a secondary buffer, you cannot change its format anymore so
344  * you have to release the current and create another one.
345  *****************************************************************************/
346 static int DirectxCreateSecondaryBuffer( aout_instance_t *p_aout )
347 {
348     WAVEFORMATEX         waveformat;
349     DSBUFFERDESC         dsbdesc;
350     DSBCAPS              dsbcaps;
351
352     /* First set the buffer format */
353     memset(&waveformat, 0, sizeof(WAVEFORMATEX)); 
354     waveformat.wFormatTag      = WAVE_FORMAT_PCM; 
355     waveformat.nChannels       = p_aout->output.output.i_channels;
356     waveformat.nSamplesPerSec  = p_aout->output.output.i_rate; 
357     waveformat.wBitsPerSample  = 16; 
358     waveformat.nBlockAlign     = waveformat.wBitsPerSample / 8 *
359                                  waveformat.nChannels;
360     waveformat.nAvgBytesPerSec = waveformat.nSamplesPerSec *
361                                      waveformat.nBlockAlign;
362
363     /* Then fill in the descriptor */
364     memset(&dsbdesc, 0, sizeof(DSBUFFERDESC)); 
365     dsbdesc.dwSize = sizeof(DSBUFFERDESC); 
366     dsbdesc.dwFlags = DSBCAPS_GETCURRENTPOSITION2/* Better position accuracy */
367                     | DSBCAPS_CTRLPOSITIONNOTIFY     /* We need notification */
368                     | DSBCAPS_GLOBALFOCUS;      /* Allows background playing */
369     dsbdesc.dwBufferBytes = FRAME_SIZE * 2 /* frames*/ *      /* buffer size */
370                             sizeof(s16) * p_aout->output.output.i_channels;
371     dsbdesc.lpwfxFormat = &waveformat; 
372  
373     if( IDirectSound_CreateSoundBuffer( p_aout->output.p_sys->p_dsobject,
374                                         &dsbdesc,
375                                         &p_aout->output.p_sys->p_dsbuffer,
376                                         NULL) != DS_OK )
377     {
378         msg_Warn( p_aout, "cannot create direct sound secondary buffer" );
379         goto error;
380     }
381
382     /* backup the size of the secondary sound buffer */
383     memset(&dsbcaps, 0, sizeof(DSBCAPS)); 
384     dsbcaps.dwSize = sizeof(DSBCAPS);
385     IDirectSoundBuffer_GetCaps( p_aout->output.p_sys->p_dsbuffer, &dsbcaps  );
386
387     msg_Dbg( p_aout, "DirectxCreateSecondaryBuffer: %li",
388              dsbcaps.dwBufferBytes );
389
390     /* Now the secondary buffer is created, we need to setup its position
391      * notification */
392     p_aout->output.p_sys->p_notif->p_events[0].dwOffset = 0;
393     p_aout->output.p_sys->p_notif->p_events[1].dwOffset =
394         p_aout->output.p_sys->p_notif->i_buffer_size;
395
396     /* Get the IDirectSoundNotify interface */
397     if FAILED( IDirectSoundBuffer_QueryInterface(
398                                 p_aout->output.p_sys->p_dsbuffer,
399                                 &IID_IDirectSoundNotify,
400                                 (LPVOID *)&p_aout->output.p_sys->p_dsnotify ) )
401     {
402         msg_Warn( p_aout, "cannot get Notify interface" );
403         goto error;
404     }
405         
406     if FAILED( IDirectSoundNotify_SetNotificationPositions(
407                                     p_aout->output.p_sys->p_dsnotify, 2,
408                                     p_aout->output.p_sys->p_notif->p_events ) )
409     {
410         msg_Warn( p_aout, "cannot set position Notification" );
411         goto error;
412     }
413
414     p_aout->output.output.i_format = AOUT_FMT_S16_NE;
415     p_aout->output.i_nb_samples = FRAME_SIZE;
416
417     return 0;
418
419  error:
420     if( p_aout->output.p_sys->p_dsbuffer )
421     {
422         IDirectSoundBuffer_Release( p_aout->output.p_sys->p_dsbuffer );
423         p_aout->output.p_sys->p_dsbuffer = NULL;
424     }
425     if( p_aout->output.p_sys->p_dsnotify )
426     {
427         IDirectSoundBuffer_Release( p_aout->output.p_sys->p_dsbuffer );
428         p_aout->output.p_sys->p_dsnotify = NULL;
429     }
430     return 1;
431 }
432
433 /*****************************************************************************
434  * DirectxCreateSecondaryBuffer
435  *****************************************************************************
436  * This function destroys the secondary buffer.
437  *****************************************************************************/
438 static void DirectxDestroySecondaryBuffer( aout_instance_t *p_aout )
439 {
440     /* make sure the buffer isn't playing */
441     if( p_aout->output.p_sys->p_dsbuffer )
442         IDirectSoundBuffer_Stop( p_aout->output.p_sys->p_dsbuffer );
443
444     if( p_aout->output.p_sys->p_dsnotify )
445     {
446         IDirectSoundNotify_Release( p_aout->output.p_sys->p_dsnotify );
447         p_aout->output.p_sys->p_dsnotify = NULL;
448     }
449
450     if( p_aout->output.p_sys->p_dsbuffer )
451     {
452         IDirectSoundBuffer_Release( p_aout->output.p_sys->p_dsbuffer );
453         p_aout->output.p_sys->p_dsbuffer = NULL;
454     }
455 }
456
457 /*****************************************************************************
458  * DirectSoundThread: this thread will capture play notification events. 
459  *****************************************************************************
460  * We use this thread to emulate a callback mechanism. The thread probes for
461  * event notification and fills up the DS secondary buffer when needed.
462  *****************************************************************************/
463 static void DirectSoundThread( notification_thread_t *p_notif )
464 {
465     HANDLE  notification_events[2];
466     HRESULT dsresult;
467     aout_instance_t *p_aout = p_notif->p_aout;
468
469     notification_events[0] = p_notif->p_events[0].hEventNotify;
470     notification_events[1] = p_notif->p_events[1].hEventNotify;
471
472     /* Tell the main thread that we are ready */
473     vlc_thread_ready( p_notif );
474
475     /* this thread must be high-priority */
476     if( !SetThreadPriority( GetCurrentThread(),
477                             THREAD_PRIORITY_ABOVE_NORMAL ) )
478     {
479         msg_Warn( p_notif, "DirectSoundThread could not raise its priority" );
480     }
481
482     msg_Dbg( p_notif, "DirectSoundThread ready" );
483
484     while( !p_notif->b_die )
485     {
486         int i_which_event;
487         void *p_write_position, *p_wrap_around;
488         long l_bytes1, l_bytes2;
489         aout_buffer_t * p_buffer;
490
491         /* wait for the position notification */
492         i_which_event = WaitForMultipleObjects( 2, notification_events, 0,
493                                                 INFINITE ) - WAIT_OBJECT_0;
494
495         vlc_mutex_lock( &p_aout->output.p_sys->buffer_lock );
496
497         if( p_notif->b_die )
498         {
499             vlc_mutex_unlock( &p_aout->output.p_sys->buffer_lock );
500             break;
501         }
502
503         /* Before copying anything, we have to lock the buffer */
504         dsresult = IDirectSoundBuffer_Lock(
505                                                                 /* DS buffer */
506             p_aout->output.p_sys->p_dsbuffer,
507                                                      /* Offset of lock start */
508             i_which_event ? 0 : p_notif->i_buffer_size,
509             p_notif->i_buffer_size,                 /* Number of bytes */
510             &p_write_position,                      /* Address of lock start */
511             &l_bytes1,           /* Count of bytes locked before wrap around */
512             &p_wrap_around,                /* Buffer adress (if wrap around) */
513             &l_bytes2,                   /* Count of bytes after wrap around */
514             0 );                                                    /* Flags */
515         if( dsresult == DSERR_BUFFERLOST )
516         {
517             IDirectSoundBuffer_Restore( p_aout->output.p_sys->p_dsbuffer );
518             dsresult = IDirectSoundBuffer_Lock(
519                            p_aout->output.p_sys->p_dsbuffer,
520                            i_which_event ? 0 : p_notif->i_buffer_size,
521                            p_notif->i_buffer_size,
522                            &p_write_position,
523                            &l_bytes1,
524                            &p_wrap_around,
525                            &l_bytes2,
526                            0 );
527         }
528         if( dsresult != DS_OK )
529         {
530             msg_Warn( p_notif, "cannot lock buffer" );
531             vlc_mutex_unlock( &p_aout->output.p_sys->buffer_lock );
532             continue;
533         }
534
535         /* FIXME : take into account DirectSound latency instead of mdate() */
536         p_buffer = aout_OutputNextBuffer( p_aout, mdate(), VLC_FALSE );
537
538         /* Now do the actual memcpy into the circular buffer */
539         if ( l_bytes1 != p_notif->i_buffer_size )
540             msg_Err( p_aout, "Wrong buffer size: %d, %d", l_bytes1,
541                      p_notif->i_buffer_size );
542
543         if ( p_buffer != NULL )
544         {
545             p_aout->p_vlc->pf_memcpy( p_write_position, p_buffer->p_buffer,
546                                       l_bytes1 );
547             aout_BufferFree( p_buffer );
548         }
549         else
550         {
551             memset( p_write_position, 0, l_bytes1 );
552         }
553
554         /* Now the data has been copied, unlock the buffer */
555         IDirectSoundBuffer_Unlock( p_aout->output.p_sys->p_dsbuffer,
556                         p_write_position, l_bytes1, p_wrap_around, l_bytes2 );
557
558         vlc_mutex_unlock( &p_aout->output.p_sys->buffer_lock );
559
560     }
561
562     /* free the events */
563     CloseHandle( notification_events[0] );
564     CloseHandle( notification_events[1] );
565
566     msg_Dbg( p_notif, "DirectSoundThread exiting" );
567
568 }