X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=modules%2Faudio_output%2Fwaveout.c;h=41505c360700decb3d061105f7f0823e4f6a10ab;hb=d5eaf4a600043f14245176cd8dbade8c88a97ee8;hp=34cd18c5226c88c2921743de36e278d19b2ae5ee;hpb=da1b738305444f06fe36e761ab19c390383b524e;p=vlc diff --git a/modules/audio_output/waveout.c b/modules/audio_output/waveout.c index 34cd18c522..41505c3607 100644 --- a/modules/audio_output/waveout.c +++ b/modules/audio_output/waveout.c @@ -2,7 +2,7 @@ * waveout.c : Windows waveOut plugin for vlc ***************************************************************************** * Copyright (C) 2001 VideoLAN - * $Id: waveout.c,v 1.11 2002/11/15 16:27:10 gbazin Exp $ + * $Id: waveout.c,v 1.26 2003/07/11 23:14:03 gbazin Exp $ * * Authors: Gildas Bazin * @@ -24,10 +24,7 @@ /***************************************************************************** * Preamble *****************************************************************************/ -#include /* ENOMEM */ -#include /* open(), O_WRONLY */ #include /* strerror() */ - #include /* calloc(), malloc(), free() */ #include @@ -38,36 +35,104 @@ #include #define FRAME_SIZE 1024 /* The size is in samples, not in bytes */ -#define FRAMES_NUM 4 +#define FRAMES_NUM 8 /***************************************************************************** * Useful macros *****************************************************************************/ +#ifdef UNDER_CE +# define DWORD_PTR DWORD +#endif + #ifndef WAVE_FORMAT_IEEE_FLOAT # define WAVE_FORMAT_IEEE_FLOAT 0x0003 #endif +#ifndef WAVE_FORMAT_DOLBY_AC3_SPDIF +# define WAVE_FORMAT_DOLBY_AC3_SPDIF 0x0092 +#endif + +#ifndef WAVE_FORMAT_EXTENSIBLE +#define WAVE_FORMAT_EXTENSIBLE 0xFFFE +#endif + +#ifndef SPEAKER_FRONT_LEFT +# define SPEAKER_FRONT_LEFT 0x1 +# define SPEAKER_FRONT_RIGHT 0x2 +# define SPEAKER_FRONT_CENTER 0x4 +# define SPEAKER_LOW_FREQUENCY 0x8 +# define SPEAKER_BACK_LEFT 0x10 +# define SPEAKER_BACK_RIGHT 0x20 +# define SPEAKER_FRONT_LEFT_OF_CENTER 0x40 +# define SPEAKER_FRONT_RIGHT_OF_CENTER 0x80 +# define SPEAKER_BACK_CENTER 0x100 +# define SPEAKER_SIDE_LEFT 0x200 +# define SPEAKER_SIDE_RIGHT 0x400 +# define SPEAKER_TOP_CENTER 0x800 +# define SPEAKER_TOP_FRONT_LEFT 0x1000 +# define SPEAKER_TOP_FRONT_CENTER 0x2000 +# define SPEAKER_TOP_FRONT_RIGHT 0x4000 +# define SPEAKER_TOP_BACK_LEFT 0x8000 +# define SPEAKER_TOP_BACK_CENTER 0x10000 +# define SPEAKER_TOP_BACK_RIGHT 0x20000 +# define SPEAKER_RESERVED 0x80000000 +#endif + +#ifndef _WAVEFORMATEXTENSIBLE_ +typedef struct { + WAVEFORMATEX Format; + union { + WORD wValidBitsPerSample; /* bits of precision */ + WORD wSamplesPerBlock; /* valid if wBitsPerSample==0 */ + WORD wReserved; /* If neither applies, set to zero. */ + } Samples; + DWORD dwChannelMask; /* which channels are */ + /* present in stream */ + GUID SubFormat; +} WAVEFORMATEXTENSIBLE, *PWAVEFORMATEXTENSIBLE; +#endif + +#include +DEFINE_GUID( __KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, WAVE_FORMAT_IEEE_FLOAT, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 ); +DEFINE_GUID( __KSDATAFORMAT_SUBTYPE_PCM, WAVE_FORMAT_PCM, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 ); +DEFINE_GUID( __KSDATAFORMAT_SUBTYPE_DOLBY_AC3_SPDIF, WAVE_FORMAT_DOLBY_AC3_SPDIF, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 ); + /***************************************************************************** * Local prototypes *****************************************************************************/ -static int Open ( vlc_object_t * ); -static void Close ( vlc_object_t * ); +static int Open ( vlc_object_t * ); +static void Close ( vlc_object_t * ); static void Play ( aout_instance_t * ); +/***************************************************************************** + * notification_thread_t: waveOut event thread + *****************************************************************************/ +typedef struct notification_thread_t +{ + VLC_COMMON_MEMBERS + aout_instance_t *p_aout; + +} notification_thread_t; + /* local functions */ -static int OpenWaveOut ( aout_instance_t *p_aout, int i_format, - int i_channels, int i_rate ); +static void Probe ( aout_instance_t * ); +static int OpenWaveOut ( aout_instance_t *, int, int, int, int, vlc_bool_t ); +static int OpenWaveOutPCM( aout_instance_t *, int*, int, int, int, vlc_bool_t ); +static void CheckReordering( aout_instance_t *, int ); static int PlayWaveOut ( aout_instance_t *, HWAVEOUT, WAVEHDR *, aout_buffer_t * ); -static void CALLBACK WaveOutCallback ( HWAVEOUT h_waveout, UINT uMsg, - DWORD _p_aout, - DWORD dwParam1, DWORD dwParam2 ); + +static void CALLBACK WaveOutCallback ( HWAVEOUT, UINT, DWORD, DWORD, DWORD ); +static void WaveOutThread( notification_thread_t * ); + +static void InterleaveFloat32( float *, int *, int ); +static void InterleaveS16( int16_t *, int *, int ); /***************************************************************************** * Module descriptor *****************************************************************************/ vlc_module_begin(); - set_description( _("Win32 waveOut extension module") ); + set_description( _("Win32 waveOut extension output") ); set_capability( "audio output", 50 ); set_callbacks( Open, Close ); vlc_module_end(); @@ -82,24 +147,44 @@ struct aout_sys_t { HWAVEOUT h_waveout; /* handle to waveout instance */ - WAVEFORMATEX waveformat; /* audio format */ + WAVEFORMATEXTENSIBLE waveformat; /* audio format */ WAVEHDR waveheader[FRAMES_NUM]; + notification_thread_t *p_notif; /* WaveOutThread id */ + HANDLE event; + int i_buffer_size; byte_t *p_silence_buffer; /* buffer we use to play silence */ + + vlc_bool_t b_chan_reorder; /* do we need channel reordering */ + int *pi_chan_table; }; +static const uint32_t pi_channels_in[] = + { AOUT_CHAN_LEFT, AOUT_CHAN_RIGHT, + AOUT_CHAN_REARLEFT, AOUT_CHAN_REARRIGHT, + AOUT_CHAN_CENTER, AOUT_CHAN_LFE }; +static const uint32_t pi_channels_out[] = + { SPEAKER_FRONT_LEFT, SPEAKER_FRONT_RIGHT, + SPEAKER_BACK_LEFT, SPEAKER_BACK_RIGHT, + SPEAKER_FRONT_CENTER, SPEAKER_LOW_FREQUENCY }; +static const uint32_t pi_channels_ordered[] = + { SPEAKER_FRONT_LEFT, SPEAKER_FRONT_RIGHT, SPEAKER_FRONT_CENTER, + SPEAKER_LOW_FREQUENCY, + SPEAKER_BACK_LEFT, SPEAKER_BACK_RIGHT }; + /***************************************************************************** * Open: open the audio device ***************************************************************************** * This function opens and setups Win32 waveOut *****************************************************************************/ static int Open( vlc_object_t *p_this ) -{ +{ aout_instance_t *p_aout = (aout_instance_t *)p_this; - int i_nb_channels, i; + vlc_value_t val; + int i; /* Allocate structure */ p_aout->output.p_sys = malloc( sizeof( aout_sys_t ) ); @@ -111,72 +196,251 @@ static int Open( vlc_object_t *p_this ) } p_aout->output.pf_play = Play; - aout_VolumeSoftInit( p_aout ); + p_aout->output.p_sys->pi_chan_table = NULL; + p_aout->b_die = VLC_FALSE; - i_nb_channels = aout_FormatNbChannels( &p_aout->output.output ); - if ( i_nb_channels >= 2 ) + if( var_Type( p_aout, "audio-device" ) == 0 ) { - i_nb_channels = 2; - p_aout->output.output.i_physical_channels = - AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT; + Probe( p_aout ); } - else + + if( var_Get( p_aout, "audio-device", &val ) < 0 ) { - i_nb_channels = 1; - p_aout->output.output.i_physical_channels = - AOUT_CHAN_CENTER; + /* Probe() has failed. */ + free( p_aout->output.p_sys ); + return VLC_EGENERIC; } - /* We need to open the device with default values to be sure it is - * available */ - p_aout->output.output.i_format = VLC_FOURCC('f','l','3','2'); - if ( OpenWaveOut( p_aout, WAVE_FORMAT_IEEE_FLOAT, i_nb_channels, - p_aout->output.output.i_rate ) ) + /* Open the device */ + if( val.i_int == AOUT_VAR_SPDIF ) { - msg_Err( p_aout, "Audio device doesn't allow WAVE_FORMAT_IEEE_FLOAT" ); + p_aout->output.output.i_format = VLC_FOURCC('s','p','d','i'); - p_aout->output.output.i_format = VLC_FOURCC('s','1','6','l'); - if ( OpenWaveOut( p_aout, WAVE_FORMAT_PCM, i_nb_channels, - p_aout->output.output.i_rate ) ) + if( OpenWaveOut( p_aout, VLC_FOURCC('s','p','d','i'), + p_aout->output.output.i_physical_channels, + aout_FormatNbChannels( &p_aout->output.output ), + p_aout->output.output.i_rate, VLC_FALSE ) + != VLC_SUCCESS ) { msg_Err( p_aout, "cannot open waveout audio device" ); + free( p_aout->output.p_sys ); return VLC_EGENERIC; } + + /* Calculate the frame size in bytes */ + p_aout->output.i_nb_samples = A52_FRAME_NB; + p_aout->output.output.i_bytes_per_frame = AOUT_SPDIF_SIZE; + p_aout->output.output.i_frame_length = A52_FRAME_NB; + p_aout->output.p_sys->i_buffer_size = + p_aout->output.output.i_bytes_per_frame; + + aout_VolumeNoneInit( p_aout ); } + else + { + if( val.i_int == AOUT_VAR_5_1 ) + { + p_aout->output.output.i_physical_channels + = AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT | AOUT_CHAN_CENTER + | AOUT_CHAN_REARLEFT | AOUT_CHAN_REARRIGHT + | AOUT_CHAN_LFE; + } + else if( val.i_int == AOUT_VAR_2F2R ) + { + p_aout->output.output.i_physical_channels + = AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT + | AOUT_CHAN_REARLEFT | AOUT_CHAN_REARRIGHT; + } + else if( val.i_int == AOUT_VAR_MONO ) + { + p_aout->output.output.i_physical_channels = AOUT_CHAN_CENTER; + } + else + { + p_aout->output.output.i_physical_channels + = AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT; + } + + if( OpenWaveOutPCM( p_aout, &p_aout->output.output.i_format, + p_aout->output.output.i_physical_channels, + aout_FormatNbChannels( &p_aout->output.output ), + p_aout->output.output.i_rate, VLC_FALSE ) + != VLC_SUCCESS ) + { + msg_Err( p_aout, "cannot open waveout audio device" ); + free( p_aout->output.p_sys ); + return VLC_EGENERIC; + } + + /* Calculate the frame size in bytes */ + p_aout->output.i_nb_samples = FRAME_SIZE; + aout_FormatPrepare( &p_aout->output.output ); + p_aout->output.p_sys->i_buffer_size = FRAME_SIZE * + p_aout->output.output.i_bytes_per_frame; + + aout_VolumeSoftInit( p_aout ); + } + waveOutReset( p_aout->output.p_sys->h_waveout ); - /* Calculate the frame size in bytes */ - p_aout->output.i_nb_samples = FRAME_SIZE; - aout_FormatPrepare( &p_aout->output.output ); - p_aout->output.p_sys->i_buffer_size = FRAME_SIZE * - p_aout->output.output.i_bytes_per_frame; /* Allocate silence buffer */ p_aout->output.p_sys->p_silence_buffer = - calloc( p_aout->output.p_sys->i_buffer_size, 1 ); + malloc( p_aout->output.p_sys->i_buffer_size ); if( p_aout->output.p_sys->p_silence_buffer == NULL ) { + free( p_aout->output.p_sys ); msg_Err( p_aout, "out of memory" ); return 1; } + /* Zero the buffer. WinCE doesn't have calloc(). */ + memset( p_aout->output.p_sys->p_silence_buffer, 0, + p_aout->output.p_sys->i_buffer_size ); + + /* Now we need to setup our waveOut play notification structure */ + p_aout->output.p_sys->p_notif = + vlc_object_create( p_aout, sizeof(notification_thread_t) ); + p_aout->output.p_sys->p_notif->p_aout = p_aout; + p_aout->output.p_sys->event = CreateEvent( NULL, FALSE, FALSE, NULL ); + + /* Then launch the notification thread */ + if( vlc_thread_create( p_aout->output.p_sys->p_notif, + "waveOut Notification Thread", WaveOutThread, + VLC_THREAD_PRIORITY_HIGHEST, VLC_FALSE ) ) + { + msg_Err( p_aout, "cannot create WaveOutThread" ); + } + /* We need to kick off the playback in order to have the callback properly * working */ for( i = 0; i < FRAMES_NUM; i++ ) { - PlayWaveOut( p_aout, p_aout->output.p_sys->h_waveout, - &p_aout->output.p_sys->waveheader[i], NULL ); + p_aout->output.p_sys->waveheader[i].dwFlags = WHDR_DONE; } + PlayWaveOut( p_aout, p_aout->output.p_sys->h_waveout, + &p_aout->output.p_sys->waveheader[0], NULL ); + return 0; } +/***************************************************************************** + * Probe: probe the audio device for available formats and channels + *****************************************************************************/ +static void Probe( aout_instance_t * p_aout ) +{ + vlc_value_t val, text; + int i_format; + unsigned int i_physical_channels; + + var_Create( p_aout, "audio-device", VLC_VAR_INTEGER | VLC_VAR_HASCHOICE ); + text.psz_string = _("Audio device"); + var_Change( p_aout, "audio-device", VLC_VAR_SETTEXT, &text, NULL ); + + /* Test for 5.1 support */ + i_physical_channels = AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT | + AOUT_CHAN_CENTER | AOUT_CHAN_REARLEFT | + AOUT_CHAN_REARRIGHT | AOUT_CHAN_LFE; + if( p_aout->output.output.i_physical_channels == i_physical_channels ) + { + if( OpenWaveOutPCM( p_aout, &i_format, + i_physical_channels, 6, + p_aout->output.output.i_rate, VLC_TRUE ) + == VLC_SUCCESS ) + { + val.i_int = AOUT_VAR_5_1; + text.psz_string = N_("5.1"); + var_Change( p_aout, "audio-device", + VLC_VAR_ADDCHOICE, &val, &text ); + msg_Dbg( p_aout, "device supports 5.1 channels" ); + } + } + + /* Test for 2 Front 2 Rear support */ + i_physical_channels = AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT | + AOUT_CHAN_REARLEFT | AOUT_CHAN_REARRIGHT; + if( ( p_aout->output.output.i_physical_channels & i_physical_channels ) + == i_physical_channels ) + { + if( OpenWaveOutPCM( p_aout, &i_format, + i_physical_channels, 4, + p_aout->output.output.i_rate, VLC_TRUE ) + == VLC_SUCCESS ) + { + val.i_int = AOUT_VAR_2F2R; + text.psz_string = N_("2 Front 2 Rear"); + var_Change( p_aout, "audio-device", + VLC_VAR_ADDCHOICE, &val, &text ); + msg_Dbg( p_aout, "device supports 4 channels" ); + } + } + + /* Test for stereo support */ + i_physical_channels = AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT; + if( OpenWaveOutPCM( p_aout, &i_format, + i_physical_channels, 2, + p_aout->output.output.i_rate, VLC_TRUE ) + == VLC_SUCCESS ) + { + val.i_int = AOUT_VAR_STEREO; + text.psz_string = N_("Stereo"); + var_Change( p_aout, "audio-device", VLC_VAR_ADDCHOICE, &val, &text ); + msg_Dbg( p_aout, "device supports 2 channels" ); + } + + /* Test for mono support */ + i_physical_channels = AOUT_CHAN_CENTER; + if( OpenWaveOutPCM( p_aout, &i_format, + i_physical_channels, 1, + p_aout->output.output.i_rate, VLC_TRUE ) + == VLC_SUCCESS ) + { + val.i_int = AOUT_VAR_MONO; + text.psz_string = N_("Mono"); + var_Change( p_aout, "audio-device", VLC_VAR_ADDCHOICE, &val, &text ); + msg_Dbg( p_aout, "device supports 1 channel" ); + } + + /* Test for SPDIF support */ + if ( AOUT_FMT_NON_LINEAR( &p_aout->output.output ) ) + { + if( OpenWaveOut( p_aout, VLC_FOURCC('s','p','d','i'), + p_aout->output.output.i_physical_channels, + aout_FormatNbChannels( &p_aout->output.output ), + p_aout->output.output.i_rate, VLC_TRUE ) + == VLC_SUCCESS ) + { + msg_Dbg( p_aout, "device supports A/52 over S/PDIF" ); + val.i_int = AOUT_VAR_SPDIF; + text.psz_string = N_("A/52 over S/PDIF"); + var_Change( p_aout, "audio-device", + VLC_VAR_ADDCHOICE, &val, &text ); + if( config_GetInt( p_aout, "spdif" ) ) + var_Set( p_aout, "audio-device", val ); + } + } + + var_Change( p_aout, "audio-device", VLC_VAR_CHOICESCOUNT, &val, NULL ); + if( val.i_int <= 0 ) + { + /* Probe() has failed. */ + var_Destroy( p_aout, "audio-device" ); + return; + } + + var_AddCallback( p_aout, "audio-device", aout_ChannelsRestart, NULL ); + + val.b_bool = VLC_TRUE; + var_Set( p_aout, "intf-change", val ); +} + /***************************************************************************** * Play: play a sound buffer ***************************************************************************** * This doesn't actually play the buffer. This just stores the buffer so it * can be played by the callback thread. *****************************************************************************/ -static void Play( aout_instance_t *p_aout ) +static void Play( aout_instance_t *_p_aout ) { } @@ -184,30 +448,46 @@ static void Play( aout_instance_t *p_aout ) * Close: close the audio device *****************************************************************************/ static void Close( vlc_object_t *p_this ) -{ +{ aout_instance_t *p_aout = (aout_instance_t *)p_this; /* Before calling waveOutClose we must reset the device */ p_aout->b_die = VLC_TRUE; - //Hmmm, waveOutReset never seems to return... why ??? - //waveOutReset( p_aout->output.p_sys->h_waveout ); + + /* wake up the audio thread */ + SetEvent( p_aout->output.p_sys->event ); + vlc_thread_join( p_aout->output.p_sys->p_notif ); + CloseHandle( p_aout->output.p_sys->event ); /* Wait for the waveout buffers to be freed */ - while( !(p_aout->output.p_sys->waveheader[0].dwFlags & WHDR_DONE) || - !(p_aout->output.p_sys->waveheader[1].dwFlags & WHDR_DONE) ) + while( VLC_TRUE ) { + int i; + vlc_bool_t b_not_done = VLC_FALSE; + + for( i = 0; i < FRAMES_NUM; i++ ) + { + if( !(p_aout->output.p_sys->waveheader[i].dwFlags & WHDR_DONE) ) + b_not_done = VLC_TRUE; + } + + if( !b_not_done ) + break; + msleep( 1000 ); } + waveOutReset( p_aout->output.p_sys->h_waveout ); + /* Close the device */ if( waveOutClose( p_aout->output.p_sys->h_waveout ) != MMSYSERR_NOERROR ) { msg_Err( p_aout, "waveOutClose failed" ); } - /* Free silence buffer */ free( p_aout->output.p_sys->p_silence_buffer ); - + if( p_aout->output.p_sys->pi_chan_table ) + free( p_aout->output.p_sys->pi_chan_table ); free( p_aout->output.p_sys ); } @@ -215,48 +495,186 @@ static void Close( vlc_object_t *p_this ) * OpenWaveOut: open the waveout sound device ****************************************************************************/ static int OpenWaveOut( aout_instance_t *p_aout, int i_format, - int i_channels, int i_rate ) + int i_channels, int i_nb_channels, int i_rate, + vlc_bool_t b_probe ) { MMRESULT result; + unsigned int i; /* Set sound format */ - p_aout->output.p_sys->waveformat.wFormatTag = i_format; - p_aout->output.p_sys->waveformat.nChannels = i_channels; - p_aout->output.p_sys->waveformat.nSamplesPerSec = i_rate; + +#define waveformat p_aout->output.p_sys->waveformat + + waveformat.dwChannelMask = 0; + for( i = 0; i < sizeof(pi_channels_in)/sizeof(uint32_t); i++ ) + { + if( i_channels & pi_channels_in[i] ) + waveformat.dwChannelMask |= pi_channels_out[i]; + } switch( i_format ) { - case WAVE_FORMAT_PCM: - p_aout->output.p_sys->waveformat.wBitsPerSample = 16; + case VLC_FOURCC('s','p','d','i'): + i_nb_channels = 2; + /* To prevent channel re-ordering */ + waveformat.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT; + waveformat.Format.wBitsPerSample = 16; + waveformat.Samples.wValidBitsPerSample = + waveformat.Format.wBitsPerSample; + waveformat.Format.wFormatTag = WAVE_FORMAT_DOLBY_AC3_SPDIF; + waveformat.SubFormat = __KSDATAFORMAT_SUBTYPE_DOLBY_AC3_SPDIF; + break; + + case VLC_FOURCC('f','l','3','2'): + waveformat.Format.wBitsPerSample = sizeof(float) * 8; + waveformat.Samples.wValidBitsPerSample = + waveformat.Format.wBitsPerSample; + waveformat.Format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT; + waveformat.SubFormat = __KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; break; - case WAVE_FORMAT_IEEE_FLOAT: - p_aout->output.p_sys->waveformat.wBitsPerSample = sizeof(float) * 8; + + case VLC_FOURCC('s','1','6','l'): + waveformat.Format.wBitsPerSample = 16; + waveformat.Samples.wValidBitsPerSample = + waveformat.Format.wBitsPerSample; + waveformat.Format.wFormatTag = WAVE_FORMAT_PCM; + waveformat.SubFormat = __KSDATAFORMAT_SUBTYPE_PCM; break; } - p_aout->output.p_sys->waveformat.nBlockAlign = - p_aout->output.p_sys->waveformat.wBitsPerSample / 8 * i_channels; - p_aout->output.p_sys->waveformat.nAvgBytesPerSec = - p_aout->output.p_sys->waveformat.nSamplesPerSec * - p_aout->output.p_sys->waveformat.nBlockAlign; + waveformat.Format.nChannels = i_nb_channels; + waveformat.Format.nSamplesPerSec = i_rate; + waveformat.Format.nBlockAlign = + waveformat.Format.wBitsPerSample / 8 * i_nb_channels; + waveformat.Format.nAvgBytesPerSec = + waveformat.Format.nSamplesPerSec * waveformat.Format.nBlockAlign; + + /* Only use the new WAVE_FORMAT_EXTENSIBLE format for multichannel audio */ + if( i_nb_channels <= 2 ) + { + waveformat.Format.cbSize = 0; + } + else + { + waveformat.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; + waveformat.Format.cbSize = + sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); + } /* Open the device */ result = waveOutOpen( &p_aout->output.p_sys->h_waveout, WAVE_MAPPER, - &p_aout->output.p_sys->waveformat, + (WAVEFORMATEX *)&waveformat, (DWORD_PTR)WaveOutCallback, (DWORD_PTR)p_aout, - CALLBACK_FUNCTION ); + CALLBACK_FUNCTION | (b_probe?WAVE_FORMAT_QUERY:0) ); if( result == WAVERR_BADFORMAT ) { - msg_Err( p_aout, "waveOutOpen failed WAVERR_BADFORMAT" ); - return( 1 ); + msg_Warn( p_aout, "waveOutOpen failed WAVERR_BADFORMAT" ); + return VLC_EGENERIC; + } + if( result == MMSYSERR_ALLOCATED ) + { + msg_Warn( p_aout, "waveOutOpen failed WAVERR_ALLOCATED" ); + return VLC_EGENERIC; } if( result != MMSYSERR_NOERROR ) { - msg_Err( p_aout, "waveOutOpen failed" ); - return 1; + msg_Warn( p_aout, "waveOutOpen failed" ); + return VLC_EGENERIC; } - return 0; + CheckReordering( p_aout, i_nb_channels ); + + return VLC_SUCCESS; + +#undef waveformat + +} + +/***************************************************************************** + * OpenWaveOutPCM: open a PCM waveout sound device + ****************************************************************************/ +static int OpenWaveOutPCM( aout_instance_t *p_aout, int *i_format, + int i_channels, int i_nb_channels, int i_rate, + vlc_bool_t b_probe ) +{ + if( OpenWaveOut( p_aout, VLC_FOURCC('f','l','3','2'), + i_channels, i_nb_channels, i_rate, b_probe ) + != VLC_SUCCESS ) + { + if ( OpenWaveOut( p_aout, VLC_FOURCC('s','1','6','l'), + i_channels, i_nb_channels, i_rate, b_probe ) + != VLC_SUCCESS ) + { + return VLC_EGENERIC; + } + else + { + *i_format = VLC_FOURCC('s','1','6','l'); + return VLC_SUCCESS; + } + } + else + { + *i_format = VLC_FOURCC('f','l','3','2'); + return VLC_SUCCESS; + } +} + +/***************************************************************************** + * CheckReordering: Check if we need to do some channel re-ordering (the ac3 + * channel order is different from the one chosen by + * Microsoft). + *****************************************************************************/ +static void CheckReordering( aout_instance_t *p_aout, int i_nb_channels ) +{ + int i, j, k, l; + +#define waveformat p_aout->output.p_sys->waveformat +#define pi_chan_table p_aout->output.p_sys->pi_chan_table + + p_aout->output.p_sys->b_chan_reorder = VLC_FALSE; + + pi_chan_table = malloc( i_nb_channels * sizeof(int) ); + if( !pi_chan_table ) + { + return; + } + + for( i = 0, j = 0; + i < (int)(sizeof(pi_channels_out)/sizeof(uint32_t)); i++ ) + { + if( waveformat.dwChannelMask & pi_channels_out[i] ) + { + for( k = 0, l = 0; + pi_channels_out[i] != pi_channels_ordered[k]; k++ ) + { + if( waveformat.dwChannelMask & pi_channels_ordered[k] ) + { + l++; + } + } + + pi_chan_table[j] = l; + + j++; + } + } + + for( i = 0; i < i_nb_channels; i++ ) + { + if( pi_chan_table[i] != i ) + { + p_aout->output.p_sys->b_chan_reorder = VLC_TRUE; + } + } + + if( p_aout->output.p_sys->b_chan_reorder ) + { + msg_Dbg( p_aout, "channel reordering needed" ); + } + +#undef pi_chan_table +#undef waveformat } /***************************************************************************** @@ -282,7 +700,7 @@ static int PlayWaveOut( aout_instance_t *p_aout, HWAVEOUT h_waveout, if( result != MMSYSERR_NOERROR ) { msg_Err( p_aout, "waveOutPrepareHeader failed" ); - return 1; + return VLC_EGENERIC; } /* Send the buffer to the waveOut queue */ @@ -290,10 +708,10 @@ static int PlayWaveOut( aout_instance_t *p_aout, HWAVEOUT h_waveout, if( result != MMSYSERR_NOERROR ) { msg_Err( p_aout, "waveOutWrite failed" ); - return 1; + return VLC_EGENERIC; } - return 0; + return VLC_SUCCESS; } /***************************************************************************** @@ -305,7 +723,7 @@ static void CALLBACK WaveOutCallback( HWAVEOUT h_waveout, UINT uMsg, { aout_instance_t *p_aout = (aout_instance_t *)_p_aout; WAVEHDR *p_waveheader = (WAVEHDR *)dwParam1; - aout_buffer_t *p_buffer; + int i, i_queued_frames = 0; if( uMsg != WOM_DONE ) return; @@ -316,10 +734,130 @@ static void CALLBACK WaveOutCallback( HWAVEOUT h_waveout, UINT uMsg, if( p_aout->b_die ) return; - /* Take into account WaveOut latency ( 1 FRAME ) */ - p_buffer = aout_OutputNextBuffer( p_aout, - mdate() + 1000000 / p_aout->output.output.i_rate * FRAME_SIZE, - VLC_FALSE ); + /* Find out the current latency */ + for( i = 0; i < FRAMES_NUM; i++ ) + { + /* Check if frame buf is available */ + if( !(p_aout->output.p_sys->waveheader[i].dwFlags & WHDR_DONE) ) + { + i_queued_frames++; + } + } + + /* Don't wake up the thread too much */ + if( i_queued_frames < FRAMES_NUM / 2 ) + SetEvent( p_aout->output.p_sys->event ); +} - PlayWaveOut( p_aout, h_waveout, p_waveheader, p_buffer ); +/***************************************************************************** + * InterleaveFloat32/S16: change the channel order to the Microsoft one. + *****************************************************************************/ +static void InterleaveFloat32( float *p_buf, int *pi_chan_table, + int i_nb_channels ) +{ + int i, j; + float p_tmp[10]; + + for( i = 0; i < FRAME_SIZE; i++ ) + { + for( j = 0; j < i_nb_channels; j++ ) + { + p_tmp[pi_chan_table[j]] = p_buf[i*i_nb_channels + j]; + } + + memcpy( &p_buf[i*i_nb_channels], p_tmp, + i_nb_channels * sizeof(float) ); + } +} + +static void InterleaveS16( int16_t *p_buf, int *pi_chan_table, + int i_nb_channels ) +{ + int i, j; + int16_t p_tmp[10]; + + for( i = 0; i < FRAME_SIZE; i++ ) + { + for( j = 0; j < i_nb_channels; j++ ) + { + p_tmp[pi_chan_table[j]] = p_buf[i*i_nb_channels + j]; + } + + memcpy( &p_buf[i*i_nb_channels], p_tmp, + i_nb_channels * sizeof(int16_t) ); + } +} + +/***************************************************************************** + * WaveOutThread: this thread will capture play notification events. + ***************************************************************************** + * We use this thread to feed new audio samples to the sound card because + * we are not authorized to use waveOutWrite() directly in the waveout + * callback. + *****************************************************************************/ +static void WaveOutThread( notification_thread_t *p_notif ) +{ + aout_instance_t *p_aout = p_notif->p_aout; + aout_buffer_t *p_buffer = NULL; + vlc_bool_t b_sleek; + int i, i_queued_frames; + + /* We don't want any resampling when using S/PDIF */ + b_sleek = p_aout->output.output.i_format == VLC_FOURCC('s','p','d','i'); + + while( 1 ) + { + WaitForSingleObject( p_aout->output.p_sys->event, INFINITE ); + if( p_aout->b_die ) return; + + /* Find out the current latency */ + i_queued_frames = 0; + for( i = 0; i < FRAMES_NUM; i++ ) + { + /* Check if frame buf is available */ + if( !(p_aout->output.p_sys->waveheader[i].dwFlags & WHDR_DONE) ) + { + i_queued_frames++; + } + } + + /* Try to fill in as many frame buffers as possible */ + for( i = 0; i < FRAMES_NUM; i++ ) + { + /* Check if frame buf is available */ + if( p_aout->output.p_sys->waveheader[i].dwFlags & WHDR_DONE ) + { + /* Take into account the latency */ + p_buffer = aout_OutputNextBuffer( p_aout, + mdate() + 1000000 * i_queued_frames / + p_aout->output.output.i_rate * p_aout->output.i_nb_samples, + b_sleek ); + + if( !p_buffer && i_queued_frames ) + { + /* We aren't late so no need to play a blank sample */ + break; + } + + /* Do the channel reordering here */ + if( p_buffer && p_aout->output.p_sys->b_chan_reorder ) + { + if( p_aout->output.output.i_format == + VLC_FOURCC('s','1','6','l') ) + InterleaveS16( (int16_t *)p_buffer->p_buffer, + p_aout->output.p_sys->pi_chan_table, + aout_FormatNbChannels( &p_aout->output.output ) ); + else + InterleaveFloat32( (float *)p_buffer->p_buffer, + p_aout->output.p_sys->pi_chan_table, + aout_FormatNbChannels( &p_aout->output.output ) ); + } + + PlayWaveOut( p_aout, p_aout->output.p_sys->h_waveout, + &p_aout->output.p_sys->waveheader[i] , p_buffer ); + + i_queued_frames++; + } + } + } }