[vlc-devel] [PATCH] audiotrack: increase audio latency

Thomas Guillem thomas at gllm.fr
Fri Feb 13 19:26:58 CET 2015


Queue more than one audio buffer at a time (limit of 15 buffers). TimeGet will
report a bigger delay, but the module will be more stable and more efficient.

AudioTrack delay is now set by JNIThread when a buffer is written. Overall
delay is calculated from TimeGet.
---
 modules/audio_output/audiotrack.c | 363 +++++++++++++++++++++++---------------
 1 file changed, 216 insertions(+), 147 deletions(-)

diff --git a/modules/audio_output/audiotrack.c b/modules/audio_output/audiotrack.c
index 4fb708e..67950f6 100644
--- a/modules/audio_output/audiotrack.c
+++ b/modules/audio_output/audiotrack.c
@@ -28,35 +28,47 @@
 #include <assert.h>
 #include <jni.h>
 #include <dlfcn.h>
+#include <stdbool.h>
+#include <sys/queue.h>
 
+#include <vlc_atomic.h>
 #include <vlc_common.h>
 #include <vlc_plugin.h>
 #include <vlc_aout.h>
 #include <vlc_threads.h>
 
+#define MAX_BUFFER_QUEUED 15
+
 static int  Open( vlc_object_t * );
 static void Close( vlc_object_t * );
 
+struct thread_cmd;
+typedef TAILQ_HEAD(, thread_cmd) THREAD_CMD_QUEUE;
+
 struct aout_sys_t {
     /* sw gain */
     float soft_gain;
     bool soft_mute;
 
+    uint32_t i_samples_written; /* samples written since start/flush */
+    int i_bytes_per_frame; /* byte per frame */
+
     /* Owned by JNIThread */
     jobject p_audiotrack; /* AudioTrack ref */
     jbyteArray p_bytearray; /* ByteArray ref */
     size_t i_bytearray_size; /* size of the ByteArray */
     audio_sample_format_t fmt; /* fmt setup by Start */
-    uint32_t i_samples_written; /* samples written since start/flush */
-    uint32_t i_dsp_initial; /* initial delay of the dsp */
-    int i_bytes_per_frame; /* byte per frame */
+    uint32_t i_pos_initial; /* initial position set by getPlaybackHeadPosition */
+    bool     b_pos_initial_set;
+    atomic_uint_fast32_t delay;
 
     /* JNIThread control */
     vlc_mutex_t mutex;
     vlc_cond_t cond;
     vlc_thread_t thread;
     bool b_thread_run; /* is thread alive */
-    struct thread_cmd *p_cmd; /* actual cmd process by JNIThread */
+    THREAD_CMD_QUEUE thread_cmd_queue; /* thread cmd queue */
+    unsigned int i_nb_buffer_queued; /* number or buffer queued */
 };
 
 /* Soft volume helper */
@@ -75,14 +87,15 @@ vlc_module_begin ()
     set_callbacks( Open, Close )
 vlc_module_end ()
 
-struct thread_cmd {
+struct thread_cmd
+{
+    TAILQ_ENTRY(thread_cmd) next;
     enum {
         CMD_START,
         CMD_STOP,
         CMD_PLAY,
         CMD_PAUSE,
         CMD_FLUSH,
-        CMD_TIME_GET,
         CMD_DONE,
     } id;
     union {
@@ -105,11 +118,9 @@ 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 * );
+    void *p_opaque;
 };
 
 #define THREAD_NAME "android_audiotrack"
@@ -291,13 +302,61 @@ check_exception( JNIEnv *env, bool *p_error, audio_output_t *p_aout,
 #define JNI_AT_CALL_VOID( method, ... ) JNI_CALL_VOID( p_sys->p_audiotrack, jfields.AudioTrack.method, ##__VA_ARGS__ )
 #define JNI_AT_CALL_STATIC_INT( method, ... ) JNI_CALL( CallStaticIntMethod, jfields.AudioTrack.clazz, jfields.AudioTrack.method, ##__VA_ARGS__ )
 
-static int
-JNIThread_TimeGet( JNIEnv *env, bool *p_error, audio_output_t *p_aout,
-                   mtime_t *p_delay )
+static struct thread_cmd *
+ThreadCmd_New( int id )
+{
+    struct thread_cmd *p_cmd = calloc( 1, sizeof(struct thread_cmd) );
+
+    if( p_cmd )
+        p_cmd->id = id;
+
+    return p_cmd;
+}
+
+static void
+ThreadCmd_InsertHead( aout_sys_t *p_sys, struct thread_cmd *p_cmd )
+{
+    TAILQ_INSERT_HEAD( &p_sys->thread_cmd_queue, p_cmd, next);
+    vlc_cond_signal( &p_sys->cond );
+}
+
+static void
+ThreadCmd_InsertTail( aout_sys_t *p_sys, struct thread_cmd *p_cmd )
+{
+    TAILQ_INSERT_TAIL( &p_sys->thread_cmd_queue, p_cmd, next);
+    vlc_cond_signal( &p_sys->cond );
+}
+
+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  )
+        vlc_cond_wait( &p_sys->cond, &p_sys->mutex );
+
+    return p_cmd->id == CMD_DONE;
+}
+
+static void
+ThreadCmd_FlushQueue( aout_sys_t *p_sys )
+{
+    struct thread_cmd *p_cmd, *p_cmd_next;
+
+    for ( p_cmd = TAILQ_FIRST( &p_sys->thread_cmd_queue );
+          p_cmd != NULL; p_cmd = p_cmd_next )
+    {
+        p_cmd_next = TAILQ_NEXT( p_cmd, next );
+        TAILQ_REMOVE( &p_sys->thread_cmd_queue, p_cmd, next );
+        if( p_cmd->pf_destroy )
+            p_cmd->pf_destroy( p_cmd );
+    }
+}
+
+static void
+JNIThread_SetDelay( JNIEnv *env, bool *p_error, audio_output_t *p_aout )
 {
     VLC_UNUSED( p_error );
     aout_sys_t *p_sys = p_aout->sys;
-    uint32_t dsp;
+    uint32_t i_pos;
 
     /* Android doc:
      * getPlaybackHeadPosition: Returns the playback head position expressed in
@@ -308,23 +367,13 @@ JNIThread_TimeGet( JNIEnv *env, bool *p_error, audio_output_t *p_aout,
      * once every 27:03:11 hours:minutes:seconds at 44.1 kHz. It is reset to
      * zero by flush(), reload(), and stop().
      */
-
-    dsp = (uint32_t )JNI_AT_CALL_INT( getPlaybackHeadPosition );
-
-    if( p_sys->i_samples_written == 0 ) {
-        p_sys->i_dsp_initial = dsp;
-        return -1;
+    i_pos = JNI_AT_CALL_INT( getPlaybackHeadPosition );
+    if( !p_sys->b_pos_initial_set )
+    {
+        p_sys->i_pos_initial = i_pos;
+        p_sys->b_pos_initial_set = true;
     }
-
-    dsp -= p_sys->i_dsp_initial;
-    if( dsp == 0 )
-        return -1;
-
-    if( p_delay )
-        *p_delay = ((mtime_t)p_sys->i_samples_written - dsp) *
-                   CLOCK_FREQ / p_sys->fmt.i_rate;
-
-    return 0;
+    atomic_store( &p_sys->delay, i_pos - p_sys->i_pos_initial );
 }
 
 static int
@@ -404,11 +453,9 @@ JNIThread_Start( JNIEnv *env, bool *p_error, audio_output_t *p_aout )
         return VLC_EGENERIC;
 
     p_sys->fmt.i_rate = i_rate;
-    p_sys->i_samples_written = 0;
-    p_sys->i_bytes_per_frame = i_nb_channels * i_format_size;
 
-    /* Gets the initial value of DAC samples counter */
-    JNIThread_TimeGet( env, p_error, p_aout, NULL );
+    p_sys->b_pos_initial_set = false;
+    JNIThread_SetDelay( env, p_error, p_aout );
 
     JNI_AT_CALL_VOID( play );
 
@@ -497,9 +544,9 @@ JNIThread_Play( JNIEnv *env, bool *p_error, audio_output_t *p_aout,
             break;
         }
 
-        p_sys->i_samples_written += i_ret / p_sys->i_bytes_per_frame;
         i_offset += i_ret;
     }
+    JNIThread_SetDelay( env, p_error, p_aout );
 }
 
 static void
@@ -538,8 +585,6 @@ JNIThread_Flush( JNIEnv *env, bool *p_error, audio_output_t *p_aout,
      * 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.
      */
-    if( !p_sys->i_samples_written )
-        return;
     if( b_wait )
     {
         JNI_AT_CALL_VOID( stop );
@@ -553,7 +598,8 @@ JNIThread_Flush( JNIEnv *env, bool *p_error, audio_output_t *p_aout,
             return;
         JNI_AT_CALL_VOID( flush );
     }
-    p_sys->i_samples_written = 0;
+    p_sys->b_pos_initial_set = false;
+    JNIThread_SetDelay( env, p_error, p_aout );
     JNI_AT_CALL_VOID( play );
     CHECK_EXCEPTION( "play" );
 }
@@ -564,6 +610,7 @@ JNIThread( void *data )
     audio_output_t *p_aout = data;
     aout_sys_t *p_sys = p_aout->sys;
     bool b_error = false;
+    bool b_paused = false;
     JNIEnv* env;
 
     jni_attach_thread( &env, THREAD_NAME );
@@ -574,50 +621,65 @@ JNIThread( void *data )
 
     while( p_sys->b_thread_run )
     {
+        struct thread_cmd *p_cmd;
+
         /* wait to process a command */
-        while( p_sys->b_thread_run && p_sys->p_cmd == NULL )
+        while( ( p_cmd = TAILQ_FIRST( &p_sys->thread_cmd_queue ) ) == NULL
+               && p_sys->b_thread_run )
             vlc_cond_wait( &p_sys->cond, &p_sys->mutex );
-        if( !p_sys->b_thread_run || p_sys->p_cmd == NULL )
+
+        if( !p_sys->b_thread_run || p_cmd == NULL )
             break;
 
+        if( b_paused && p_cmd->id == CMD_PLAY )
+        {
+            vlc_cond_wait( &p_sys->cond, &p_sys->mutex );
+            continue;
+        }
+
+        TAILQ_REMOVE( &p_sys->thread_cmd_queue, p_cmd, next );
+        vlc_mutex_unlock( &p_sys->mutex );
         /* process a command */
-        switch( p_sys->p_cmd->id )
+        switch( p_cmd->id )
         {
             case CMD_START:
-                p_sys->fmt = *p_sys->p_cmd->in.start.p_fmt;
-                p_sys->p_cmd->out.start.i_ret =
-                    JNIThread_Start( env, &b_error, p_aout );
-                p_sys->p_cmd->out.start.p_fmt = &p_sys->fmt;
+                p_sys->fmt = *p_cmd->in.start.p_fmt;
+                p_cmd->out.start.i_ret =
+                        JNIThread_Start( env, &b_error, 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 );
+                b_paused = false;
                 break;
             case CMD_PLAY:
                 JNIThread_Play( env, &b_error, p_aout,
-                                 p_sys->p_cmd->in.play.p_buffer );
+                                p_cmd->in.play.p_buffer );
                 break;
             case CMD_PAUSE:
                 JNIThread_Pause( env, &b_error, p_aout,
-                                 p_sys->p_cmd->in.pause.b_pause,
-                                 p_sys->p_cmd->in.pause.i_date );
+                                 p_cmd->in.pause.b_pause,
+                                 p_cmd->in.pause.i_date );
+                b_paused = p_cmd->in.pause.b_pause;
                 break;
             case CMD_FLUSH:
                 JNIThread_Flush( env, &b_error, p_aout,
-                                 p_sys->p_cmd->in.flush.b_wait );
-                break;
-            case CMD_TIME_GET:
-                p_sys->p_cmd->out.time_get.i_ret =
-                    JNIThread_TimeGet( env, &b_error, p_aout,
-                                       &p_sys->p_cmd->out.time_get.i_delay );
+                                 p_cmd->in.flush.b_wait );
                 break;
             default:
                 assert( false );
                 break;
         }
+        vlc_mutex_lock( &p_sys->mutex );
+
+        p_cmd->id = CMD_DONE;
+        if( p_cmd->pf_destroy )
+            p_cmd->pf_destroy( p_cmd );
+
         if( b_error )
             p_sys->b_thread_run = false;
-        p_sys->p_cmd->id = CMD_DONE;
-        p_sys->p_cmd = NULL;
+
         /* signal that command is processed */
         vlc_cond_signal( &p_sys->cond );
     }
@@ -637,13 +699,13 @@ end:
 static int
 Start( audio_output_t *p_aout, audio_sample_format_t *restrict p_fmt )
 {
-    int i_ret;
-    struct thread_cmd cmd;
+    int i_ret = VLC_EGENERIC;
+    struct thread_cmd *p_cmd;
     aout_sys_t *p_sys = p_aout->sys;
 
     vlc_mutex_lock( &p_sys->mutex );
 
-    assert( !p_sys->b_thread_run && p_sys->p_cmd == NULL );
+    assert( !p_sys->b_thread_run );
 
     /* create JNIThread */
     p_sys->b_thread_run = true;
@@ -655,25 +717,29 @@ Start( audio_output_t *p_aout, audio_sample_format_t *restrict p_fmt )
         return VLC_EGENERIC;
     }
 
-    /* ask the thread to process the Start command */
-    cmd.id = CMD_START;
-    cmd.in.start.p_fmt = p_fmt;
-    p_sys->p_cmd = &cmd;
-    vlc_cond_signal( &p_sys->cond );
-
-    /* wait for the thread */
-    while( cmd.id != CMD_DONE && p_sys->b_thread_run )
-        vlc_cond_wait( &p_sys->cond, &p_sys->mutex );
+    p_cmd = ThreadCmd_New( CMD_START );
+    if( p_cmd )
+    {
+        /* ask the thread to process the Start command */
+        p_cmd->in.start.p_fmt = p_fmt;
 
+        ThreadCmd_InsertHead( p_sys, p_cmd );
+        if( ThreadCmd_Wait( p_sys, p_cmd ) )
+        {
+            i_ret = p_cmd->out.start.i_ret;
+            if( i_ret == VLC_SUCCESS )
+            {
+                *p_fmt = *p_cmd->out.start.p_fmt;
+                p_sys->i_bytes_per_frame = aout_FormatNbChannels( p_fmt )
+                            * aout_BitsPerSample( p_sys->fmt.i_format ) / 8;
+            }
+        }
+        free( p_cmd );
+    }
     vlc_mutex_unlock( &p_sys->mutex );
 
-    /* retrieve results */
-    i_ret = cmd.out.start.i_ret;
     if( i_ret == VLC_SUCCESS )
-    {
-        *p_fmt = *cmd.out.start.p_fmt;
         aout_SoftVolumeStart( p_aout );
-    }
 
     return i_ret;
 }
@@ -685,21 +751,21 @@ Stop( audio_output_t *p_aout )
 
     vlc_mutex_lock( &p_sys->mutex );
 
-    assert( p_sys->p_cmd == NULL );
-
     if( p_sys->b_thread_run )
     {
-        struct thread_cmd cmd;
+        struct thread_cmd *p_cmd;
 
-        /* ask the thread to process the Stop command */
-        cmd.id = CMD_STOP;
-        p_sys->p_cmd = &cmd;
-        vlc_cond_signal( &p_sys->cond );
+        ThreadCmd_FlushQueue( p_sys );
 
-        /* wait for the thread */
-        while( cmd.id != CMD_DONE && p_sys->b_thread_run )
-            vlc_cond_wait( &p_sys->cond, &p_sys->mutex );
+        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 );
@@ -710,31 +776,50 @@ Stop( audio_output_t *p_aout )
 }
 
 static void
+PlayCmd_Destroy( struct thread_cmd *p_cmd )
+{
+    audio_output_t *p_aout = p_cmd->p_opaque;
+    aout_sys_t *p_sys = p_aout->sys;
+
+    p_sys->i_nb_buffer_queued--;
+
+    block_Release( p_cmd->in.play.p_buffer );
+    free( p_cmd );
+}
+
+static void
 Play( audio_output_t *p_aout, block_t *p_buffer )
 {
     aout_sys_t *p_sys = p_aout->sys;
-    vlc_mutex_lock( &p_sys->mutex );
 
-    assert( p_sys->p_cmd == NULL );
+    vlc_mutex_lock( &p_sys->mutex );
 
     if( p_sys->b_thread_run )
     {
-        struct thread_cmd cmd;
+        struct thread_cmd *p_cmd;
 
-        /* ask the thread to process the Play command */
-        cmd.id = CMD_PLAY;
-        cmd.in.play.p_buffer = p_buffer;
-        p_sys->p_cmd = &cmd;
-        vlc_cond_signal( &p_sys->cond );
-
-        /* wait for the thread */
-        while( cmd.id != CMD_DONE && p_sys->b_thread_run )
+        while( p_sys->i_nb_buffer_queued > MAX_BUFFER_QUEUED
+               && p_sys->b_thread_run )
             vlc_cond_wait( &p_sys->cond, &p_sys->mutex );
-    }
 
-    vlc_mutex_unlock( &p_sys->mutex );
+        p_cmd = ThreadCmd_New( CMD_PLAY );
 
-    block_Release( p_buffer );
+        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;
+            p_cmd->p_opaque = p_aout;
+
+            ThreadCmd_InsertTail( p_sys, p_cmd );
+
+            p_sys->i_nb_buffer_queued++;
+            p_sys->i_samples_written += p_buffer->i_buffer /
+                                        p_sys->i_bytes_per_frame;
+        } else
+             block_Release( p_cmd->in.play.p_buffer );
+    }
+    vlc_mutex_unlock( &p_sys->mutex );
 }
 
 static void
@@ -744,85 +829,68 @@ Pause( audio_output_t *p_aout, bool b_pause, mtime_t i_date )
 
     vlc_mutex_lock( &p_sys->mutex );
 
-    assert( p_sys->p_cmd == NULL );
-
     if( p_sys->b_thread_run )
     {
-        struct thread_cmd cmd;
+        struct thread_cmd *p_cmd = ThreadCmd_New( CMD_PAUSE );
 
-        /* ask the thread to process the Pause command */
-        cmd.id = CMD_PAUSE;
-        cmd.in.pause.b_pause = b_pause;
-        cmd.in.pause.i_date = i_date;
-        p_sys->p_cmd = &cmd;
-        vlc_cond_signal( &p_sys->cond );
+        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;
 
-        /* wait for the thread */
-        while( cmd.id != CMD_DONE && p_sys->b_thread_run  )
-            vlc_cond_wait( &p_sys->cond, &p_sys->mutex );
-    }
+            ThreadCmd_InsertHead( p_sys, p_cmd );
+            ThreadCmd_Wait( p_sys, p_cmd );
 
+            free( p_cmd );
+        }
+    }
     vlc_mutex_unlock( &p_sys->mutex );
 }
 
 static void
-Flush ( audio_output_t *p_aout, bool b_wait )
+Flush( audio_output_t *p_aout, bool b_wait )
 {
     aout_sys_t *p_sys = p_aout->sys;
 
     vlc_mutex_lock( &p_sys->mutex );
 
-    assert( p_sys->p_cmd == NULL );
+    p_sys->i_samples_written = 0;
 
     if( p_sys->b_thread_run )
     {
-        struct thread_cmd cmd;
+        struct thread_cmd *p_cmd;
 
-        /* ask the thread to process the Flush command */
-        cmd.id = CMD_FLUSH;
-        cmd.in.flush.b_wait = b_wait;
-        p_sys->p_cmd = &cmd;
-        vlc_cond_signal( &p_sys->cond );
+        ThreadCmd_FlushQueue( p_sys );
 
-        /* wait for the thread */
-        while( cmd.id != CMD_DONE && p_sys->b_thread_run  )
-            vlc_cond_wait( &p_sys->cond, &p_sys->mutex );
-    }
+        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 );
 
+            free( p_cmd );
+        }
+    }
     vlc_mutex_unlock( &p_sys->mutex );
 }
 
 static int
 TimeGet( audio_output_t *p_aout, mtime_t *restrict p_delay )
 {
-    int i_ret = -1;
     aout_sys_t *p_sys = p_aout->sys;
+    uint32_t i_delay = atomic_load( &p_sys->delay );
 
-    vlc_mutex_lock( &p_sys->mutex );
-
-    assert( p_sys->p_cmd == NULL );
-
-    if( p_sys->b_thread_run )
+    if( p_sys->i_samples_written != 0 )
     {
-        struct thread_cmd cmd;
-
-        /* ask the thread to process the TimeGet */
-        cmd.id = CMD_TIME_GET;
-        p_sys->p_cmd = &cmd;
-        vlc_cond_signal( &p_sys->cond );
-
-        /* wait for the thread */
-        while( cmd.id != CMD_DONE && p_sys->b_thread_run  )
-            vlc_cond_wait( &p_sys->cond, &p_sys->mutex );
-
-        /* retrieve results */
-        i_ret = cmd.out.time_get.i_ret;
-        *p_delay = cmd.out.time_get.i_delay;
-    }
-
-    vlc_mutex_unlock( &p_sys->mutex );
-
-    return i_ret;
+        *p_delay = (mtime_t)(p_sys->i_samples_written - i_delay) *
+                   CLOCK_FREQ / p_sys->fmt.i_rate;
+        return 0;
+    } else
+        return -1;
 }
 
 
@@ -842,6 +910,7 @@ Open( vlc_object_t *obj )
 
     vlc_mutex_init( &p_sys->mutex );
     vlc_cond_init( &p_sys->cond );
+    TAILQ_INIT( &p_sys->thread_cmd_queue );
 
     p_aout->sys = p_sys;
     p_aout->start = Start;
-- 
2.1.3




More information about the vlc-devel mailing list