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