[vlc-devel] [PATCH] audiotrack: add 5.1 and 7.1 support

Tristan Matthews tmatth at videolan.org
Tue Mar 10 15:06:47 CET 2015


On Tue, Mar 10, 2015 at 8:39 AM, Thomas Guillem <thomas at gllm.fr> wrote:
>
>
> On Tue, Mar 10, 2015, at 11:24, Rémi Denis-Courmont wrote:
>> Le 2015-03-09 19:48, Thomas Guillem a écrit :
>> > 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;
>> > +    }
>>
>> Why bother with those special cases? Why don't you always check the
>> order?
>
> This function also check if Android can handle the channel
> configuration. If it's not 5.1 or 7.1, it must return false and VLC will
> downscale to stereo. Maybe I'll split this function in two, one that
> reorder channels no matter what, and one that test compatibility with
> Android.
>
>>
>> > +
>> > +    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;
>> > +    }
>>
>> Can't you use a single variable to index the table?
>
> Hum yes I can, sometimes I don't see the most obvious things.
>
>>
>> > +
>> > +#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 )
>
> Thanks for the review.
>
>>
>> --
>> Rémi Denis-Courmont
>> _______________________________________________
>> vlc-devel mailing list
>> To unsubscribe or modify your subscription options:
>> https://mailman.videolan.org/listinfo/vlc-devel
> _______________________________________________
> vlc-devel mailing list
> To unsubscribe or modify your subscription options:
> https://mailman.videolan.org/listinfo/vlc-devel



More information about the vlc-devel mailing list