]> git.sesse.net Git - vlc/blob - modules/audio_output/directx.c
* configure.ac.in: libvorbis depends on libogg.
[vlc] / modules / audio_output / directx.c
1 /*****************************************************************************
2  * directx.c: Windows DirectX audio output method
3  *****************************************************************************
4  * Copyright (C) 2001 VideoLAN
5  * $Id: directx.c,v 1.5 2002/10/28 22:31:49 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  * Useful macros
53  *****************************************************************************/
54 #ifndef WAVE_FORMAT_IEEE_FLOAT
55 #   define WAVE_FORMAT_IEEE_FLOAT 0x0003
56 #endif
57
58 /*****************************************************************************
59  * notification_thread_t: DirectX event thread
60  *****************************************************************************/
61 typedef struct notification_thread_t
62 {
63     VLC_COMMON_MEMBERS
64
65     aout_instance_t * p_aout;
66     DSBPOSITIONNOTIFY p_events[2];               /* play notification events */
67     int i_buffer_size;                         /* Size in bytes of one frame */
68
69 } notification_thread_t;
70
71 /*****************************************************************************
72  * aout_sys_t: directx audio output method descriptor
73  *****************************************************************************
74  * This structure is part of the audio output thread descriptor.
75  * It describes the direct sound specific properties of an audio device.
76  *****************************************************************************/
77 struct aout_sys_t
78 {
79     LPDIRECTSOUND       p_dsobject;              /* main Direct Sound object */
80
81     LPDIRECTSOUNDBUFFER p_dsbuffer_primary;     /* the actual sound card buffer
82                                                    (not used directly) */
83
84     LPDIRECTSOUNDBUFFER p_dsbuffer;   /* the sound buffer we use (direct sound
85                                        * takes care of mixing all the
86                                        * secondary buffers into the primary) */
87
88     LPDIRECTSOUNDNOTIFY p_dsnotify;         /* the position notify interface */
89
90     HINSTANCE           hdsound_dll;      /* handle of the opened dsound dll */
91
92     notification_thread_t * p_notif;                 /* DirectSoundThread id */
93
94 };
95
96 /*****************************************************************************
97  * Local prototypes.
98  *****************************************************************************/
99 static int  OpenAudio  ( vlc_object_t * );
100 static void CloseAudio ( vlc_object_t * );
101
102 static void Play       ( aout_instance_t * );
103
104 /* local functions */
105 static int  DirectxCreateSecondaryBuffer ( aout_instance_t * );
106 static void DirectxDestroySecondaryBuffer( aout_instance_t * );
107 static int  DirectxInitDSound            ( aout_instance_t * );
108 static void DirectSoundThread            ( notification_thread_t * );
109
110 /*****************************************************************************
111  * Module descriptor
112  *****************************************************************************/
113 vlc_module_begin();
114     set_description( _("DirectX audio module") );
115     set_capability( "audio output", 100 );
116     add_shortcut( "directx" );
117     set_callbacks( OpenAudio, CloseAudio );
118 vlc_module_end();
119
120 /*****************************************************************************
121  * OpenAudio: open the audio device
122  *****************************************************************************
123  * This function opens and setups Direct Sound.
124  *****************************************************************************/
125 static int OpenAudio( vlc_object_t *p_this )
126 {
127     aout_instance_t * p_aout = (aout_instance_t *)p_this;
128     HRESULT dsresult;
129     DSBUFFERDESC dsbuffer_desc;
130
131     msg_Dbg( p_aout, "Open" );
132
133    /* Allocate structure */
134     p_aout->output.p_sys = malloc( sizeof( aout_sys_t ) );
135     if( p_aout->output.p_sys == NULL )
136     {
137         msg_Err( p_aout, "out of memory" );
138         return VLC_EGENERIC;
139     }
140
141     /* Initialize some variables */
142     p_aout->output.p_sys->p_dsobject = NULL;
143     p_aout->output.p_sys->p_dsbuffer_primary = NULL;
144     p_aout->output.p_sys->p_dsbuffer = NULL;
145     p_aout->output.p_sys->p_dsnotify = NULL;
146     p_aout->output.p_sys->p_notif = NULL;
147
148     p_aout->output.pf_play = Play;
149     aout_VolumeSoftInit( p_aout );
150
151     /* Initialise DirectSound */
152     if( DirectxInitDSound( p_aout ) )
153     {
154         msg_Err( p_aout, "cannot initialize DirectSound" );
155         goto error;
156     }
157
158     /* Obtain (not create) Direct Sound primary buffer */
159     memset( &dsbuffer_desc, 0, sizeof(DSBUFFERDESC) );
160     dsbuffer_desc.dwSize = sizeof(DSBUFFERDESC);
161     dsbuffer_desc.dwFlags = DSBCAPS_PRIMARYBUFFER;
162     msg_Warn( p_aout, "create direct sound primary buffer" );
163     dsresult = IDirectSound_CreateSoundBuffer(p_aout->output.p_sys->p_dsobject,
164                                      &dsbuffer_desc,
165                                      &p_aout->output.p_sys->p_dsbuffer_primary,
166                                      NULL);
167     if( dsresult != DS_OK )
168     {
169         msg_Err( p_aout, "cannot create direct sound primary buffer" );
170         goto error;
171     }
172
173     /* Now we need to setup DirectSound play notification */
174     p_aout->output.p_sys->p_notif =
175         vlc_object_create( p_aout, sizeof(notification_thread_t) );
176     p_aout->output.p_sys->p_notif->p_aout = p_aout;
177
178     /* first we need to create the notification events */
179     p_aout->output.p_sys->p_notif->p_events[0].hEventNotify =
180         CreateEvent( NULL, FALSE, FALSE, NULL );
181     p_aout->output.p_sys->p_notif->p_events[1].hEventNotify =
182         CreateEvent( NULL, FALSE, FALSE, NULL );
183
184     /* then create a new secondary buffer */
185     p_aout->output.output.i_format = VLC_FOURCC('f','l','3','2');
186     if( DirectxCreateSecondaryBuffer( p_aout ) )
187     {
188         msg_Err( p_aout, "cannot create WAVE_FORMAT_IEEE_FLOAT buffer" );
189
190         p_aout->output.output.i_format = VLC_FOURCC('s','1','6','l');
191         if( DirectxCreateSecondaryBuffer( p_aout ) )
192         {
193             msg_Err( p_aout, "cannot create WAVE_FORMAT_PCM buffer" );
194             return 1;
195         }
196     }
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,
221                            THREAD_PRIORITY_TIME_CRITICAL, 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 VLC_EGENERIC;
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     free( p_aout->output.p_sys );
280 }
281
282 /*****************************************************************************
283  * DirectxInitDSound: handle all the gory details of DirectSound initialisation
284  *****************************************************************************/
285 static int DirectxInitDSound( aout_instance_t *p_aout )
286 {
287     HRESULT (WINAPI *OurDirectSoundCreate)(LPGUID, LPDIRECTSOUND *, LPUNKNOWN);
288
289     p_aout->output.p_sys->hdsound_dll = LoadLibrary("DSOUND.DLL");
290     if( p_aout->output.p_sys->hdsound_dll == NULL )
291     {
292         msg_Warn( p_aout, "cannot open DSOUND.DLL" );
293         goto error;
294     }
295
296     OurDirectSoundCreate = (void *)GetProcAddress(
297                                            p_aout->output.p_sys->hdsound_dll,
298                                            "DirectSoundCreate" );
299     if( OurDirectSoundCreate == NULL )
300     {
301         msg_Warn( p_aout, "GetProcAddress FAILED" );
302         goto error;
303     }
304
305     /* Create the direct sound object */
306     if( OurDirectSoundCreate( NULL, &p_aout->output.p_sys->p_dsobject, NULL )
307         != DS_OK )
308     {
309         msg_Warn( p_aout, "cannot create a direct sound device" );
310         goto error;
311     }
312
313     /* Set DirectSound Cooperative level, ie what control we want over Windows
314      * sound device. In our case, DSSCL_EXCLUSIVE means that we can modify the
315      * settings of the primary buffer, but also that only the sound of our
316      * application will be hearable when it will have the focus.
317      * !!! (this is not really working as intended yet because to set the
318      * cooperative level you need the window handle of your application, and
319      * I don't know of any easy way to get it. Especially since we might play
320      * sound without any video, and so what window handle should we use ???
321      * The hack for now is to use the Desktop window handle - it seems to be
322      * working */
323     if( IDirectSound_SetCooperativeLevel( p_aout->output.p_sys->p_dsobject,
324                                           GetDesktopWindow(),
325                                           DSSCL_EXCLUSIVE) )
326     {
327         msg_Warn( p_aout, "cannot set direct sound cooperative level" );
328     }
329
330     return 0;
331
332  error:
333     p_aout->output.p_sys->p_dsobject = NULL;
334     if( p_aout->output.p_sys->hdsound_dll )
335     {
336         FreeLibrary( p_aout->output.p_sys->hdsound_dll );
337         p_aout->output.p_sys->hdsound_dll = NULL;
338     }
339     return 1;
340
341 }
342
343 /*****************************************************************************
344  * DirectxCreateSecondaryBuffer
345  *****************************************************************************
346  * This function creates the buffer we'll use to play audio.
347  * In DirectSound there are two kinds of buffers:
348  * - the primary buffer: which is the actual buffer that the soundcard plays
349  * - the secondary buffer(s): these buffers are the one actually used by
350  *    applications and DirectSound takes care of mixing them into the primary.
351  *
352  * Once you create a secondary buffer, you cannot change its format anymore so
353  * you have to release the current and create another one.
354  *****************************************************************************/
355 static int DirectxCreateSecondaryBuffer( aout_instance_t *p_aout )
356 {
357     WAVEFORMATEX         waveformat;
358     DSBUFFERDESC         dsbdesc;
359     DSBCAPS              dsbcaps;
360     int                  i_nb_channels;
361
362     i_nb_channels = aout_FormatNbChannels( &p_aout->output.output );
363     if ( i_nb_channels > 2 )
364     {
365         i_nb_channels = 2;
366         p_aout->output.output.i_channels = AOUT_CHAN_STEREO;
367     }
368
369     /* First set the buffer format */
370     memset(&waveformat, 0, sizeof(WAVEFORMATEX));
371     switch( p_aout->output.output.i_format )
372     {
373     case VLC_FOURCC('s','1','6','l'):
374         waveformat.wFormatTag     = WAVE_FORMAT_PCM;
375         waveformat.wBitsPerSample = 16;
376         break;
377     case VLC_FOURCC('f','l','3','2'):
378         waveformat.wFormatTag     = WAVE_FORMAT_IEEE_FLOAT;
379         waveformat.wBitsPerSample = sizeof(float) * 8;
380         break;
381     }
382     waveformat.nChannels       = i_nb_channels;
383     waveformat.nSamplesPerSec  = p_aout->output.output.i_rate;
384     waveformat.nBlockAlign     = waveformat.wBitsPerSample / 8 *
385                                  waveformat.nChannels;
386     waveformat.nAvgBytesPerSec = waveformat.nSamplesPerSec *
387                                      waveformat.nBlockAlign;
388
389     aout_FormatPrepare( &p_aout->output.output );
390
391     /* Then fill in the descriptor */
392     memset(&dsbdesc, 0, sizeof(DSBUFFERDESC));
393     dsbdesc.dwSize = sizeof(DSBUFFERDESC);
394     dsbdesc.dwFlags = DSBCAPS_GETCURRENTPOSITION2/* Better position accuracy */
395                     | DSBCAPS_CTRLPOSITIONNOTIFY     /* We need notification */
396                     | DSBCAPS_GLOBALFOCUS;      /* Allows background playing */
397     dsbdesc.dwBufferBytes = FRAME_SIZE * 2 /* frames*/        /* buffer size */
398                             * p_aout->output.output.i_bytes_per_frame;
399     dsbdesc.lpwfxFormat = &waveformat;
400  
401     if( IDirectSound_CreateSoundBuffer( p_aout->output.p_sys->p_dsobject,
402                                         &dsbdesc,
403                                         &p_aout->output.p_sys->p_dsbuffer,
404                                         NULL) != DS_OK )
405     {
406         msg_Warn( p_aout, "cannot create direct sound secondary buffer" );
407         goto error;
408     }
409
410     /* backup the size of a frame */
411     p_aout->output.p_sys->p_notif->i_buffer_size = FRAME_SIZE *
412         p_aout->output.output.i_bytes_per_frame;
413
414     memset(&dsbcaps, 0, sizeof(DSBCAPS));
415     dsbcaps.dwSize = sizeof(DSBCAPS);
416     IDirectSoundBuffer_GetCaps( p_aout->output.p_sys->p_dsbuffer, &dsbcaps  );
417     msg_Dbg( p_aout, "requested %li bytes buffer and got %li bytes.",
418              2 * p_aout->output.p_sys->p_notif->i_buffer_size,
419              dsbcaps.dwBufferBytes );
420
421     /* Now the secondary buffer is created, we need to setup its position
422      * notification */
423     p_aout->output.p_sys->p_notif->p_events[0].dwOffset = 0;
424     p_aout->output.p_sys->p_notif->p_events[1].dwOffset =
425         p_aout->output.p_sys->p_notif->i_buffer_size;
426
427     /* Get the IDirectSoundNotify interface */
428     if FAILED( IDirectSoundBuffer_QueryInterface(
429                                 p_aout->output.p_sys->p_dsbuffer,
430                                 &IID_IDirectSoundNotify,
431                                 (LPVOID *)&p_aout->output.p_sys->p_dsnotify ) )
432     {
433         msg_Err( p_aout, "cannot get Notify interface" );
434         goto error;
435     }
436         
437     if FAILED( IDirectSoundNotify_SetNotificationPositions(
438                                     p_aout->output.p_sys->p_dsnotify, 2,
439                                     p_aout->output.p_sys->p_notif->p_events ) )
440     {
441         msg_Err( p_aout, "cannot set position Notification" );
442         goto error;
443     }
444
445     p_aout->output.i_nb_samples = FRAME_SIZE;
446
447     return 0;
448
449  error:
450     if( p_aout->output.p_sys->p_dsbuffer )
451     {
452         IDirectSoundBuffer_Release( p_aout->output.p_sys->p_dsbuffer );
453         p_aout->output.p_sys->p_dsbuffer = NULL;
454     }
455     if( p_aout->output.p_sys->p_dsnotify )
456     {
457         IDirectSoundBuffer_Release( p_aout->output.p_sys->p_dsbuffer );
458         p_aout->output.p_sys->p_dsnotify = NULL;
459     }
460     return VLC_EGENERIC;
461 }
462
463 /*****************************************************************************
464  * DirectxCreateSecondaryBuffer
465  *****************************************************************************
466  * This function destroys the secondary buffer.
467  *****************************************************************************/
468 static void DirectxDestroySecondaryBuffer( aout_instance_t *p_aout )
469 {
470     /* make sure the buffer isn't playing */
471     if( p_aout->output.p_sys->p_dsbuffer )
472         IDirectSoundBuffer_Stop( p_aout->output.p_sys->p_dsbuffer );
473
474     if( p_aout->output.p_sys->p_dsnotify )
475     {
476         IDirectSoundNotify_Release( p_aout->output.p_sys->p_dsnotify );
477         p_aout->output.p_sys->p_dsnotify = NULL;
478     }
479
480     if( p_aout->output.p_sys->p_dsbuffer )
481     {
482         IDirectSoundBuffer_Release( p_aout->output.p_sys->p_dsbuffer );
483         p_aout->output.p_sys->p_dsbuffer = NULL;
484     }
485 }
486
487 /*****************************************************************************
488  * DirectSoundThread: this thread will capture play notification events. 
489  *****************************************************************************
490  * We use this thread to emulate a callback mechanism. The thread probes for
491  * event notification and fills up the DS secondary buffer when needed.
492  *****************************************************************************/
493 static void DirectSoundThread( notification_thread_t *p_notif )
494 {
495     HANDLE  notification_events[2];
496     HRESULT dsresult;
497     aout_instance_t *p_aout = p_notif->p_aout;
498
499     notification_events[0] = p_notif->p_events[0].hEventNotify;
500     notification_events[1] = p_notif->p_events[1].hEventNotify;
501
502     /* Tell the main thread that we are ready */
503     vlc_thread_ready( p_notif );
504
505     msg_Dbg( p_notif, "DirectSoundThread ready" );
506
507     while( !p_notif->b_die )
508     {
509         int i_which_event;
510         void *p_write_position, *p_wrap_around;
511         long l_bytes1, l_bytes2;
512         aout_buffer_t * p_buffer;
513         long l_play_position;
514
515         /* wait for the position notification */
516         i_which_event = WaitForMultipleObjects( 2, notification_events, 0,
517                                                 INFINITE ) - WAIT_OBJECT_0;
518
519         if( p_notif->b_die )
520             break;
521
522         /* Before copying anything, we have to lock the buffer */
523         dsresult = IDirectSoundBuffer_Lock(
524                                                                 /* DS buffer */
525             p_aout->output.p_sys->p_dsbuffer,
526                                                      /* Offset of lock start */
527             i_which_event ? 0 : p_notif->i_buffer_size,
528             p_notif->i_buffer_size,                 /* Number of bytes */
529             &p_write_position,                      /* Address of lock start */
530             &l_bytes1,           /* Count of bytes locked before wrap around */
531             &p_wrap_around,                /* Buffer adress (if wrap around) */
532             &l_bytes2,                   /* Count of bytes after wrap around */
533             0 );                                                    /* Flags */
534         if( dsresult == DSERR_BUFFERLOST )
535         {
536             IDirectSoundBuffer_Restore( p_aout->output.p_sys->p_dsbuffer );
537             dsresult = IDirectSoundBuffer_Lock(
538                            p_aout->output.p_sys->p_dsbuffer,
539                            i_which_event ? 0 : p_notif->i_buffer_size,
540                            p_notif->i_buffer_size,
541                            &p_write_position,
542                            &l_bytes1,
543                            &p_wrap_around,
544                            &l_bytes2,
545                            0 );
546         }
547         if( dsresult != DS_OK )
548         {
549             msg_Warn( p_notif, "cannot lock buffer" );
550             continue;
551         }
552
553         /* We take into account the current latency */
554         if( IDirectSoundBuffer_GetCurrentPosition(
555                 p_aout->output.p_sys->p_dsbuffer,
556                 &l_play_position, NULL ) == DS_OK )
557         {
558             if( l_play_position > (i_which_event * FRAME_SIZE)
559                   && l_play_position < ((i_which_event+1) * FRAME_SIZE) )
560             {
561                 l_play_position = FRAME_SIZE - ( l_play_position /
562                                       p_aout->output.output.i_bytes_per_frame %
563                                       FRAME_SIZE );
564             }
565             else
566             {
567                 l_play_position = 2 * FRAME_SIZE - ( l_play_position /
568                                       p_aout->output.output.i_bytes_per_frame %
569                                       FRAME_SIZE );
570             }
571         }
572         else
573         {
574             l_play_position = FRAME_SIZE;
575         }
576
577         p_buffer = aout_OutputNextBuffer( p_aout,
578             mdate() + 1000000 / p_aout->output.output.i_rate * l_play_position,
579             VLC_FALSE );
580
581         /* Now do the actual memcpy into the circular buffer */
582         if ( l_bytes1 != p_notif->i_buffer_size )
583             msg_Err( p_aout, "Wrong buffer size: %d, %d", l_bytes1,
584                      p_notif->i_buffer_size );
585
586         if ( p_buffer != NULL )
587         {
588             p_aout->p_vlc->pf_memcpy( p_write_position, p_buffer->p_buffer,
589                                       l_bytes1 );
590             aout_BufferFree( p_buffer );
591         }
592         else
593         {
594             memset( p_write_position, 0, l_bytes1 );
595         }
596
597         /* Now the data has been copied, unlock the buffer */
598         IDirectSoundBuffer_Unlock( p_aout->output.p_sys->p_dsbuffer,
599                         p_write_position, l_bytes1, p_wrap_around, l_bytes2 );
600
601     }
602
603     /* free the events */
604     CloseHandle( notification_events[0] );
605     CloseHandle( notification_events[1] );
606
607     msg_Dbg( p_notif, "DirectSoundThread exiting" );
608 }