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