[vlc-devel] [PATCH] audiotrack: add 5.1 and 7.1 support
Thomas Guillem
thomas at gllm.fr
Mon Mar 9 17:48:22 CET 2015
Works with SPDIF and HDMI audio output. Android will downmix to stereo if the
audio output is stereo.
7.1 support was added since Android 5.0.
5.1 support was added since the beginning but doesn't work on old devices.
That's why we fallback to stereo if AudioTrack fails to init with more than 2
channels.
---
modules/audio_output/audiotrack.c | 282 +++++++++++++++++++++++++++++++-------
1 file changed, 231 insertions(+), 51 deletions(-)
diff --git a/modules/audio_output/audiotrack.c b/modules/audio_output/audiotrack.c
index a2419ac..39d116f 100644
--- a/modules/audio_output/audiotrack.c
+++ b/modules/audio_output/audiotrack.c
@@ -66,6 +66,8 @@ struct aout_sys_t {
mtime_t i_play_time; /* time when play was called */
bool b_audiotrack_exception; /* true if audiotrack throwed an exception */
int i_audiotrack_stuck_count;
+ uint8_t i_chans_to_reorder; /* do we need channel reordering */
+ uint8_t p_chan_table[AOUT_CHAN_MAX];
/* JNIThread control */
vlc_mutex_t mutex;
@@ -172,6 +174,15 @@ static struct
bool has_ENCODING_PCM_FLOAT;
jint CHANNEL_OUT_MONO;
jint CHANNEL_OUT_STEREO;
+ jint CHANNEL_OUT_BACK_LEFT;
+ jint CHANNEL_OUT_BACK_RIGHT;
+ jint CHANNEL_OUT_FRONT_CENTER;
+ jint CHANNEL_OUT_LOW_FREQUENCY;
+ jint CHANNEL_OUT_BACK_CENTER;
+ jint CHANNEL_OUT_5POINT1;
+ jint CHANNEL_OUT_SIDE_LEFT;
+ jint CHANNEL_OUT_SIDE_RIGHT;
+ bool has_CHANNEL_OUT_SIDE;
} AudioFormat;
struct {
jint ERROR_DEAD_OBJECT;
@@ -314,6 +325,19 @@ InitJNIFields( audio_output_t *p_aout )
#endif
GET_CONST_INT( AudioFormat.CHANNEL_OUT_MONO, "CHANNEL_OUT_MONO", true );
GET_CONST_INT( AudioFormat.CHANNEL_OUT_STEREO, "CHANNEL_OUT_STEREO", true );
+ GET_CONST_INT( AudioFormat.CHANNEL_OUT_5POINT1, "CHANNEL_OUT_5POINT1", true );
+ GET_CONST_INT( AudioFormat.CHANNEL_OUT_BACK_LEFT, "CHANNEL_OUT_BACK_LEFT", true );
+ GET_CONST_INT( AudioFormat.CHANNEL_OUT_BACK_RIGHT, "CHANNEL_OUT_BACK_RIGHT", true );
+ GET_CONST_INT( AudioFormat.CHANNEL_OUT_FRONT_CENTER, "CHANNEL_OUT_FRONT_CENTER", true );
+ GET_CONST_INT( AudioFormat.CHANNEL_OUT_LOW_FREQUENCY, "CHANNEL_OUT_LOW_FREQUENCY", true );
+ GET_CONST_INT( AudioFormat.CHANNEL_OUT_BACK_CENTER, "CHANNEL_OUT_BACK_CENTER", true );
+ GET_CONST_INT( AudioFormat.CHANNEL_OUT_SIDE_LEFT, "CHANNEL_OUT_SIDE_LEFT", false );
+ if( field != NULL )
+ {
+ GET_CONST_INT( AudioFormat.CHANNEL_OUT_SIDE_RIGHT, "CHANNEL_OUT_SIDE_RIGHT", true );
+ jfields.AudioFormat.has_CHANNEL_OUT_SIDE = true;
+ } else
+ jfields.AudioFormat.has_CHANNEL_OUT_SIDE = false;
/* AudioManager class init */
GET_CLASS( "android/media/AudioManager", true );
@@ -532,84 +556,233 @@ JNIThread_TimeGet( JNIEnv *env, audio_output_t *p_aout, mtime_t *p_delay )
return -1;
}
+static bool
+AudioTrack_GetChanOrder( int i_mask, uint32_t p_chans_out[] )
+{
+#define HAS_CHAN( x ) ( ( i_mask & (jfields.AudioFormat.x) ) == (jfields.AudioFormat.x) )
+ const int i_sides = jfields.AudioFormat.CHANNEL_OUT_SIDE_LEFT |
+ jfields.AudioFormat.CHANNEL_OUT_SIDE_RIGHT;
+ const int i_backs = jfields.AudioFormat.CHANNEL_OUT_BACK_LEFT |
+ jfields.AudioFormat.CHANNEL_OUT_BACK_RIGHT;
+
+ /* verify has FL/FR */
+ if ( !HAS_CHAN( CHANNEL_OUT_STEREO ) )
+ return false;
+
+ /* verify uses SIDE as a pair (ok if not using SIDE at all) */
+ bool b_has_sides = false;
+ if( jfields.AudioFormat.has_CHANNEL_OUT_SIDE && ( i_mask & i_sides ) != 0 )
+ {
+ if( ( i_mask & i_sides ) != i_sides )
+ return false;
+ b_has_sides = true;
+ }
+ /* verify uses BACK as a pair (ok if not using BACK at all) */
+ bool b_has_backs = false;
+ if( ( i_mask & i_backs ) != 0 )
+ {
+ if( ( i_mask & i_backs ) != i_backs )
+ return false;
+ b_has_backs = true;
+ }
+
+ const bool b_has_FC = HAS_CHAN( CHANNEL_OUT_FRONT_CENTER );
+ const bool b_has_LFE = HAS_CHAN( CHANNEL_OUT_LOW_FREQUENCY );
+ const bool b_has_BC = HAS_CHAN( CHANNEL_OUT_BACK_CENTER );
+
+ /* compute at what index each channel is: samples will be in the following
+ * order: FL FR FC LFE BL BR BC SL SR
+ * when a channel is not present, its index is set to the same as the index
+ * of the preceding channel. */
+
+ const int i_FC = b_has_FC ? 2 : 1;
+ const int i_LFE = b_has_LFE ? i_FC + 1 : i_FC;
+ const int i_BL = b_has_backs ? i_LFE + 1 : i_LFE;
+ const int i_BR = b_has_backs ? i_BL + 1 : i_BL;
+ const int i_BC = b_has_BC ? i_BR + 1 : i_BR;
+ const int i_SL = b_has_sides ? i_BC + 1 : i_BC;
+ const int i_SR = b_has_sides ? i_SL + 1 : i_SL;
+
+ p_chans_out[0] = AOUT_CHAN_LEFT;
+ p_chans_out[1] = AOUT_CHAN_RIGHT;
+ if( b_has_FC )
+ p_chans_out[i_FC] = AOUT_CHAN_CENTER;
+ if( b_has_LFE )
+ p_chans_out[i_LFE] = AOUT_CHAN_LFE;
+ if( b_has_backs )
+ {
+ p_chans_out[i_BL] = AOUT_CHAN_REARLEFT;
+ p_chans_out[i_BR] = AOUT_CHAN_REARRIGHT;
+ }
+ if( b_has_BC )
+ p_chans_out[i_BC] = AOUT_CHAN_REARCENTER;
+ if( b_has_sides )
+ {
+ p_chans_out[i_SL] = AOUT_CHAN_MIDDLELEFT;
+ p_chans_out[i_SR] = AOUT_CHAN_MIDDLERIGHT;
+ }
+
+#undef HAS_CHAN
+ return true;
+}
+
+
+/**
+ * Configure and create an Android AudioTrack.
+ * returns -1 on critical error, 0 on success, 1 on configuration error
+ */
static int
-JNIThread_Start( JNIEnv *env, audio_output_t *p_aout )
+JNIThread_Configure( JNIEnv *env, audio_output_t *p_aout )
{
struct aout_sys_t *p_sys = p_aout->sys;
- int i_size, i_min_buffer_size, i_channel_config, i_rate, i_format,
+ int i_size, i_min_buffer_size, i_channel_config, i_format,
i_format_size, i_nb_channels;
+ uint8_t i_chans_to_reorder = 0;
+ uint8_t p_chan_table[AOUT_CHAN_MAX];
jobject p_audiotrack;
+ audio_sample_format_t fmt = p_sys->fmt;
/* 4000 <= frequency <= 48000 */
- i_rate = p_sys->fmt.i_rate;
- if( i_rate < 4000 )
- i_rate = 4000;
- if( i_rate > 48000 )
- i_rate = 48000;
-
- /* We can only accept U8, S16N, and FL32 (depending on Android version) */
- if( p_sys->fmt.i_format != VLC_CODEC_U8
- && p_sys->fmt.i_format != VLC_CODEC_S16N
- && p_sys->fmt.i_format != VLC_CODEC_FL32 )
- p_sys->fmt.i_format = VLC_CODEC_S16N;
-
- if( p_sys->fmt.i_format == VLC_CODEC_FL32
- && !jfields.AudioFormat.has_ENCODING_PCM_FLOAT )
- p_sys->fmt.i_format = VLC_CODEC_S16N;
-
- if( p_sys->fmt.i_format == VLC_CODEC_S16N )
- {
- i_format = jfields.AudioFormat.ENCODING_PCM_16BIT;
- i_format_size = 2;
- } else if( p_sys->fmt.i_format == VLC_CODEC_FL32 )
+ if( fmt.i_rate < 4000 )
+ fmt.i_rate = 4000;
+ if( fmt.i_rate > 48000 )
+ fmt.i_rate = 48000;
+
+ /* We can only accept U8, S16N, FL32 */
+ switch( fmt.i_format )
{
- i_format = jfields.AudioFormat.ENCODING_PCM_FLOAT;
- i_format_size = 4;
- } else
+ case VLC_CODEC_U8:
+ break;
+ case VLC_CODEC_S16N:
+ break;
+ case VLC_CODEC_FL32:
+ if( !jfields.AudioFormat.has_ENCODING_PCM_FLOAT )
+ fmt.i_format = VLC_CODEC_S16N;
+ break;
+ default:
+ fmt.i_format = VLC_CODEC_S16N;
+ break;
+ }
+ switch( fmt.i_format )
{
- i_format = jfields.AudioFormat.ENCODING_PCM_8BIT;
- i_format_size = 1;
+ case VLC_CODEC_U8:
+ i_format = jfields.AudioFormat.ENCODING_PCM_8BIT;
+ i_format_size = 1;
+ break;
+ case VLC_CODEC_S16N:
+ i_format = jfields.AudioFormat.ENCODING_PCM_16BIT;
+ i_format_size = 2;
+ break;
+ case VLC_CODEC_FL32:
+ i_format = jfields.AudioFormat.ENCODING_PCM_FLOAT;
+ i_format_size = 4;
+ break;
+ default:
+ vlc_assert_unreachable();
}
- p_sys->fmt.i_original_channels = p_sys->fmt.i_physical_channels;
- i_nb_channels = aout_FormatNbChannels( &p_sys->fmt );
- switch( i_nb_channels )
+ i_nb_channels = aout_FormatNbChannels( &fmt );
+
+ /* Android AudioTrack support only mono, stereo, 5.1 and 7.1.
+ * Android will downmix to stereo if audio output doesn't handle 5.1 or 7.1
+ */
+ if( i_nb_channels > 5 )
+ {
+ uint32_t p_chans_out[AOUT_CHAN_MAX];
+
+ if( i_nb_channels > 7 && jfields.AudioFormat.has_CHANNEL_OUT_SIDE )
+ {
+ fmt.i_physical_channels = AOUT_CHANS_7_1;
+ /* bitmak of CHANNEL_OUT_7POINT1 doesn't correspond to 5POINT1 and
+ * SIDES */
+ i_channel_config = jfields.AudioFormat.CHANNEL_OUT_5POINT1 |
+ jfields.AudioFormat.CHANNEL_OUT_SIDE_LEFT |
+ jfields.AudioFormat.CHANNEL_OUT_SIDE_RIGHT;
+ } else
+ {
+ fmt.i_physical_channels = AOUT_CHANS_5_1;
+ i_channel_config = jfields.AudioFormat.CHANNEL_OUT_5POINT1;
+ }
+ if( AudioTrack_GetChanOrder( i_channel_config, p_chans_out ) )
+ i_chans_to_reorder =
+ aout_CheckChannelReorder( NULL, p_chans_out,
+ fmt.i_physical_channels,
+ p_chan_table );
+ else
+ return 1;
+ } else
{
- case 1:
- i_channel_config = jfields.AudioFormat.CHANNEL_OUT_MONO;
- p_sys->fmt.i_physical_channels = AOUT_CHAN_CENTER;
- break;
- default:
- i_nb_channels = 2; // XXX: AudioTrack handle only stereo for now
- case 2:
- i_channel_config = jfields.AudioFormat.CHANNEL_OUT_STEREO;
- p_sys->fmt.i_physical_channels = AOUT_CHANS_STEREO;
- break;
+ if( i_nb_channels == 1 )
+ {
+ i_channel_config = jfields.AudioFormat.CHANNEL_OUT_MONO;
+ } else
+ {
+ fmt.i_physical_channels = AOUT_CHANS_STEREO;
+ i_channel_config = jfields.AudioFormat.CHANNEL_OUT_STEREO;
+ }
}
+ i_nb_channels = aout_FormatNbChannels( &fmt );
- i_min_buffer_size = JNI_AT_CALL_STATIC_INT( getMinBufferSize, i_rate,
+ i_min_buffer_size = JNI_AT_CALL_STATIC_INT( getMinBufferSize, fmt.i_rate,
i_channel_config, i_format );
if( i_min_buffer_size <= 0 )
{
msg_Warn( p_aout, "getMinBufferSize returned an invalid size" ) ;
- /* use a defaut min buffer size (shouldn't happen) */
- i_min_buffer_size = i_nb_channels * i_format_size * 2048;
+ return 1;
}
-
i_size = i_min_buffer_size * 4;
- p_sys->i_bytes_per_frame = i_nb_channels * i_format_size;
- p_sys->i_max_audiotrack_samples = i_size / p_sys->i_bytes_per_frame;
/* create AudioTrack object */
- p_audiotrack = JNI_AT_NEW( jfields.AudioManager.STREAM_MUSIC, i_rate,
+ p_audiotrack = JNI_AT_NEW( jfields.AudioManager.STREAM_MUSIC, fmt.i_rate,
i_channel_config, i_format, i_size,
jfields.AudioTrack.MODE_STREAM );
if( CHECK_AT_EXCEPTION( "AudioTrack<init>" ) || !p_audiotrack )
- return VLC_EGENERIC;
+ return 1;
p_sys->p_audiotrack = (*env)->NewGlobalRef( env, p_audiotrack );
(*env)->DeleteLocalRef( env, p_audiotrack );
if( !p_sys->p_audiotrack )
+ return -1;
+
+ p_sys->i_chans_to_reorder = i_chans_to_reorder;
+ if( i_chans_to_reorder )
+ memcpy( p_sys->p_chan_table, p_chan_table, sizeof(p_sys->p_chan_table) );
+ p_sys->i_bytes_per_frame = i_nb_channels * i_format_size;
+ p_sys->i_max_audiotrack_samples = i_size / p_sys->i_bytes_per_frame;
+ p_sys->fmt = fmt;
+
+ return 0;
+}
+
+static int
+JNIThread_Start( JNIEnv *env, audio_output_t *p_aout )
+{
+ aout_sys_t *p_sys = p_aout->sys;
+ int i_ret;
+
+ aout_FormatPrint( p_aout, "VLC is looking for:", &p_sys->fmt );
+ p_sys->fmt.i_original_channels = p_sys->fmt.i_physical_channels;
+
+ i_ret = JNIThread_Configure( env, p_aout );
+ if( i_ret == 1 )
+ {
+ /* configuration error, try to fallback to stereo */
+ if( ( p_sys->fmt.i_format != VLC_CODEC_U8 &&
+ p_sys->fmt.i_format != VLC_CODEC_S16N ) ||
+ aout_FormatNbChannels( &p_sys->fmt ) > 2 )
+ {
+ msg_Warn( p_aout,
+ "AudioTrack configuration failed, try again in stereo" );
+ p_sys->fmt.i_format = VLC_CODEC_S16N;
+ p_sys->fmt.i_physical_channels = AOUT_CHANS_STEREO;
+
+ i_ret = JNIThread_Configure( env, p_aout );
+ }
+ }
+ if( i_ret != 0 )
return VLC_EGENERIC;
+
+ aout_FormatPrint( p_aout, "VLC will output:", &p_sys->fmt );
+
if( JNI_AT_CALL_INT( getState ) != jfields.AudioTrack.STATE_INITIALIZED )
{
msg_Err( p_aout, "AudioTrack init failed" );
@@ -632,8 +805,6 @@ JNIThread_Start( JNIEnv *env, audio_output_t *p_aout )
}
#endif
- p_sys->fmt.i_rate = i_rate;
-
JNI_AT_CALL_VOID( play );
CHECK_AT_EXCEPTION( "play" );
p_sys->i_play_time = mdate();
@@ -997,7 +1168,16 @@ JNIThread( void *data )
if( b_error )
break;
if( p_buffer == NULL )
+ {
p_buffer = p_cmd->in.play.p_buffer;
+ if( p_sys->i_chans_to_reorder )
+ aout_ChannelReorder( p_buffer->p_buffer,
+ p_buffer->i_buffer,
+ p_sys->i_chans_to_reorder,
+ p_sys->p_chan_table,
+ p_sys->fmt.i_format );
+
+ }
b_error = JNIThread_Play( env, p_aout, &p_buffer,
&i_play_wait ) != VLC_SUCCESS;
if( p_buffer != NULL )
--
2.1.3
More information about the vlc-devel
mailing list