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