[vlc-devel] [PATCH 2/2] audiotrack: add java module

Thomas Guillem thomas at gllm.fr
Tue Feb 10 10:58:51 CET 2015


This module is based on the old native audiotrack but uses the public Java
AudioTrack API via JNI. All JNI operations are done in a separate thread in
order to avoid the overhead when attaching and detaching the current thread to
the Java VM.

News since native audiotrack:
 - Flush discards audio immediately (if not wait).
 - It can handle a mediaserver crash and restart (for Android 5.0 and after).
---
 modules/audio_output/Makefile.am  |   3 +-
 modules/audio_output/audiotrack.c | 871 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 872 insertions(+), 2 deletions(-)
 create mode 100644 modules/audio_output/audiotrack.c

diff --git a/modules/audio_output/Makefile.am b/modules/audio_output/Makefile.am
index 1e6a233..589e5fe 100644
--- a/modules/audio_output/Makefile.am
+++ b/modules/audio_output/Makefile.am
@@ -5,8 +5,7 @@ libopensles_android_plugin_la_SOURCES = audio_output/opensles_android.c
 libopensles_android_plugin_la_LIBADD = $(LIBDL) $(LIBM)
 
 libandroid_audiotrack_plugin_la_SOURCES = audio_output/audiotrack.c
-libandroid_audiotrack_plugin_la_CPPFLAGS = $(AM_CPPFLAGS)
-libandroid_audiotrack_plugin_la_LIBADD = $(LIBDL)
+libandroid_audiotrack_plugin_la_CFLAGS = $(AM_CFLAGS)
 
 if HAVE_ANDROID
 aout_LTLIBRARIES += libandroid_audiotrack_plugin.la libopensles_android_plugin.la
diff --git a/modules/audio_output/audiotrack.c b/modules/audio_output/audiotrack.c
new file mode 100644
index 0000000..9113c3f
--- /dev/null
+++ b/modules/audio_output/audiotrack.c
@@ -0,0 +1,871 @@
+/*****************************************************************************
+ * audiotrack.c: Android Java AudioTrack audio output module
+ *****************************************************************************
+ * Copyright © 2012-2015 VLC authors and VideoLAN
+ *
+ * Authors: Thomas Guillem <thomas at gllm.fr>
+ *          Ming Hu <tewilove at gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <assert.h>
+#include <jni.h>
+
+#include <vlc_common.h>
+#include <vlc_plugin.h>
+#include <vlc_aout.h>
+#include <vlc_threads.h>
+
+#include <dlfcn.h>
+#include <assert.h>
+
+static int  Open( vlc_object_t * );
+static void Close( vlc_object_t * );
+
+struct aout_sys_t {
+    /* sw gain */
+    float soft_gain;
+    bool soft_mute;
+
+    /* 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 */
+
+    /* 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 */
+};
+
+/* Soft volume helper */
+#include "audio_output/volume.h"
+
+//#define AUDIOTRACK_USE_FLOAT
+
+vlc_module_begin ()
+    set_shortname( "AudioTrack" )
+    set_description( N_( "Android AudioTrack audio output" ) )
+    set_capability( "audio output", 180 )
+    set_category( CAT_AUDIO )
+    set_subcategory( SUBCAT_AUDIO_AOUT )
+    add_sw_gain()
+    add_shortcut( "audiotrack" )
+    set_callbacks( Open, Close )
+vlc_module_end ()
+
+struct thread_cmd {
+    enum {
+        CMD_START,
+        CMD_STOP,
+        CMD_PLAY,
+        CMD_PAUSE,
+        CMD_FLUSH,
+        CMD_TIME_GET,
+        CMD_DONE,
+    } id;
+    union {
+        struct {
+            audio_sample_format_t *p_fmt;
+        } start;
+        struct {
+            block_t *p_buffer;
+        } play;
+        struct {
+            bool b_pause;
+            mtime_t i_date;
+        } pause;
+        struct {
+            bool b_wait;
+        } flush;
+    } in;
+    union {
+        struct {
+            int i_ret;
+            audio_sample_format_t *p_fmt;
+        } start;
+        struct {
+            int i_ret;
+            mtime_t i_delay;
+        } time_get;
+    } out;
+};
+
+#define THREAD_NAME "android_audiotrack"
+
+extern int jni_attach_thread(JNIEnv **env, const char *thread_name);
+extern void jni_detach_thread();
+extern int jni_get_env(JNIEnv **env);
+
+static struct
+{
+    struct {
+        jclass clazz;
+        jmethodID ctor;
+        jmethodID release;
+        jmethodID play;
+        jmethodID stop;
+        jmethodID flush;
+        jmethodID pause;
+        jmethodID write;
+        jmethodID getPlaybackHeadPosition;
+        jmethodID getMinBufferSize;
+        jint MODE_STREAM;
+        jint ERROR;
+        jint ERROR_BAD_VALUE;
+        jint ERROR_INVALID_OPERATION;
+    } AudioTrack;
+    struct {
+        jint ENCODING_PCM_8BIT;
+        jint ENCODING_PCM_16BIT;
+        jint ENCODING_PCM_FLOAT;
+        bool has_ENCODING_PCM_FLOAT;
+        jint CHANNEL_OUT_MONO;
+        jint CHANNEL_OUT_STEREO;
+    } AudioFormat;
+    struct {
+        jint ERROR_DEAD_OBJECT;
+        bool has_ERROR_DEAD_OBJECT;
+        jint STREAM_MUSIC;
+    } AudioManager;
+} jfields;
+
+/* init all jni fields.
+ * Done only one time during the first initialisation */
+static bool
+InitJNIFields( audio_output_t *p_aout )
+{
+    static vlc_mutex_t lock = VLC_STATIC_MUTEX;
+    static int i_init_state = -1;
+    bool ret, b_attached = false;
+    jclass clazz;
+    jfieldID field;
+    JNIEnv* env = NULL;
+
+    vlc_mutex_lock( &lock );
+
+    if( i_init_state != -1 )
+        goto end;
+
+    if( jni_get_env(&env) < 0 )
+    {
+        jni_attach_thread( &env, THREAD_NAME );
+        if( !env )
+        {
+            i_init_state = 0;
+            goto end;
+        }
+        b_attached = true;
+    }
+
+#define CHECK_EXCEPTION( what, critical ) do { \
+    if( (*env)->ExceptionOccurred( env ) ) \
+    { \
+        msg_Err( p_aout, "%s failed", what ); \
+        (*env)->ExceptionClear( env ); \
+        if( (critical) ) \
+        { \
+            i_init_state = 0; \
+            goto end; \
+        } \
+    } \
+} while( 0 )
+#define GET_CLASS( str, critical ) do { \
+    clazz = (*env)->FindClass( env, (str) ); \
+    CHECK_EXCEPTION( str, critical ); \
+} while( 0 )
+#define GET_ID( get, id, str, args, critical ) do { \
+    jfields.id = (*env)->get( env, clazz, (str), (args) ); \
+    CHECK_EXCEPTION( #get, critical ); \
+} while( 0 )
+#define GET_CONST_INT( id, str, critical ) do { \
+    field = NULL; \
+    field = (*env)->GetStaticFieldID( env, clazz, (str), "I" ); \
+    CHECK_EXCEPTION( #id, critical ); \
+    if( field ) \
+    { \
+        jfields.id = (*env)->GetStaticIntField( env, clazz, field ); \
+        CHECK_EXCEPTION( #id, critical ); \
+    } \
+} while( 0 )
+
+    GET_CLASS( "android/media/AudioTrack", true );
+    jfields.AudioTrack.clazz = (jclass) (*env)->NewGlobalRef( env, clazz );
+    CHECK_EXCEPTION( "NewGlobalRef", true );
+
+    GET_ID( GetMethodID, AudioTrack.ctor, "<init>", "(IIIIII)V", true );
+    GET_ID( GetMethodID, AudioTrack.release, "release", "()V", 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.getPlaybackHeadPosition,
+            "getPlaybackHeadPosition", "()I", true );
+    GET_ID( GetStaticMethodID, AudioTrack.getMinBufferSize, "getMinBufferSize",
+            "(III)I", 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 ,
+                   "ERROR_INVALID_OPERATION", true );
+
+    GET_CLASS( "android/media/AudioFormat", true );
+    GET_CONST_INT( AudioFormat.ENCODING_PCM_8BIT, "ENCODING_PCM_8BIT", true );
+    GET_CONST_INT( AudioFormat.ENCODING_PCM_16BIT, "ENCODING_PCM_16BIT", true );
+#ifdef AUDIOTRACK_USE_FLOAT
+    GET_CONST_INT( AudioFormat.ENCODING_PCM_FLOAT, "ENCODING_PCM_FLOAT",
+                   false );
+    jfields.AudioFormat.has_ENCODING_PCM_FLOAT = field != NULL;
+#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_CLASS( "android/media/AudioManager", true );
+    GET_CONST_INT( AudioManager.ERROR_DEAD_OBJECT, "ERROR_DEAD_OBJECT", false );
+    jfields.AudioManager.has_ERROR_DEAD_OBJECT = field != NULL;
+    GET_CONST_INT( AudioManager.STREAM_MUSIC, "STREAM_MUSIC", true );
+
+#undef CHECK_EXCEPTION
+#undef GET_CLASS
+#undef GET_ID
+#undef GET_CONST_INT
+
+    i_init_state = 1;
+end:
+    ret = i_init_state == 1;
+    if( !ret )
+        msg_Err( p_aout, "AudioTrack jni init failed" );
+    if( b_attached )
+        jni_detach_thread();
+    vlc_mutex_unlock( &lock );
+    return ret;
+}
+
+static inline bool
+check_exception( JNIEnv *env, bool *p_error, audio_output_t *p_aout,
+                 const char *method )
+{
+    if( (*env)->ExceptionOccurred( env ) )
+    {
+        (*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 JNI_CALL( what, obj, method, ... ) (*env)->what( env, obj, method, ##__VA_ARGS__ )
+
+#define JNI_CALL_INT( obj, method, ... ) JNI_CALL( CallIntMethod, obj, method, ##__VA_ARGS__ )
+#define JNI_CALL_VOID( obj, method, ... ) JNI_CALL( CallVoidMethod, obj, method, ##__VA_ARGS__ )
+#define JNI_CALL_STATIC_INT( clazz, method, ... ) JNI_CALL( CallStaticIntMethod, clazz, method, ##__VA_ARGS__ )
+
+#define JNI_AT_NEW( ... ) JNI_CALL( NewObject, jfields.AudioTrack.clazz, jfields.AudioTrack.ctor, ##__VA_ARGS__ )
+#define JNI_AT_CALL_INT( method, ... ) JNI_CALL_INT( p_sys->p_audiotrack, jfields.AudioTrack.method, ##__VA_ARGS__ )
+#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 )
+{
+    VLC_UNUSED( p_error );
+    aout_sys_t *p_sys = p_aout->sys;
+    uint32_t dsp;
+
+    /* 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().
+     */
+
+    dsp = (uint32_t )JNI_AT_CALL_INT( getPlaybackHeadPosition );
+
+    if( p_sys->i_samples_written == 0 ) {
+        p_sys->i_dsp_initial = dsp;
+        return -1;
+    }
+
+    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;
+}
+
+static int
+JNIThread_Start( JNIEnv *env, bool *p_error, audio_output_t *p_aout )
+{
+    struct aout_sys_t *p_sys = p_aout->sys;
+    int i_size, i_min_buffer_size, i_channel_config, i_rate, i_format,
+        i_format_size, i_nb_channels;
+    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
+    {
+        i_format = jfields.AudioFormat.ENCODING_PCM_8BIT;
+        i_format_size = 1;
+    }
+    p_sys->fmt.i_original_channels = p_sys->fmt.i_physical_channels;
+
+    i_nb_channels = aout_FormatNbChannels( &p_sys->fmt );
+    switch( i_nb_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;
+    }
+
+    i_min_buffer_size = JNI_AT_CALL_STATIC_INT( getMinBufferSize, i_rate,
+                                                i_channel_config, i_format );
+    if( i_min_buffer_size <= 0 )
+    {
+        msg_Warn( p_aout, "getMinBufferSize returned an invalid size" ) ;
+        /* use a defaut min buffer size (shouldn't happen) */
+        i_min_buffer_size = i_nb_channels * i_format_size * 2024;
+    }
+
+    i_size = i_min_buffer_size * 2; // double buffering
+
+    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( "<init>" ) || !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;
+
+    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 );
+
+    JNI_AT_CALL_VOID( play );
+
+    return VLC_SUCCESS;
+}
+
+static void
+JNIThread_Stop( JNIEnv *env, bool *p_error, 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;
+}
+
+static void
+JNIThread_Play( JNIEnv *env, bool *p_error, audio_output_t *p_aout,
+                block_t *p_buffer )
+{
+    aout_sys_t *p_sys = p_aout->sys;
+    int i_offset = 0;
+
+    /* 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;
+        }
+
+        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 )
+    {
+        *p_error = true;
+        return;
+    }
+
+    /* copy p_buffer in to ByteArray */
+    (*env)->SetByteArrayRegion( env, p_sys->p_bytearray, 0,
+                                p_buffer->i_buffer,
+                                (jbyte *)p_buffer->p_buffer);
+
+    while ( p_buffer->i_buffer > (unsigned int) i_offset )
+    {
+        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 )
+            {
+                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
+            {
+                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_error = true;
+            break;
+        }
+
+        p_sys->i_samples_written += i_ret / p_sys->i_bytes_per_frame;
+        i_offset += i_ret;
+    }
+}
+
+static void
+JNIThread_Pause( JNIEnv *env, bool *p_error, audio_output_t *p_aout,
+                 bool b_pause, mtime_t i_date )
+{
+    VLC_UNUSED( i_date );
+
+    aout_sys_t *p_sys = p_aout->sys;
+
+    if( b_pause )
+    {
+        JNI_AT_CALL_VOID( pause );
+        CHECK_EXCEPTION( "pause" );
+    } else
+    {
+        JNI_AT_CALL_VOID( play );
+        CHECK_EXCEPTION( "play" );
+    }
+}
+
+static void
+JNIThread_Flush( JNIEnv *env, bool *p_error, audio_output_t *p_aout,
+                 bool b_wait )
+{
+    aout_sys_t *p_sys = p_aout->sys;
+
+    /* 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.
+     */
+    if( !p_sys->i_samples_written )
+        return;
+    if( b_wait )
+    {
+        JNI_AT_CALL_VOID( stop );
+        if( CHECK_EXCEPTION( "stop" ) )
+            return;
+    } else
+    {
+
+        JNI_AT_CALL_VOID( pause );
+        if( CHECK_EXCEPTION( "pause" ) )
+            return;
+        JNI_AT_CALL_VOID( flush );
+    }
+    p_sys->i_samples_written = 0;
+    JNI_AT_CALL_VOID( play );
+    CHECK_EXCEPTION( "play" );
+}
+
+static void *
+JNIThread( void *data )
+{
+    audio_output_t *p_aout = data;
+    aout_sys_t *p_sys = p_aout->sys;
+    bool b_error = false;
+    JNIEnv* env;
+
+    jni_attach_thread( &env, THREAD_NAME );
+
+    vlc_mutex_lock( &p_sys->mutex );
+    if( !env )
+        goto end;
+
+    while( p_sys->b_thread_run )
+    {
+        /* wait to process a command */
+        while( p_sys->b_thread_run && p_sys->p_cmd == NULL )
+            vlc_cond_wait( &p_sys->cond, &p_sys->mutex );
+        if( !p_sys->b_thread_run || p_sys->p_cmd == NULL )
+            break;
+
+        /* process a command */
+        switch( p_sys->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;
+                break;
+            case CMD_STOP:
+                JNIThread_Stop( env, &b_error, p_aout );
+                break;
+            case CMD_PLAY:
+                JNIThread_Play( env, &b_error, p_aout,
+                                 p_sys->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 );
+                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 );
+                break;
+            default:
+                assert( false );
+                break;
+        }
+        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 );
+    }
+end:
+    if( env )
+    {
+        if( p_sys->p_bytearray )
+            (*env)->DeleteGlobalRef( env, p_sys->p_bytearray );
+        jni_detach_thread();
+    }
+    p_sys->b_thread_run = false;
+    vlc_cond_signal( &p_sys->cond );
+    vlc_mutex_unlock( &p_sys->mutex );
+    return NULL;
+}
+
+static int
+Start( audio_output_t *p_aout, audio_sample_format_t *restrict p_fmt )
+{
+    int i_ret;
+    struct thread_cmd 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 );
+
+    /* 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;
+    }
+
+    /* 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 );
+
+    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;
+}
+
+static void
+Stop( audio_output_t *p_aout )
+{
+    aout_sys_t *p_sys = p_aout->sys;
+
+    vlc_mutex_lock( &p_sys->mutex );
+
+    assert( p_sys->p_cmd == NULL );
+
+    if( p_sys->b_thread_run )
+    {
+        struct thread_cmd cmd;
+
+        /* ask the thread to process the Stop command */
+        cmd.id = CMD_STOP;
+        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 );
+
+        /* kill the thread */
+        p_sys->b_thread_run = false;
+        vlc_cond_signal( &p_sys->cond );
+    }
+    vlc_mutex_unlock( &p_sys->mutex );
+
+    vlc_join( p_sys->thread, NULL );
+}
+
+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 );
+
+    if( p_sys->b_thread_run )
+    {
+        struct thread_cmd 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 )
+            vlc_cond_wait( &p_sys->cond, &p_sys->mutex );
+    }
+
+    vlc_mutex_unlock( &p_sys->mutex );
+
+    block_Release( p_buffer );
+}
+
+static void
+Pause( audio_output_t *p_aout, bool b_pause, mtime_t i_date )
+{
+    aout_sys_t *p_sys = p_aout->sys;
+
+    vlc_mutex_lock( &p_sys->mutex );
+
+    assert( p_sys->p_cmd == NULL );
+
+    if( p_sys->b_thread_run )
+    {
+        struct thread_cmd cmd;
+
+        /* 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 );
+
+        /* wait for the thread */
+        while( cmd.id != CMD_DONE && p_sys->b_thread_run  )
+            vlc_cond_wait( &p_sys->cond, &p_sys->mutex );
+    }
+
+    vlc_mutex_unlock( &p_sys->mutex );
+}
+
+static void
+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 );
+
+    if( p_sys->b_thread_run )
+    {
+        struct thread_cmd 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 );
+
+        /* wait for the thread */
+        while( cmd.id != CMD_DONE && p_sys->b_thread_run  )
+            vlc_cond_wait( &p_sys->cond, &p_sys->mutex );
+    }
+
+    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;
+
+    vlc_mutex_lock( &p_sys->mutex );
+
+    assert( p_sys->p_cmd == NULL );
+
+    if( p_sys->b_thread_run )
+    {
+        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;
+}
+
+
+static int
+Open( vlc_object_t *obj )
+{
+    audio_output_t *p_aout = (audio_output_t *) obj;
+    aout_sys_t *p_sys;
+
+    if( !InitJNIFields( p_aout ) )
+        return VLC_EGENERIC;
+
+    p_sys = calloc( 1, sizeof (aout_sys_t) );
+
+    if( unlikely( p_sys == NULL ) )
+        return VLC_ENOMEM;
+
+    vlc_mutex_init( &p_sys->mutex );
+    vlc_cond_init( &p_sys->cond );
+
+    p_aout->sys = p_sys;
+    p_aout->start = Start;
+    p_aout->stop = Stop;
+    p_aout->play = Play;
+    p_aout->pause = Pause;
+    p_aout->flush = Flush;
+    p_aout->time_get = TimeGet;
+
+    aout_SoftVolumeInit( p_aout );
+
+    return VLC_SUCCESS;
+}
+
+static void
+Close( vlc_object_t *obj )
+{
+    audio_output_t *p_aout = (audio_output_t *) obj;
+    aout_sys_t *p_sys = p_aout->sys;
+
+    vlc_mutex_destroy( &p_sys->mutex );
+    vlc_cond_destroy( &p_sys->cond );
+
+    free( p_sys );
+}
-- 
2.1.3




More information about the vlc-devel mailing list