]> git.sesse.net Git - vlc/blobdiff - modules/audio_output/audiotrack.c
audiotrack: finish float support
[vlc] / modules / audio_output / audiotrack.c
index 10a65fa5db0dba5d50a1742d0eb9ad0af98d69fe..ab398c9326d53d6ae7b721be304515b8d9fe5295 100644 (file)
@@ -43,6 +43,7 @@
 
 static int  Open( vlc_object_t * );
 static void Close( vlc_object_t * );
+static void JNIThread_Stop( JNIEnv *env, audio_output_t *p_aout );
 
 struct thread_cmd;
 typedef TAILQ_HEAD(, thread_cmd) THREAD_CMD_QUEUE;
@@ -55,12 +56,26 @@ struct aout_sys_t {
     /* Owned by JNIThread */
     jobject p_audiotrack; /* AudioTrack ref */
     jobject p_audioTimestamp; /* AudioTimestamp ref */
-    jbyteArray p_bytearray; /* ByteArray ref */
+    jbyteArray p_bytearray; /* ByteArray ref (for Write) */
     size_t i_bytearray_size; /* size of the ByteArray */
+    jfloatArray p_floatarray; /* FloatArray ref (for WriteFloat) */
+    size_t i_floatarray_size; /* size of the FloatArray */
+    jobject p_bytebuffer; /* ByteBuffer ref (for WriteV21) */
     audio_sample_format_t fmt; /* fmt setup by Start */
     uint32_t i_pos_initial; /* initial position set by getPlaybackHeadPosition */
     uint32_t i_samples_written; /* number of samples written since last flush */
+    uint32_t i_bytes_per_frame; /* byte per frame */
+    uint32_t i_max_audiotrack_samples;
     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];
+    enum {
+        WRITE,
+        WRITE_V21,
+        WRITE_FLOAT
+    } i_write_type;
 
     /* JNIThread control */
     vlc_mutex_t mutex;
@@ -71,13 +86,14 @@ struct aout_sys_t {
     bool b_thread_run; /* is thread alive */
     THREAD_CMD_QUEUE thread_cmd_queue; /* thread cmd queue */
     uint32_t i_samples_queued; /* number of samples queued */
-    uint32_t i_audiotrack_delay; /* audiotrack delay in samples */
 };
 
 /* Soft volume helper */
 #include "audio_output/volume.h"
 
-//#define AUDIOTRACK_USE_FLOAT
+#define AUDIOTRACK_USE_FLOAT
+// TODO: activate getTimestamp for new android versions
+//#define AUDIOTRACK_USE_TIMESTAMP
 
 vlc_module_begin ()
     set_shortname( "AudioTrack" )
@@ -98,6 +114,7 @@ struct thread_cmd
         CMD_STOP,
         CMD_PLAY,
         CMD_PAUSE,
+        CMD_TIME_GET,
         CMD_FLUSH,
         CMD_DONE,
     } id;
@@ -121,6 +138,10 @@ struct thread_cmd
             int i_ret;
             audio_sample_format_t *p_fmt;
         } start;
+        struct {
+            int i_ret;
+            mtime_t i_delay;
+        } time_get;
     } out;
     void ( *pf_destroy )( struct thread_cmd * );
 };
@@ -137,18 +158,23 @@ static struct
         jclass clazz;
         jmethodID ctor;
         jmethodID release;
+        jmethodID getState;
         jmethodID play;
         jmethodID stop;
         jmethodID flush;
         jmethodID pause;
         jmethodID write;
+        jmethodID writeV21;
+        jmethodID writeFloat;
         jmethodID getPlaybackHeadPosition;
         jmethodID getTimestamp;
         jmethodID getMinBufferSize;
+        jint STATE_INITIALIZED;
         jint MODE_STREAM;
         jint ERROR;
         jint ERROR_BAD_VALUE;
         jint ERROR_INVALID_OPERATION;
+        jint WRITE_NON_BLOCKING;
     } AudioTrack;
     struct {
         jint ENCODING_PCM_8BIT;
@@ -157,6 +183,17 @@ static struct
         bool has_ENCODING_PCM_FLOAT;
         jint CHANNEL_OUT_MONO;
         jint CHANNEL_OUT_STEREO;
+        jint CHANNEL_OUT_FRONT_LEFT;
+        jint CHANNEL_OUT_FRONT_RIGHT;
+        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;
@@ -237,11 +274,21 @@ InitJNIFields( audio_output_t *p_aout )
 
     GET_ID( GetMethodID, AudioTrack.ctor, "<init>", "(IIIIII)V", true );
     GET_ID( GetMethodID, AudioTrack.release, "release", "()V", true );
+    GET_ID( GetMethodID, AudioTrack.getState, "getState", "()I", true );
     GET_ID( GetMethodID, AudioTrack.play, "play", "()V", true );
     GET_ID( GetMethodID, AudioTrack.stop, "stop", "()V", true );
     GET_ID( GetMethodID, AudioTrack.flush, "flush", "()V", true );
     GET_ID( GetMethodID, AudioTrack.pause, "pause", "()V", true );
-    GET_ID( GetMethodID, AudioTrack.write, "write", "([BII)I", true );
+
+    GET_ID( GetMethodID, AudioTrack.writeV21, "write", "(Ljava/nio/ByteBuffer;II)I", false );
+    if( jfields.AudioTrack.writeV21 )
+    {
+        GET_CONST_INT( AudioTrack.WRITE_NON_BLOCKING, "WRITE_NON_BLOCKING", true );
+#ifdef AUDIOTRACK_USE_FLOAT
+        GET_ID( GetMethodID, AudioTrack.writeFloat, "write", "([FIII)I", true );
+#endif
+    } else
+        GET_ID( GetMethodID, AudioTrack.write, "write", "([BII)I", true );
 
     GET_ID( GetMethodID, AudioTrack.getTimestamp,
             "getTimestamp", "(Landroid/media/AudioTimestamp;)Z", false );
@@ -250,10 +297,11 @@ InitJNIFields( audio_output_t *p_aout )
 
     GET_ID( GetStaticMethodID, AudioTrack.getMinBufferSize, "getMinBufferSize",
             "(III)I", true );
+    GET_CONST_INT( AudioTrack.STATE_INITIALIZED, "STATE_INITIALIZED", true );
     GET_CONST_INT( AudioTrack.MODE_STREAM, "MODE_STREAM", true );
     GET_CONST_INT( AudioTrack.ERROR, "ERROR", true );
     GET_CONST_INT( AudioTrack.ERROR_BAD_VALUE , "ERROR_BAD_VALUE", true );
-    GET_CONST_INT( AudioTrack.ERROR_INVALID_OPERATION ,
+    GET_CONST_INT( AudioTrack.ERROR_INVALID_OPERATION,
                    "ERROR_INVALID_OPERATION", true );
 
     /* AudioTimestamp class init (if any) */
@@ -269,12 +317,6 @@ InitJNIFields( audio_output_t *p_aout )
                 "framePosition", "J", true );
         GET_ID( GetFieldID, AudioTimestamp.nanoTime,
                 "nanoTime", "J", true );
-    } else
-    {
-        jfields.AudioTimestamp.clazz = NULL;
-        jfields.AudioTimestamp.ctor = NULL;
-        jfields.AudioTimestamp.framePosition = NULL;
-        jfields.AudioTimestamp.nanoTime = NULL;
     }
 
     /* AudioFormat class init */
@@ -284,12 +326,28 @@ InitJNIFields( audio_output_t *p_aout )
 #ifdef AUDIOTRACK_USE_FLOAT
     GET_CONST_INT( AudioFormat.ENCODING_PCM_FLOAT, "ENCODING_PCM_FLOAT",
                    false );
-    jfields.AudioFormat.has_ENCODING_PCM_FLOAT = field != NULL;
+    jfields.AudioFormat.has_ENCODING_PCM_FLOAT = field != NULL &&
+                                                 jfields.AudioTrack.writeFloat;
 #else
     jfields.AudioFormat.has_ENCODING_PCM_FLOAT = false;
 #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_FRONT_LEFT, "CHANNEL_OUT_FRONT_LEFT", true );
+    GET_CONST_INT( AudioFormat.CHANNEL_OUT_FRONT_RIGHT, "CHANNEL_OUT_FRONT_RIGHT", 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 );
@@ -314,19 +372,21 @@ end:
 }
 
 static inline bool
-check_exception( JNIEnv *env, bool *p_error, audio_output_t *p_aout,
+check_exception( JNIEnv *env, audio_output_t *p_aout,
                  const char *method )
 {
     if( (*env)->ExceptionOccurred( env ) )
     {
+        aout_sys_t *p_sys = p_aout->sys;
+
+        p_sys->b_audiotrack_exception = true;
         (*env)->ExceptionClear( env );
-        *p_error = true;
         msg_Err( p_aout, "AudioTrack.%s triggered an exception !", method );
         return true;
     } else
         return false;
 }
-#define CHECK_EXCEPTION( method ) check_exception( env, p_error, p_aout, method )
+#define CHECK_AT_EXCEPTION( method ) check_exception( env, p_aout, method )
 
 #define JNI_CALL( what, obj, method, ... ) (*env)->what( env, obj, method, ##__VA_ARGS__ )
 
@@ -378,7 +438,7 @@ ThreadCmd_InsertTail( aout_sys_t *p_sys, struct thread_cmd *p_cmd )
 static bool
 ThreadCmd_Wait( aout_sys_t *p_sys, struct thread_cmd *p_cmd )
 {
-    while( p_cmd->id != CMD_DONE && p_sys->b_thread_run  )
+    while( p_cmd->id != CMD_DONE )
         vlc_cond_wait( &p_sys->cond, &p_sys->mutex );
 
     return p_cmd->id == CMD_DONE;
@@ -400,11 +460,14 @@ ThreadCmd_FlushQueue( aout_sys_t *p_sys )
 }
 
 static void
-JNIThread_InitDelay( JNIEnv *env, audio_output_t *p_aout, uint32_t *p_delay )
+JNIThread_InitDelay( JNIEnv *env, audio_output_t *p_aout )
 {
     aout_sys_t *p_sys = p_aout->sys;
 
-    p_sys->i_pos_initial = JNI_AT_CALL_INT( getPlaybackHeadPosition );
+    if( p_sys->p_audiotrack )
+        p_sys->i_pos_initial = JNI_AT_CALL_INT( getPlaybackHeadPosition );
+    else
+        p_sys->i_pos_initial = 0;
 
     /* HACK: On some broken devices, head position is still moving after a
      * flush or a stop. So, wait for the head position to be stabilized. */
@@ -418,19 +481,39 @@ JNIThread_InitDelay( JNIEnv *env, audio_output_t *p_aout, uint32_t *p_delay )
         } while( p_sys->i_pos_initial != i_last_pos );
     }
     p_sys->i_samples_written = 0;
-    *p_delay = 0;
+    p_sys->i_samples_queued = 0;
 }
 
-static void
-JNIThread_SetDelay( JNIEnv *env, audio_output_t *p_aout, uint32_t *p_delay )
+static uint32_t
+JNIThread_GetAudioTrackPos( JNIEnv *env, audio_output_t *p_aout )
+{
+    aout_sys_t *p_sys = p_aout->sys;
+
+    /* Android doc:
+     * getPlaybackHeadPosition: Returns the playback head position expressed in
+     * frames. Though the "int" type is signed 32-bits, the value should be
+     * reinterpreted as if it is unsigned 32-bits. That is, the next position
+     * after 0x7FFFFFFF is (int) 0x80000000. This is a continuously advancing
+     * counter. It will wrap (overflow) periodically, for example approximately
+     * once every 27:03:11 hours:minutes:seconds at 44.1 kHz. It is reset to
+     * zero by flush(), reload(), and stop().
+     */
+
+    return JNI_AT_CALL_INT( getPlaybackHeadPosition ) - p_sys->i_pos_initial;
+}
+
+static int
+JNIThread_TimeGet( JNIEnv *env, audio_output_t *p_aout, mtime_t *p_delay )
 {
     aout_sys_t *p_sys = p_aout->sys;
-    bool b_frame_delay_set = false;
     jlong i_frame_pos;
-    mtime_t i_current_time = mdate();
+    uint32_t i_audiotrack_delay = 0;
 
+    if( p_sys->i_samples_queued == 0 )
+        return -1;
     if( p_sys->p_audioTimestamp )
     {
+        mtime_t i_current_time = mdate();
         /* Android doc:
          * getTimestamp: Poll for a timestamp on demand.
          *
@@ -462,85 +545,110 @@ JNIThread_SetDelay( JNIEnv *env, audio_output_t *p_aout, uint32_t *p_delay )
                                       / CLOCK_FREQ;
                 i_frame_pos = JNI_AUDIOTIMESTAMP_GET_LONG( framePosition )
                               + i_frames_diff;
-                b_frame_delay_set = true;
+                if( p_sys->i_samples_written > i_frame_pos )
+                    i_audiotrack_delay =  p_sys->i_samples_written - i_frame_pos;
             }
         }
     }
-    if( !b_frame_delay_set )
+    if( i_audiotrack_delay == 0 )
     {
-        /* Android doc:
-         * getPlaybackHeadPosition: Returns the playback head position
-         * expressed in frames. Though the "int" type is signed 32-bits, the
-         * value should be reinterpreted as if it is unsigned 32-bits. That is,
-         * the next position after 0x7FFFFFFF is (int) 0x80000000. This is a
-         * continuously advancing counter. It will wrap (overflow)
-         * periodically, for example approximately once every 27:03:11
-         * hours:minutes:seconds at 44.1 kHz. It is reset to zero by flush(),
-         * reload(), and stop().
-         */
+        uint32_t i_audiotrack_pos = JNIThread_GetAudioTrackPos( env, p_aout );
 
-        uint32_t i_head_pos = JNI_AT_CALL_INT( getPlaybackHeadPosition );
-        i_frame_pos = i_head_pos - p_sys->i_pos_initial;
-        b_frame_delay_set = true;
+        if( p_sys->i_samples_written > i_audiotrack_pos )
+            i_audiotrack_delay = p_sys->i_samples_written - i_audiotrack_pos;
     }
 
-    if( b_frame_delay_set && p_sys->i_samples_written > i_frame_pos )
-        *p_delay = p_sys->i_samples_written - i_frame_pos;
+    if( i_audiotrack_delay > 0 )
+    {
+        *p_delay = FRAMES_TO_US( p_sys->i_samples_queued + i_audiotrack_delay );
+        return 0;
+    } else
+        return -1;
 }
 
-static int
-JNIThread_Start( JNIEnv *env, bool *p_error, audio_output_t *p_aout )
+static void
+AudioTrack_GetChanOrder( uint16_t i_physical_channels, uint32_t p_chans_out[] )
+{
+#define HAS_CHAN( x ) ( ( i_physical_channels & (x) ) == (x) )
+    /* samples will be in the following order: FL FR FC LFE BL BR BC SL SR */
+    int i = 0;
+
+    if( HAS_CHAN( AOUT_CHAN_LEFT ) )
+        p_chans_out[i++] = AOUT_CHAN_LEFT;
+    if( HAS_CHAN( AOUT_CHAN_RIGHT ) )
+        p_chans_out[i++] = AOUT_CHAN_RIGHT;
+
+    if( HAS_CHAN( AOUT_CHAN_CENTER ) )
+        p_chans_out[i++] = AOUT_CHAN_CENTER;
+
+    if( HAS_CHAN( AOUT_CHAN_LFE ) )
+        p_chans_out[i++] = AOUT_CHAN_LFE;
+
+    if( HAS_CHAN( AOUT_CHAN_REARLEFT ) )
+        p_chans_out[i++] = AOUT_CHAN_REARLEFT;
+    if( HAS_CHAN( AOUT_CHAN_REARRIGHT ) )
+        p_chans_out[i++] = AOUT_CHAN_REARRIGHT;
+
+    if( HAS_CHAN( AOUT_CHAN_REARCENTER ) )
+        p_chans_out[i++] = AOUT_CHAN_REARCENTER;
+
+    if( HAS_CHAN( AOUT_CHAN_MIDDLELEFT ) )
+        p_chans_out[i++] = AOUT_CHAN_MIDDLELEFT;
+    if( HAS_CHAN( AOUT_CHAN_MIDDLERIGHT ) )
+        p_chans_out[i++] = AOUT_CHAN_MIDDLERIGHT;
+
+    assert( i <= AOUT_CHAN_MAX );
+#undef HAS_CHAN
+}
+
+/**
+ * Configure and create an Android AudioTrack.
+ * returns NULL on configuration error
+ */
+static jobject
+JNIThread_NewAudioTrack( JNIEnv *env, audio_output_t *p_aout,
+                         unsigned int i_rate,
+                         vlc_fourcc_t i_vlc_format,
+                         uint16_t i_physical_channels,
+                         int *p_audiotrack_size )
 {
-    struct aout_sys_t *p_sys = p_aout->sys;
-    int i_size, i_min_buffer_size, i_channel_config, i_rate, i_format,
-        i_format_size, i_nb_channels;
+    int i_size, i_min_buffer_size, i_channel_config, i_format;
     jobject p_audiotrack;
 
-    /* 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 )
-    {
-        i_format = jfields.AudioFormat.ENCODING_PCM_FLOAT;
-        i_format_size = 4;
-    } else
+    switch( i_vlc_format )
     {
-        i_format = jfields.AudioFormat.ENCODING_PCM_8BIT;
-        i_format_size = 1;
+        case VLC_CODEC_U8:
+            i_format = jfields.AudioFormat.ENCODING_PCM_8BIT;
+            break;
+        case VLC_CODEC_S16N:
+            i_format = jfields.AudioFormat.ENCODING_PCM_16BIT;
+            break;
+        case VLC_CODEC_FL32:
+            i_format = jfields.AudioFormat.ENCODING_PCM_FLOAT;
+            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 )
+    switch( i_physical_channels )
     {
-    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;
+        case AOUT_CHANS_7_1:
+            /* bitmask 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;
+            break;
+        case AOUT_CHANS_5_1:
+            i_channel_config = jfields.AudioFormat.CHANNEL_OUT_5POINT1;
+            break;
+        case AOUT_CHAN_LEFT:
+            i_channel_config = jfields.AudioFormat.CHANNEL_OUT_MONO;
+            break;
+        default:
+        case AOUT_CHANS_STEREO:
+            i_channel_config = jfields.AudioFormat.CHANNEL_OUT_STEREO;
+            break;
     }
 
     i_min_buffer_size = JNI_AT_CALL_STATIC_INT( getMinBufferSize, i_rate,
@@ -548,65 +656,196 @@ JNIThread_Start( JNIEnv *env, bool *p_error, audio_output_t *p_aout )
     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 * 2024;
+        return NULL;
     }
-
-    i_size = i_min_buffer_size * 2; // double buffering
+    i_size = i_min_buffer_size * 4;
 
     /* create AudioTrack object */
     p_audiotrack = JNI_AT_NEW( jfields.AudioManager.STREAM_MUSIC, i_rate,
                                i_channel_config, i_format, i_size,
                                jfields.AudioTrack.MODE_STREAM );
-    if( CHECK_EXCEPTION( "AudioTrack<init>" ) || !p_audiotrack )
+    if( CHECK_AT_EXCEPTION( "AudioTrack<init>" ) || !p_audiotrack )
+    {
+        msg_Warn( p_aout, "AudioTrack Init failed" ) ;
+        return NULL;
+    }
+    if( JNI_CALL_INT( p_audiotrack, jfields.AudioTrack.getState )
+        != jfields.AudioTrack.STATE_INITIALIZED )
+    {
+        JNI_CALL_VOID( p_audiotrack, jfields.AudioTrack.release );
+        (*env)->DeleteLocalRef( env, p_audiotrack );
+        msg_Err( p_aout, "AudioTrack getState failed" );
+        return NULL;
+    }
+    *p_audiotrack_size = i_size;
+
+    return p_audiotrack;
+}
+
+static int
+JNIThread_Start( JNIEnv *env, audio_output_t *p_aout )
+{
+    aout_sys_t *p_sys = p_aout->sys;
+    jobject p_audiotrack = NULL;
+    int i_nb_channels, i_audiotrack_size;
+    uint32_t p_chans_out[AOUT_CHAN_MAX];
+
+    aout_FormatPrint( p_aout, "VLC is looking for:", &p_sys->fmt );
+
+    p_sys->fmt.i_original_channels = p_sys->fmt.i_physical_channels;
+
+    /* 4000 <= frequency <= 48000 */
+    p_sys->fmt.i_rate = VLC_CLIP( p_sys->fmt.i_rate, 4000, 48000 );
+
+    /* We can only accept U8, S16N, FL32, and AC3 */
+    switch( p_sys->fmt.i_format )
+    {
+        case VLC_CODEC_U8:
+            break;
+        case VLC_CODEC_S16N:
+            break;
+        case VLC_CODEC_FL32:
+            if( !jfields.AudioFormat.has_ENCODING_PCM_FLOAT )
+                p_sys->fmt.i_format = VLC_CODEC_S16N;
+            break;
+        default:
+            p_sys->fmt.i_format = VLC_CODEC_S16N;
+            break;
+    }
+
+    /* 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
+     */
+    i_nb_channels = aout_FormatNbChannels( &p_sys->fmt );
+    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_nb_channels = aout_FormatNbChannels( &p_sys->fmt );
+
+    do
+    {
+        /* Try to create an AudioTrack with the most advanced channel and
+         * format configuration. If NewAudioTrack fails, try again with a less
+         * advanced format (PCM S16N). If it fails again, try again with Stereo
+         * channels. */
+        p_audiotrack = JNIThread_NewAudioTrack( env, p_aout, p_sys->fmt.i_rate,
+                                                p_sys->fmt.i_format,
+                                                p_sys->fmt.i_physical_channels,
+                                                &i_audiotrack_size );
+        if( !p_audiotrack )
+        {
+            if( p_sys->fmt.i_format == VLC_CODEC_FL32 )
+            {
+                msg_Warn( p_aout, "FL32 configuration failed, "
+                                  "fallback to S16N PCM" );
+                p_sys->fmt.i_format = VLC_CODEC_S16N;
+            }
+            else if( i_nb_channels > 5 )
+            {
+                msg_Warn( p_aout, "5.1 or 7.1 configuration failed, "
+                                  "fallback to Stereo" );
+                p_sys->fmt.i_physical_channels = AOUT_CHANS_STEREO;
+                i_nb_channels = aout_FormatNbChannels( &p_sys->fmt );
+            }
+            else
+                break;
+        }
+    } while( !p_audiotrack );
+
+    if( !p_audiotrack )
         return VLC_EGENERIC;
+
     p_sys->p_audiotrack = (*env)->NewGlobalRef( env, p_audiotrack );
     (*env)->DeleteLocalRef( env, p_audiotrack );
     if( !p_sys->p_audiotrack )
         return VLC_EGENERIC;
 
+    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 );
+
+    p_sys->i_bytes_per_frame = i_nb_channels *
+                               aout_BitsPerSample( p_sys->fmt.i_format ) /
+                               8;
+    p_sys->i_max_audiotrack_samples = i_audiotrack_size /
+                                      p_sys->i_bytes_per_frame;
+
+#ifdef AUDIOTRACK_USE_TIMESTAMP
     if( jfields.AudioTimestamp.clazz )
     {
         /* create AudioTimestamp object */
         jobject p_audioTimestamp = JNI_CALL( NewObject,
                                              jfields.AudioTimestamp.clazz,
                                              jfields.AudioTimestamp.ctor );
-        if( CHECK_EXCEPTION( "AudioTimestamp<init>" ) || !p_audioTimestamp )
-            goto error;
+        if( !p_audioTimestamp )
+        {
+            JNIThread_Stop( env, p_aout );
+            return VLC_EGENERIC;
+        }
         p_sys->p_audioTimestamp = (*env)->NewGlobalRef( env, p_audioTimestamp );
         (*env)->DeleteLocalRef( env, p_audioTimestamp );
         if( !p_sys->p_audioTimestamp )
-            goto error;
+        {
+            JNIThread_Stop( env, p_aout );
+            return VLC_EGENERIC;
+        }
     }
+#endif
 
-    p_sys->fmt.i_rate = i_rate;
+    if( p_sys->fmt.i_format == VLC_CODEC_FL32 )
+    {
+        msg_Dbg( p_aout, "using WRITE_FLOAT");
+        p_sys->i_write_type = WRITE_FLOAT;
+    }
+    else if( jfields.AudioTrack.writeV21 )
+        msg_Dbg( p_aout, "using WRITE_V21");
+        p_sys->i_write_type = WRITE_V21;
+    }
+    else
+    {
+        msg_Dbg( p_aout, "using WRITE");
+        p_sys->i_write_type = WRITE;
+    }
 
     JNI_AT_CALL_VOID( play );
-    CHECK_EXCEPTION( "play" );
+    CHECK_AT_EXCEPTION( "play" );
     p_sys->i_play_time = mdate();
 
+    aout_FormatPrint( p_aout, "VLC will output:", &p_sys->fmt );
+
     return VLC_SUCCESS;
-error:
-    if( p_sys->p_audiotrack )
-    {
-        JNI_AT_CALL_VOID( release );
-        (*env)->DeleteGlobalRef( env, p_sys->p_audiotrack );
-        p_sys->p_audiotrack = NULL;
-    }
-    return VLC_EGENERIC;
 }
 
 static void
-JNIThread_Stop( JNIEnv *env, bool *p_error, audio_output_t *p_aout )
+JNIThread_Stop( JNIEnv *env, audio_output_t *p_aout )
 {
     aout_sys_t *p_sys = p_aout->sys;
 
-    JNI_AT_CALL_VOID( stop );
-    CHECK_EXCEPTION( "stop" );
-
-    JNI_AT_CALL_VOID( release );
-    (*env)->DeleteGlobalRef( env, p_sys->p_audiotrack );
-    p_sys->p_audiotrack = NULL;
+    if( p_sys->p_audiotrack )
+    {
+        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 );
+        p_sys->p_audiotrack = NULL;
+    }
+    p_sys->b_audiotrack_exception = false;
 
     if( p_sys->p_audioTimestamp )
     {
@@ -615,82 +854,278 @@ JNIThread_Stop( JNIEnv *env, bool *p_error, audio_output_t *p_aout )
     }
 }
 
-static void
-JNIThread_Play( JNIEnv *env, bool *p_error, audio_output_t *p_aout,
-                block_t *p_buffer )
+/**
+ * Non blocking write function.
+ * Do a calculation between current position and audiotrack position and assure
+ * that we won't wait in AudioTrack.write() method
+ */
+static int
+JNIThread_Write( JNIEnv *env, audio_output_t *p_aout, block_t *p_buffer,
+                 size_t i_buffer_offset )
 {
     aout_sys_t *p_sys = p_aout->sys;
-    int i_offset = 0;
+    size_t i_data;
+    uint32_t i_samples;
+    uint32_t i_audiotrack_pos;
+    uint32_t i_samples_pending;
+
+    i_data = p_buffer->i_buffer - i_buffer_offset;
+    i_audiotrack_pos = JNIThread_GetAudioTrackPos( env, p_aout );
+    if( i_audiotrack_pos > p_sys->i_samples_written )
+    {
+        msg_Warn( p_aout, "audiotrack position is ahead. Should NOT happen" );
+        JNIThread_InitDelay( env, p_aout );
+        return 0;
+    }
+    i_samples_pending = p_sys->i_samples_written - i_audiotrack_pos;
 
-    /* check if we need to realloc a ByteArray */
-    if( p_buffer->i_buffer > p_sys->i_bytearray_size )
+    /* check if audiotrack buffer is not full before writing on it. */
+    if( i_samples_pending >= p_sys->i_max_audiotrack_samples )
     {
-        jbyteArray p_bytearray;
 
-        if( p_sys->p_bytearray )
+        /* HACK: AudioFlinger can drop frames without notifying us and there is
+         * no way to know it. It 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 can happen only after
+         * Android 4.4.2 if we send frames too quickly. This HACK is just an
+         * other precaution since it shouldn't happen anymore thanks to the
+         * HACK in JNIThread_Play */
+
+        p_sys->i_audiotrack_stuck_count++;
+        if( p_sys->i_audiotrack_stuck_count > 100 )
         {
-            (*env)->DeleteGlobalRef( env, p_sys->p_bytearray );
-            p_sys->p_bytearray = NULL;
+            msg_Warn( p_aout, "AudioFlinger underrun, force write" );
+            i_samples_pending = 0;
+            p_sys->i_audiotrack_stuck_count = 0;
         }
+    } else
+        p_sys->i_audiotrack_stuck_count = 0;
+    i_samples = __MIN( p_sys->i_max_audiotrack_samples - i_samples_pending,
+                       BYTES_TO_FRAMES( i_data ) );
+
+    i_data = i_samples * p_sys->i_bytes_per_frame;
+
+    return JNI_AT_CALL_INT( write, p_sys->p_bytearray,
+                            i_buffer_offset, i_data );
+}
 
-        p_bytearray = (*env)->NewByteArray( env, p_buffer->i_buffer );
-        if( p_bytearray )
+/**
+ * Non blocking write function for Lollipop and after.
+ * It calls a new write method with WRITE_NON_BLOCKING flags.
+ */
+static int
+JNIThread_WriteV21( JNIEnv *env, audio_output_t *p_aout, block_t *p_buffer,
+                    size_t i_buffer_offset )
+{
+    aout_sys_t *p_sys = p_aout->sys;
+    int i_ret;
+    size_t i_data = p_buffer->i_buffer - i_buffer_offset;
+    uint8_t *p_data = p_buffer->p_buffer + i_buffer_offset;
+
+    if( !p_sys->p_bytebuffer )
+    {
+        jobject p_bytebuffer;
+
+        p_bytebuffer = (*env)->NewDirectByteBuffer( env, p_data, i_data );
+        if( !p_bytebuffer )
+            return jfields.AudioTrack.ERROR_BAD_VALUE;
+
+        p_sys->p_bytebuffer = (*env)->NewGlobalRef( env, p_bytebuffer );
+        (*env)->DeleteLocalRef( env, p_bytebuffer );
+
+        if( !p_sys->p_bytebuffer || (*env)->ExceptionOccurred( env ) )
         {
-            p_sys->p_bytearray = (*env)->NewGlobalRef( env, p_bytearray );
-            (*env)->DeleteLocalRef( env, p_bytearray );
+            p_sys->p_bytebuffer = NULL;
+            (*env)->ExceptionClear( env );
+            return jfields.AudioTrack.ERROR_BAD_VALUE;
         }
-        p_sys->i_bytearray_size = p_buffer->i_buffer;
     }
-    if( !p_sys->p_bytearray )
+
+    i_ret = JNI_AT_CALL_INT( writeV21, p_sys->p_bytebuffer, i_data,
+                             jfields.AudioTrack.WRITE_NON_BLOCKING );
+    if( i_ret > 0 )
     {
-        *p_error = true;
-        return;
+        /* don't delete the bytebuffer if we wrote nothing, keep it for next
+         * call */
+        (*env)->DeleteGlobalRef( env, p_sys->p_bytebuffer );
+        p_sys->p_bytebuffer = NULL;
     }
+    return i_ret;
+}
+
+/**
+ * Non blocking write float function for Lollipop and after.
+ * It calls a new write method with WRITE_NON_BLOCKING flags.
+ */
+static int
+JNIThread_WriteFloat( JNIEnv *env, audio_output_t *p_aout, block_t *p_buffer,
+                      size_t i_buffer_offset )
+{
+    aout_sys_t *p_sys = p_aout->sys;
+    int i_ret;
+    size_t i_data;
+
+    i_buffer_offset /= 4;
+    i_data = p_buffer->i_buffer / 4 - i_buffer_offset;
+
+    i_ret = JNI_AT_CALL_INT( writeFloat, p_sys->p_floatarray,
+                             i_buffer_offset, i_data,
+                             jfields.AudioTrack.WRITE_NON_BLOCKING );
+    if( i_ret < 0 )
+        return i_ret;
+    else
+        return i_ret * 4;
+}
+
+static int
+JNIThread_PreparePlay( JNIEnv *env, audio_output_t *p_aout,
+                       block_t *p_buffer )
+{
+    aout_sys_t *p_sys = p_aout->sys;
+
+    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 );
 
-    /* copy p_buffer in to ByteArray */
-    (*env)->SetByteArrayRegion( env, p_sys->p_bytearray, 0,
-                                p_buffer->i_buffer,
-                                (jbyte *)p_buffer->p_buffer);
+    switch( p_sys->i_write_type )
+    {
+    case WRITE:
+        /* check if we need to realloc a ByteArray */
+        if( p_buffer->i_buffer > p_sys->i_bytearray_size )
+        {
+            jbyteArray p_bytearray;
+
+            if( p_sys->p_bytearray )
+            {
+                (*env)->DeleteGlobalRef( env, p_sys->p_bytearray );
+                p_sys->p_bytearray = NULL;
+            }
 
-    while ( p_buffer->i_buffer > (unsigned int) i_offset )
+            p_bytearray = (*env)->NewByteArray( env, p_buffer->i_buffer );
+            if( p_bytearray )
+            {
+                p_sys->p_bytearray = (*env)->NewGlobalRef( env, p_bytearray );
+                (*env)->DeleteLocalRef( env, p_bytearray );
+            }
+            p_sys->i_bytearray_size = p_buffer->i_buffer;
+        }
+        if( !p_sys->p_bytearray )
+            return VLC_EGENERIC;
+
+        /* copy p_buffer in to ByteArray */
+        (*env)->SetByteArrayRegion( env, p_sys->p_bytearray, 0,
+                                    p_buffer->i_buffer,
+                                    (jbyte *)p_buffer->p_buffer);
+        break;
+    case WRITE_FLOAT:
     {
-        int i_ret;
-
-        /* write ByteArray */
-        i_ret = JNI_AT_CALL_INT( write, p_sys->p_bytearray, i_offset,
-                                 p_buffer->i_buffer - i_offset);
-        if( i_ret < 0 ) {
-            if( jfields.AudioManager.has_ERROR_DEAD_OBJECT
-                && i_ret == jfields.AudioManager.ERROR_DEAD_OBJECT )
+        size_t i_data = p_buffer->i_buffer / 4;
+
+        /* check if we need to realloc a floatArray */
+        if( i_data > p_sys->i_floatarray_size )
+        {
+            jfloatArray p_floatarray;
+
+            if( p_sys->p_floatarray )
             {
-                msg_Warn( p_aout, "ERROR_DEAD_OBJECT: "
-                                  "try recreating AudioTrack" );
-                JNIThread_Stop( env, p_error, p_aout );
-                i_ret = JNIThread_Start( env, p_error, p_aout );
-                if( i_ret == VLC_SUCCESS )
-                    continue;
-            } else
+                (*env)->DeleteGlobalRef( env, p_sys->p_floatarray );
+                p_sys->p_floatarray = NULL;
+            }
+
+            p_floatarray = (*env)->NewFloatArray( env, i_data );
+            if( p_floatarray )
             {
-                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( p_aout, "Write failed: %s", str );
+                p_sys->p_floatarray = (*env)->NewGlobalRef( env, p_floatarray );
+                (*env)->DeleteLocalRef( env, p_floatarray );
             }
-            *p_error = true;
-            break;
+            p_sys->i_floatarray_size = i_data;
+        }
+        if( !p_sys->p_floatarray )
+            return VLC_EGENERIC;
+
+        /* copy p_buffer in to FloatArray */
+        (*env)->SetFloatArrayRegion( env, p_sys->p_floatarray, 0, i_data,
+                                    (jfloat *)p_buffer->p_buffer);
+
+        break;
+    }
+    case WRITE_V21:
+        break;
+    }
+
+    return VLC_SUCCESS;
+}
+
+static int
+JNIThread_Play( JNIEnv *env, audio_output_t *p_aout,
+                block_t *p_buffer, size_t *p_buffer_offset, mtime_t *p_wait )
+{
+    aout_sys_t *p_sys = p_aout->sys;
+    int i_ret;
+
+    switch( p_sys->i_write_type )
+    {
+    case WRITE_V21:
+        i_ret = JNIThread_WriteV21( env, p_aout, p_buffer, *p_buffer_offset );
+        break;
+    case WRITE:
+        i_ret = JNIThread_Write( env, p_aout, p_buffer, *p_buffer_offset );
+        break;
+    case WRITE_FLOAT:
+        i_ret = JNIThread_WriteFloat( env, p_aout, p_buffer, *p_buffer_offset );
+        break;
+    default:
+        vlc_assert_unreachable();
+    }
+
+    if( i_ret < 0 ) {
+        if( jfields.AudioManager.has_ERROR_DEAD_OBJECT
+            && i_ret == jfields.AudioManager.ERROR_DEAD_OBJECT )
+        {
+            msg_Warn( p_aout, "ERROR_DEAD_OBJECT: "
+                              "try recreating AudioTrack" );
+            JNIThread_Stop( env, p_aout );
+            i_ret = JNIThread_Start( env, p_aout );
+        } 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( p_aout, "Write failed: %s", str );
         }
+    } else if( i_ret == 0 )
+    {
+        /* audiotrack internal buffer is full, wait a little: between 10ms and
+         * 20ms depending on devices or rate */
+        *p_wait = FRAMES_TO_US( p_sys->i_max_audiotrack_samples / 20 );
+    } else
+    {
+        uint32_t i_samples = i_ret / p_sys->i_bytes_per_frame;
+        p_sys->i_samples_queued -= i_samples;
+        p_sys->i_samples_written += i_samples;
+
+        *p_buffer_offset += i_ret;
+
+        /* HACK: There is a known issue in audiotrack, "due to an internal
+         * timeout within the AudioTrackThread". It happens after android
+         * 4.4.2, it's not a problem for Android 5.0 since we use an other way
+         * to write samples. A working hack is to wait a little between each
+         * write. This hack is done only for API 19 (AudioTimestamp was added
+         * in API 19). */
 
-        i_offset += i_ret;
+        if( p_sys->i_write_type == WRITE && jfields.AudioTimestamp.clazz )
+            *p_wait = FRAMES_TO_US( i_samples ) / 2;
     }
-    p_sys->i_samples_written += p_buffer->i_nb_samples;
+    return i_ret >= 0 ? VLC_SUCCESS : VLC_EGENERIC;
 }
 
 static void
-JNIThread_Pause( JNIEnv *env, bool *p_error, audio_output_t *p_aout,
+JNIThread_Pause( JNIEnv *env, audio_output_t *p_aout,
                  bool b_pause, mtime_t i_date )
 {
     VLC_UNUSED( i_date );
@@ -700,17 +1135,17 @@ JNIThread_Pause( JNIEnv *env, bool *p_error, audio_output_t *p_aout,
     if( b_pause )
     {
         JNI_AT_CALL_VOID( pause );
-        CHECK_EXCEPTION( "pause" );
+        CHECK_AT_EXCEPTION( "pause" );
     } else
     {
         JNI_AT_CALL_VOID( play );
-        CHECK_EXCEPTION( "play" );
+        CHECK_AT_EXCEPTION( "play" );
         p_sys->i_play_time = mdate();
     }
 }
 
 static void
-JNIThread_Flush( JNIEnv *env, bool *p_error, audio_output_t *p_aout,
+JNIThread_Flush( JNIEnv *env, audio_output_t *p_aout,
                  bool b_wait )
 {
     aout_sys_t *p_sys = p_aout->sys;
@@ -729,18 +1164,24 @@ JNIThread_Flush( JNIEnv *env, bool *p_error, audio_output_t *p_aout,
     if( b_wait )
     {
         JNI_AT_CALL_VOID( stop );
-        if( CHECK_EXCEPTION( "stop" ) )
+        if( CHECK_AT_EXCEPTION( "stop" ) )
             return;
     } else
     {
         JNI_AT_CALL_VOID( pause );
-        if( CHECK_EXCEPTION( "pause" ) )
+        if( CHECK_AT_EXCEPTION( "pause" ) )
             return;
         JNI_AT_CALL_VOID( flush );
     }
     JNI_AT_CALL_VOID( play );
-    CHECK_EXCEPTION( "play" );
+    CHECK_AT_EXCEPTION( "play" );
     p_sys->i_play_time = mdate();
+
+    if( p_sys->p_bytebuffer )
+    {
+        (*env)->DeleteGlobalRef( env, p_sys->p_bytebuffer );
+        p_sys->p_bytebuffer = NULL;
+    }
 }
 
 static void *
@@ -750,7 +1191,9 @@ JNIThread( void *data )
     aout_sys_t *p_sys = p_aout->sys;
     bool b_error = false;
     bool b_paused = false;
-    uint32_t i_audiotrack_delay = 0;
+    block_t *p_buffer = NULL;
+    size_t i_buffer_offset = 0;
+    mtime_t i_play_deadline = 0;
     JNIEnv* env;
 
     jni_attach_thread( &env, THREAD_NAME );
@@ -762,6 +1205,7 @@ JNIThread( void *data )
     while( p_sys->b_thread_run )
     {
         struct thread_cmd *p_cmd;
+        bool b_remove_cmd = true;
 
         /* wait to process a command */
         while( ( p_cmd = TAILQ_FIRST( &p_sys->thread_cmd_queue ) ) == NULL
@@ -777,60 +1221,122 @@ JNIThread( void *data )
             continue;
         }
 
-        TAILQ_REMOVE( &p_sys->thread_cmd_queue, p_cmd, next );
-
-        if( p_cmd->id == CMD_PLAY )
+        if( p_cmd->id == CMD_PLAY && i_play_deadline > 0 )
         {
-            p_sys->i_samples_queued -= p_cmd->in.play.p_buffer->i_nb_samples;
-            vlc_cond_signal( &p_sys->cond );
+            if( mdate() > i_play_deadline )
+                i_play_deadline = 0;
+            else
+            {
+                int i_ret = 0;
+                while( p_cmd == TAILQ_FIRST( &p_sys->thread_cmd_queue )
+                       && i_ret != ETIMEDOUT && p_sys->b_thread_run )
+                    i_ret = vlc_cond_timedwait( &p_sys->cond, &p_sys->mutex,
+                                                i_play_deadline );
+                continue;
+            }
         }
 
-        vlc_mutex_unlock( &p_sys->mutex );
         /* process a command */
         switch( p_cmd->id )
         {
             case CMD_START:
+                assert( !p_sys->p_audiotrack );
+                if( b_error ) {
+                    p_cmd->out.start.i_ret = -1;
+                    break;
+                }
                 p_sys->fmt = *p_cmd->in.start.p_fmt;
                 p_cmd->out.start.i_ret =
-                        JNIThread_Start( env, &b_error, p_aout );
-                JNIThread_InitDelay( env, p_aout, &i_audiotrack_delay );
+                        JNIThread_Start( env, p_aout );
+                JNIThread_InitDelay( env, p_aout );
                 p_cmd->out.start.p_fmt = &p_sys->fmt;
                 b_paused = false;
                 break;
             case CMD_STOP:
-                JNIThread_Stop( env, &b_error, p_aout );
+                assert( p_sys->p_audiotrack );
+                JNIThread_Stop( env, p_aout );
+                JNIThread_InitDelay( env, p_aout );
                 b_paused = false;
+                b_error = false;
+                p_buffer = NULL;
                 break;
             case CMD_PLAY:
-                JNIThread_Play( env, &b_error, p_aout,
-                                p_cmd->in.play.p_buffer );
-                JNIThread_SetDelay( env, p_aout, &i_audiotrack_delay );
+            {
+                mtime_t i_play_wait = 0;
+
+                assert( p_sys->p_audiotrack );
+                if( b_error )
+                    break;
+                if( p_buffer == NULL )
+                {
+                    p_buffer = p_cmd->in.play.p_buffer;
+                    b_error = JNIThread_PreparePlay( env, p_aout, p_buffer )
+                              != VLC_SUCCESS;
+                }
+                if( b_error )
+                    break;
+                b_error = JNIThread_Play( env, p_aout, p_buffer,
+                                          &i_buffer_offset,
+                                          &i_play_wait ) != VLC_SUCCESS;
+                if( i_buffer_offset < p_buffer->i_buffer )
+                {
+                    /* buffer is not fully processed, try again with the
+                     * same cmd and buffer */
+                    b_remove_cmd = false;
+                }
+                else
+                {
+                    p_buffer = NULL;
+                    i_buffer_offset = 0;
+                }
+                if( i_play_wait > 0 )
+                    i_play_deadline = mdate() + i_play_wait;
                 break;
+            }
             case CMD_PAUSE:
-                JNIThread_Pause( env, &b_error, p_aout,
+                assert( p_sys->p_audiotrack );
+                if( b_error )
+                    break;
+                JNIThread_Pause( env, p_aout,
                                  p_cmd->in.pause.b_pause,
                                  p_cmd->in.pause.i_date );
                 b_paused = p_cmd->in.pause.b_pause;
                 break;
+            case CMD_TIME_GET:
+                assert( p_sys->p_audiotrack );
+                if( b_error )
+                {
+                    p_cmd->out.time_get.i_ret = -1;
+                    break;
+                }
+                p_cmd->out.time_get.i_ret =
+                        JNIThread_TimeGet( env, p_aout,
+                                           &p_cmd->out.time_get.i_delay );
+                break;
             case CMD_FLUSH:
-                JNIThread_Flush( env, &b_error, p_aout,
+                assert( p_sys->p_audiotrack );
+                if( b_error )
+                    break;
+                JNIThread_Flush( env, p_aout,
                                  p_cmd->in.flush.b_wait );
-                JNIThread_InitDelay( env, p_aout, &i_audiotrack_delay );
+                JNIThread_InitDelay( env, p_aout );
+                p_buffer = NULL;
                 break;
             default:
                 vlc_assert_unreachable();
         }
-
-        vlc_mutex_lock( &p_sys->mutex );
-
-        p_sys->i_audiotrack_delay = i_audiotrack_delay;
-
-        p_cmd->id = CMD_DONE;
-        if( p_cmd->pf_destroy )
-            p_cmd->pf_destroy( p_cmd );
-
+        if( p_sys->b_audiotrack_exception )
+            b_error = true;
         if( b_error )
-            p_sys->b_thread_run = false;
+            p_sys->i_samples_queued = 0;
+
+        if( b_remove_cmd )
+        {
+            TAILQ_REMOVE( &p_sys->thread_cmd_queue, p_cmd, next );
+            p_cmd->id = CMD_DONE;
+            if( p_cmd->pf_destroy )
+                p_cmd->pf_destroy( p_cmd );
+        }
 
         /* signal that command is processed */
         vlc_cond_signal( &p_sys->cond );
@@ -840,10 +1346,12 @@ end:
     {
         if( p_sys->p_bytearray )
             (*env)->DeleteGlobalRef( env, p_sys->p_bytearray );
+        if( p_sys->p_floatarray )
+            (*env)->DeleteGlobalRef( env, p_sys->p_floatarray );
+        if( p_sys->p_bytebuffer )
+            (*env)->DeleteGlobalRef( env, p_sys->p_bytebuffer );
         jni_detach_thread();
     }
-    p_sys->b_thread_run = false;
-    vlc_cond_signal( &p_sys->cond );
     vlc_mutex_unlock( &p_sys->mutex );
     return NULL;
 }
@@ -857,18 +1365,6 @@ Start( audio_output_t *p_aout, audio_sample_format_t *restrict p_fmt )
 
     vlc_mutex_lock( &p_sys->mutex );
 
-    assert( !p_sys->b_thread_run );
-
-    /* create JNIThread */
-    p_sys->b_thread_run = true;
-    if( vlc_clone( &p_sys->thread,
-                   JNIThread, p_aout, VLC_THREAD_PRIORITY_AUDIO ) )
-    {
-        msg_Err( p_aout, "JNIThread creation failed" );
-        vlc_mutex_unlock( &p_sys->mutex );
-        return VLC_EGENERIC;
-    }
-
     p_cmd = ThreadCmd_New( CMD_START );
     if( p_cmd )
     {
@@ -884,6 +1380,7 @@ Start( audio_output_t *p_aout, audio_sample_format_t *restrict p_fmt )
         }
         free( p_cmd );
     }
+
     vlc_mutex_unlock( &p_sys->mutex );
 
     if( i_ret == VLC_SUCCESS )
@@ -896,32 +1393,23 @@ static void
 Stop( audio_output_t *p_aout )
 {
     aout_sys_t *p_sys = p_aout->sys;
+    struct thread_cmd *p_cmd;
 
     vlc_mutex_lock( &p_sys->mutex );
 
-    if( p_sys->b_thread_run )
-    {
-        struct thread_cmd *p_cmd;
-
-        p_sys->i_samples_queued = 0;
-        ThreadCmd_FlushQueue( p_sys );
+    ThreadCmd_FlushQueue( p_sys );
 
-        p_cmd = ThreadCmd_New( CMD_STOP );
-        if( p_cmd )
-        {
-            /* ask the thread to process the Stop command */
-            ThreadCmd_InsertHead( p_sys, p_cmd );
-            ThreadCmd_Wait( p_sys, p_cmd );
+    p_cmd = ThreadCmd_New( CMD_STOP );
+    if( p_cmd )
+    {
+        /* ask the thread to process the Stop command */
+        ThreadCmd_InsertHead( p_sys, p_cmd );
+        ThreadCmd_Wait( p_sys, p_cmd );
 
-            free( p_cmd );
-        }
-        /* kill the thread */
-        p_sys->b_thread_run = false;
-        vlc_cond_signal( &p_sys->cond );
+        free( p_cmd );
     }
-    vlc_mutex_unlock( &p_sys->mutex );
 
-    vlc_join( p_sys->thread, NULL );
+    vlc_mutex_unlock( &p_sys->mutex );
 }
 
 static void
@@ -935,32 +1423,28 @@ static void
 Play( audio_output_t *p_aout, block_t *p_buffer )
 {
     aout_sys_t *p_sys = p_aout->sys;
+    struct thread_cmd *p_cmd;
 
     vlc_mutex_lock( &p_sys->mutex );
 
-    if( p_sys->b_thread_run )
-    {
-        struct thread_cmd *p_cmd;
-
-        while( p_sys->i_samples_queued != 0 && p_sys->b_thread_run
-               && FRAMES_TO_US( p_sys->i_samples_queued +
-                                p_buffer->i_nb_samples ) >= MAX_QUEUE_US )
-            vlc_cond_wait( &p_sys->cond, &p_sys->mutex );
+    while( p_sys->i_samples_queued != 0
+           && FRAMES_TO_US( p_sys->i_samples_queued +
+                            p_buffer->i_nb_samples ) >= MAX_QUEUE_US )
+        vlc_cond_wait( &p_sys->cond, &p_sys->mutex );
 
-        p_cmd = ThreadCmd_New( CMD_PLAY );
+    p_cmd = ThreadCmd_New( CMD_PLAY );
+    if( p_cmd )
+    {
+        /* ask the thread to process the Play command */
+        p_cmd->in.play.p_buffer = p_buffer;
+        p_cmd->pf_destroy = PlayCmd_Destroy;
 
-        if( p_cmd )
-        {
-            /* ask the thread to process the Play command */
-            p_cmd->in.play.p_buffer = p_buffer;
-            p_cmd->pf_destroy = PlayCmd_Destroy;
+        ThreadCmd_InsertTail( p_sys, p_cmd );
 
-            ThreadCmd_InsertTail( p_sys, p_cmd );
+        p_sys->i_samples_queued += p_buffer->i_nb_samples;
+    } else
+         block_Release( p_cmd->in.play.p_buffer );
 
-            p_sys->i_samples_queued += p_buffer->i_nb_samples;
-        } else
-             block_Release( p_cmd->in.play.p_buffer );
-    }
     vlc_mutex_unlock( &p_sys->mutex );
 }
 
@@ -968,25 +1452,23 @@ static void
 Pause( audio_output_t *p_aout, bool b_pause, mtime_t i_date )
 {
     aout_sys_t *p_sys = p_aout->sys;
+    struct thread_cmd *p_cmd;
 
     vlc_mutex_lock( &p_sys->mutex );
 
-    if( p_sys->b_thread_run )
+    p_cmd = ThreadCmd_New( CMD_PAUSE );
+    if( p_cmd )
     {
-        struct thread_cmd *p_cmd = ThreadCmd_New( CMD_PAUSE );
-
-        if( p_cmd )
-        {
-            /* ask the thread to process the Pause command */
-            p_cmd->in.pause.b_pause = b_pause;
-            p_cmd->in.pause.i_date = i_date;
+        /* ask the thread to process the Pause command */
+        p_cmd->in.pause.b_pause = b_pause;
+        p_cmd->in.pause.i_date = i_date;
 
-            ThreadCmd_InsertHead( p_sys, p_cmd );
-            ThreadCmd_Wait( p_sys, p_cmd );
+        ThreadCmd_InsertHead( p_sys, p_cmd );
+        ThreadCmd_Wait( p_sys, p_cmd );
 
-            free( p_cmd );
-        }
+        free( p_cmd );
     }
+
     vlc_mutex_unlock( &p_sys->mutex );
 }
 
@@ -994,28 +1476,24 @@ static void
 Flush( audio_output_t *p_aout, bool b_wait )
 {
     aout_sys_t *p_sys = p_aout->sys;
+    struct thread_cmd *p_cmd;
 
     vlc_mutex_lock( &p_sys->mutex );
 
-    if( p_sys->b_thread_run )
-    {
-        struct thread_cmd *p_cmd;
-
-        p_sys->i_samples_queued = 0;
-        ThreadCmd_FlushQueue( p_sys );
+    ThreadCmd_FlushQueue( p_sys );
 
-        p_cmd = ThreadCmd_New( CMD_FLUSH );
-        if( p_cmd)
-        {
-            /* ask the thread to process the Flush command */
-            p_cmd->in.flush.b_wait = b_wait;
+    p_cmd = ThreadCmd_New( CMD_FLUSH );
+    if( p_cmd)
+    {
+        /* ask the thread to process the Flush command */
+        p_cmd->in.flush.b_wait = b_wait;
 
-            ThreadCmd_InsertHead( p_sys, p_cmd );
-            ThreadCmd_Wait( p_sys, p_cmd );
+        ThreadCmd_InsertHead( p_sys, p_cmd );
+        ThreadCmd_Wait( p_sys, p_cmd );
 
-            free( p_cmd );
-        }
+        free( p_cmd );
     }
+
     vlc_mutex_unlock( &p_sys->mutex );
 }
 
@@ -1023,16 +1501,22 @@ static int
 TimeGet( audio_output_t *p_aout, mtime_t *restrict p_delay )
 {
     aout_sys_t *p_sys = p_aout->sys;
-    int i_ret;
+    struct thread_cmd *p_cmd;
+    int i_ret = -1;
 
     vlc_mutex_lock( &p_sys->mutex );
-    if( p_sys->i_samples_queued != 0 )
+
+    p_cmd = ThreadCmd_New( CMD_TIME_GET );
+    if( p_cmd)
     {
-        *p_delay = FRAMES_TO_US( p_sys->i_samples_queued +
-                                 p_sys->i_audiotrack_delay );
-        i_ret = 0;
-    } else
-        i_ret = -1;
+        ThreadCmd_InsertHead( p_sys, p_cmd );
+        ThreadCmd_Wait( p_sys, p_cmd );
+
+        i_ret = p_cmd->out.time_get.i_ret;
+        *p_delay = p_cmd->out.time_get.i_delay;
+        free( p_cmd );
+    }
+
     vlc_mutex_unlock( &p_sys->mutex );
 
     return i_ret;
@@ -1067,6 +1551,17 @@ Open( vlc_object_t *obj )
 
     aout_SoftVolumeInit( p_aout );
 
+    /* create JNIThread */
+    p_sys->b_thread_run = true;
+    if( vlc_clone( &p_sys->thread,
+                   JNIThread, p_aout, VLC_THREAD_PRIORITY_AUDIO ) )
+    {
+        msg_Err( p_aout, "JNIThread creation failed" );
+        p_sys->b_thread_run = false;
+        Close( obj );
+        return VLC_EGENERIC;
+    }
+
     return VLC_SUCCESS;
 }
 
@@ -1076,6 +1571,17 @@ Close( vlc_object_t *obj )
     audio_output_t *p_aout = (audio_output_t *) obj;
     aout_sys_t *p_sys = p_aout->sys;
 
+    /* kill the thread */
+    vlc_mutex_lock( &p_sys->mutex );
+    if( p_sys->b_thread_run )
+    {
+        p_sys->b_thread_run = false;
+        vlc_cond_signal( &p_sys->cond );
+        vlc_mutex_unlock( &p_sys->mutex );
+        vlc_join( p_sys->thread, NULL );
+    } else
+        vlc_mutex_unlock( &p_sys->mutex );
+
     vlc_mutex_destroy( &p_sys->mutex );
     vlc_cond_destroy( &p_sys->cond );