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