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