[vlc-commits] [Git][videolan/vlc][master] 4 commits: audiotrack: reorder to avoid forward declaration

Steve Lhomme (@robUx4) gitlab at videolan.org
Fri Mar 17 21:03:24 UTC 2023



Steve Lhomme pushed to branch master at VideoLAN / VLC


Commits:
6202abd9 by Thomas Guillem at 2023-03-17T20:44:54+00:00
audiotrack: reorder to avoid forward declaration

No functional changes.

- - - - -
8d0819f5 by Thomas Guillem at 2023-03-17T20:44:54+00:00
audiotrack: remove unused cond

Leftover from 582570aad10b0dda1a725039ab6c35dc3e9d51c3

- - - - -
4bb250cd by Thomas Guillem at 2023-03-17T20:44:54+00:00
audiotrack: use TimingReport API

Remove the smoothpos hack that was only needed for time_get.

- - - - -
e476328e by Thomas Guillem at 2023-03-17T20:44:54+00:00
audiotrack: use precise latency

AudioTrack#getTimestamp, present since API 19, is the most precise,
[-20ms;0ms] delay tested with the Sync-One2.

- - - - -


1 changed file:

- modules/audio_output/android/audiotrack.c


Changes:

=====================================
modules/audio_output/android/audiotrack.c
=====================================
@@ -45,13 +45,7 @@
 
 #define AUDIOTRACK_MAX_BUFFER_US VLC_TICK_FROM_MS(750) // 750ms
 
-static void Stop( aout_stream_t * );
-static void Play( aout_stream_t *, block_t *, vlc_tick_t );
-static void Flush( aout_stream_t * );
-static void Pause( aout_stream_t *, bool, vlc_tick_t );
-static void VolumeSet( aout_stream_t *, float );
-static void MuteSet( aout_stream_t *, bool );
-static void *AudioTrack_Thread( void * );
+#define TIMING_REPORT_DELAY_TICKS VLC_TICK_FROM_MS(1000)
 
 typedef struct
 {
@@ -78,24 +72,11 @@ typedef struct
     /* Used by AudioTrack_GetTimestampPositionUs */
     struct {
         jobject p_obj; /* AudioTimestamp ref */
-        vlc_tick_t i_frame_us;
 
         jlong i_frame_post_last;
         uint64_t i_frame_wrap_count;
-        uint64_t i_frame_pos;
-
-        vlc_tick_t i_last_time;
     } timestamp;
 
-    /* Used by AudioTrack_GetSmoothPositionUs */
-    struct {
-        uint32_t i_idx;
-        uint32_t i_count;
-        vlc_tick_t p_us[SMOOTHPOS_SAMPLE_COUNT];
-        vlc_tick_t i_us;
-        vlc_tick_t i_last_time;
-    } smoothpos;
-
     uint32_t i_max_audiotrack_samples;
     long long i_encoding_flags;
     bool b_passthrough;
@@ -112,7 +93,6 @@ typedef struct
 
     vlc_thread_t thread;    /* AudioTrack_Thread */
     vlc_mutex_t lock;
-    vlc_cond_t aout_cond;   /* cond owned by aout */
     vlc_cond_t thread_cond; /* cond owned by AudioTrack_Thread */
 
     /* These variables need locking on read and write */
@@ -133,12 +113,16 @@ typedef struct
 
     vlc_frame_t *frame_chain;
     vlc_frame_t **frame_last;
+
+    /* Bytes written since the last timing report */
+    size_t timing_report_last_written_bytes;
+    /* Number of bytes to write before sending a timing report */
+    size_t timing_report_delay_bytes;
 } aout_sys_t;
 
 
 // Don't use Float for now since 5.1/7.1 Float is down sampled to Stereo Float
 //#define AUDIOTRACK_USE_FLOAT
-//#define AUDIOTRACK_HW_LATENCY
 
 /* Get AudioTrack native sample rate: if activated, most of  the resampling
  * will be done by VLC */
@@ -316,9 +300,7 @@ AudioTrack_InitJNI( vlc_object_t *p_aout)
     GET_ID( GetMethodID, AudioTrack.getBufferSizeInFrames,
             "getBufferSizeInFrames", "()I", false );
 
-#ifdef AUDIOTRACK_HW_LATENCY
     GET_ID( GetMethodID, AudioTrack.getLatency, "getLatency", "()I", false );
-#endif
 
     GET_ID( GetMethodID, AudioTrack.getTimestamp,
             "getTimestamp", "(Landroid/media/AudioTimestamp;)Z", false );
@@ -561,25 +543,6 @@ AudioTrack_ResetWrapCount( JNIEnv *env, aout_stream_t *stream )
     p_sys->timestamp.i_frame_wrap_count = 0;
 }
 
-/**
- * Reset AudioTrack SmoothPosition and TimestampPosition
- */
-static void
-AudioTrack_ResetPositions( JNIEnv *env, aout_stream_t *stream )
-{
-    aout_sys_t *p_sys = stream->sys;
-    VLC_UNUSED( env );
-
-    p_sys->timestamp.i_last_time = 0;
-    p_sys->timestamp.i_frame_us = 0;
-    p_sys->timestamp.i_frame_pos = 0;
-
-    p_sys->smoothpos.i_count = 0;
-    p_sys->smoothpos.i_idx = 0;
-    p_sys->smoothpos.i_last_time = 0;
-    p_sys->smoothpos.i_us = 0;
-}
-
 /**
  * Reset all AudioTrack positions and internal state
  */
@@ -588,9 +551,10 @@ AudioTrack_Reset( JNIEnv *env, aout_stream_t *stream )
 {
     aout_sys_t *p_sys = stream->sys;
 
-    AudioTrack_ResetPositions( env, stream );
     AudioTrack_ResetWrapCount( env, stream );
     p_sys->i_samples_written = 0;
+    p_sys->timing_report_last_written_bytes = 0;
+    p_sys->timing_report_delay_bytes = 0;
 }
 
 static vlc_tick_t
@@ -616,179 +580,6 @@ AudioTrack_GetLatencyUs( JNIEnv *env, aout_stream_t *stream )
     return 0;
 }
 
-/**
- * Get a smooth AudioTrack position
- *
- * This function smooth out the AudioTrack position since it has a very bad
- * precision (+/- 20ms on old devices).
- */
-static vlc_tick_t
-AudioTrack_GetSmoothPositionUs( JNIEnv *env, aout_stream_t *stream )
-{
-    aout_sys_t *p_sys = stream->sys;
-    uint64_t i_audiotrack_us;
-    vlc_tick_t i_now = vlc_tick_now();
-
-    /* Fetch an AudioTrack position every SMOOTHPOS_INTERVAL_US (30ms) */
-    if( i_now - p_sys->smoothpos.i_last_time >= SMOOTHPOS_INTERVAL_US )
-    {
-        i_audiotrack_us = FRAMES_TO_US( AudioTrack_getPlaybackHeadPosition( env, stream ) );
-        if( i_audiotrack_us == 0 )
-            goto bailout;
-
-        p_sys->smoothpos.i_last_time = i_now;
-
-        /* Base the position off the current time */
-        p_sys->smoothpos.p_us[p_sys->smoothpos.i_idx] = i_audiotrack_us - i_now;
-        p_sys->smoothpos.i_idx = (p_sys->smoothpos.i_idx + 1)
-                                 % SMOOTHPOS_SAMPLE_COUNT;
-        if( p_sys->smoothpos.i_count < SMOOTHPOS_SAMPLE_COUNT )
-            p_sys->smoothpos.i_count++;
-
-        /* Calculate the average position based off the current time */
-        p_sys->smoothpos.i_us = 0;
-        for( uint32_t i = 0; i < p_sys->smoothpos.i_count; ++i )
-            p_sys->smoothpos.i_us += p_sys->smoothpos.p_us[i];
-        p_sys->smoothpos.i_us /= p_sys->smoothpos.i_count;
-
-    }
-    if( p_sys->smoothpos.i_us != 0 )
-        return p_sys->smoothpos.i_us + i_now - AudioTrack_GetLatencyUs( env, stream );
-
-bailout:
-    return 0;
-}
-
-static vlc_tick_t
-AudioTrack_GetTimestampPositionUs( JNIEnv *env, aout_stream_t *stream )
-{
-    aout_sys_t *p_sys = stream->sys;
-    vlc_tick_t i_now;
-
-    if( !p_sys->timestamp.p_obj )
-        return 0;
-
-    i_now = vlc_tick_now();
-
-    /* Android doc:
-     * getTimestamp: Poll for a timestamp on demand.
-     *
-     * If you need to track timestamps during initial warmup or after a
-     * routing or mode change, you should request a new timestamp once per
-     * second until the reported timestamps show that the audio clock is
-     * stable. Thereafter, query for a new timestamp approximately once
-     * every 10 seconds to once per minute. Calling this method more often
-     * is inefficient. It is also counter-productive to call this method
-     * more often than recommended, because the short-term differences
-     * between successive timestamp reports are not meaningful. If you need
-     * a high-resolution mapping between frame position and presentation
-     * time, consider implementing that at application level, based on
-     * low-resolution timestamps.
-     */
-
-    /* Fetch an AudioTrack timestamp every AUDIOTIMESTAMP_INTERVAL_US (500ms) */
-    if( i_now - p_sys->timestamp.i_last_time >= AUDIOTIMESTAMP_INTERVAL_US )
-    {
-        if( JNI_AT_CALL_BOOL( getTimestamp, p_sys->timestamp.p_obj ) )
-        {
-            p_sys->timestamp.i_frame_us = VLC_TICK_FROM_NS(JNI_AUDIOTIMESTAMP_GET_LONG( nanoTime ));
-
-            /* the low-order 32 bits of position is in wrapping frame units
-             * similar to AudioTrack#getPlaybackHeadPosition. */
-            jlong i_frame_post_last = JNI_AUDIOTIMESTAMP_GET_LONG( framePosition );
-            if( p_sys->timestamp.i_frame_post_last > i_frame_post_last )
-                p_sys->timestamp.i_frame_wrap_count++;
-            p_sys->timestamp.i_frame_post_last = i_frame_post_last;
-            p_sys->timestamp.i_frame_pos = i_frame_post_last
-                                         + (p_sys->timestamp.i_frame_wrap_count << 32);
-
-            /* frame time should be after last play time
-             * frame time shouldn't be in the future
-             * frame time should be less than 10 seconds old */
-            if( p_sys->timestamp.i_frame_us != 0 && p_sys->timestamp.i_frame_pos != 0
-             && i_now > p_sys->timestamp.i_frame_us
-             && ( i_now - p_sys->timestamp.i_frame_us ) <= VLC_TICK_FROM_SEC(10) )
-                p_sys->timestamp.i_last_time = i_now;
-            else
-            {
-                p_sys->timestamp.i_last_time = 0;
-                p_sys->timestamp.i_frame_us = 0;
-            }
-        }
-        else
-            p_sys->timestamp.i_frame_us = 0;
-    }
-
-    if( p_sys->timestamp.i_frame_us != 0 )
-    {
-        vlc_tick_t i_time_diff = i_now - p_sys->timestamp.i_frame_us;
-        jlong i_frames_diff = samples_from_vlc_tick(i_time_diff, p_sys->fmt.i_rate);
-        return FRAMES_TO_US( p_sys->timestamp.i_frame_pos + i_frames_diff );
-    } else
-        return 0;
-}
-
-static int
-TimeGet( aout_stream_t *stream, vlc_tick_t *restrict p_delay )
-{
-    aout_sys_t *p_sys = stream->sys;
-    vlc_tick_t i_audiotrack_us;
-    JNIEnv *env;
-
-    if( p_sys->b_passthrough )
-        return -1;
-
-    vlc_mutex_lock( &p_sys->lock );
-
-    if( p_sys->b_error || !p_sys->i_samples_written || !( env = GET_ENV() ) )
-        goto bailout;
-
-    i_audiotrack_us = AudioTrack_GetTimestampPositionUs( env, stream );
-
-    if( i_audiotrack_us <= 0 )
-        i_audiotrack_us = AudioTrack_GetSmoothPositionUs(env, stream );
-
-/* Debug log for both delays */
-#if 0
-{
-    vlc_tick_t i_ts_us = AudioTrack_GetTimestampPositionUs( env, stream );
-    vlc_tick_t i_smooth_us = AudioTrack_GetSmoothPositionUs(env, stream );
-    vlc_tick_t i_latency_us = AudioTrack_GetLatencyUs( env, stream );
-
-    msg_Err( stream, "TimeGet: TimeStamp: %"PRId64", Smooth: %"PRId64" (latency: %"PRId64")",
-             i_ts_us, i_smooth_us, i_latency_us );
-}
-#endif
-
-    if( i_audiotrack_us > 0 )
-    {
-        /* AudioTrack delay */
-        vlc_tick_t i_delay = FRAMES_TO_US( p_sys->i_samples_written )
-                        - i_audiotrack_us;
-        if( i_delay >= 0 )
-        {
-            /* Frame FIFO + jarray delay */
-            size_t total_size;
-            vlc_frame_ChainProperties(p_sys->frame_chain, NULL, &total_size, NULL);
-            i_delay += BYTES_TO_US(total_size + p_sys->jbuffer.size
-                                   - p_sys->jbuffer.offset);
-
-            *p_delay = i_delay;
-            vlc_mutex_unlock( &p_sys->lock );
-            return 0;
-        }
-        else
-        {
-            msg_Warn( stream, "timing screwed, reset positions" );
-            AudioTrack_ResetPositions( env, stream );
-        }
-    }
-
-bailout:
-    vlc_mutex_unlock( &p_sys->lock );
-    return -1;
-}
-
 static void
 AudioTrack_GetChanOrder( uint16_t i_physical_channels, uint32_t p_chans_out[] )
 {
@@ -1058,1033 +849,1083 @@ AudioTrack_Create( JNIEnv *env, aout_stream_t *stream,
     return 0;
 }
 
-static int GetPassthroughFmt( bool compat, audio_sample_format_t *fmt, int *at_format )
+static void
+AudioTrack_ConsumeFrame(aout_stream_t *stream, vlc_frame_t *f)
 {
-    if( !compat && jfields.AudioFormat.has_ENCODING_IEC61937 )
-    {
-        *at_format = jfields.AudioFormat.ENCODING_IEC61937;
-        switch( fmt->i_format )
-        {
-            case VLC_CODEC_TRUEHD:
-            case VLC_CODEC_MLP:
-                fmt->i_rate = 192000;
-                fmt->i_bytes_per_frame = 16;
+    aout_sys_t *p_sys = stream->sys;
+    assert(f != NULL && f == p_sys->frame_chain);
 
-                /* AudioFormat.ENCODING_IEC61937 documentation says that the
-                 * channel layout must be stereo. Well, not for TrueHD
-                 * apparently */
-                fmt->i_physical_channels = AOUT_CHANS_7_1;
-                break;
-            case VLC_CODEC_DTS:
-                fmt->i_bytes_per_frame = 4;
-                fmt->i_physical_channels = AOUT_CHANS_STEREO;
-                break;
-            case VLC_CODEC_DTSHD:
-                fmt->i_bytes_per_frame = 4;
-                fmt->i_physical_channels = AOUT_CHANS_STEREO;
-                fmt->i_rate = 192000;
-                fmt->i_bytes_per_frame = 16;
-                break;
-            case VLC_CODEC_EAC3:
-                fmt->i_rate = 192000;
-            case VLC_CODEC_A52:
-                fmt->i_physical_channels = AOUT_CHANS_STEREO;
-                fmt->i_bytes_per_frame = 4;
-                break;
-            default:
-                return VLC_EGENERIC;
-        }
-        fmt->i_frame_length = 1;
-        fmt->i_channels = aout_FormatNbChannels( fmt );
-        fmt->i_format = VLC_CODEC_SPDIFL;
-    }
-    else
-    {
-        if( vlc_android_AudioFormat_FourCCToEncoding( fmt->i_format, at_format )
-                != VLC_SUCCESS )
-            return VLC_EGENERIC;
-        fmt->i_bytes_per_frame = 4;
-        fmt->i_frame_length = 1;
-        fmt->i_physical_channels = AOUT_CHANS_STEREO;
-        fmt->i_channels = 2;
-        fmt->i_format = VLC_CODEC_SPDIFB;
-    }
+    p_sys->frame_chain = f->p_next;
+    if (p_sys->frame_chain == NULL)
+        p_sys->frame_last = &p_sys->frame_chain;
 
-    return VLC_SUCCESS;
+    vlc_frame_Release(f);
 }
 
 static int
-StartPassthrough( JNIEnv *env, aout_stream_t *stream )
+AudioTrack_AllocJArray(JNIEnv *env, aout_stream_t *stream, size_t size,
+                       jarray (*new)(JNIEnv *env, jsize size))
 {
     aout_sys_t *p_sys = stream->sys;
 
-    /* Try ENCODING_IEC61937 first, then fallback to ENCODING_[AC3|DTS|...] */
-    unsigned nb_fmt = jfields.AudioFormat.has_ENCODING_IEC61937 ? 2 : 1;
-    int i_ret;
-    for( unsigned i = 0; i < nb_fmt; ++i )
+    if (size > p_sys->jbuffer.maxsize)
     {
-        int i_at_format;
-        bool compat = i == 1;
-        audio_sample_format_t fmt = p_sys->fmt;
+        p_sys->jbuffer.maxsize = 0;
+        if (p_sys->jbuffer.array != NULL)
+            (*env)->DeleteLocalRef(env, p_sys->jbuffer.array);
 
-        i_ret = GetPassthroughFmt( compat, &fmt, &i_at_format );
-        if( i_ret != VLC_SUCCESS )
-            return i_ret;
+        p_sys->jbuffer.array = new(env, size);
+        if (p_sys->jbuffer.array != NULL)
+            p_sys->jbuffer.maxsize = size;
+        else
+            msg_Err(stream, "jarray allocation failed");
+    }
 
-        p_sys->b_passthrough = true;
-        i_ret = AudioTrack_Create( env, stream, fmt.i_rate, i_at_format,
-                                   fmt.i_physical_channels );
+    if (p_sys->jbuffer.array == NULL)
+        return jfields.AudioTrack.ERROR;
 
-        if( i_ret == VLC_SUCCESS )
-        {
-            msg_Dbg( stream, "Using passthrough format: %d", i_at_format );
-            p_sys->i_chans_to_reorder = 0;
-            p_sys->fmt = fmt;
-            return VLC_SUCCESS;
-        }
-    }
+    p_sys->jbuffer.size = size;
+    p_sys->jbuffer.offset = 0;
 
-    p_sys->b_passthrough = false;
-    msg_Warn( stream, "SPDIF configuration failed" );
-    return VLC_EGENERIC;
+    return 0;
 }
 
+/**
+ * Non blocking write function, run from AudioTrack_Thread.
+ * Do a calculation between current position and audiotrack position and assure
+ * that we won't wait in AudioTrack.write() method.
+ */
 static int
-StartPCM( JNIEnv *env, aout_stream_t *stream, unsigned i_max_channels )
+AudioTrack_WriteByteArray( JNIEnv *env, aout_stream_t *stream, bool b_force )
 {
     aout_sys_t *p_sys = stream->sys;
-    unsigned i_nb_channels;
-    int i_at_format, i_ret;
-
-    if (jfields.AudioTrack.getNativeOutputSampleRate)
-        p_sys->fmt.i_rate =
-            JNI_AT_CALL_STATIC_INT( getNativeOutputSampleRate,
-                                    jfields.AudioManager.STREAM_MUSIC );
-    else
-        p_sys->fmt.i_rate = VLC_CLIP( p_sys->fmt.i_rate, 4000, 48000 );
+    uint64_t i_samples;
+    uint64_t i_audiotrack_pos;
+    uint64_t i_samples_pending;
 
-    do
+    int ret;
+    if (p_sys->jbuffer.offset == p_sys->jbuffer.size)
     {
-        /* We can only accept U8, S16N, FL32, and AC3 */
-        switch( p_sys->fmt.i_format )
-        {
-        case VLC_CODEC_U8:
-            i_at_format = jfields.AudioFormat.ENCODING_PCM_8BIT;
-            break;
-        case VLC_CODEC_S16N:
-            i_at_format = jfields.AudioFormat.ENCODING_PCM_16BIT;
-            break;
-        case VLC_CODEC_FL32:
-            if( jfields.AudioFormat.has_ENCODING_PCM_FLOAT )
-                i_at_format = jfields.AudioFormat.ENCODING_PCM_FLOAT;
-            else
-            {
-                p_sys->fmt.i_format = VLC_CODEC_S16N;
-                i_at_format = jfields.AudioFormat.ENCODING_PCM_16BIT;
-            }
-            break;
-        default:
-            p_sys->fmt.i_format = VLC_CODEC_S16N;
-            i_at_format = jfields.AudioFormat.ENCODING_PCM_16BIT;
-            break;
-        }
+        vlc_frame_t *f = p_sys->frame_chain;
+        assert(f != NULL);
+        ret = AudioTrack_AllocJArray(env, stream, f->i_buffer,
+                                     (*env)->NewByteArray);
+        if (ret != 0)
+            return ret;
 
-        /* Android AudioTrack supports only mono, stereo, 5.1 and 7.1.
-         * Android will downmix to stereo if audio output doesn't handle 5.1 or 7.1
-         */
+        (*env)->SetByteArrayRegion(env, p_sys->jbuffer.array,
+                                   0, f->i_buffer, (jbyte *)f->p_buffer);
+        AudioTrack_ConsumeFrame(stream, f);
+    }
 
-        i_nb_channels = aout_FormatNbChannels( &p_sys->fmt );
-        if( i_nb_channels == 0 )
-            return VLC_EGENERIC;
-        if( AOUT_FMT_LINEAR( &p_sys->fmt ) )
-            i_nb_channels = __MIN( i_max_channels, i_nb_channels );
-        if( i_nb_channels > 5 )
-        {
-            if( i_nb_channels > 7 && jfields.AudioFormat.has_CHANNEL_OUT_SIDE )
-                p_sys->fmt.i_physical_channels = AOUT_CHANS_7_1;
-            else
-                p_sys->fmt.i_physical_channels = AOUT_CHANS_5_1;
-        } else
-        {
-            if( i_nb_channels == 1 )
-                p_sys->fmt.i_physical_channels = AOUT_CHAN_LEFT;
-            else
-                p_sys->fmt.i_physical_channels = AOUT_CHANS_STEREO;
-        }
+    i_audiotrack_pos = AudioTrack_getPlaybackHeadPosition( env, stream );
 
-        /* Try to create an AudioTrack with the most advanced channel and
-         * format configuration. If AudioTrack_Create fails, try again with a
-         * less advanced format (PCM S16N). If it fails again, try again with
-         * Stereo channels. */
-        i_ret = AudioTrack_Create( env, stream, p_sys->fmt.i_rate, i_at_format,
-                                   p_sys->fmt.i_physical_channels );
-        if( i_ret != 0 )
-        {
-            if( p_sys->fmt.i_format == VLC_CODEC_FL32 )
-            {
-                msg_Warn( stream, "FL32 configuration failed, "
-                                  "fallback to S16N PCM" );
-                p_sys->fmt.i_format = VLC_CODEC_S16N;
-            }
-            else if( p_sys->fmt.i_physical_channels & AOUT_CHANS_5_1 )
-            {
-                msg_Warn( stream, "5.1 or 7.1 configuration failed, "
-                                  "fallback to Stereo" );
-                p_sys->fmt.i_physical_channels = AOUT_CHANS_STEREO;
-            }
-            else
-                break;
-        }
-    } while( i_ret != 0 );
+    assert( i_audiotrack_pos <= p_sys->i_samples_written );
+    if( i_audiotrack_pos > p_sys->i_samples_written )
+    {
+        msg_Err( stream, "audiotrack position is ahead. Should NOT happen" );
+        p_sys->i_samples_written = 0;
+        p_sys->b_error = true;
+        return 0;
+    }
+    i_samples_pending = p_sys->i_samples_written - i_audiotrack_pos;
 
-    if( i_ret != VLC_SUCCESS )
-        return i_ret;
+    /* check if audiotrack buffer is not full before writing on it. */
+    if( b_force )
+    {
+        msg_Warn( stream, "Force write. It may block..." );
+        i_samples_pending = 0;
+    } else if( i_samples_pending >= p_sys->i_max_audiotrack_samples )
+        return 0;
 
-    uint32_t p_chans_out[AOUT_CHAN_MAX];
-    memset( p_chans_out, 0, sizeof(p_chans_out) );
-    AudioTrack_GetChanOrder( p_sys->fmt.i_physical_channels, p_chans_out );
-    p_sys->i_chans_to_reorder =
-        aout_CheckChannelReorder( NULL, p_chans_out,
-                                  p_sys->fmt.i_physical_channels,
-                                  p_sys->p_chan_table );
-    aout_FormatPrepare( &p_sys->fmt );
-    return VLC_SUCCESS;
+    i_samples = __MIN( p_sys->i_max_audiotrack_samples - i_samples_pending,
+                       BYTES_TO_FRAMES(p_sys->jbuffer.size - p_sys->jbuffer.offset));
+
+    ret = JNI_AT_CALL_INT( write, p_sys->jbuffer.array, p_sys->jbuffer.offset,
+                           FRAMES_TO_BYTES( i_samples ) );
+    if (ret > 0)
+        p_sys->jbuffer.offset += ret;
+
+    return ret;
 }
 
+/**
+ * Non blocking write function for Android M and after, run from
+ * AudioTrack_Thread. It calls a new write method with WRITE_NON_BLOCKING
+ * flags.
+ */
 static int
-Start( aout_stream_t *stream, audio_sample_format_t *restrict p_fmt,
-       enum android_audio_device_type adev )
+AudioTrack_WriteByteArrayV23(JNIEnv *env, aout_stream_t *stream)
 {
-    JNIEnv *env;
-    int i_ret;
-    bool b_try_passthrough;
-    unsigned i_max_channels;
+    aout_sys_t *p_sys = stream->sys;
 
-    if( adev == ANDROID_AUDIO_DEVICE_ENCODED )
+    int ret;
+    if (p_sys->jbuffer.offset == p_sys->jbuffer.size)
     {
-        b_try_passthrough = true;
-        i_max_channels = ANDROID_AUDIO_DEVICE_MAX_CHANNELS;
+        vlc_frame_t *f = p_sys->frame_chain;
+        assert(f != NULL);
+        ret = AudioTrack_AllocJArray(env, stream, f->i_buffer,
+                                     (*env)->NewByteArray);
+        if (ret != 0)
+            return ret;
+
+        (*env)->SetByteArrayRegion(env, p_sys->jbuffer.array,
+                                   0, f->i_buffer, (jbyte *)f->p_buffer);
+        AudioTrack_ConsumeFrame(stream, f);
     }
-    else
+
+    ret = JNI_AT_CALL_INT( writeV23, p_sys->jbuffer.array,
+                           p_sys->jbuffer.offset,
+                           p_sys->jbuffer.size - p_sys->jbuffer.offset,
+                           jfields.AudioTrack.WRITE_NON_BLOCKING );
+    if (ret > 0)
+        p_sys->jbuffer.offset += ret;
+
+    return ret;
+}
+
+/**
+ * Non blocking play function for Lollipop and after, run from
+ * AudioTrack_Thread. It calls a new write method with WRITE_NON_BLOCKING
+ * flags.
+ */
+static int
+AudioTrack_WriteByteBuffer(JNIEnv *env, aout_stream_t *stream)
+{
+    aout_sys_t *p_sys = stream->sys;
+
+    if (p_sys->jbuffer.offset == p_sys->jbuffer.size)
     {
-        b_try_passthrough = false;
-        i_max_channels = adev == ANDROID_AUDIO_DEVICE_STEREO ? 2 : ANDROID_AUDIO_DEVICE_MAX_CHANNELS;
+        vlc_frame_t *f = p_sys->frame_chain;
+        assert(f != NULL);
+        if (p_sys->jbuffer.array != NULL)
+            (*env)->DeleteLocalRef(env, p_sys->jbuffer.array);
+        p_sys->jbuffer.array = (*env)->NewDirectByteBuffer(env,
+                                                           f->p_buffer,
+                                                           f->i_buffer);
+        if (p_sys->jbuffer.array == NULL)
+        {
+            if ((*env)->ExceptionCheck(env))
+                (*env)->ExceptionClear(env);
+            return jfields.AudioTrack.ERROR;
+        }
+        p_sys->jbuffer.offset = 0;
+        p_sys->jbuffer.size = f->i_buffer;
+        /* Don't take account of the current frame for the delay */
+        f->i_buffer = 0;
     }
 
-    if( !( env = GET_ENV() ) )
-        return VLC_EGENERIC;
+    int ret = JNI_AT_CALL_INT(writeBufferV21, p_sys->jbuffer.array,
+                              p_sys->jbuffer.size - p_sys->jbuffer.offset,
+                              jfields.AudioTrack.WRITE_NON_BLOCKING);
+    if (ret > 0)
+    {
+        p_sys->jbuffer.offset += ret;
+        /* The ByteBuffer reference directly the data from the frame, so it
+         * should stay allocated until all the data is written */
+        if (p_sys->jbuffer.offset == p_sys->jbuffer.size)
+            AudioTrack_ConsumeFrame(stream, p_sys->frame_chain);
+    }
 
-    AudioTrack_InitJNI(VLC_OBJECT(stream));
+    return ret;
+}
 
-    aout_sys_t *p_sys = stream->sys = calloc( 1, sizeof (aout_sys_t) );
+/**
+ * Non blocking short write function for Android M and after, run from
+ * AudioTrack_Thread. It calls a new write method with WRITE_NON_BLOCKING
+ * flags.
+ */
+static int
+AudioTrack_WriteShortArrayV23(JNIEnv *env, aout_stream_t *stream)
+{
+    aout_sys_t *p_sys = stream->sys;
 
-    if( unlikely( p_sys == NULL ) )
-        return VLC_ENOMEM;
+    int ret;
+    if (p_sys->jbuffer.offset == p_sys->jbuffer.size)
+    {
+        vlc_frame_t *f = p_sys->frame_chain;
+        assert(f != NULL);
+        ret = AudioTrack_AllocJArray(env, stream, f->i_buffer / 2,
+                                     (*env)->NewShortArray);
+        if (ret != 0)
+            return ret;
 
-    vlc_mutex_init(&p_sys->lock);
-    vlc_cond_init(&p_sys->aout_cond);
-    vlc_cond_init(&p_sys->thread_cond);
+        (*env)->SetShortArrayRegion(env, p_sys->jbuffer.array,
+                                    0, f->i_buffer / 2, (jshort *)f->p_buffer);
+        AudioTrack_ConsumeFrame(stream, f);
+    }
 
-    p_sys->volume = 1.0f;
-    p_sys->mute = false;
+    ret = JNI_AT_CALL_INT( writeShortV23, p_sys->jbuffer.array,
+                           p_sys->jbuffer.offset,
+                           p_sys->jbuffer.size - p_sys->jbuffer.offset,
+                           jfields.AudioTrack.WRITE_NON_BLOCKING );
+    if (ret > 0)
+    {
+        p_sys->jbuffer.offset += ret;
+        ret *= 2;
+    }
 
-    p_sys->fmt = *p_fmt;
+    return ret;
+}
 
-    aout_FormatPrint( stream, "VLC is looking for:", &p_sys->fmt );
+/**
+ * Non blocking play float function for Lollipop and after, run from
+ * AudioTrack_Thread. It calls a new write method with WRITE_NON_BLOCKING
+ * flags.
+ */
+static int
+AudioTrack_WriteFloatArray(JNIEnv *env, aout_stream_t *stream)
+{
+    aout_sys_t *p_sys = stream->sys;
+    int ret;
 
-    if (p_sys->fmt.channel_type == AUDIO_CHANNEL_TYPE_AMBISONICS)
+    if (p_sys->jbuffer.offset == p_sys->jbuffer.size)
     {
-        p_sys->fmt.channel_type = AUDIO_CHANNEL_TYPE_BITMAP;
+        vlc_frame_t *f = p_sys->frame_chain;
+        assert(f != NULL);
+        ret = AudioTrack_AllocJArray(env, stream, f->i_buffer / 4,
+                                     (*env)->NewFloatArray);
+        if (ret != 0)
+            return ret;
 
-        /* TODO: detect sink channel layout */
-        p_sys->fmt.i_physical_channels = AOUT_CHANS_STEREO;
-        aout_FormatPrepare(&p_sys->fmt);
+        (*env)->SetFloatArrayRegion(env, p_sys->jbuffer.array,
+                                   0, f->i_buffer / 4, (jfloat *)f->p_buffer);
+        AudioTrack_ConsumeFrame(stream, f);
     }
 
-    if( AOUT_FMT_LINEAR( &p_sys->fmt ) )
-        i_ret = StartPCM( env, stream, i_max_channels );
-    else if( b_try_passthrough )
-        i_ret = StartPassthrough( env, stream );
-    else
-        i_ret = VLC_EGENERIC;
+    ret = JNI_AT_CALL_INT(writeFloat, p_sys->jbuffer.array,
+                          p_sys->jbuffer.offset,
+                          p_sys->jbuffer.size - p_sys->jbuffer.offset,
+                          jfields.AudioTrack.WRITE_NON_BLOCKING);
 
-    if( i_ret != 0 )
+    if (ret > 0)
     {
-        free( p_sys );
-        return VLC_EGENERIC;
+        p_sys->jbuffer.offset += ret;
+        ret *= 4;
     }
 
-    if( jfields.AudioTrack.getBufferSizeInFrames )
-        p_sys->i_max_audiotrack_samples = JNI_AT_CALL_INT( getBufferSizeInFrames );
-    else
-        p_sys->i_max_audiotrack_samples = BYTES_TO_FRAMES( p_sys->audiotrack_args.i_size );
+    return ret;
+}
 
-#ifdef AUDIOTRACK_HW_LATENCY
-    if( jfields.AudioTimestamp.clazz )
+static int
+AudioTrack_Write( JNIEnv *env, aout_stream_t *stream, bool b_force )
+{
+    aout_sys_t *p_sys = stream->sys;
+    int i_ret;
+
+    switch( p_sys->i_write_type )
     {
-        /* create AudioTimestamp object */
-        jobject p_obj = JNI_CALL( NewObject, jfields.AudioTimestamp.clazz,
-                                 jfields.AudioTimestamp.ctor );
-        if( p_obj )
+    case WRITE_BYTEARRAYV23:
+        i_ret = AudioTrack_WriteByteArrayV23(env, stream);
+        break;
+    case WRITE_BYTEBUFFER:
+        i_ret = AudioTrack_WriteByteBuffer(env, stream);
+        break;
+    case WRITE_SHORTARRAYV23:
+        i_ret = AudioTrack_WriteShortArrayV23(env, stream);
+        break;
+    case WRITE_BYTEARRAY:
+        i_ret = AudioTrack_WriteByteArray(env, stream, b_force);
+        break;
+    case WRITE_FLOATARRAY:
+        i_ret = AudioTrack_WriteFloatArray(env, stream);
+        break;
+    default:
+        vlc_assert_unreachable();
+    }
+
+    if( i_ret < 0 ) {
+        if( jfields.AudioManager.has_ERROR_DEAD_OBJECT
+            && i_ret == jfields.AudioManager.ERROR_DEAD_OBJECT )
         {
-            p_sys->timestamp.p_obj = (*env)->NewGlobalRef( env, p_obj );
-            (*env)->DeleteLocalRef( env, p_obj );
+            msg_Warn( stream, "ERROR_DEAD_OBJECT: "
+                              "try recreating AudioTrack" );
+            if( ( i_ret = AudioTrack_Recreate( env, stream ) ) == 0 )
+            {
+                AudioTrack_Reset( env, stream );
+                JNI_AT_CALL_VOID( play );
+                CHECK_AT_EXCEPTION( "play" );
+            }
+        } else
+        {
+            const char *str;
+            if( i_ret == jfields.AudioTrack.ERROR_INVALID_OPERATION )
+                str = "ERROR_INVALID_OPERATION";
+            else if( i_ret == jfields.AudioTrack.ERROR_BAD_VALUE )
+                str = "ERROR_BAD_VALUE";
+            else
+                str = "ERROR";
+            msg_Err( stream, "Write failed: %s", str );
+            p_sys->b_error = true;
         }
-        if( !p_sys->timestamp.p_obj )
-            goto error;
-    }
-#endif
+    } else
+        p_sys->i_samples_written += BYTES_TO_FRAMES( i_ret );
+    return i_ret;
+}
 
-    p_sys->jbuffer.array = NULL;
-    p_sys->jbuffer.size = p_sys->jbuffer.offset = 0;
-    p_sys->jbuffer.maxsize = 0;
-    p_sys->frame_chain = NULL;
-    p_sys->frame_last = &p_sys->frame_chain;
-    AudioTrack_Reset( env, stream );
+static int
+AudioTrack_ReportTiming( JNIEnv *env, aout_stream_t *stream )
+{
+    aout_sys_t *p_sys = stream->sys;
 
-    if( p_sys->fmt.i_format == VLC_CODEC_FL32 )
-    {
-        msg_Dbg( stream, "using WRITE_FLOATARRAY");
-        p_sys->i_write_type = WRITE_FLOATARRAY;
-    }
-    else if( p_sys->fmt.i_format == VLC_CODEC_SPDIFL )
-    {
-        assert( jfields.AudioFormat.has_ENCODING_IEC61937 );
-        msg_Dbg( stream, "using WRITE_SHORTARRAYV23");
-        p_sys->i_write_type = WRITE_SHORTARRAYV23;
-    }
-    else if( jfields.AudioTrack.writeV23 )
+    if( p_sys->timestamp.p_obj != NULL
+     && JNI_AT_CALL_BOOL( getTimestamp, p_sys->timestamp.p_obj ) )
     {
-        msg_Dbg( stream, "using WRITE_BYTEARRAYV23");
-        p_sys->i_write_type = WRITE_BYTEARRAYV23;
-    }
-    else if( jfields.AudioTrack.writeBufferV21 )
-    {
-        msg_Dbg( stream, "using WRITE_BYTEBUFFER");
-        p_sys->i_write_type = WRITE_BYTEBUFFER;
-    }
-    else
-    {
-        msg_Dbg( stream, "using WRITE_BYTEARRAY");
-        p_sys->i_write_type = WRITE_BYTEARRAY;
+        vlc_tick_t frame_date_us =
+            VLC_TICK_FROM_NS(JNI_AUDIOTIMESTAMP_GET_LONG( nanoTime ));
+        jlong frame_post_last = JNI_AUDIOTIMESTAMP_GET_LONG( framePosition );
+
+        /* the low-order 32 bits of position is in wrapping frame units
+         * similar to AudioTrack#getPlaybackHeadPosition. */
+        if( p_sys->timestamp.i_frame_post_last > frame_post_last )
+            p_sys->timestamp.i_frame_wrap_count++;
+        p_sys->timestamp.i_frame_post_last = frame_post_last;
+        uint64_t frame_pos = frame_post_last
+                           + (p_sys->timestamp.i_frame_wrap_count << 32);
+
+        aout_stream_TimingReport( stream, frame_date_us,
+                                  FRAMES_TO_US( frame_pos ) );
+        return VLC_SUCCESS;
     }
 
-    /* Run AudioTrack_Thread */
-    p_sys->b_thread_running = true;
-    p_sys->b_thread_paused = false;
-    if ( vlc_clone( &p_sys->thread, AudioTrack_Thread, stream ) )
+    vlc_tick_t now = vlc_tick_now();
+    vlc_tick_t frame_us = FRAMES_TO_US( AudioTrack_getPlaybackHeadPosition( env, stream ) );
+    if( frame_us == 0 )
+        return VLC_EGENERIC;
+
+    vlc_tick_t latency_us = AudioTrack_GetLatencyUs( env, stream );
+    frame_us -= latency_us;
+
+    if( frame_us <= 0 )
+        return VLC_EGENERIC;
+    aout_stream_TimingReport( stream, now, frame_us );
+
+    return VLC_SUCCESS;
+}
+
+/**
+ * This thread will play the data coming from the jbuffer buffer.
+ */
+static void *
+AudioTrack_Thread( void *p_data )
+{
+    aout_stream_t *stream = p_data;
+    aout_sys_t *p_sys = stream->sys;
+    JNIEnv *env = GET_ENV();
+    vlc_tick_t i_last_time_blocked = 0;
+
+    vlc_thread_set_name("vlc-audiotrack");
+
+    if( !env )
+        return NULL;
+
+    for( ;; )
     {
-        msg_Err(stream, "vlc clone failed");
-        goto error;
-    }
+        int i_ret = 0;
+        bool b_forced;
 
-    JNI_AT_CALL_VOID( play );
-    CHECK_AT_EXCEPTION( "play" );
+        vlc_mutex_lock( &p_sys->lock );
 
-    *p_fmt = p_sys->fmt;
+        /* Wait for not paused state */
+        while( p_sys->b_thread_running && p_sys->b_thread_paused )
+        {
+            i_last_time_blocked = 0;
+            vlc_cond_wait( &p_sys->thread_cond, &p_sys->lock );
+        }
 
-    aout_FormatPrint( stream, "VLC will output:", &p_sys->fmt );
+        /* Wait for more data in the jbuffer buffer */
+        while (p_sys->b_thread_running
+            && p_sys->jbuffer.offset == p_sys->jbuffer.size
+            && p_sys->frame_chain == NULL)
+            vlc_cond_wait( &p_sys->thread_cond, &p_sys->lock );
 
-    stream->stop = Stop;
-    stream->play = Play;
-    stream->pause = Pause;
-    stream->flush = Flush;
-    stream->time_get = TimeGet;
-    stream->volume_set = VolumeSet;
-    stream->mute_set = MuteSet;
+        if( !p_sys->b_thread_running || p_sys->b_error )
+        {
+            vlc_mutex_unlock( &p_sys->lock );
+            break;
+        }
 
-    msg_Dbg(stream, "using AudioTrack API");
-    return VLC_SUCCESS;
+        /* HACK: AudioFlinger can drop frames without notifying us and there is
+         * no way to know it. If it happens, i_audiotrack_pos won't move and
+         * the current code will be stuck because it'll assume that audiotrack
+         * internal buffer is full when it's not. It may happen only after
+         * Android 4.4.2 if we send frames too quickly. To fix this issue,
+         * force the writing of the buffer after a certain delay. */
+        if( i_last_time_blocked != 0 )
+            b_forced = vlc_tick_now() - i_last_time_blocked >
+                       FRAMES_TO_US( p_sys->i_max_audiotrack_samples ) * 2;
+        else
+            b_forced = false;
+
+        i_ret = AudioTrack_Write( env, stream, b_forced );
+        if( i_ret >= 0 )
+        {
+            if( i_ret == 0 )
+            {
+                vlc_tick_t i_now = vlc_tick_now();
+
+                /* cf. b_forced HACK comment */
+                if( p_sys->i_write_type == WRITE_BYTEARRAY && i_last_time_blocked == 0 )
+                    i_last_time_blocked = i_now;
+
+                /* Wait for free space in Audiotrack internal buffer */
+                vlc_tick_t i_play_deadline = i_now + __MAX( 10000,
+                        FRAMES_TO_US( p_sys->i_max_audiotrack_samples / 5 ) );
 
-error:
-    Stop( stream );
-    return VLC_EGENERIC;
-}
+                while( p_sys->b_thread_running && i_ret == 0 )
+                    i_ret = vlc_cond_timedwait( &p_sys->thread_cond, &p_sys->lock,
+                                                i_play_deadline );
 
-static void
-Stop( aout_stream_t *stream )
-{
-    aout_sys_t *p_sys = stream->sys;
-    JNIEnv *env;
+            }
+            else
+            {
+                i_last_time_blocked = 0;
 
-    if( !( env = GET_ENV() ) )
-        return;
+                if( !p_sys->b_passthrough )
+                {
+                    p_sys->timing_report_last_written_bytes += i_ret;
+
+                    if( p_sys->timing_report_last_written_bytes >=
+                            p_sys->timing_report_delay_bytes
+                     && AudioTrack_ReportTiming( env, stream ) == VLC_SUCCESS )
+                    {
+                        p_sys->timing_report_last_written_bytes = 0;
+                        /* From now on, fetch the timestamp every 1 seconds */
+                        p_sys->timing_report_delay_bytes =
+                            US_TO_BYTES( TIMING_REPORT_DELAY_TICKS );
+                    }
+                }
+            }
+        }
 
-    /* Stop the AudioTrack thread */
-    vlc_mutex_lock( &p_sys->lock );
-    if( p_sys->b_thread_running )
-    {
-        p_sys->b_thread_running = false;
-        vlc_cond_signal( &p_sys->thread_cond );
         vlc_mutex_unlock( &p_sys->lock );
-        vlc_join( p_sys->thread, NULL );
     }
-    else
-        vlc_mutex_unlock(  &p_sys->lock );
 
-    /* Release the AudioTrack object */
-    if( p_sys->p_audiotrack )
+    if (p_sys->jbuffer.array != NULL)
     {
-        if( !p_sys->b_audiotrack_exception )
-        {
-            JNI_AT_CALL_VOID( stop );
-            if( !CHECK_AT_EXCEPTION( "stop" ) )
-                JNI_AT_CALL_VOID( release );
-        }
-        (*env)->DeleteGlobalRef( env, p_sys->p_audiotrack );
+        (*env)->DeleteLocalRef(env, p_sys->jbuffer.array);
+        p_sys->jbuffer.array = NULL;
     }
 
-    if( p_sys->p_dp != NULL )
-        DynamicsProcessing_Delete( stream, p_sys->p_dp );
-
-    /* Release the timestamp object */
-    if( p_sys->timestamp.p_obj )
-        (*env)->DeleteGlobalRef( env, p_sys->timestamp.p_obj );
-
-    free( p_sys );
+    return NULL;
 }
 
-static void
-AudioTrack_ConsumeFrame(aout_stream_t *stream, vlc_frame_t *f)
+static int
+ConvertFromIEC61937( aout_stream_t *stream, block_t *p_buffer )
 {
-    aout_sys_t *p_sys = stream->sys;
-    assert(f != NULL && f == p_sys->frame_chain);
-
-    p_sys->frame_chain = f->p_next;
-    if (p_sys->frame_chain == NULL)
-        p_sys->frame_last = &p_sys->frame_chain;
+    /* This function is only used for Android API 23 when AudioTrack is
+     * configured with ENCODING_ AC3/E_AC3/DTS. In that case, only the codec
+     * data is needed (without the IEC61937 encapsulation). This function
+     * recovers the codec data from an EC61937 frame. It is the opposite of the
+     * code found in converter/tospdif.c. We could also request VLC core to
+     * send us the codec data directly, but in that case, we wouldn't benefit
+     * from the eac3 block merger of tospdif.c. */
 
-    vlc_frame_Release(f);
-}
+    VLC_UNUSED( stream );
+    uint8_t i_length_mul;
 
-static int
-AudioTrack_AllocJArray(JNIEnv *env, aout_stream_t *stream, size_t size,
-                       jarray (*new)(JNIEnv *env, jsize size))
-{
-    aout_sys_t *p_sys = stream->sys;
+    if( p_buffer->i_buffer < 6 )
+        return -1;
 
-    if (size > p_sys->jbuffer.maxsize)
+    switch( GetWBE( &p_buffer->p_buffer[4] ) & 0xFF )
     {
-        p_sys->jbuffer.maxsize = 0;
-        if (p_sys->jbuffer.array != NULL)
-            (*env)->DeleteLocalRef(env, p_sys->jbuffer.array);
-
-        p_sys->jbuffer.array = new(env, size);
-        if (p_sys->jbuffer.array != NULL)
-            p_sys->jbuffer.maxsize = size;
-        else
-            msg_Err(stream, "jarray allocation failed");
+        case 0x01: /* IEC61937_AC3 */
+            i_length_mul = 8;
+            break;
+        case 0x15: /* IEC61937_EAC3 */
+            i_length_mul = 1;
+            break;
+        case 0x0B: /* IEC61937_DTS1 */
+        case 0x0C: /* IEC61937_DTS2 */
+        case 0x0D: /* IEC61937_DTS3 */
+            i_length_mul = 8;
+            break;
+        case 0x11: /* IEC61937_DTSHD */
+            i_length_mul = 1;
+            break;
+        default:
+            vlc_assert_unreachable();
     }
+    uint16_t i_length = GetWBE( &p_buffer->p_buffer[6] );
+    if( i_length == 0 )
+        return -1;
 
-    if (p_sys->jbuffer.array == NULL)
-        return jfields.AudioTrack.ERROR;
+    i_length /= i_length_mul;
+    if( i_length > p_buffer->i_buffer - 8 )
+        return -1;
 
-    p_sys->jbuffer.size = size;
-    p_sys->jbuffer.offset = 0;
+    p_buffer->p_buffer += 8; /* SPDIF_HEADER_SIZE */
+    p_buffer->i_buffer = i_length;
 
     return 0;
 }
 
-/**
- * Non blocking write function, run from AudioTrack_Thread.
- * Do a calculation between current position and audiotrack position and assure
- * that we won't wait in AudioTrack.write() method.
- */
-static int
-AudioTrack_WriteByteArray( JNIEnv *env, aout_stream_t *stream, bool b_force )
+static void
+Play( aout_stream_t *stream, block_t *p_buffer, vlc_tick_t i_date )
 {
+    JNIEnv *env = NULL;
     aout_sys_t *p_sys = stream->sys;
-    uint64_t i_samples;
-    uint64_t i_audiotrack_pos;
-    uint64_t i_samples_pending;
 
-    int ret;
-    if (p_sys->jbuffer.offset == p_sys->jbuffer.size)
+    if( p_sys->b_passthrough && p_sys->fmt.i_format == VLC_CODEC_SPDIFB
+     && ConvertFromIEC61937( stream, p_buffer ) != 0 )
     {
-        vlc_frame_t *f = p_sys->frame_chain;
-        assert(f != NULL);
-        ret = AudioTrack_AllocJArray(env, stream, f->i_buffer,
-                                     (*env)->NewByteArray);
-        if (ret != 0)
-            return ret;
-
-        (*env)->SetByteArrayRegion(env, p_sys->jbuffer.array,
-                                   0, f->i_buffer, (jbyte *)f->p_buffer);
-        AudioTrack_ConsumeFrame(stream, f);
+        block_Release(p_buffer);
+        return;
     }
 
-    i_audiotrack_pos = AudioTrack_getPlaybackHeadPosition( env, stream );
+    vlc_mutex_lock( &p_sys->lock );
 
-    assert( i_audiotrack_pos <= p_sys->i_samples_written );
-    if( i_audiotrack_pos > p_sys->i_samples_written )
+    if( p_sys->b_error || !( env = GET_ENV() ) )
     {
-        msg_Err( stream, "audiotrack position is ahead. Should NOT happen" );
-        p_sys->i_samples_written = 0;
-        p_sys->b_error = true;
-        return 0;
+        block_Release( p_buffer );
+        goto bailout;
     }
-    i_samples_pending = p_sys->i_samples_written - i_audiotrack_pos;
 
-    /* check if audiotrack buffer is not full before writing on it. */
-    if( b_force )
-    {
-        msg_Warn( stream, "Force write. It may block..." );
-        i_samples_pending = 0;
-    } else if( i_samples_pending >= p_sys->i_max_audiotrack_samples )
-        return 0;
-
-    i_samples = __MIN( p_sys->i_max_audiotrack_samples - i_samples_pending,
-                       BYTES_TO_FRAMES(p_sys->jbuffer.size - p_sys->jbuffer.offset));
+    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 );
 
-    ret = JNI_AT_CALL_INT( write, p_sys->jbuffer.array, p_sys->jbuffer.offset,
-                           FRAMES_TO_BYTES( i_samples ) );
-    if (ret > 0)
-        p_sys->jbuffer.offset += ret;
+    vlc_frame_ChainLastAppend(&p_sys->frame_last, p_buffer);
+    vlc_cond_signal(&p_sys->thread_cond);
 
-    return ret;
+bailout:
+    vlc_mutex_unlock( &p_sys->lock );
+    (void) i_date;
 }
 
-/**
- * Non blocking write function for Android M and after, run from
- * AudioTrack_Thread. It calls a new write method with WRITE_NON_BLOCKING
- * flags.
- */
-static int
-AudioTrack_WriteByteArrayV23(JNIEnv *env, aout_stream_t *stream)
+static void
+Pause( aout_stream_t *stream, bool b_pause, vlc_tick_t i_date )
 {
     aout_sys_t *p_sys = stream->sys;
+    JNIEnv *env;
+    VLC_UNUSED( i_date );
 
-    int ret;
-    if (p_sys->jbuffer.offset == p_sys->jbuffer.size)
-    {
-        vlc_frame_t *f = p_sys->frame_chain;
-        assert(f != NULL);
-        ret = AudioTrack_AllocJArray(env, stream, f->i_buffer,
-                                     (*env)->NewByteArray);
-        if (ret != 0)
-            return ret;
-
-        (*env)->SetByteArrayRegion(env, p_sys->jbuffer.array,
-                                   0, f->i_buffer, (jbyte *)f->p_buffer);
-        AudioTrack_ConsumeFrame(stream, f);
-    }
-
-    ret = JNI_AT_CALL_INT( writeV23, p_sys->jbuffer.array,
-                           p_sys->jbuffer.offset,
-                           p_sys->jbuffer.size - p_sys->jbuffer.offset,
-                           jfields.AudioTrack.WRITE_NON_BLOCKING );
-    if (ret > 0)
-        p_sys->jbuffer.offset += ret;
-
-    return ret;
-}
-
-/**
- * Non blocking play function for Lollipop and after, run from
- * AudioTrack_Thread. It calls a new write method with WRITE_NON_BLOCKING
- * flags.
- */
-static int
-AudioTrack_WriteByteBuffer(JNIEnv *env, aout_stream_t *stream)
-{
-    aout_sys_t *p_sys = stream->sys;
+    vlc_mutex_lock( &p_sys->lock );
 
-    if (p_sys->jbuffer.offset == p_sys->jbuffer.size)
-    {
-        vlc_frame_t *f = p_sys->frame_chain;
-        assert(f != NULL);
-        if (p_sys->jbuffer.array != NULL)
-            (*env)->DeleteLocalRef(env, p_sys->jbuffer.array);
-        p_sys->jbuffer.array = (*env)->NewDirectByteBuffer(env,
-                                                           f->p_buffer,
-                                                           f->i_buffer);
-        if (p_sys->jbuffer.array == NULL)
-        {
-            if ((*env)->ExceptionCheck(env))
-                (*env)->ExceptionClear(env);
-            return jfields.AudioTrack.ERROR;
-        }
-        p_sys->jbuffer.offset = 0;
-        p_sys->jbuffer.size = f->i_buffer;
-        /* Don't take account of the current frame for the delay */
-        f->i_buffer = 0;
-    }
+    if( p_sys->b_error || !( env = GET_ENV() ) )
+        goto bailout;
 
-    int ret = JNI_AT_CALL_INT(writeBufferV21, p_sys->jbuffer.array,
-                              p_sys->jbuffer.size - p_sys->jbuffer.offset,
-                              jfields.AudioTrack.WRITE_NON_BLOCKING);
-    if (ret > 0)
+    if( b_pause )
     {
-        p_sys->jbuffer.offset += ret;
-        /* The ByteBuffer reference directly the data from the frame, so it
-         * should stay allocated until all the data is written */
-        if (p_sys->jbuffer.offset == p_sys->jbuffer.size)
-            AudioTrack_ConsumeFrame(stream, p_sys->frame_chain);
+        p_sys->b_thread_paused = true;
+        JNI_AT_CALL_VOID( pause );
+        CHECK_AT_EXCEPTION( "pause" );
+    } else
+    {
+        p_sys->b_thread_paused = false;
+        JNI_AT_CALL_VOID( play );
+        CHECK_AT_EXCEPTION( "play" );
     }
 
-    return ret;
+bailout:
+    vlc_mutex_unlock( &p_sys->lock );
 }
 
-/**
- * Non blocking short write function for Android M and after, run from
- * AudioTrack_Thread. It calls a new write method with WRITE_NON_BLOCKING
- * flags.
- */
-static int
-AudioTrack_WriteShortArrayV23(JNIEnv *env, aout_stream_t *stream)
+static void
+Flush( aout_stream_t *stream )
 {
     aout_sys_t *p_sys = stream->sys;
+    JNIEnv *env;
 
-    int ret;
-    if (p_sys->jbuffer.offset == p_sys->jbuffer.size)
-    {
-        vlc_frame_t *f = p_sys->frame_chain;
-        assert(f != NULL);
-        ret = AudioTrack_AllocJArray(env, stream, f->i_buffer / 2,
-                                     (*env)->NewShortArray);
-        if (ret != 0)
-            return ret;
+    vlc_mutex_lock( &p_sys->lock );
 
-        (*env)->SetShortArrayRegion(env, p_sys->jbuffer.array,
-                                    0, f->i_buffer / 2, (jshort *)f->p_buffer);
-        AudioTrack_ConsumeFrame(stream, f);
-    }
+    p_sys->jbuffer.size = p_sys->jbuffer.offset = 0;
+    vlc_frame_ChainRelease(p_sys->frame_chain);
+    p_sys->frame_chain = NULL;
+    p_sys->frame_last = &p_sys->frame_chain;
 
-    ret = JNI_AT_CALL_INT( writeShortV23, p_sys->jbuffer.array,
-                           p_sys->jbuffer.offset,
-                           p_sys->jbuffer.size - p_sys->jbuffer.offset,
-                           jfields.AudioTrack.WRITE_NON_BLOCKING );
-    if (ret > 0)
+    if( p_sys->b_error || !( env = GET_ENV() ) )
+        goto bailout;
+
+    /* Android doc:
+     * stop(): Stops playing the audio data. When used on an instance created
+     * in MODE_STREAM mode, audio will stop playing after the last buffer that
+     * was written has been played. For an immediate stop, use pause(),
+     * followed by flush() to discard audio data that hasn't been played back
+     * yet.
+     *
+     * flush(): Flushes the audio data currently queued for playback. Any data
+     * that has not been played back will be discarded.  No-op if not stopped
+     * or paused, or if the track's creation mode is not MODE_STREAM.
+     */
+    JNI_AT_CALL_VOID( pause );
+    if( CHECK_AT_EXCEPTION( "pause" ) )
+        goto bailout;
+    JNI_AT_CALL_VOID( flush );
+
+    /* HACK: Before Android 4.4, the head position is not reset to zero and is
+     * still moving after a flush or a stop. This prevents to get a precise
+     * head position and there is no way to know when it stabilizes. Therefore
+     * recreate an AudioTrack object in that case. The AudioTimestamp class was
+     * added in API Level 19, so if this class is not found, the Android
+     * Version is 4.3 or before */
+    if( !jfields.AudioTimestamp.clazz && p_sys->i_samples_written > 0 )
     {
-        p_sys->jbuffer.offset += ret;
-        ret *= 2;
+        if( AudioTrack_Recreate( env, stream ) != 0 )
+        {
+            p_sys->b_error = true;
+            goto bailout;
+        }
     }
+    AudioTrack_Reset( env, stream );
+    JNI_AT_CALL_VOID( play );
+    CHECK_AT_EXCEPTION( "play" );
 
-    return ret;
+bailout:
+    vlc_mutex_unlock( &p_sys->lock );
 }
 
-/**
- * Non blocking play float function for Lollipop and after, run from
- * AudioTrack_Thread. It calls a new write method with WRITE_NON_BLOCKING
- * flags.
- */
-static int
-AudioTrack_WriteFloatArray(JNIEnv *env, aout_stream_t *stream)
+static void
+AudioTrack_SetVolume( JNIEnv *env, aout_stream_t *stream, float volume )
 {
     aout_sys_t *p_sys = stream->sys;
-    int ret;
 
-    if (p_sys->jbuffer.offset == p_sys->jbuffer.size)
+    if( jfields.AudioTrack.setVolume )
     {
-        vlc_frame_t *f = p_sys->frame_chain;
-        assert(f != NULL);
-        ret = AudioTrack_AllocJArray(env, stream, f->i_buffer / 4,
-                                     (*env)->NewFloatArray);
-        if (ret != 0)
-            return ret;
-
-        (*env)->SetFloatArrayRegion(env, p_sys->jbuffer.array,
-                                   0, f->i_buffer / 4, (jfloat *)f->p_buffer);
-        AudioTrack_ConsumeFrame(stream, f);
-    }
-
-    ret = JNI_AT_CALL_INT(writeFloat, p_sys->jbuffer.array,
-                          p_sys->jbuffer.offset,
-                          p_sys->jbuffer.size - p_sys->jbuffer.offset,
-                          jfields.AudioTrack.WRITE_NON_BLOCKING);
-
-    if (ret > 0)
+        JNI_AT_CALL_INT( setVolume, volume );
+        CHECK_AT_EXCEPTION( "setVolume" );
+    } else
     {
-        p_sys->jbuffer.offset += ret;
-        ret *= 4;
+        JNI_AT_CALL_INT( setStereoVolume, volume, volume );
+        CHECK_AT_EXCEPTION( "setStereoVolume" );
     }
-
-    return ret;
 }
 
-static int
-AudioTrack_Write( JNIEnv *env, aout_stream_t *stream, bool b_force )
+static void
+VolumeSet( aout_stream_t *stream, float volume )
 {
     aout_sys_t *p_sys = stream->sys;
-    int i_ret;
+    JNIEnv *env;
+    float gain = 1.0f;
 
-    switch( p_sys->i_write_type )
+    p_sys->volume = volume;
+    if (volume > 1.f)
     {
-    case WRITE_BYTEARRAYV23:
-        i_ret = AudioTrack_WriteByteArrayV23(env, stream);
-        break;
-    case WRITE_BYTEBUFFER:
-        i_ret = AudioTrack_WriteByteBuffer(env, stream);
-        break;
-    case WRITE_SHORTARRAYV23:
-        i_ret = AudioTrack_WriteShortArrayV23(env, stream);
-        break;
-    case WRITE_BYTEARRAY:
-        i_ret = AudioTrack_WriteByteArray(env, stream, b_force);
-        break;
-    case WRITE_FLOATARRAY:
-        i_ret = AudioTrack_WriteFloatArray(env, stream);
-        break;
-    default:
-        vlc_assert_unreachable();
+        gain = volume * volume * volume;
+        volume = 1.f;
     }
 
-    if( i_ret < 0 ) {
-        if( jfields.AudioManager.has_ERROR_DEAD_OBJECT
-            && i_ret == jfields.AudioManager.ERROR_DEAD_OBJECT )
+    if( !p_sys->b_error && p_sys->p_audiotrack != NULL && ( env = GET_ENV() ) )
+    {
+        AudioTrack_SetVolume( env, stream, volume );
+
+        /* Apply gain > 1.0f via DynamicsProcessing if possible */
+        if( p_sys->p_dp != NULL )
         {
-            msg_Warn( stream, "ERROR_DEAD_OBJECT: "
-                              "try recreating AudioTrack" );
-            if( ( i_ret = AudioTrack_Recreate( env, stream ) ) == 0 )
+            if( gain <= 1.0f )
             {
-                AudioTrack_Reset( env, stream );
-                JNI_AT_CALL_VOID( play );
-                CHECK_AT_EXCEPTION( "play" );
+                /* DynamicsProcessing is not needed anymore (using AudioTrack
+                 * volume) */
+                DynamicsProcessing_Disable( stream, p_sys->p_dp );
             }
-        } else
-        {
-            const char *str;
-            if( i_ret == jfields.AudioTrack.ERROR_INVALID_OPERATION )
-                str = "ERROR_INVALID_OPERATION";
-            else if( i_ret == jfields.AudioTrack.ERROR_BAD_VALUE )
-                str = "ERROR_BAD_VALUE";
             else
-                str = "ERROR";
-            msg_Err( stream, "Write failed: %s", str );
-            p_sys->b_error = true;
+            {
+                int ret = DynamicsProcessing_SetVolume( stream, p_sys->p_dp, gain );
+
+                if( ret == VLC_SUCCESS )
+                    gain = 1.0; /* reset sw gain */
+                else
+                    msg_Warn( stream, "failed to set gain via DynamicsProcessing, fallback to sw gain");
+            }
         }
-    } else
-        p_sys->i_samples_written += BYTES_TO_FRAMES( i_ret );
-    return i_ret;
+    }
+
+    aout_stream_GainRequest(stream, gain);
+}
+
+static void
+MuteSet( aout_stream_t *stream, bool mute )
+{
+    aout_sys_t *p_sys = stream->sys;
+    JNIEnv *env;
+    p_sys->mute = mute;
+
+    if( !p_sys->b_error && p_sys->p_audiotrack != NULL && ( env = GET_ENV() ) )
+        AudioTrack_SetVolume( env, stream, mute ? 0.0f : p_sys->volume );
+}
+
+static int GetPassthroughFmt( bool compat, audio_sample_format_t *fmt, int *at_format )
+{
+    if( !compat && jfields.AudioFormat.has_ENCODING_IEC61937 )
+    {
+        *at_format = jfields.AudioFormat.ENCODING_IEC61937;
+        switch( fmt->i_format )
+        {
+            case VLC_CODEC_TRUEHD:
+            case VLC_CODEC_MLP:
+                fmt->i_rate = 192000;
+                fmt->i_bytes_per_frame = 16;
+
+                /* AudioFormat.ENCODING_IEC61937 documentation says that the
+                 * channel layout must be stereo. Well, not for TrueHD
+                 * apparently */
+                fmt->i_physical_channels = AOUT_CHANS_7_1;
+                break;
+            case VLC_CODEC_DTS:
+                fmt->i_bytes_per_frame = 4;
+                fmt->i_physical_channels = AOUT_CHANS_STEREO;
+                break;
+            case VLC_CODEC_DTSHD:
+                fmt->i_bytes_per_frame = 4;
+                fmt->i_physical_channels = AOUT_CHANS_STEREO;
+                fmt->i_rate = 192000;
+                fmt->i_bytes_per_frame = 16;
+                break;
+            case VLC_CODEC_EAC3:
+                fmt->i_rate = 192000;
+            case VLC_CODEC_A52:
+                fmt->i_physical_channels = AOUT_CHANS_STEREO;
+                fmt->i_bytes_per_frame = 4;
+                break;
+            default:
+                return VLC_EGENERIC;
+        }
+        fmt->i_frame_length = 1;
+        fmt->i_channels = aout_FormatNbChannels( fmt );
+        fmt->i_format = VLC_CODEC_SPDIFL;
+    }
+    else
+    {
+        if( vlc_android_AudioFormat_FourCCToEncoding( fmt->i_format, at_format )
+                != VLC_SUCCESS )
+            return VLC_EGENERIC;
+        fmt->i_bytes_per_frame = 4;
+        fmt->i_frame_length = 1;
+        fmt->i_physical_channels = AOUT_CHANS_STEREO;
+        fmt->i_channels = 2;
+        fmt->i_format = VLC_CODEC_SPDIFB;
+    }
+
+    return VLC_SUCCESS;
 }
 
-/**
- * This thread will play the data coming from the jbuffer buffer.
- */
-static void *
-AudioTrack_Thread( void *p_data )
+static int
+StartPassthrough( JNIEnv *env, aout_stream_t *stream )
 {
-    aout_stream_t *stream = p_data;
     aout_sys_t *p_sys = stream->sys;
-    JNIEnv *env = GET_ENV();
-    vlc_tick_t i_last_time_blocked = 0;
-
-    vlc_thread_set_name("vlc-audiotrack");
-
-    if( !env )
-        return NULL;
 
-    for( ;; )
+    /* Try ENCODING_IEC61937 first, then fallback to ENCODING_[AC3|DTS|...] */
+    unsigned nb_fmt = jfields.AudioFormat.has_ENCODING_IEC61937 ? 2 : 1;
+    int i_ret;
+    for( unsigned i = 0; i < nb_fmt; ++i )
     {
-        int i_ret = 0;
-        bool b_forced;
+        int i_at_format;
+        bool compat = i == 1;
+        audio_sample_format_t fmt = p_sys->fmt;
 
-        vlc_mutex_lock( &p_sys->lock );
+        i_ret = GetPassthroughFmt( compat, &fmt, &i_at_format );
+        if( i_ret != VLC_SUCCESS )
+            return i_ret;
 
-        /* Wait for not paused state */
-        while( p_sys->b_thread_running && p_sys->b_thread_paused )
+        p_sys->b_passthrough = true;
+        i_ret = AudioTrack_Create( env, stream, fmt.i_rate, i_at_format,
+                                   fmt.i_physical_channels );
+
+        if( i_ret == VLC_SUCCESS )
         {
-            i_last_time_blocked = 0;
-            vlc_cond_wait( &p_sys->thread_cond, &p_sys->lock );
+            msg_Dbg( stream, "Using passthrough format: %d", i_at_format );
+            p_sys->i_chans_to_reorder = 0;
+            p_sys->fmt = fmt;
+            return VLC_SUCCESS;
         }
+    }
 
-        /* Wait for more data in the jbuffer buffer */
-        while (p_sys->b_thread_running
-            && p_sys->jbuffer.offset == p_sys->jbuffer.size
-            && p_sys->frame_chain == NULL)
-            vlc_cond_wait( &p_sys->thread_cond, &p_sys->lock );
+    p_sys->b_passthrough = false;
+    msg_Warn( stream, "SPDIF configuration failed" );
+    return VLC_EGENERIC;
+}
 
-        if( !p_sys->b_thread_running || p_sys->b_error )
-        {
-            vlc_mutex_unlock( &p_sys->lock );
-            break;
-        }
+static int
+StartPCM( JNIEnv *env, aout_stream_t *stream, unsigned i_max_channels )
+{
+    aout_sys_t *p_sys = stream->sys;
+    unsigned i_nb_channels;
+    int i_at_format, i_ret;
 
-        /* HACK: AudioFlinger can drop frames without notifying us and there is
-         * no way to know it. If it happens, i_audiotrack_pos won't move and
-         * the current code will be stuck because it'll assume that audiotrack
-         * internal buffer is full when it's not. It may happen only after
-         * Android 4.4.2 if we send frames too quickly. To fix this issue,
-         * force the writing of the buffer after a certain delay. */
-        if( i_last_time_blocked != 0 )
-            b_forced = vlc_tick_now() - i_last_time_blocked >
-                       FRAMES_TO_US( p_sys->i_max_audiotrack_samples ) * 2;
-        else
-            b_forced = false;
+    if (jfields.AudioTrack.getNativeOutputSampleRate)
+        p_sys->fmt.i_rate =
+            JNI_AT_CALL_STATIC_INT( getNativeOutputSampleRate,
+                                    jfields.AudioManager.STREAM_MUSIC );
+    else
+        p_sys->fmt.i_rate = VLC_CLIP( p_sys->fmt.i_rate, 4000, 48000 );
 
-        i_ret = AudioTrack_Write( env, stream, b_forced );
-        if( i_ret >= 0 )
+    do
+    {
+        /* We can only accept U8, S16N, FL32, and AC3 */
+        switch( p_sys->fmt.i_format )
         {
-            if( i_ret == 0 )
+        case VLC_CODEC_U8:
+            i_at_format = jfields.AudioFormat.ENCODING_PCM_8BIT;
+            break;
+        case VLC_CODEC_S16N:
+            i_at_format = jfields.AudioFormat.ENCODING_PCM_16BIT;
+            break;
+        case VLC_CODEC_FL32:
+            if( jfields.AudioFormat.has_ENCODING_PCM_FLOAT )
+                i_at_format = jfields.AudioFormat.ENCODING_PCM_FLOAT;
+            else
             {
-                vlc_tick_t i_now = vlc_tick_now();
-
-                /* cf. b_forced HACK comment */
-                if( p_sys->i_write_type == WRITE_BYTEARRAY && i_last_time_blocked == 0 )
-                    i_last_time_blocked = i_now;
+                p_sys->fmt.i_format = VLC_CODEC_S16N;
+                i_at_format = jfields.AudioFormat.ENCODING_PCM_16BIT;
+            }
+            break;
+        default:
+            p_sys->fmt.i_format = VLC_CODEC_S16N;
+            i_at_format = jfields.AudioFormat.ENCODING_PCM_16BIT;
+            break;
+        }
 
-                /* Wait for free space in Audiotrack internal buffer */
-                vlc_tick_t i_play_deadline = i_now + __MAX( 10000,
-                        FRAMES_TO_US( p_sys->i_max_audiotrack_samples / 5 ) );
+        /* Android AudioTrack supports only mono, stereo, 5.1 and 7.1.
+         * Android will downmix to stereo if audio output doesn't handle 5.1 or 7.1
+         */
 
-                while( p_sys->b_thread_running && i_ret == 0 )
-                    i_ret = vlc_cond_timedwait( &p_sys->thread_cond, &p_sys->lock,
-                                                i_play_deadline );
+        i_nb_channels = aout_FormatNbChannels( &p_sys->fmt );
+        if( i_nb_channels == 0 )
+            return VLC_EGENERIC;
+        if( AOUT_FMT_LINEAR( &p_sys->fmt ) )
+            i_nb_channels = __MIN( i_max_channels, i_nb_channels );
+        if( i_nb_channels > 5 )
+        {
+            if( i_nb_channels > 7 && jfields.AudioFormat.has_CHANNEL_OUT_SIDE )
+                p_sys->fmt.i_physical_channels = AOUT_CHANS_7_1;
+            else
+                p_sys->fmt.i_physical_channels = AOUT_CHANS_5_1;
+        } else
+        {
+            if( i_nb_channels == 1 )
+                p_sys->fmt.i_physical_channels = AOUT_CHAN_LEFT;
+            else
+                p_sys->fmt.i_physical_channels = AOUT_CHANS_STEREO;
+        }
 
+        /* Try to create an AudioTrack with the most advanced channel and
+         * format configuration. If AudioTrack_Create fails, try again with a
+         * less advanced format (PCM S16N). If it fails again, try again with
+         * Stereo channels. */
+        i_ret = AudioTrack_Create( env, stream, p_sys->fmt.i_rate, i_at_format,
+                                   p_sys->fmt.i_physical_channels );
+        if( i_ret != 0 )
+        {
+            if( p_sys->fmt.i_format == VLC_CODEC_FL32 )
+            {
+                msg_Warn( stream, "FL32 configuration failed, "
+                                  "fallback to S16N PCM" );
+                p_sys->fmt.i_format = VLC_CODEC_S16N;
             }
-            else
+            else if( p_sys->fmt.i_physical_channels & AOUT_CHANS_5_1 )
             {
-                i_last_time_blocked = 0;
-                vlc_cond_signal( &p_sys->aout_cond );
+                msg_Warn( stream, "5.1 or 7.1 configuration failed, "
+                                  "fallback to Stereo" );
+                p_sys->fmt.i_physical_channels = AOUT_CHANS_STEREO;
             }
+            else
+                break;
         }
+    } while( i_ret != 0 );
 
-        vlc_mutex_unlock( &p_sys->lock );
-    }
-
-    if (p_sys->jbuffer.array != NULL)
-    {
-        (*env)->DeleteLocalRef(env, p_sys->jbuffer.array);
-        p_sys->jbuffer.array = NULL;
-    }
+    if( i_ret != VLC_SUCCESS )
+        return i_ret;
 
-    return NULL;
+    uint32_t p_chans_out[AOUT_CHAN_MAX];
+    memset( p_chans_out, 0, sizeof(p_chans_out) );
+    AudioTrack_GetChanOrder( p_sys->fmt.i_physical_channels, p_chans_out );
+    p_sys->i_chans_to_reorder =
+        aout_CheckChannelReorder( NULL, p_chans_out,
+                                  p_sys->fmt.i_physical_channels,
+                                  p_sys->p_chan_table );
+    aout_FormatPrepare( &p_sys->fmt );
+    return VLC_SUCCESS;
 }
 
-static int
-ConvertFromIEC61937( aout_stream_t *stream, block_t *p_buffer )
+static void
+Stop( aout_stream_t *stream )
 {
-    /* This function is only used for Android API 23 when AudioTrack is
-     * configured with ENCODING_ AC3/E_AC3/DTS. In that case, only the codec
-     * data is needed (without the IEC61937 encapsulation). This function
-     * recovers the codec data from an EC61937 frame. It is the opposite of the
-     * code found in converter/tospdif.c. We could also request VLC core to
-     * send us the codec data directly, but in that case, we wouldn't benefit
-     * from the eac3 block merger of tospdif.c. */
+    aout_sys_t *p_sys = stream->sys;
+    JNIEnv *env;
 
-    VLC_UNUSED( stream );
-    uint8_t i_length_mul;
+    if( !( env = GET_ENV() ) )
+        return;
 
-    if( p_buffer->i_buffer < 6 )
-        return -1;
+    /* Stop the AudioTrack thread */
+    vlc_mutex_lock( &p_sys->lock );
+    if( p_sys->b_thread_running )
+    {
+        p_sys->b_thread_running = false;
+        vlc_cond_signal( &p_sys->thread_cond );
+        vlc_mutex_unlock( &p_sys->lock );
+        vlc_join( p_sys->thread, NULL );
+    }
+    else
+        vlc_mutex_unlock(  &p_sys->lock );
 
-    switch( GetWBE( &p_buffer->p_buffer[4] ) & 0xFF )
+    /* Release the AudioTrack object */
+    if( p_sys->p_audiotrack )
     {
-        case 0x01: /* IEC61937_AC3 */
-            i_length_mul = 8;
-            break;
-        case 0x15: /* IEC61937_EAC3 */
-            i_length_mul = 1;
-            break;
-        case 0x0B: /* IEC61937_DTS1 */
-        case 0x0C: /* IEC61937_DTS2 */
-        case 0x0D: /* IEC61937_DTS3 */
-            i_length_mul = 8;
-            break;
-        case 0x11: /* IEC61937_DTSHD */
-            i_length_mul = 1;
-            break;
-        default:
-            vlc_assert_unreachable();
+        if( !p_sys->b_audiotrack_exception )
+        {
+            JNI_AT_CALL_VOID( stop );
+            if( !CHECK_AT_EXCEPTION( "stop" ) )
+                JNI_AT_CALL_VOID( release );
+        }
+        (*env)->DeleteGlobalRef( env, p_sys->p_audiotrack );
     }
-    uint16_t i_length = GetWBE( &p_buffer->p_buffer[6] );
-    if( i_length == 0 )
-        return -1;
 
-    i_length /= i_length_mul;
-    if( i_length > p_buffer->i_buffer - 8 )
-        return -1;
+    if( p_sys->p_dp != NULL )
+        DynamicsProcessing_Delete( stream, p_sys->p_dp );
 
-    p_buffer->p_buffer += 8; /* SPDIF_HEADER_SIZE */
-    p_buffer->i_buffer = i_length;
+    /* Release the timestamp object */
+    if( p_sys->timestamp.p_obj )
+        (*env)->DeleteGlobalRef( env, p_sys->timestamp.p_obj );
 
-    return 0;
+    free( p_sys );
 }
 
-static void
-Play( aout_stream_t *stream, block_t *p_buffer, vlc_tick_t i_date )
+static int
+Start( aout_stream_t *stream, audio_sample_format_t *restrict p_fmt,
+       enum android_audio_device_type adev )
 {
-    JNIEnv *env = NULL;
-    aout_sys_t *p_sys = stream->sys;
+    JNIEnv *env;
+    int i_ret;
+    bool b_try_passthrough;
+    unsigned i_max_channels;
 
-    if( p_sys->b_passthrough && p_sys->fmt.i_format == VLC_CODEC_SPDIFB
-     && ConvertFromIEC61937( stream, p_buffer ) != 0 )
+    if( adev == ANDROID_AUDIO_DEVICE_ENCODED )
     {
-        block_Release(p_buffer);
-        return;
+        b_try_passthrough = true;
+        i_max_channels = ANDROID_AUDIO_DEVICE_MAX_CHANNELS;
     }
-
-    vlc_mutex_lock( &p_sys->lock );
-
-    if( p_sys->b_error || !( env = GET_ENV() ) )
+    else
     {
-        block_Release( p_buffer );
-        goto bailout;
+        b_try_passthrough = false;
+        i_max_channels = adev == ANDROID_AUDIO_DEVICE_STEREO ? 2 : ANDROID_AUDIO_DEVICE_MAX_CHANNELS;
     }
 
-    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 );
+    if( !( env = GET_ENV() ) )
+        return VLC_EGENERIC;
 
-    vlc_frame_ChainLastAppend(&p_sys->frame_last, p_buffer);
-    vlc_cond_signal(&p_sys->thread_cond);
+    AudioTrack_InitJNI(VLC_OBJECT(stream));
 
-bailout:
-    vlc_mutex_unlock( &p_sys->lock );
-    (void) i_date;
-}
+    aout_sys_t *p_sys = stream->sys = calloc( 1, sizeof (aout_sys_t) );
 
-static void
-Pause( aout_stream_t *stream, bool b_pause, vlc_tick_t i_date )
-{
-    aout_sys_t *p_sys = stream->sys;
-    JNIEnv *env;
-    VLC_UNUSED( i_date );
+    if( unlikely( p_sys == NULL ) )
+        return VLC_ENOMEM;
 
-    vlc_mutex_lock( &p_sys->lock );
+    vlc_mutex_init(&p_sys->lock);
+    vlc_cond_init(&p_sys->thread_cond);
 
-    if( p_sys->b_error || !( env = GET_ENV() ) )
-        goto bailout;
+    p_sys->volume = 1.0f;
+    p_sys->mute = false;
 
-    if( b_pause )
-    {
-        p_sys->b_thread_paused = true;
-        JNI_AT_CALL_VOID( pause );
-        CHECK_AT_EXCEPTION( "pause" );
-    } else
-    {
-        p_sys->b_thread_paused = false;
-        AudioTrack_ResetPositions( env, stream );
-        JNI_AT_CALL_VOID( play );
-        CHECK_AT_EXCEPTION( "play" );
-    }
+    p_sys->fmt = *p_fmt;
 
-bailout:
-    vlc_mutex_unlock( &p_sys->lock );
-}
+    aout_FormatPrint( stream, "VLC is looking for:", &p_sys->fmt );
 
-static void
-Flush( aout_stream_t *stream )
-{
-    aout_sys_t *p_sys = stream->sys;
-    JNIEnv *env;
+    if (p_sys->fmt.channel_type == AUDIO_CHANNEL_TYPE_AMBISONICS)
+    {
+        p_sys->fmt.channel_type = AUDIO_CHANNEL_TYPE_BITMAP;
 
-    vlc_mutex_lock( &p_sys->lock );
+        /* TODO: detect sink channel layout */
+        p_sys->fmt.i_physical_channels = AOUT_CHANS_STEREO;
+        aout_FormatPrepare(&p_sys->fmt);
+    }
 
-    p_sys->jbuffer.size = p_sys->jbuffer.offset = 0;
-    vlc_frame_ChainRelease(p_sys->frame_chain);
-    p_sys->frame_chain = NULL;
-    p_sys->frame_last = &p_sys->frame_chain;
+    if( AOUT_FMT_LINEAR( &p_sys->fmt ) )
+        i_ret = StartPCM( env, stream, i_max_channels );
+    else if( b_try_passthrough )
+        i_ret = StartPassthrough( env, stream );
+    else
+        i_ret = VLC_EGENERIC;
 
-    if( p_sys->b_error || !( env = GET_ENV() ) )
-        goto bailout;
+    if( i_ret != 0 )
+    {
+        free( p_sys );
+        return VLC_EGENERIC;
+    }
 
-    /* Android doc:
-     * stop(): Stops playing the audio data. When used on an instance created
-     * in MODE_STREAM mode, audio will stop playing after the last buffer that
-     * was written has been played. For an immediate stop, use pause(),
-     * followed by flush() to discard audio data that hasn't been played back
-     * yet.
-     *
-     * flush(): Flushes the audio data currently queued for playback. Any data
-     * that has not been played back will be discarded.  No-op if not stopped
-     * or paused, or if the track's creation mode is not MODE_STREAM.
-     */
-    JNI_AT_CALL_VOID( pause );
-    if( CHECK_AT_EXCEPTION( "pause" ) )
-        goto bailout;
-    JNI_AT_CALL_VOID( flush );
+    if( jfields.AudioTrack.getBufferSizeInFrames )
+        p_sys->i_max_audiotrack_samples = JNI_AT_CALL_INT( getBufferSizeInFrames );
+    else
+        p_sys->i_max_audiotrack_samples = BYTES_TO_FRAMES( p_sys->audiotrack_args.i_size );
 
-    /* HACK: Before Android 4.4, the head position is not reset to zero and is
-     * still moving after a flush or a stop. This prevents to get a precise
-     * head position and there is no way to know when it stabilizes. Therefore
-     * recreate an AudioTrack object in that case. The AudioTimestamp class was
-     * added in API Level 19, so if this class is not found, the Android
-     * Version is 4.3 or before */
-    if( !jfields.AudioTimestamp.clazz && p_sys->i_samples_written > 0 )
+    if( jfields.AudioTimestamp.clazz )
     {
-        if( AudioTrack_Recreate( env, stream ) != 0 )
+        /* create AudioTimestamp object */
+        jobject p_obj = JNI_CALL( NewObject, jfields.AudioTimestamp.clazz,
+                                 jfields.AudioTimestamp.ctor );
+        if( p_obj )
         {
-            p_sys->b_error = true;
-            goto bailout;
+            p_sys->timestamp.p_obj = (*env)->NewGlobalRef( env, p_obj );
+            (*env)->DeleteLocalRef( env, p_obj );
         }
+        if( !p_sys->timestamp.p_obj )
+            goto error;
     }
-    AudioTrack_Reset( env, stream );
-    JNI_AT_CALL_VOID( play );
-    CHECK_AT_EXCEPTION( "play" );
-
-bailout:
-    vlc_mutex_unlock( &p_sys->lock );
-}
 
-static void
-AudioTrack_SetVolume( JNIEnv *env, aout_stream_t *stream, float volume )
-{
-    aout_sys_t *p_sys = stream->sys;
+    p_sys->jbuffer.array = NULL;
+    p_sys->jbuffer.size = p_sys->jbuffer.offset = 0;
+    p_sys->jbuffer.maxsize = 0;
+    p_sys->frame_chain = NULL;
+    p_sys->frame_last = &p_sys->frame_chain;
+    AudioTrack_Reset( env, stream );
 
-    if( jfields.AudioTrack.setVolume )
+    if( p_sys->fmt.i_format == VLC_CODEC_FL32 )
     {
-        JNI_AT_CALL_INT( setVolume, volume );
-        CHECK_AT_EXCEPTION( "setVolume" );
-    } else
+        msg_Dbg( stream, "using WRITE_FLOATARRAY");
+        p_sys->i_write_type = WRITE_FLOATARRAY;
+    }
+    else if( p_sys->fmt.i_format == VLC_CODEC_SPDIFL )
     {
-        JNI_AT_CALL_INT( setStereoVolume, volume, volume );
-        CHECK_AT_EXCEPTION( "setStereoVolume" );
+        assert( jfields.AudioFormat.has_ENCODING_IEC61937 );
+        msg_Dbg( stream, "using WRITE_SHORTARRAYV23");
+        p_sys->i_write_type = WRITE_SHORTARRAYV23;
     }
-}
-
-static void
-VolumeSet( aout_stream_t *stream, float volume )
-{
-    aout_sys_t *p_sys = stream->sys;
-    JNIEnv *env;
-    float gain = 1.0f;
-
-    p_sys->volume = volume;
-    if (volume > 1.f)
+    else if( jfields.AudioTrack.writeV23 )
     {
-        gain = volume * volume * volume;
-        volume = 1.f;
+        msg_Dbg( stream, "using WRITE_BYTEARRAYV23");
+        p_sys->i_write_type = WRITE_BYTEARRAYV23;
+    }
+    else if( jfields.AudioTrack.writeBufferV21 )
+    {
+        msg_Dbg( stream, "using WRITE_BYTEBUFFER");
+        p_sys->i_write_type = WRITE_BYTEBUFFER;
+    }
+    else
+    {
+        msg_Dbg( stream, "using WRITE_BYTEARRAY");
+        p_sys->i_write_type = WRITE_BYTEARRAY;
     }
 
-    if( !p_sys->b_error && p_sys->p_audiotrack != NULL && ( env = GET_ENV() ) )
+    /* Run AudioTrack_Thread */
+    p_sys->b_thread_running = true;
+    p_sys->b_thread_paused = false;
+    if ( vlc_clone( &p_sys->thread, AudioTrack_Thread, stream ) )
     {
-        AudioTrack_SetVolume( env, stream, volume );
+        msg_Err(stream, "vlc clone failed");
+        goto error;
+    }
 
-        /* Apply gain > 1.0f via DynamicsProcessing if possible */
-        if( p_sys->p_dp != NULL )
-        {
-            if( gain <= 1.0f )
-            {
-                /* DynamicsProcessing is not needed anymore (using AudioTrack
-                 * volume) */
-                DynamicsProcessing_Disable( stream, p_sys->p_dp );
-            }
-            else
-            {
-                int ret = DynamicsProcessing_SetVolume( stream, p_sys->p_dp, gain );
+    JNI_AT_CALL_VOID( play );
+    CHECK_AT_EXCEPTION( "play" );
 
-                if( ret == VLC_SUCCESS )
-                    gain = 1.0; /* reset sw gain */
-                else
-                    msg_Warn( stream, "failed to set gain via DynamicsProcessing, fallback to sw gain");
-            }
-        }
-    }
+    *p_fmt = p_sys->fmt;
 
-    aout_stream_GainRequest(stream, gain);
-}
+    aout_FormatPrint( stream, "VLC will output:", &p_sys->fmt );
 
-static void
-MuteSet( aout_stream_t *stream, bool mute )
-{
-    aout_sys_t *p_sys = stream->sys;
-    JNIEnv *env;
-    p_sys->mute = mute;
+    stream->stop = Stop;
+    stream->play = Play;
+    stream->pause = Pause;
+    stream->flush = Flush;
+    stream->time_get = NULL;
+    stream->volume_set = VolumeSet;
+    stream->mute_set = MuteSet;
 
-    if( !p_sys->b_error && p_sys->p_audiotrack != NULL && ( env = GET_ENV() ) )
-        AudioTrack_SetVolume( env, stream, mute ? 0.0f : p_sys->volume );
+    msg_Dbg(stream, "using AudioTrack API");
+    return VLC_SUCCESS;
+
+error:
+    Stop( stream );
+    return VLC_EGENERIC;
 }
 
 vlc_module_begin ()



View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/cb8d5930131fac647a08b035352b628edcda8d85...e476328eb6fb6acd93cb0791f3d0f807a85c9254

-- 
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/cb8d5930131fac647a08b035352b628edcda8d85...e476328eb6fb6acd93cb0791f3d0f807a85c9254
You're receiving this email because of your account on code.videolan.org.


VideoLAN code repository instance


More information about the vlc-commits mailing list