[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