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