]> git.sesse.net Git - vlc/blob - modules/audio_output/directsound.c
Remove bad unlock
[vlc] / modules / audio_output / directsound.c
1 /*****************************************************************************
2  * directsound.c: DirectSound audio output plugin for VLC
3  *****************************************************************************
4  * Copyright (C) 2001-2009 VLC authors and VideoLAN
5  * $Id$
6  *
7  * Authors: Gildas Bazin <gbazin@videolan.org>
8  *
9  * This program is free software; you can redistribute it and/or modify it
10  * under the terms of the GNU Lesser General Public License as published by
11  * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public License
20  * along with this program; if not, write to the Free Software Foundation, Inc.,
21  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23
24 /*****************************************************************************
25  * Preamble
26  *****************************************************************************/
27
28 #ifdef HAVE_CONFIG_H
29 # include "config.h"
30 #endif
31
32 #include <math.h>
33
34 #include <vlc_common.h>
35 #include <vlc_plugin.h>
36 #include <vlc_aout.h>
37 #include <vlc_charset.h>
38
39 #include "audio_output/windows_audio_common.h"
40 #include "audio_output/mmdevice.h"
41 #include <mmdeviceapi.h>
42
43 #define DS_BUF_SIZE (6*1024*1024)
44
45 static int  Open( vlc_object_t * );
46 static void Close( vlc_object_t * );
47 static HRESULT StreamStart( aout_stream_t *, audio_sample_format_t *,
48                             const GUID * );
49 static HRESULT StreamStop( aout_stream_t * );
50 static int ReloadDirectXDevices( vlc_object_t *, const char *,
51                                  char ***, char *** );
52 static void * PlayedDataEraser( void * );
53 /* Speaker setup override options list */
54 static const char *const speaker_list[] = { "Windows default", "Mono", "Stereo",
55                                             "Quad", "5.1", "7.1" };
56
57 /*****************************************************************************
58  * Module descriptor
59  *****************************************************************************/
60 #define DEVICE_TEXT N_("Output device")
61 #define DEVICE_LONGTEXT N_("Select your audio output device")
62
63 #define SPEAKER_TEXT N_("Speaker configuration")
64 #define SPEAKER_LONGTEXT N_("Select speaker configuration you want to use. " \
65     "This option doesn't upmix! So NO e.g. Stereo -> 5.1 conversion." )
66
67 #define VOLUME_TEXT N_("Audio volume")
68 #define VOLUME_LONGTEXT N_("Audio volume in hundredths of decibels (dB).")
69
70 vlc_module_begin ()
71     set_description( N_("DirectX audio output") )
72     set_shortname( "DirectX" )
73     set_capability( "audio output", 100 )
74     set_category( CAT_AUDIO )
75     set_subcategory( SUBCAT_AUDIO_AOUT )
76     add_shortcut( "directx", "aout_directx" )
77
78     add_string( "directx-audio-device", NULL,
79              DEVICE_TEXT, DEVICE_LONGTEXT, false )
80         change_string_cb( ReloadDirectXDevices )
81     add_obsolete_string( "directx-audio-device-name")
82     add_bool( "directx-audio-float32", true, FLOAT_TEXT,
83               FLOAT_LONGTEXT, true )
84     add_string( "directx-audio-speaker", "Windows default",
85                  SPEAKER_TEXT, SPEAKER_LONGTEXT, true )
86         change_string_list( speaker_list, speaker_list )
87     add_float( "directx-volume", 1.0f,
88                  VOLUME_TEXT, VOLUME_LONGTEXT, true )
89         change_integer_range( DSBVOLUME_MIN, DSBVOLUME_MAX )
90
91     set_callbacks( Open, Close )
92
93     add_submodule()
94         set_capability( "aout stream", 30 )
95         set_callbacks( StreamStart, StreamStop )
96 vlc_module_end ()
97
98 typedef struct aout_stream_sys
99 {
100     LPDIRECTSOUND       p_dsobject; /*< main Direct Sound object */
101     LPDIRECTSOUNDBUFFER p_dsbuffer; /*< the sound buffer we use (direct sound
102                                         takes care of mixing all the secondary
103                                         buffers into the primary) */
104     LPDIRECTSOUNDNOTIFY p_notify;
105
106     int      i_bytes_per_sample;    /*< Size in bytes of one frame */
107     int      i_rate;                /*< Sample rate */
108
109     uint8_t  chans_to_reorder;      /*< Do we need channel reordering? */
110     uint8_t  chan_table[AOUT_CHAN_MAX];
111     uint32_t i_channel_mask;
112     vlc_fourcc_t format;
113
114     size_t  i_write;
115     size_t  i_last_read;
116     int64_t i_data;
117
118     bool b_playing;
119     vlc_mutex_t lock;
120     vlc_cond_t cond;
121     vlc_thread_t eraser_thread;
122 } aout_stream_sys_t;
123
124 /**
125  * DirectSound audio output method descriptor
126  *
127  * This structure is part of the audio output thread descriptor.
128  * It describes the direct sound specific properties of an audio device.
129  */
130 struct aout_sys_t
131 {
132     aout_stream_sys_t s;
133     struct
134     {
135         float         volume;
136         LONG          mb;
137         bool          mute;
138     } volume;
139     HINSTANCE         hdsound_dll; /*< handle of the opened dsound DLL */
140 };
141
142 static HRESULT Flush( aout_stream_sys_t *sys, bool drain);
143 static HRESULT TimeGet( aout_stream_sys_t *sys, mtime_t *delay )
144 {
145     DWORD read, status;
146     HRESULT hr;
147     mtime_t size;
148
149     hr = IDirectSoundBuffer_GetStatus( sys->p_dsbuffer, &status );
150     if(hr != DS_OK || !(status & DSBSTATUS_PLAYING))
151         return 1;
152
153     hr = IDirectSoundBuffer_GetCurrentPosition( sys->p_dsbuffer, &read, NULL );
154     if( hr != DS_OK )
155         return hr;
156
157     size = read - sys->i_last_read;
158
159     /* GetCurrentPosition cannot be trusted if the return doesn't change
160      * Just return an error */
161     if( size ==  0 )
162         return 1;
163     else if( size < 0 )
164       size += DS_BUF_SIZE;
165
166     sys->i_data -= size;
167     sys->i_last_read = read;
168
169     if( sys->i_data < 0 )
170         /* underrun */
171         Flush(sys, false);
172
173     *delay = ( sys->i_data / sys->i_bytes_per_sample ) * CLOCK_FREQ / sys->i_rate;
174
175     return DS_OK;
176 }
177
178 static HRESULT StreamTimeGet( aout_stream_t *s, mtime_t *delay )
179 {
180     return TimeGet( s->sys, delay );
181 }
182
183 static int OutputTimeGet( audio_output_t *aout, mtime_t *delay )
184 {
185     return (TimeGet( &aout->sys->s, delay ) == DS_OK) ? 0 : -1;
186 }
187
188 /**
189  * Fills in one of the DirectSound frame buffers.
190  *
191  * @return VLC_SUCCESS on success.
192  */
193 static HRESULT FillBuffer( vlc_object_t *obj, aout_stream_sys_t *p_sys,
194                            block_t *p_buffer )
195 {
196     size_t towrite = (p_buffer != NULL) ? p_buffer->i_buffer : DS_BUF_SIZE;
197     void *p_write_position, *p_wrap_around;
198     unsigned long l_bytes1, l_bytes2;
199     HRESULT dsresult;
200
201     vlc_mutex_lock( &p_sys->lock );
202
203     /* Before copying anything, we have to lock the buffer */
204     dsresult = IDirectSoundBuffer_Lock(
205            p_sys->p_dsbuffer,    /* DS buffer */
206            p_sys->i_write,       /* Start offset */
207            towrite,                /* Number of bytes */
208            &p_write_position,    /* Address of lock start */
209            &l_bytes1,            /* Count of bytes locked before wrap around */
210            &p_wrap_around,       /* Buffer address (if wrap around) */
211            &l_bytes2,            /* Count of bytes after wrap around */
212            0 );                  /* Flags: DSBLOCK_FROMWRITECURSOR is buggy */
213     if( dsresult == DSERR_BUFFERLOST )
214     {
215         IDirectSoundBuffer_Restore( p_sys->p_dsbuffer );
216         dsresult = IDirectSoundBuffer_Lock(
217                                p_sys->p_dsbuffer,
218                                p_sys->i_write,
219                                towrite,
220                                &p_write_position,
221                                &l_bytes1,
222                                &p_wrap_around,
223                                &l_bytes2,
224                                0 );
225     }
226     if( dsresult != DS_OK )
227     {
228         msg_Warn( obj, "cannot lock buffer" );
229         if( p_buffer != NULL )
230             block_Release( p_buffer );
231         vlc_mutex_unlock( &p_sys->lock );
232         return dsresult;
233     }
234
235     if( p_buffer == NULL )
236     {
237         memset( p_write_position, 0, l_bytes1 );
238         memset( p_wrap_around, 0, l_bytes2 );
239     }
240     else
241     {
242         if( p_sys->chans_to_reorder ) /* Do the channel reordering here */
243             aout_ChannelReorder( p_buffer->p_buffer, p_buffer->i_buffer,
244                                  p_sys->chans_to_reorder, p_sys->chan_table,
245                                  p_sys->format );
246
247         memcpy( p_write_position, p_buffer->p_buffer, l_bytes1 );
248         if( p_wrap_around && l_bytes2 )
249             memcpy( p_wrap_around, p_buffer->p_buffer + l_bytes1, l_bytes2 );
250
251         if( unlikely( ( l_bytes1 + l_bytes2 ) < p_buffer->i_buffer ) )
252             msg_Err( obj, "Buffer overrun");
253
254         block_Release( p_buffer );
255     }
256
257     /* Now the data has been copied, unlock the buffer */
258     IDirectSoundBuffer_Unlock( p_sys->p_dsbuffer, p_write_position, l_bytes1,
259                                p_wrap_around, l_bytes2 );
260
261     p_sys->i_write += towrite;
262     p_sys->i_write %= DS_BUF_SIZE;
263     p_sys->i_data += towrite;
264     vlc_mutex_unlock( &p_sys->lock );
265
266     return DS_OK;
267 }
268
269 static HRESULT Play( vlc_object_t *obj, aout_stream_sys_t *sys,
270                      block_t *p_buffer )
271 {
272     HRESULT dsresult;
273     dsresult = FillBuffer( obj, sys, p_buffer );
274     if( dsresult != DS_OK )
275         return dsresult;
276
277     /* start playing the buffer */
278     dsresult = IDirectSoundBuffer_Play( sys->p_dsbuffer, 0, 0,
279                                         DSBPLAY_LOOPING );
280     if( dsresult == DSERR_BUFFERLOST )
281     {
282         IDirectSoundBuffer_Restore( sys->p_dsbuffer );
283         dsresult = IDirectSoundBuffer_Play( sys->p_dsbuffer,
284                                             0, 0, DSBPLAY_LOOPING );
285     }
286     if( dsresult != DS_OK )
287         msg_Err( obj, "cannot start playing buffer" );
288     else
289     {
290         vlc_mutex_lock( &sys->lock );
291         sys->b_playing = true;
292         vlc_cond_signal(&sys->cond);
293         vlc_mutex_unlock( &sys->lock );
294
295     }
296     return dsresult;
297 }
298
299 static HRESULT StreamPlay( aout_stream_t *s, block_t *block )
300 {
301     return Play( VLC_OBJECT(s), s->sys, block );
302 }
303
304 static void OutputPlay( audio_output_t *aout, block_t *block )
305 {
306     Play( VLC_OBJECT(aout), &aout->sys->s, block );
307 }
308
309 static HRESULT Pause( aout_stream_sys_t *sys, bool pause )
310 {
311     HRESULT hr;
312
313     if( pause )
314         hr = IDirectSoundBuffer_Stop( sys->p_dsbuffer );
315     else
316         hr = IDirectSoundBuffer_Play( sys->p_dsbuffer, 0, 0, DSBPLAY_LOOPING );
317     if( hr == DS_OK )
318     {
319         vlc_mutex_lock( &sys->lock );
320         sys->b_playing = !pause;
321         if( sys->b_playing )
322             vlc_cond_signal( &sys->cond );
323         vlc_mutex_unlock( &sys->lock );
324     }
325     return hr;
326 }
327
328 static HRESULT StreamPause( aout_stream_t *s, bool pause )
329 {
330     return Pause( s->sys, pause );
331 }
332
333 static void OutputPause( audio_output_t *aout, bool pause, mtime_t date )
334 {
335     Pause( &aout->sys->s, pause );
336     (void) date;
337 }
338
339 static HRESULT Flush( aout_stream_sys_t *sys, bool drain)
340 {
341     HRESULT ret = IDirectSoundBuffer_Stop( sys->p_dsbuffer );
342     if( ret == DS_OK && !drain )
343     {
344         vlc_mutex_lock(&sys->lock);
345         sys->i_data = 0;
346         sys->i_last_read = sys->i_write;
347         IDirectSoundBuffer_SetCurrentPosition( sys->p_dsbuffer, sys->i_write);
348         sys->b_playing = false;
349         vlc_mutex_unlock(&sys->lock);
350     }
351     return ret;
352 }
353
354 static HRESULT StreamFlush( aout_stream_t *s )
355 {
356     return Flush( s->sys, false );
357 }
358
359 static void OutputFlush( audio_output_t *aout, bool drain )
360 {
361     aout_stream_sys_t *sys = &aout->sys->s;
362     Flush( sys, drain );
363 }
364
365 /**
366  * Creates a DirectSound buffer of the required format.
367  *
368  * This function creates the buffer we'll use to play audio.
369  * In DirectSound there are two kinds of buffers:
370  * - the primary buffer: which is the actual buffer that the soundcard plays
371  * - the secondary buffer(s): these buffers are the one actually used by
372  *    applications and DirectSound takes care of mixing them into the primary.
373  *
374  * Once you create a secondary buffer, you cannot change its format anymore so
375  * you have to release the current one and create another.
376  */
377 static HRESULT CreateDSBuffer( vlc_object_t *obj, aout_stream_sys_t *sys,
378                                int i_format, int i_channels, int i_nb_channels,
379                                int i_rate, bool b_probe )
380 {
381     WAVEFORMATEXTENSIBLE waveformat;
382     DSBUFFERDESC         dsbdesc;
383     HRESULT              hr;
384
385     /* First set the sound buffer format */
386     waveformat.dwChannelMask = 0;
387     for( unsigned i = 0; pi_vlc_chan_order_wg4[i]; i++ )
388         if( i_channels & pi_vlc_chan_order_wg4[i] )
389             waveformat.dwChannelMask |= pi_channels_in[i];
390
391     switch( i_format )
392     {
393     case VLC_CODEC_SPDIFL:
394         i_nb_channels = 2;
395         /* To prevent channel re-ordering */
396         waveformat.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
397         waveformat.Format.wBitsPerSample = 16;
398         waveformat.Samples.wValidBitsPerSample =
399             waveformat.Format.wBitsPerSample;
400         waveformat.Format.wFormatTag = WAVE_FORMAT_DOLBY_AC3_SPDIF;
401         waveformat.SubFormat = _KSDATAFORMAT_SUBTYPE_DOLBY_AC3_SPDIF;
402         break;
403
404     case VLC_CODEC_FL32:
405         waveformat.Format.wBitsPerSample = sizeof(float) * 8;
406         waveformat.Samples.wValidBitsPerSample =
407             waveformat.Format.wBitsPerSample;
408         waveformat.Format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
409         waveformat.SubFormat = _KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
410         break;
411
412     case VLC_CODEC_S16N:
413         waveformat.Format.wBitsPerSample = 16;
414         waveformat.Samples.wValidBitsPerSample =
415             waveformat.Format.wBitsPerSample;
416         waveformat.Format.wFormatTag = WAVE_FORMAT_PCM;
417         waveformat.SubFormat = _KSDATAFORMAT_SUBTYPE_PCM;
418         break;
419     }
420
421     waveformat.Format.nChannels = i_nb_channels;
422     waveformat.Format.nSamplesPerSec = i_rate;
423     waveformat.Format.nBlockAlign =
424         waveformat.Format.wBitsPerSample / 8 * i_nb_channels;
425     waveformat.Format.nAvgBytesPerSec =
426         waveformat.Format.nSamplesPerSec * waveformat.Format.nBlockAlign;
427
428     sys->i_bytes_per_sample = waveformat.Format.nBlockAlign;
429     sys->format = i_format;
430
431     /* Then fill in the direct sound descriptor */
432     memset(&dsbdesc, 0, sizeof(DSBUFFERDESC));
433     dsbdesc.dwSize = sizeof(DSBUFFERDESC);
434     dsbdesc.dwFlags = DSBCAPS_GETCURRENTPOSITION2 /* Better position accuracy */
435                     | DSBCAPS_GLOBALFOCUS         /* Allows background playing */
436                     | DSBCAPS_CTRLVOLUME          /* Allows volume control */
437                     | DSBCAPS_CTRLPOSITIONNOTIFY; /* Allow position notifications */
438
439     /* Only use the new WAVE_FORMAT_EXTENSIBLE format for multichannel audio */
440     if( i_nb_channels <= 2 )
441     {
442         waveformat.Format.cbSize = 0;
443     }
444     else
445     {
446         waveformat.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
447         waveformat.Format.cbSize =
448             sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
449
450         /* Needed for 5.1 on emu101k */
451         dsbdesc.dwFlags |= DSBCAPS_LOCHARDWARE;
452     }
453
454     dsbdesc.dwBufferBytes = DS_BUF_SIZE; /* buffer size */
455     dsbdesc.lpwfxFormat = (WAVEFORMATEX *)&waveformat;
456
457     /* CreateSoundBuffer doesn't allow volume control for non-PCM buffers */
458     if ( i_format == VLC_CODEC_SPDIFL )
459         dsbdesc.dwFlags &= ~DSBCAPS_CTRLVOLUME;
460
461     hr = IDirectSound_CreateSoundBuffer( sys->p_dsobject, &dsbdesc,
462                                          &sys->p_dsbuffer, NULL );
463     if( FAILED(hr) )
464     {
465         if( !(dsbdesc.dwFlags & DSBCAPS_LOCHARDWARE) )
466             return hr;
467
468         /* Try without DSBCAPS_LOCHARDWARE */
469         dsbdesc.dwFlags &= ~DSBCAPS_LOCHARDWARE;
470         hr = IDirectSound_CreateSoundBuffer( sys->p_dsobject, &dsbdesc,
471                                              &sys->p_dsbuffer, NULL );
472         if( FAILED(hr) )
473             return hr;
474         if( !b_probe )
475             msg_Dbg( obj, "couldn't use hardware sound buffer" );
476     }
477
478     /* Stop here if we were just probing */
479     if( b_probe )
480     {
481         IDirectSoundBuffer_Release( sys->p_dsbuffer );
482         sys->p_dsbuffer = NULL;
483         return DS_OK;
484     }
485
486     sys->i_rate = i_rate;
487     sys->i_channel_mask = waveformat.dwChannelMask;
488     sys->chans_to_reorder =
489         aout_CheckChannelReorder( pi_channels_in, pi_channels_out,
490                                   waveformat.dwChannelMask, sys->chan_table );
491     if( sys->chans_to_reorder )
492         msg_Dbg( obj, "channel reordering needed" );
493
494     hr = IDirectSoundBuffer_QueryInterface( sys->p_dsbuffer,
495                                             &IID_IDirectSoundNotify,
496                                             (void **) &sys->p_notify );
497     if( hr != DS_OK )
498     {
499         msg_Err( obj, "Couldn't query IDirectSoundNotify" );
500         sys->p_notify = NULL;
501     }
502
503     FillBuffer( obj, sys, NULL );
504     return DS_OK;
505 }
506
507 /**
508  * Creates a PCM DirectSound buffer.
509  *
510  * We first try to create a WAVE_FORMAT_IEEE_FLOAT buffer if supported by
511  * the hardware, otherwise we create a WAVE_FORMAT_PCM buffer.
512  */
513 static HRESULT CreateDSBufferPCM( vlc_object_t *obj, aout_stream_sys_t *sys,
514                                   vlc_fourcc_t *i_format, int i_channels,
515                                   int i_rate, bool b_probe )
516 {
517     HRESULT hr;
518     unsigned i_nb_channels = popcount( i_channels );
519
520     if( var_GetBool( obj, "directx-audio-float32" ) )
521     {
522         hr = CreateDSBuffer( obj, sys, VLC_CODEC_FL32, i_channels,
523                              i_nb_channels, i_rate, b_probe );
524         if( hr == DS_OK )
525         {
526             *i_format = VLC_CODEC_FL32;
527             return DS_OK;
528         }
529     }
530
531     hr = CreateDSBuffer( obj, sys, VLC_CODEC_S16N, i_channels, i_nb_channels,
532                          i_rate, b_probe );
533     if( hr == DS_OK )
534     {
535         *i_format = VLC_CODEC_S16N;
536         return DS_OK;
537     }
538
539     return hr;
540 }
541
542 /**
543  * Closes the audio device.
544  */
545 static HRESULT Stop( aout_stream_sys_t *p_sys )
546 {
547     vlc_mutex_lock( &p_sys->lock );
548     p_sys->b_playing =  true;
549     vlc_cond_signal( &p_sys->cond );
550     vlc_cancel( p_sys->eraser_thread );
551     vlc_mutex_unlock( &p_sys->lock );
552     vlc_join( p_sys->eraser_thread, NULL );
553     if( p_sys->p_notify != NULL )
554     {
555         IDirectSoundNotify_Release(p_sys->p_notify );
556         p_sys->p_notify = NULL;
557     }
558     if( p_sys->p_dsbuffer != NULL )
559     {
560         IDirectSoundBuffer_Stop( p_sys->p_dsbuffer );
561         IDirectSoundBuffer_Release( p_sys->p_dsbuffer );
562         p_sys->p_dsbuffer = NULL;
563     }
564     if( p_sys->p_dsobject != NULL )
565     {
566         IDirectSound_Release( p_sys->p_dsobject );
567         p_sys->p_dsobject = NULL;
568     }
569     return DS_OK;
570 }
571
572 static HRESULT StreamStop( aout_stream_t *s )
573 {
574     HRESULT hr;
575
576     hr = Stop( s->sys );
577     free( s->sys );
578     return hr;
579 }
580
581 static void OutputStop( audio_output_t *aout )
582 {
583     msg_Dbg( aout, "closing audio device" );
584     Stop( &aout->sys->s );
585 }
586
587 static HRESULT Start( vlc_object_t *obj, aout_stream_sys_t *sys,
588                       audio_sample_format_t *restrict fmt )
589 {
590 #if !VLC_WINSTORE_APP
591     /* Set DirectSound Cooperative level, ie what control we want over Windows
592      * sound device. In our case, DSSCL_EXCLUSIVE means that we can modify the
593      * settings of the primary buffer, but also that only the sound of our
594      * application will be hearable when it will have the focus.
595      * !!! (this is not really working as intended yet because to set the
596      * cooperative level you need the window handle of your application, and
597      * I don't know of any easy way to get it. Especially since we might play
598      * sound without any video, and so what window handle should we use ???
599      * The hack for now is to use the Desktop window handle - it seems to be
600      * working */
601     if( IDirectSound_SetCooperativeLevel( sys->p_dsobject, GetDesktopWindow(),
602                                           DSSCL_EXCLUSIVE) )
603         msg_Warn( obj, "cannot set direct sound cooperative level" );
604 #endif
605
606     const char *const *ppsz_compare = speaker_list;
607     char *psz_speaker;
608     int i = 0;
609     HRESULT hr;
610
611     /* Retrieve config values */
612     var_Create( obj, "directx-audio-float32",
613                 VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
614     psz_speaker = var_CreateGetString( obj, "directx-audio-speaker" );
615
616     while ( *ppsz_compare != NULL )
617     {
618         if ( !strncmp( *ppsz_compare, psz_speaker, strlen(*ppsz_compare) ) )
619         {
620             break;
621         }
622         ppsz_compare++; i++;
623     }
624
625     if ( *ppsz_compare == NULL )
626     {
627         msg_Err( obj, "(%s) isn't valid speaker setup option", psz_speaker );
628         msg_Err( obj, "Defaulting to Windows default speaker config");
629         i = 0;
630     }
631     free( psz_speaker );
632
633     if( AOUT_FMT_SPDIF( fmt ) && var_InheritBool( obj, "spdif" ) )
634     {
635         hr = CreateDSBuffer( obj, sys, VLC_CODEC_SPDIFL,
636                              fmt->i_physical_channels,
637                              aout_FormatNbChannels(fmt), fmt->i_rate, false );
638         if( hr == DS_OK )
639         {
640             msg_Dbg( obj, "using A/52 pass-through over S/PDIF" );
641             fmt->i_format = VLC_CODEC_SPDIFL;
642
643             /* Calculate the frame size in bytes */
644             fmt->i_bytes_per_frame = AOUT_SPDIF_SIZE;
645             fmt->i_frame_length = A52_FRAME_NB;
646         }
647     }
648     else
649         hr = DSERR_UNSUPPORTED;
650
651     if( hr != DS_OK )
652     {
653         if( i == 0 )
654         {
655             DWORD ui_speaker_config;
656             int i_channels = 2; /* Default to stereo */
657             int i_orig_channels = aout_FormatNbChannels( fmt );
658
659             /* Check the speaker configuration to determine which channel
660              * config should be the default */
661             hr = IDirectSound_GetSpeakerConfig( sys->p_dsobject,
662                                                 &ui_speaker_config );
663             if( FAILED(hr) )
664             {
665                 ui_speaker_config = DSSPEAKER_STEREO;
666                 msg_Dbg( obj, "GetSpeakerConfig failed" );
667             }
668
669             const char *name = "Unknown";
670             switch( DSSPEAKER_CONFIG(ui_speaker_config) )
671             {
672                 case DSSPEAKER_7POINT1:
673                 case DSSPEAKER_7POINT1_SURROUND:
674                     name = "7.1";
675                     i_channels = 8;
676                     break;
677                 case DSSPEAKER_5POINT1:
678                 case DSSPEAKER_5POINT1_SURROUND:
679                     name = "5.1";
680                     i_channels = 6;
681                     break;
682                 case DSSPEAKER_QUAD:
683                     name = "Quad";
684                     i_channels = 4;
685                     break;
686 #if 0 /* Lots of people just get their settings wrong and complain that
687        * this is a problem with VLC so just don't ever set mono by default. */
688                 case DSSPEAKER_MONO:
689                     name = "Mono";
690                     i_channels = 1;
691                     break;
692 #endif
693                 case DSSPEAKER_SURROUND:
694                     name = "Surround";
695                     i_channels = 4;
696                     break;
697                 case DSSPEAKER_STEREO:
698                     name = "Stereo";
699                     i_channels = 2;
700                     break;
701             }
702
703             if( i_channels >= i_orig_channels )
704                 i_channels = i_orig_channels;
705
706             msg_Dbg( obj, "%s speaker config: %s and stream has "
707                      "%d channels, using %d channels", "Windows", name,
708                      i_orig_channels, i_channels );
709
710             switch( i_channels )
711             {
712                 case 8:
713                     fmt->i_physical_channels = AOUT_CHANS_7_1;
714                     break;
715                 case 7:
716                 case 6:
717                     fmt->i_physical_channels = AOUT_CHANS_5_1;
718                     break;
719                 case 5:
720                 case 4:
721                     fmt->i_physical_channels = AOUT_CHANS_4_0;
722                     break;
723                 default:
724                     fmt->i_physical_channels = AOUT_CHANS_2_0;
725                     break;
726             }
727         }
728         else
729         {   /* Overriden speaker configuration */
730             const char *name = "Non-existant";
731             switch( i )
732             {
733                 case 1: /* Mono */
734                     name = "Mono";
735                     fmt->i_physical_channels = AOUT_CHAN_CENTER;
736                     break;
737                 case 2: /* Stereo */
738                     name = "Stereo";
739                     fmt->i_physical_channels = AOUT_CHANS_2_0;
740                     break;
741                 case 3: /* Quad */
742                     name = "Quad";
743                     fmt->i_physical_channels = AOUT_CHANS_4_0;
744                     break;
745                 case 4: /* 5.1 */
746                     name = "5.1";
747                     fmt->i_physical_channels = AOUT_CHANS_5_1;
748                     break;
749                 case 5: /* 7.1 */
750                     name = "7.1";
751                     fmt->i_physical_channels = AOUT_CHANS_7_1;
752                     break;
753             }
754             msg_Dbg( obj, "%s speaker config: %s", "VLC", name );
755         }
756
757         /* Open the device */
758         aout_FormatPrepare( fmt );
759
760         hr = CreateDSBufferPCM( obj, sys, &fmt->i_format,
761                                 fmt->i_physical_channels, fmt->i_rate, false );
762         if( hr != DS_OK )
763         {
764             msg_Err( obj, "cannot open directx audio device" );
765             goto error;
766         }
767     }
768
769     int ret = vlc_clone(&sys->eraser_thread, PlayedDataEraser, (void*) obj,
770                         VLC_THREAD_PRIORITY_LOW);
771     if( unlikely( ret ) )
772     {
773         if( ret != ENOMEM )
774             msg_Err( obj, "Couldn't start eraser thread" );
775         if( sys->p_notify != NULL )
776         {
777             IDirectSoundNotify_Release( sys->p_notify );
778             sys->p_notify = NULL;
779         }
780         IDirectSoundBuffer_Release( sys->p_dsbuffer );
781         sys->p_dsbuffer = NULL;
782         IDirectSound_Release( sys->p_dsobject );
783         sys->p_dsobject = NULL;
784         return ret;
785     }
786     sys->b_playing = false;
787     sys->i_write = 0;
788     sys->i_last_read =  0;
789     sys->i_data = 0;
790
791     return DS_OK;
792
793 error:
794     Stop( sys );
795     return hr;
796 }
797
798 static HRESULT StreamStart( aout_stream_t *s,
799                             audio_sample_format_t *restrict fmt,
800                             const GUID *sid )
801 {
802     aout_stream_sys_t *sys = calloc( 1, sizeof( *sys ) );
803     if( unlikely(sys == NULL) )
804         return E_OUTOFMEMORY;
805
806     DIRECTX_AUDIO_ACTIVATION_PARAMS params = {
807         .cbDirectXAudioActivationParams = sizeof( params ),
808         .guidAudioSession = *sid,
809         .dwAudioStreamFlags = 0,
810     };
811     PROPVARIANT prop;
812
813     PropVariantInit( &prop );
814     prop.vt = VT_BLOB;
815     prop.blob.cbSize = sizeof( params );
816     prop.blob.pBlobData = (BYTE *)&params;
817
818     void *pv;
819     HRESULT hr = aout_stream_Activate( s, &IID_IDirectSound, &prop, &pv );
820     if( FAILED(hr) )
821         goto error;
822
823     sys->p_dsobject = pv;
824
825     hr = Start( VLC_OBJECT(s), sys, fmt );
826     if( FAILED(hr) )
827         goto error;
828
829     s->sys = sys;
830     s->time_get = StreamTimeGet;
831     s->play = StreamPlay;
832     s->pause = StreamPause;
833     s->flush = StreamFlush;
834     return S_OK;
835 error:
836     free( sys );
837     return hr;
838 }
839
840 /**
841  * Handles all the gory details of DirectSound initialization.
842  */
843 static int InitDirectSound( audio_output_t *p_aout )
844 {
845     aout_sys_t *sys = p_aout->sys;
846     GUID guid, *p_guid = NULL;
847     HRESULT (WINAPI *OurDirectSoundCreate)(LPGUID, LPDIRECTSOUND *, LPUNKNOWN);
848
849     OurDirectSoundCreate = (void *)
850         GetProcAddress( p_aout->sys->hdsound_dll,
851                         "DirectSoundCreate" );
852     if( OurDirectSoundCreate == NULL )
853     {
854         msg_Warn( p_aout, "GetProcAddress FAILED" );
855         goto error;
856     }
857
858     char *dev = var_GetNonEmptyString( p_aout, "directx-audio-device" );
859     if( dev != NULL )
860     {
861         LPOLESTR lpsz = ToWide( dev );
862         free( dev );
863
864         if( SUCCEEDED( IIDFromString( lpsz, &guid ) ) )
865             p_guid = &guid;
866         else
867             msg_Err( p_aout, "bad device GUID: %ls", lpsz );
868         free( lpsz );
869     }
870
871     /* Create the direct sound object */
872     if FAILED( OurDirectSoundCreate( p_guid, &sys->s.p_dsobject, NULL ) )
873     {
874         msg_Warn( p_aout, "cannot create a direct sound device" );
875         goto error;
876     }
877
878     return VLC_SUCCESS;
879
880 error:
881     sys->s.p_dsobject = NULL;
882     return VLC_EGENERIC;
883
884 }
885
886 static int VolumeSet( audio_output_t *p_aout, float volume )
887 {
888     aout_sys_t *sys = p_aout->sys;
889     int ret = 0;
890
891     /* Directsound doesn't support amplification, so we use software
892        gain if we need it and only for this */
893     float gain = volume > 1.f ? volume * volume * volume : 1.f;
894     aout_GainRequest( p_aout, gain );
895
896     /* millibels from linear amplification */
897     LONG mb = lroundf( 6000.f * log10f( __MIN( volume, 1.f ) ));
898
899     /* Clamp to allowed DirectSound range */
900     static_assert( DSBVOLUME_MIN < DSBVOLUME_MAX, "DSBVOLUME_* confused" );
901     if( mb > DSBVOLUME_MAX )
902     {
903         mb = DSBVOLUME_MAX;
904         ret = -1;
905     }
906     if( mb <= DSBVOLUME_MIN )
907         mb = DSBVOLUME_MIN;
908
909     sys->volume.mb = mb;
910     sys->volume.volume = volume;
911     if( !sys->volume.mute && sys->s.p_dsbuffer != NULL &&
912         IDirectSoundBuffer_SetVolume( sys->s.p_dsbuffer, mb ) != DS_OK )
913         return -1;
914     /* Convert back to UI volume */
915     aout_VolumeReport( p_aout, volume );
916
917     if( var_InheritBool( p_aout, "volume-save" ) )
918         config_PutFloat( p_aout, "directx-volume", volume );
919     return ret;
920 }
921
922 static int MuteSet( audio_output_t *p_aout, bool mute )
923 {
924     HRESULT res = DS_OK;
925     aout_sys_t *sys = p_aout->sys;
926
927     sys->volume.mute = mute;
928
929     if( sys->s.p_dsbuffer != NULL )
930         res = IDirectSoundBuffer_SetVolume( sys->s.p_dsbuffer,
931                                             mute? DSBVOLUME_MIN : sys->volume.mb );
932
933     aout_MuteReport( p_aout, mute );
934     return (res != DS_OK);
935 }
936
937 static int OutputStart( audio_output_t *p_aout,
938                         audio_sample_format_t *restrict fmt )
939 {
940     msg_Dbg( p_aout, "Opening DirectSound Audio Output" );
941
942     /* Initialise DirectSound */
943     if( InitDirectSound( p_aout ) )
944     {
945         msg_Err( p_aout, "cannot initialize DirectSound" );
946         return -1;
947     }
948
949     HRESULT hr = Start( VLC_OBJECT(p_aout), &p_aout->sys->s, fmt );
950     if( FAILED(hr) )
951         return -1;
952
953     /* Force volume update */
954     VolumeSet( p_aout, p_aout->sys->volume.volume );
955     MuteSet( p_aout, p_aout->sys->volume.mute );
956
957     /* then launch the notification thread */
958     p_aout->time_get = OutputTimeGet;
959     p_aout->play = OutputPlay;
960     p_aout->pause = OutputPause;
961     p_aout->flush = OutputFlush;
962
963     return 0;
964 }
965
966 typedef struct
967 {
968     unsigned count;
969     char **ids;
970     char **names;
971 } ds_list_t;
972
973 static int CALLBACK DeviceEnumCallback( LPGUID guid, LPCWSTR desc,
974                                         LPCWSTR mod, LPVOID data )
975 {
976     ds_list_t *list = data;
977     OLECHAR buf[48];
978
979     if( StringFromGUID2( guid, buf, 48 ) <= 0 )
980         return true;
981
982     list->count++;
983     list->ids = xrealloc( list->ids, list->count * sizeof(char *) );
984     list->names = xrealloc( list->names, list->count * sizeof(char *) );
985     list->ids[list->count - 1] = FromWide( buf );
986     list->names[list->count - 1] = FromWide( desc );
987     if( list->ids == NULL || list->names == NULL )
988         abort();
989
990     (void) mod;
991     return true;
992 }
993
994 /**
995  * Stores the list of devices in preferences
996  */
997 static int ReloadDirectXDevices( vlc_object_t *p_this, char const *psz_name,
998                                  char ***values, char ***descs )
999 {
1000     ds_list_t list = {
1001         .count = 1,
1002         .ids = xmalloc(sizeof (char *)),
1003         .names = xmalloc(sizeof (char *)),
1004     };
1005     list.ids[0] = xstrdup("");
1006     list.names[0] = xstrdup(_("Default"));
1007
1008     (void) psz_name;
1009
1010     HANDLE hdsound_dll = LoadLibrary(_T("DSOUND.DLL"));
1011     if( hdsound_dll == NULL )
1012     {
1013         msg_Warn( p_this, "cannot open DSOUND.DLL" );
1014         goto out;
1015     }
1016
1017     /* Get DirectSoundEnumerate */
1018     HRESULT (WINAPI *OurDirectSoundEnumerate)(LPDSENUMCALLBACKW, LPVOID) =
1019             (void *)GetProcAddress( hdsound_dll, "DirectSoundEnumerateW" );
1020     if( OurDirectSoundEnumerate != NULL )
1021     {
1022         OurDirectSoundEnumerate( DeviceEnumCallback, &list );
1023         msg_Dbg( p_this, "found %u devices", list.count );
1024     }
1025     FreeLibrary(hdsound_dll);
1026
1027 out:
1028     *values = list.ids;
1029     *descs = list.names;
1030     return list.count;
1031 }
1032
1033 static int DeviceSelect (audio_output_t *aout, const char *id)
1034 {
1035     var_SetString(aout, "directx-audio-device", (id != NULL) ? id : "");
1036     aout_DeviceReport (aout, id);
1037     aout_RestartRequest (aout, AOUT_RESTART_OUTPUT);
1038     return 0;
1039 }
1040
1041 static int Open(vlc_object_t *obj)
1042 {
1043     audio_output_t *aout = (audio_output_t *)obj;
1044
1045     HINSTANCE hdsound_dll = LoadLibrary(_T("DSOUND.DLL"));
1046     if (hdsound_dll == NULL)
1047     {
1048         msg_Warn(aout, "cannot open DSOUND.DLL");
1049         return VLC_EGENERIC;
1050     }
1051
1052     aout_sys_t *sys = calloc(1, sizeof (*sys));
1053     if (unlikely(sys == NULL))
1054         return VLC_ENOMEM;
1055
1056     sys->hdsound_dll = hdsound_dll;
1057
1058     aout->sys = sys;
1059     aout->start = OutputStart;
1060     aout->stop = OutputStop;
1061     aout->volume_set = VolumeSet;
1062     aout->mute_set = MuteSet;
1063     aout->device_select = DeviceSelect;
1064
1065     /* Volume */
1066     sys->volume.volume = var_InheritFloat(aout, "directx-volume");
1067     aout_VolumeReport(aout, sys->volume.volume );
1068     MuteSet(aout, var_InheritBool(aout, "mute"));
1069
1070     /* DirectSound does not support hot-plug events (unless with WASAPI) */
1071     char **ids, **names;
1072     int count = ReloadDirectXDevices(obj, NULL, &ids, &names);
1073     if (count >= 0)
1074     {
1075         for (int i = 0; i < count; i++)
1076         {
1077             aout_HotplugReport(aout, ids[i], names[i]);
1078             free(names[i]);
1079             free(ids[i]);
1080         }
1081         free(names);
1082         free(ids);
1083     }
1084
1085     char *dev = var_CreateGetNonEmptyString(aout, "directx-audio-device");
1086     aout_DeviceReport(aout, dev);
1087     free(dev);
1088
1089     vlc_mutex_init(&sys->s.lock);
1090     vlc_cond_init(&sys->s.cond);
1091
1092     return VLC_SUCCESS;
1093 }
1094
1095 static void Close(vlc_object_t *obj)
1096 {
1097     audio_output_t *aout = (audio_output_t *)obj;
1098     aout_sys_t *sys = aout->sys;
1099     vlc_cond_destroy( &sys->s.cond );
1100     vlc_mutex_destroy( &sys->s.lock );
1101
1102     var_Destroy(aout, "directx-audio-device");
1103     FreeLibrary(sys->hdsound_dll); /* free DSOUND.DLL */
1104     free(sys);
1105 }
1106
1107 static void * PlayedDataEraser( void * data )
1108 {
1109     const audio_output_t *aout = (audio_output_t *) data;
1110     aout_stream_sys_t *p_sys = &aout->sys->s;
1111     void *p_write_position, *p_wrap_around;
1112     unsigned long l_bytes1, l_bytes2;
1113     DWORD i_read;
1114     int64_t toerase, tosleep;
1115     HRESULT dsresult;
1116
1117     for(;;)
1118     {
1119         int canc = vlc_savecancel();
1120         vlc_mutex_lock( &p_sys->lock );
1121
1122         while( !p_sys->b_playing )
1123            vlc_cond_wait( &p_sys->cond, &p_sys->lock );
1124
1125         toerase = 0;
1126         tosleep = 0;
1127
1128         dsresult = IDirectSoundBuffer_GetCurrentPosition( p_sys->p_dsbuffer,
1129                                                           &i_read, NULL );
1130         if( dsresult == DS_OK )
1131         {
1132             int64_t max = (int64_t) i_read - (int64_t) p_sys->i_write;
1133             tosleep = -max;
1134             if( max <= 0 )
1135                 max += DS_BUF_SIZE;
1136             else
1137                 tosleep += DS_BUF_SIZE;
1138             toerase = max;
1139             tosleep = ( tosleep / p_sys->i_bytes_per_sample ) * CLOCK_FREQ / p_sys->i_rate;
1140         }
1141
1142         tosleep = __MAX( tosleep, 20000 );
1143         dsresult = IDirectSoundBuffer_Lock( p_sys->p_dsbuffer,
1144                                             p_sys->i_write,
1145                                             toerase,
1146                                             &p_write_position,
1147                                             &l_bytes1,
1148                                             &p_wrap_around,
1149                                             &l_bytes2,
1150                                             0 );
1151         if( dsresult == DSERR_BUFFERLOST )
1152         {
1153             IDirectSoundBuffer_Restore( p_sys->p_dsbuffer );
1154             dsresult = IDirectSoundBuffer_Lock( p_sys->p_dsbuffer,
1155                                                 p_sys->i_write,
1156                                                 toerase,
1157                                                 &p_write_position,
1158                                                 &l_bytes1,
1159                                                 &p_wrap_around,
1160                                                 &l_bytes2,
1161                                                 0 );
1162         }
1163         if( dsresult != DS_OK )
1164             goto wait;
1165
1166         memset( p_write_position, 0, l_bytes1 );
1167         memset( p_wrap_around, 0, l_bytes2 );
1168
1169         IDirectSoundBuffer_Unlock( p_sys->p_dsbuffer, p_write_position, l_bytes1,
1170                                    p_wrap_around, l_bytes2 );
1171 wait:
1172         vlc_mutex_unlock(&p_sys->lock);
1173         vlc_restorecancel(canc);
1174         msleep(tosleep);
1175     }
1176     return NULL;
1177 }