]> git.sesse.net Git - vlc/blob - modules/video_output/directx/aout.c
e654a6fa731c605f551d480755a094367e526b93
[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.4 2002/08/14 00:43:52 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  * Prototypes.
93  *****************************************************************************/
94 void E_(CloseAudio) ( vlc_object_t *p_this );
95
96 /*****************************************************************************
97  * Local prototypes.
98  *****************************************************************************/
99 static int  SetFormat ( aout_instance_t * );
100 static void Play      ( aout_instance_t *, aout_buffer_t * );
101
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 * );
107
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 )
114 {
115     aout_instance_t * p_aout = (aout_instance_t *)p_this;
116     HRESULT dsresult;
117     DSBUFFERDESC dsbuffer_desc;
118
119     msg_Dbg( p_aout, "Open" );
120
121    /* Allocate structure */
122     p_aout->output.p_sys = malloc( sizeof( aout_sys_t ) );
123     if( p_aout->output.p_sys == NULL )
124     {
125         msg_Err( p_aout, "out of memory" );
126         return 1;
127     }
128
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 );
136
137     p_aout->output.pf_setformat = SetFormat;
138     p_aout->output.pf_play = Play;
139
140     /* Initialise DirectSound */
141     if( DirectxInitDSound( p_aout ) )
142     {
143         msg_Err( p_aout, "cannot initialize DirectSound" );
144         goto error;
145     }
146
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,
153                                      &dsbuffer_desc,
154                                      &p_aout->output.p_sys->p_dsbuffer_primary,
155                                      NULL);
156     if( dsresult != DS_OK )
157     {
158         msg_Err( p_aout, "cannot create direct sound primary buffer" );
159         goto error;
160     }
161
162
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;
167
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 );
173
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 ) )
179     {
180         msg_Err( p_aout, "cannot create DirectSoundThread" );
181         goto error;
182     }
183
184     vlc_object_attach( p_aout->output.p_sys->p_notif, p_aout );
185
186     return 0;
187
188  error:
189     E_(CloseAudio)( VLC_OBJECT(p_aout) );
190     return 1;
191 }
192
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 )
201 {
202     HRESULT dsresult;
203
204     msg_Dbg( p_aout, "SetFormat" );
205     vlc_mutex_lock( &p_aout->output.p_sys->buffer_lock );
206
207     /* first release the current secondary buffer */
208     DirectxDestroySecondaryBuffer( p_aout );
209
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;
213
214     /* then create a new secondary buffer */
215     if( DirectxCreateSecondaryBuffer( p_aout ) )
216     {
217         msg_Err( p_aout, "cannot create buffer" );
218         vlc_mutex_unlock( &p_aout->output.p_sys->buffer_lock );
219         return 1;
220     }
221
222     vlc_mutex_unlock( &p_aout->output.p_sys->buffer_lock );
223
224     /* start playing the buffer */
225     dsresult = IDirectSoundBuffer_Play( p_aout->output.p_sys->p_dsbuffer,
226                                         0,                         /* Unused */
227                                         0,                         /* Unused */
228                                         DSBPLAY_LOOPING );          /* Flags */
229     if( dsresult == DSERR_BUFFERLOST )
230     {
231         IDirectSoundBuffer_Restore( p_aout->output.p_sys->p_dsbuffer );
232         dsresult = IDirectSoundBuffer_Play( p_aout->output.p_sys->p_dsbuffer,
233                                             0,                     /* Unused */
234                                             0,                     /* Unused */
235                                             DSBPLAY_LOOPING );      /* Flags */
236     }
237     if( dsresult != DS_OK )
238     {
239         msg_Warn( p_aout, "cannot play buffer" );
240     }
241
242     return 0;
243 }
244
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 )
252 {
253     aout_FifoPush( p_aout, &p_aout->output.fifo, p_buffer );
254 }
255
256 /*****************************************************************************
257  * CloseAudio: close the audio device
258  *****************************************************************************/
259 void E_(CloseAudio) ( vlc_object_t *p_this )
260 {
261     aout_instance_t * p_aout = (aout_instance_t *)p_this;
262
263     msg_Dbg( p_aout, "Close" );
264
265     /* kill the position notification thread, if any */
266     if( p_aout->output.p_sys->p_notif )
267     {
268         vlc_object_detach( p_aout->output.p_sys->p_notif );
269         if( p_aout->output.p_sys->p_notif->b_thread )
270         {
271             p_aout->output.p_sys->p_notif->b_die = 1;
272             vlc_thread_join( p_aout->output.p_sys->p_notif );
273         }
274         vlc_object_destroy( p_aout->output.p_sys->p_notif );
275     }
276
277     /* release the secondary buffer */
278     DirectxDestroySecondaryBuffer( p_aout );
279
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 );
283
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 );
287     
288     /* free DSOUND.DLL */
289     if( p_aout->output.p_sys->hdsound_dll )
290        FreeLibrary( p_aout->output.p_sys->hdsound_dll );
291
292     /* Close the Output. */
293     if ( p_aout->output.p_sys  )
294     { 
295         free( p_aout->output.p_sys );
296         p_aout->output.p_sys = NULL;
297     }
298 }
299
300 /*****************************************************************************
301  * DirectxInitDSound: handle all the gory details of DirectSound initialisation
302  *****************************************************************************/
303 static int DirectxInitDSound( aout_instance_t *p_aout )
304 {
305     HRESULT (WINAPI *OurDirectSoundCreate)(LPGUID, LPDIRECTSOUND *, LPUNKNOWN);
306
307     p_aout->output.p_sys->hdsound_dll = LoadLibrary("DSOUND.DLL");
308     if( p_aout->output.p_sys->hdsound_dll == NULL )
309     {
310         msg_Warn( p_aout, "cannot open DSOUND.DLL" );
311         goto error;
312     }
313
314     OurDirectSoundCreate = (void *)GetProcAddress(
315                                            p_aout->output.p_sys->hdsound_dll,
316                                            "DirectSoundCreate" );
317     if( OurDirectSoundCreate == NULL )
318     {
319         msg_Warn( p_aout, "GetProcAddress FAILED" );
320         goto error;
321     }
322
323     /* Create the direct sound object */
324     if( OurDirectSoundCreate( NULL, &p_aout->output.p_sys->p_dsobject, NULL )
325         != DS_OK )
326     {
327         msg_Warn( p_aout, "cannot create a direct sound device" );
328         goto error;
329     }
330
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
340      * working */
341     if( IDirectSound_SetCooperativeLevel( p_aout->output.p_sys->p_dsobject,
342                                           GetDesktopWindow(),
343                                           DSSCL_EXCLUSIVE) )
344     {
345         msg_Warn( p_aout, "cannot set direct sound cooperative level" );
346     }
347
348     return 0;
349
350  error:
351     p_aout->output.p_sys->p_dsobject = NULL;
352     if( p_aout->output.p_sys->hdsound_dll )
353     {
354         FreeLibrary( p_aout->output.p_sys->hdsound_dll );
355         p_aout->output.p_sys->hdsound_dll = NULL;
356     }
357     return 1;
358
359 }
360
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.
369  *
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 )
374 {
375     WAVEFORMATEX         waveformat;
376     DSBUFFERDESC         dsbdesc;
377     DSBCAPS              dsbcaps;
378
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;
389
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; 
399  
400     if( IDirectSound_CreateSoundBuffer( p_aout->output.p_sys->p_dsobject,
401                                         &dsbdesc,
402                                         &p_aout->output.p_sys->p_dsbuffer,
403                                         NULL) != DS_OK )
404     {
405         msg_Warn( p_aout, "cannot create direct sound secondary buffer" );
406         goto error;
407     }
408
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  );
413
414     msg_Dbg( p_aout, "DirectxCreateSecondaryBuffer: %li",
415              dsbcaps.dwBufferBytes );
416
417     /* Now the secondary buffer is created, we need to setup its position
418      * notification */
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;
422
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 ) )
428     {
429         msg_Warn( p_aout, "cannot get Notify interface" );
430         goto error;
431     }
432         
433     if FAILED( IDirectSoundNotify_SetNotificationPositions(
434                                     p_aout->output.p_sys->p_dsnotify, 2,
435                                     p_aout->output.p_sys->p_notif->p_events ) )
436     {
437         msg_Warn( p_aout, "cannot set position Notification" );
438         goto error;
439     }
440
441     p_aout->output.output.i_format = AOUT_FMT_S16_NE;
442     p_aout->output.i_nb_samples = FRAME_SIZE;
443
444     return 0;
445
446  error:
447     if( p_aout->output.p_sys->p_dsbuffer )
448     {
449         IDirectSoundBuffer_Release( p_aout->output.p_sys->p_dsbuffer );
450         p_aout->output.p_sys->p_dsbuffer = NULL;
451     }
452     if( p_aout->output.p_sys->p_dsnotify )
453     {
454         IDirectSoundBuffer_Release( p_aout->output.p_sys->p_dsbuffer );
455         p_aout->output.p_sys->p_dsnotify = NULL;
456     }
457     return 1;
458 }
459
460 /*****************************************************************************
461  * DirectxCreateSecondaryBuffer
462  *****************************************************************************
463  * This function destroys the secondary buffer.
464  *****************************************************************************/
465 static void DirectxDestroySecondaryBuffer( aout_instance_t *p_aout )
466 {
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 );
470
471     if( p_aout->output.p_sys->p_dsnotify )
472     {
473         IDirectSoundNotify_Release( p_aout->output.p_sys->p_dsnotify );
474         p_aout->output.p_sys->p_dsnotify = NULL;
475     }
476
477     if( p_aout->output.p_sys->p_dsbuffer )
478     {
479         IDirectSoundBuffer_Release( p_aout->output.p_sys->p_dsbuffer );
480         p_aout->output.p_sys->p_dsbuffer = NULL;
481     }
482 }
483
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 )
491 {
492     HANDLE  notification_events[2];
493     HRESULT dsresult;
494     aout_instance_t *p_aout = p_notif->p_aout;
495
496     notification_events[0] = p_notif->p_events[0].hEventNotify;
497     notification_events[1] = p_notif->p_events[1].hEventNotify;
498
499     /* Tell the main thread that we are ready */
500     vlc_thread_ready( p_notif );
501
502     /* this thread must be high-priority */
503     if( !SetThreadPriority( GetCurrentThread(),
504                             THREAD_PRIORITY_ABOVE_NORMAL ) )
505     {
506         msg_Warn( p_notif, "DirectSoundThread could not raise its priority" );
507     }
508
509     msg_Dbg( p_notif, "DirectSoundThread ready" );
510
511     while( !p_notif->b_die )
512     {
513         int i_which_event;
514         void *p_write_position, *p_wrap_around;
515         long l_bytes1, l_bytes2;
516         aout_buffer_t * p_buffer;
517
518         /* wait for the position notification */
519         i_which_event = WaitForMultipleObjects( 2, notification_events, 0,
520                                                 INFINITE ) - WAIT_OBJECT_0;
521
522         vlc_mutex_lock( &p_aout->output.p_sys->buffer_lock );
523
524         if( p_notif->b_die )
525         {
526             vlc_mutex_unlock( &p_aout->output.p_sys->buffer_lock );
527             break;
528         }
529
530         /* Before copying anything, we have to lock the buffer */
531         dsresult = IDirectSoundBuffer_Lock(
532                                                                 /* DS buffer */
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 */
541             0 );                                                    /* Flags */
542         if( dsresult == DSERR_BUFFERLOST )
543         {
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,
549                            &p_write_position,
550                            &l_bytes1,
551                            &p_wrap_around,
552                            &l_bytes2,
553                            0 );
554         }
555         if( dsresult != DS_OK )
556         {
557             msg_Warn( p_notif, "cannot lock buffer" );
558             vlc_mutex_unlock( &p_aout->output.p_sys->buffer_lock );
559             continue;
560         }
561
562         /* FIXME : take into account DirectSound latency instead of mdate() */
563         p_buffer = aout_OutputNextBuffer( p_aout, mdate(), 0 );
564
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 );
569
570         if ( p_buffer != NULL )
571         {
572             p_aout->p_vlc->pf_memcpy( p_write_position, p_buffer->p_buffer,
573                                       l_bytes1 );
574             aout_BufferFree( p_buffer );
575         }
576         else
577         {
578             memset( p_write_position, 0, l_bytes1 );
579         }
580
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 );
584
585         vlc_mutex_unlock( &p_aout->output.p_sys->buffer_lock );
586
587     }
588
589     /* free the events */
590     CloseHandle( notification_events[0] );
591     CloseHandle( notification_events[1] );
592
593     msg_Dbg( p_notif, "DirectSoundThread exiting" );
594
595 }