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

Thomas Guillem thomas at gllm.fr
Fri Feb 6 17:59:18 CET 2015


On Fri, Feb 6, 2015, at 16:03, Thomas Guillem wrote:
> This module is based on the old native audiotrack but use the public Java
> AudioTrack API via JNI. All JNI operations are done in a separate thread
> in
> order to avoid an overhead when attaching and detaching the current
> thread to
> the Java VM.
> 
> News since native audiotrack:
>  - Flush discards audio immediatly (if not wait).
>  - It adds support for float (for android 5.0 and after).

False, Float doesn't work on the device I tested (I'll add a define to
activate it or not)

>  - It can handle a mediaserver crash and restart (for android 5.0 and
>  after).
> ---
>  modules/audio_output/audiotrack.c | 865
>  ++++++++++++++++++++++++++++++++++++++
>  1 file changed, 865 insertions(+)
>  create mode 100644 modules/audio_output/audiotrack.c
> 
> diff --git a/modules/audio_output/audiotrack.c
> b/modules/audio_output/audiotrack.c
> new file mode 100644
> index 0000000..09ef3fb
> --- /dev/null
> +++ b/modules/audio_output/audiotrack.c
> @@ -0,0 +1,865 @@
> +/*****************************************************************************
> + * 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"
> +
> +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 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 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 );
> +    GET_CONST_INT( AudioFormat.ENCODING_PCM_FLOAT, "ENCODING_PCM_FLOAT",
> +                   false );
> +    jfields.AudioFormat.has_ENCODING_PCM_FLOAT = field != NULL;
> +    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.fmt;
> +                p_sys->p_cmd->out.start.i_ret = 
> +                    JNIThread_Start( env, &b_error, p_aout );
> +                p_sys->p_cmd->out.start.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.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.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