* #27232: jni: added pjproject checkout as regular git content

We will remove it once the next release of pjsip (with Android support)
comes out and is merged into SFLphone.
diff --git a/jni/pjproject-android/pjmedia/src/pjmedia-audiodev/android_jni_dev.c b/jni/pjproject-android/pjmedia/src/pjmedia-audiodev/android_jni_dev.c
new file mode 100644
index 0000000..6025c8c
--- /dev/null
+++ b/jni/pjproject-android/pjmedia/src/pjmedia-audiodev/android_jni_dev.c
@@ -0,0 +1,1034 @@
+/* $Id: android_jni_dev.c 4435 2013-03-11 06:32:58Z nanang $ */
+/* 
+ * Copyright (C) 2012-2012 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2010-2012 Regis Montoya (aka r3gis - www.r3gis.fr)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
+ */
+/* This file is the implementation of Android JNI audio device.
+ * The original code was originally part of CSipSimple
+ * (http://code.google.com/p/csipsimple/) and was kindly donated
+ * by Regis Montoya.
+ */
+
+#include <pjmedia-audiodev/audiodev_imp.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/string.h>
+#include <pjmedia/errno.h>
+
+#if defined(PJMEDIA_AUDIO_DEV_HAS_ANDROID_JNI) && \
+    PJMEDIA_AUDIO_DEV_HAS_ANDROID_JNI != 0
+
+#include <jni.h>
+#include <sys/resource.h>
+#include <sys/system_properties.h>
+
+#define THIS_FILE	"android_jni_dev.c"
+#define DRIVER_NAME	"Android JNI"
+
+struct android_aud_factory
+{
+    pjmedia_aud_dev_factory base;
+    pj_pool_factory        *pf;
+    pj_pool_t              *pool;
+};
+
+/* 
+ * Sound stream descriptor.
+ * This struct may be used for both unidirectional or bidirectional sound
+ * streams.
+ */
+struct android_aud_stream
+{
+    pjmedia_aud_stream  base;
+    pj_pool_t          *pool;
+    pj_str_t            name;
+    pjmedia_dir         dir;
+    pjmedia_aud_param   param;
+    
+    int                 bytes_per_sample;
+    pj_uint32_t         samples_per_sec;
+    unsigned            samples_per_frame;
+    int                 channel_count;
+    void               *user_data;
+    pj_bool_t           quit_flag;
+    pj_bool_t           running;
+
+    /* Record */
+    jobject             record;
+    jclass              record_class;
+    unsigned            rec_buf_size;
+    pjmedia_aud_rec_cb  rec_cb;
+    pj_bool_t           rec_thread_exited;
+    pj_thread_t        *rec_thread;
+    pj_sem_t           *rec_sem;
+    pj_timestamp	rec_timestamp;
+    
+    /* Track */
+    jobject             track;
+    jclass              track_class;
+    unsigned            play_buf_size;
+    pjmedia_aud_play_cb play_cb;
+    pj_bool_t           play_thread_exited;
+    pj_thread_t        *play_thread;
+    pj_sem_t           *play_sem;
+    pj_timestamp	play_timestamp;
+};
+
+/* Factory prototypes */
+static pj_status_t android_init(pjmedia_aud_dev_factory *f);
+static pj_status_t android_destroy(pjmedia_aud_dev_factory *f);
+static pj_status_t android_refresh(pjmedia_aud_dev_factory *f);
+static unsigned android_get_dev_count(pjmedia_aud_dev_factory *f);
+static pj_status_t android_get_dev_info(pjmedia_aud_dev_factory *f,
+                                        unsigned index,
+                                        pjmedia_aud_dev_info *info);
+static pj_status_t android_default_param(pjmedia_aud_dev_factory *f,
+                                         unsigned index,
+                                         pjmedia_aud_param *param);
+static pj_status_t android_create_stream(pjmedia_aud_dev_factory *f,
+                                         const pjmedia_aud_param *param,
+                                         pjmedia_aud_rec_cb rec_cb,
+                                         pjmedia_aud_play_cb play_cb,
+                                         void *user_data,
+                                         pjmedia_aud_stream **p_aud_strm);
+
+/* Stream prototypes */
+static pj_status_t strm_get_param(pjmedia_aud_stream *strm,
+                                  pjmedia_aud_param *param);
+static pj_status_t strm_get_cap(pjmedia_aud_stream *strm,
+                                pjmedia_aud_dev_cap cap,
+                                void *value);
+static pj_status_t strm_set_cap(pjmedia_aud_stream *strm,
+                                pjmedia_aud_dev_cap cap,
+                                const void *value);
+static pj_status_t strm_start(pjmedia_aud_stream *strm);
+static pj_status_t strm_stop(pjmedia_aud_stream *strm);
+static pj_status_t strm_destroy(pjmedia_aud_stream *strm);
+
+static pjmedia_aud_dev_factory_op android_op =
+{
+    &android_init,
+    &android_destroy,
+    &android_get_dev_count,
+    &android_get_dev_info,
+    &android_default_param,
+    &android_create_stream,
+    &android_refresh
+};
+
+static pjmedia_aud_stream_op android_strm_op =
+{
+    &strm_get_param,
+    &strm_get_cap,
+    &strm_set_cap,
+    &strm_start,
+    &strm_stop,
+    &strm_destroy
+};
+
+JavaVM *android_jvm;
+
+JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved)
+{
+    android_jvm = vm;
+    
+    return JNI_VERSION_1_4;
+}
+
+static pj_bool_t attach_jvm(JNIEnv **jni_env)
+{
+    if ((*android_jvm)->GetEnv(android_jvm, (void **)jni_env,
+                               JNI_VERSION_1_4) < 0)
+    {
+        if ((*android_jvm)->AttachCurrentThread(android_jvm, jni_env, NULL) < 0)
+        {
+            jni_env = NULL;
+            return PJ_FALSE;
+        }
+        return PJ_TRUE;
+    }
+    
+    return PJ_FALSE;
+}
+
+#define detach_jvm(attached) \
+    if (attached) \
+        (*android_jvm)->DetachCurrentThread(android_jvm);
+
+/* Thread priority utils */
+/* TODO : port it to pj_thread functions */
+#define THREAD_PRIORITY_AUDIO -16
+#define THREAD_PRIORITY_URGENT_AUDIO -19
+
+pj_status_t set_android_thread_priority(int priority)
+{
+    jclass process_class;
+    jmethodID set_prio_method;
+    jthrowable exc;
+    pj_status_t result = PJ_SUCCESS;
+    JNIEnv *jni_env = 0;
+    pj_bool_t attached = attach_jvm(&jni_env);
+    
+    PJ_ASSERT_RETURN(jni_env, PJ_FALSE);
+
+    /* Get pointer to the java class */
+    process_class = (jclass)(*jni_env)->NewGlobalRef(jni_env, 
+                        (*jni_env)->FindClass(jni_env, "android/os/Process"));
+    if (process_class == 0) {
+        PJ_LOG(4, (THIS_FILE, "Unable to find os process class"));
+        result = PJ_EIGNORED;
+        goto on_return;
+    }
+    
+    /* Get the id of set thread priority function */
+    set_prio_method = (*jni_env)->GetStaticMethodID(jni_env, process_class,
+                                                    "setThreadPriority",
+                                                    "(I)V");
+    if (set_prio_method == 0) {
+        PJ_LOG(4, (THIS_FILE, "Unable to find setThreadPriority() method"));
+        result = PJ_EIGNORED;
+        goto on_return;
+    }
+    
+    /* Set the thread priority */
+    (*jni_env)->CallStaticVoidMethod(jni_env, process_class, set_prio_method,
+                                     priority);    
+    exc = (*jni_env)->ExceptionOccurred(jni_env);
+    if (exc) {
+        (*jni_env)->ExceptionDescribe(jni_env);
+        (*jni_env)->ExceptionClear(jni_env);
+        PJ_LOG(4, (THIS_FILE, "Failure in setting thread priority using "
+                              "Java API, fallback to setpriority()"));
+        setpriority(PRIO_PROCESS, 0, priority);
+    } else {
+        PJ_LOG(4, (THIS_FILE, "Setting thread priority successful"));        
+    }
+
+on_return:
+    detach_jvm(attached);
+    return result;
+}
+
+
+static int AndroidRecorderCallback(void *userData)
+{
+    struct android_aud_stream *stream = (struct android_aud_stream *)userData;
+    jmethodID read_method=0, record_method=0, stop_method=0;
+    int size = stream->rec_buf_size;
+    jbyteArray inputBuffer;
+    jbyte *buf;
+    JNIEnv *jni_env = 0;
+    pj_bool_t attached = attach_jvm(&jni_env);
+    
+    PJ_ASSERT_RETURN(jni_env, 0);
+    
+    if (!stream->record) {
+        goto on_return;
+    }
+
+    PJ_LOG(5, (THIS_FILE, "Recorder thread started"));
+
+    /* Get methods ids */
+    read_method = (*jni_env)->GetMethodID(jni_env, stream->record_class, 
+                                          "read", "([BII)I");
+    record_method = (*jni_env)->GetMethodID(jni_env, stream->record_class,
+                                            "startRecording", "()V");
+    stop_method = (*jni_env)->GetMethodID(jni_env, stream->record_class,
+                                          "stop", "()V");
+    if (read_method==0 || record_method==0 || stop_method==0) {
+        PJ_LOG(3, (THIS_FILE, "Unable to get recording methods"));
+        goto on_return;
+    }
+    
+    /* Create a buffer for frames read */
+    inputBuffer = (*jni_env)->NewByteArray(jni_env, size);
+    if (inputBuffer == 0) {
+        PJ_LOG(3, (THIS_FILE, "Unable to allocate input buffer"));
+        goto on_return;
+    }
+    buf = (*jni_env)->GetByteArrayElements(jni_env, inputBuffer, 0);
+    
+    /* Start recording
+     * setpriority(PRIO_PROCESS, 0, -19); //ANDROID_PRIORITY_AUDIO
+     * set priority is probably not enough because it does not change the thread
+     * group in scheduler
+     * Temporary solution is to call the java api to set the thread priority.
+     * A cool solution would be to port (if possible) the code from the
+     * android os regarding set_sched groups
+     */
+    set_android_thread_priority(THREAD_PRIORITY_URGENT_AUDIO);
+    (*jni_env)->CallVoidMethod(jni_env, stream->record, record_method);
+    
+    while (!stream->quit_flag) {
+        pjmedia_frame frame;
+        pj_status_t status;
+        int bytesRead;
+        
+        if (!stream->running) {
+            (*jni_env)->CallVoidMethod(jni_env, stream->record, stop_method);
+            pj_sem_wait(stream->rec_sem);
+            if (stream->quit_flag)
+                break;
+            (*jni_env)->CallVoidMethod(jni_env, stream->record, record_method);
+        }
+        
+        bytesRead = (*jni_env)->CallIntMethod(jni_env, stream->record,
+                                              read_method, inputBuffer,
+                                              0, size);
+        if (bytesRead <= 0 || bytesRead != size) {
+            PJ_LOG (4, (THIS_FILE, "Record thread : error %d reading data",
+                                   bytesRead));
+            continue;
+        }
+
+        frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
+        frame.size =  size;
+        frame.bit_info = 0;
+        frame.buf = (void *)buf;
+        frame.timestamp.u64 = stream->rec_timestamp.u64;
+
+        status = (*stream->rec_cb)(stream->user_data, &frame);
+
+        stream->rec_timestamp.u64 += stream->param.samples_per_frame /
+                                     stream->param.channel_count;
+    }
+
+    (*jni_env)->ReleaseByteArrayElements(jni_env, inputBuffer, buf, 0);
+    (*jni_env)->DeleteLocalRef(jni_env, inputBuffer);
+    
+on_return:
+    detach_jvm(attached);
+    PJ_LOG(5, (THIS_FILE, "Recorder thread stopped"));
+    stream->rec_thread_exited = 1;
+
+    return 0;
+}
+
+
+static int AndroidTrackCallback(void *userData)
+{
+    struct android_aud_stream *stream = (struct android_aud_stream*) userData;
+    jmethodID write_method=0, play_method=0, stop_method=0, flush_method=0;
+    int size = stream->play_buf_size;
+    jbyteArray outputBuffer;
+    jbyte *buf;
+    JNIEnv *jni_env = 0;
+    pj_bool_t attached = attach_jvm(&jni_env);
+    
+    if (!stream->track) {
+        goto on_return;
+    }
+
+    PJ_LOG(5, (THIS_FILE, "Playback thread started"));
+
+    /* Get methods ids */
+    write_method = (*jni_env)->GetMethodID(jni_env, stream->track_class,
+                                           "write", "([BII)I");
+    play_method = (*jni_env)->GetMethodID(jni_env, stream->track_class,
+                                          "play", "()V");
+    stop_method = (*jni_env)->GetMethodID(jni_env, stream->track_class,
+                                          "stop", "()V");
+    flush_method = (*jni_env)->GetMethodID(jni_env, stream->track_class,
+                                           "flush", "()V");
+    if (write_method==0 || play_method==0 || stop_method==0 ||
+        flush_method==0)
+    {
+        PJ_LOG(3, (THIS_FILE, "Unable to get audio track methods"));
+        goto on_return;
+    }
+
+    outputBuffer = (*jni_env)->NewByteArray(jni_env, size);
+    if (outputBuffer == 0) {
+        PJ_LOG(3, (THIS_FILE, "Unable to allocate output buffer"));
+        goto on_return;
+    }
+    buf = (*jni_env)->GetByteArrayElements(jni_env, outputBuffer, 0);
+
+    /* Start playing */
+    set_android_thread_priority(THREAD_PRIORITY_URGENT_AUDIO);
+    (*jni_env)->CallVoidMethod(jni_env, stream->track, play_method);
+
+    while (!stream->quit_flag) {
+        pjmedia_frame frame;
+        pj_status_t status;
+        int bytesWritten;
+
+        if (!stream->running) {
+            (*jni_env)->CallVoidMethod(jni_env, stream->track, stop_method);
+            (*jni_env)->CallVoidMethod(jni_env, stream->track, flush_method);
+            pj_sem_wait(stream->play_sem);
+            if (stream->quit_flag)
+                break;
+            (*jni_env)->CallVoidMethod(jni_env, stream->track, play_method);
+        }
+        
+        frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
+        frame.size = size;
+        frame.buf = (void *)buf;
+        frame.timestamp.u64 = stream->play_timestamp.u64;
+        frame.bit_info = 0;
+        
+        status = (*stream->play_cb)(stream->user_data, &frame);
+        if (status != PJ_SUCCESS)
+            continue;
+        
+        if (frame.type != PJMEDIA_FRAME_TYPE_AUDIO)
+            pj_bzero(frame.buf, frame.size);
+        
+        /* Write to the device output. */
+        bytesWritten = (*jni_env)->CallIntMethod(jni_env, stream->track,
+                                                 write_method, outputBuffer,
+                                                 0, size);
+        if (bytesWritten <= 0 || bytesWritten != size) {
+            PJ_LOG(4, (THIS_FILE, "Player thread: Error %d writing data",
+                                  bytesWritten));
+            continue;
+        }
+
+        stream->play_timestamp.u64 += stream->param.samples_per_frame /
+                                      stream->param.channel_count;
+    };
+    
+    (*jni_env)->ReleaseByteArrayElements(jni_env, outputBuffer, buf, 0);
+    (*jni_env)->DeleteLocalRef(jni_env, outputBuffer);
+    
+on_return:
+    detach_jvm(attached);
+    PJ_LOG(5, (THIS_FILE, "Player thread stopped"));
+    stream->play_thread_exited = 1;
+    
+    return 0;
+}
+
+/*
+ * Init Android audio driver.
+ */
+pjmedia_aud_dev_factory* pjmedia_android_factory(pj_pool_factory *pf)
+{
+    struct android_aud_factory *f;
+    pj_pool_t *pool;
+    
+    pool = pj_pool_create(pf, "androidjni", 256, 256, NULL);
+    f = PJ_POOL_ZALLOC_T(pool, struct android_aud_factory);
+    f->pf = pf;
+    f->pool = pool;
+    f->base.op = &android_op;
+    
+    return &f->base;
+}
+
+/* API: Init factory */
+static pj_status_t android_init(pjmedia_aud_dev_factory *f)
+{
+    PJ_UNUSED_ARG(f);
+    
+    PJ_LOG(4, (THIS_FILE, "Android JNI sound library initialized"));
+    
+    return PJ_SUCCESS;
+}
+
+
+/* API: refresh the list of devices */
+static pj_status_t android_refresh(pjmedia_aud_dev_factory *f)
+{
+    PJ_UNUSED_ARG(f);
+    return PJ_SUCCESS;
+}
+
+
+/* API: Destroy factory */
+static pj_status_t android_destroy(pjmedia_aud_dev_factory *f)
+{
+    struct android_aud_factory *pa = (struct android_aud_factory*)f;
+    pj_pool_t *pool;
+    
+    PJ_LOG(4, (THIS_FILE, "Android JNI sound library shutting down.."));
+    
+    pool = pa->pool;
+    pa->pool = NULL;
+    pj_pool_release(pool);
+    
+    return PJ_SUCCESS;
+}
+
+/* API: Get device count. */
+static unsigned android_get_dev_count(pjmedia_aud_dev_factory *f)
+{
+    PJ_UNUSED_ARG(f);
+    return 1;
+}
+
+/* API: Get device info. */
+static pj_status_t android_get_dev_info(pjmedia_aud_dev_factory *f,
+                                        unsigned index,
+                                        pjmedia_aud_dev_info *info)
+{
+    PJ_UNUSED_ARG(f);
+    
+    pj_bzero(info, sizeof(*info));
+    
+    pj_ansi_strcpy(info->name, "Android JNI");
+    info->default_samples_per_sec = 8000;
+    info->caps = PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING;
+    info->input_count = 1;
+    info->output_count = 1;
+    
+    return PJ_SUCCESS;
+}
+
+/* API: fill in with default parameter. */
+static pj_status_t android_default_param(pjmedia_aud_dev_factory *f,
+                                         unsigned index,
+                                         pjmedia_aud_param *param)
+{
+    pjmedia_aud_dev_info adi;
+    pj_status_t status;
+    
+    status = android_get_dev_info(f, index, &adi);
+    if (status != PJ_SUCCESS)
+	return status;
+    
+    pj_bzero(param, sizeof(*param));
+    if (adi.input_count && adi.output_count) {
+        param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
+        param->rec_id = index;
+        param->play_id = index;
+    } else if (adi.input_count) {
+        param->dir = PJMEDIA_DIR_CAPTURE;
+        param->rec_id = index;
+        param->play_id = PJMEDIA_AUD_INVALID_DEV;
+    } else if (adi.output_count) {
+        param->dir = PJMEDIA_DIR_PLAYBACK;
+        param->play_id = index;
+        param->rec_id = PJMEDIA_AUD_INVALID_DEV;
+    } else {
+        return PJMEDIA_EAUD_INVDEV;
+    }
+    
+    param->clock_rate = adi.default_samples_per_sec;
+    param->channel_count = 1;
+    param->samples_per_frame = adi.default_samples_per_sec * 20 / 1000;
+    param->bits_per_sample = 16;
+    param->input_latency_ms = PJMEDIA_SND_DEFAULT_REC_LATENCY;
+    param->output_latency_ms = PJMEDIA_SND_DEFAULT_PLAY_LATENCY;
+    
+    return PJ_SUCCESS;
+}
+
+/* API: create stream */
+static pj_status_t android_create_stream(pjmedia_aud_dev_factory *f,
+                                         const pjmedia_aud_param *param,
+                                         pjmedia_aud_rec_cb rec_cb,
+                                         pjmedia_aud_play_cb play_cb,
+                                         void *user_data,
+                                         pjmedia_aud_stream **p_aud_strm)
+{
+    struct android_aud_factory *pa = (struct android_aud_factory*)f;
+    pj_pool_t *pool;
+    struct android_aud_stream *stream;
+    pj_status_t status = PJ_SUCCESS;
+    int state = 0;
+    int buffSize, inputBuffSizePlay, inputBuffSizeRec;
+    int channelInCfg, channelOutCfg, sampleFormat;
+    jmethodID constructor_method=0, bufsize_method = 0;
+    jmethodID method_id = 0;
+    jclass jcl;
+    JNIEnv *jni_env = 0;
+    pj_bool_t attached;
+    
+    PJ_ASSERT_RETURN(param->channel_count >= 1 && param->channel_count <= 2,
+                     PJ_EINVAL);
+    PJ_ASSERT_RETURN(param->bits_per_sample==8 || param->bits_per_sample==16,
+                     PJ_EINVAL);
+    PJ_ASSERT_RETURN(play_cb && rec_cb && p_aud_strm, PJ_EINVAL);
+
+    pool = pj_pool_create(pa->pf, "jnistrm", 1024, 1024, NULL);
+    if (!pool)
+        return PJ_ENOMEM;
+
+    PJ_LOG(4, (THIS_FILE, "Creating Android JNI stream"));
+    
+    stream = PJ_POOL_ZALLOC_T(pool, struct android_aud_stream);
+    stream->pool = pool;
+    pj_strdup2_with_null(pool, &stream->name, "JNI stream");
+    stream->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
+    pj_memcpy(&stream->param, param, sizeof(*param));
+    stream->user_data = user_data;
+    stream->rec_cb = rec_cb;
+    stream->play_cb = play_cb;
+    buffSize = stream->param.samples_per_frame*stream->param.bits_per_sample/8;
+    stream->rec_buf_size = stream->play_buf_size = buffSize;
+    channelInCfg = (param->channel_count == 1)? 16 /*CHANNEL_IN_MONO*/:
+                   12 /*CHANNEL_IN_STEREO*/;
+    channelOutCfg = (param->channel_count == 1)? 4 /*CHANNEL_OUT_MONO*/:
+                    12 /*CHANNEL_OUT_STEREO*/;
+    sampleFormat = (param->bits_per_sample == 8)? 3 /*ENCODING_PCM_8BIT*/:
+                   2 /*ENCODING_PCM_16BIT*/;
+
+    attached = attach_jvm(&jni_env);
+
+    if (stream->dir & PJMEDIA_DIR_CAPTURE) {
+        /* Find audio record class and create global ref */
+        jcl = (*jni_env)->FindClass(jni_env, "android/media/AudioRecord");
+        if (jcl == NULL) {
+            PJ_LOG(3, (THIS_FILE, "Unable to find audio record class"));
+            status = PJMEDIA_EAUD_SYSERR;
+            goto on_error;
+        }
+        stream->record_class = (jclass)(*jni_env)->NewGlobalRef(jni_env, jcl);
+        (*jni_env)->DeleteLocalRef(jni_env, jcl);
+        if (stream->record_class == 0) {
+            status = PJ_ENOMEM;
+            goto on_error;
+        }
+
+        /* Get the min buffer size function */
+        bufsize_method = (*jni_env)->GetStaticMethodID(jni_env,
+                                                       stream->record_class,
+                                                       "getMinBufferSize",
+                                                       "(III)I");
+        if (bufsize_method == 0) {
+            PJ_LOG(3, (THIS_FILE, "Unable to find audio record "
+                                  "getMinBufferSize() method"));
+            status = PJMEDIA_EAUD_SYSERR;
+            goto on_error;
+        }
+
+        inputBuffSizeRec = (*jni_env)->CallStaticIntMethod(jni_env,
+                                                           stream->record_class,
+                                                           bufsize_method,
+                                                           param->clock_rate,
+                                                           channelInCfg,
+                                                           sampleFormat);
+        if (inputBuffSizeRec <= 0) {
+            PJ_LOG(3, (THIS_FILE, "Unsupported audio record params"));
+            status = PJMEDIA_EAUD_INIT;
+            goto on_error;
+        }
+    }
+    
+    if (stream->dir & PJMEDIA_DIR_PLAYBACK) {
+        /* Find audio track class and create global ref */
+        jcl = (*jni_env)->FindClass(jni_env, "android/media/AudioTrack");
+        if (jcl == NULL) {
+            PJ_LOG(3, (THIS_FILE, "Unable to find audio track class"));
+            status = PJMEDIA_EAUD_SYSERR;
+            goto on_error;
+        }
+        stream->track_class = (jclass)(*jni_env)->NewGlobalRef(jni_env, jcl);
+        (*jni_env)->DeleteLocalRef(jni_env, jcl);
+        if (stream->track_class == 0) {
+            status = PJ_ENOMEM;
+            goto on_error;
+        }
+
+        /* Get the min buffer size function */
+        bufsize_method = (*jni_env)->GetStaticMethodID(jni_env,
+                                                       stream->track_class,
+                                                       "getMinBufferSize",
+                                                       "(III)I");
+        if (bufsize_method == 0) {
+            PJ_LOG(3, (THIS_FILE, "Unable to find audio track "
+                                  "getMinBufferSize() method"));
+            status = PJMEDIA_EAUD_SYSERR;
+            goto on_error;
+        }
+        
+        inputBuffSizePlay = (*jni_env)->CallStaticIntMethod(jni_env,
+                                                            stream->track_class,
+                                                            bufsize_method,
+                                                            param->clock_rate,
+                                                            channelOutCfg,
+                                                            sampleFormat);
+        if (inputBuffSizePlay <= 0) {
+            PJ_LOG(3, (THIS_FILE, "Unsupported audio track params"));
+            status = PJMEDIA_EAUD_INIT;
+            goto on_error;
+        }
+    }
+    
+    if (stream->dir & PJMEDIA_DIR_CAPTURE) {
+        jthrowable exc;
+        int mic_source = 0; /* DEFAULT: default audio source */
+
+        /* Get pointer to the constructor */
+        constructor_method = (*jni_env)->GetMethodID(jni_env,
+                                                     stream->record_class,
+                                                     "<init>", "(IIIII)V");
+        if (constructor_method == 0) {
+            PJ_LOG(3, (THIS_FILE, "Unable to find audio record's constructor"));
+            status = PJMEDIA_EAUD_SYSERR;
+            goto on_error;
+        }
+        
+        if (mic_source == 0) {
+            char sdk_version[PROP_VALUE_MAX];
+            pj_str_t pj_sdk_version;
+            int sdk_v;
+
+            __system_property_get("ro.build.version.sdk", sdk_version);
+            pj_sdk_version = pj_str(sdk_version);
+            sdk_v = pj_strtoul(&pj_sdk_version);
+            if (sdk_v > 10)
+                mic_source = 7; /* VOICE_COMMUNICATION */
+        }
+        PJ_LOG(4, (THIS_FILE, "Using audio input source : %d", mic_source));
+        
+        do {
+            stream->record =  (*jni_env)->NewObject(jni_env,
+                                                    stream->record_class,
+                                                    constructor_method,
+                                                    mic_source, 
+                                                    param->clock_rate,
+                                                    channelInCfg,
+                                                    sampleFormat,
+                                                    inputBuffSizeRec);
+            if (stream->record == 0) {
+                PJ_LOG(3, (THIS_FILE, "Unable to create audio record object"));
+                status = PJMEDIA_EAUD_INIT;
+                goto on_error;
+            }
+        
+            exc = (*jni_env)->ExceptionOccurred(jni_env);
+            if (exc) {
+                (*jni_env)->ExceptionDescribe(jni_env);
+                (*jni_env)->ExceptionClear(jni_env);
+                PJ_LOG(3, (THIS_FILE, "Failure in audio record's constructor"));
+                if (mic_source == 0) {
+                    status = PJMEDIA_EAUD_INIT;
+                    goto on_error;
+                }
+                mic_source = 0;
+                PJ_LOG(4, (THIS_FILE, "Trying the default audio source."));
+                continue;
+            }
+
+            /* Check state */
+            method_id = (*jni_env)->GetMethodID(jni_env, stream->record_class,
+                                                "getState", "()I");
+            if (method_id == 0) {
+                PJ_LOG(3, (THIS_FILE, "Unable to find audio record getState() "
+                                      "method"));
+                status = PJMEDIA_EAUD_SYSERR;
+                goto on_error;
+            }
+            state = (*jni_env)->CallIntMethod(jni_env, stream->record,
+                                              method_id);
+            if (state == 0) { /* STATE_UNINITIALIZED */
+                PJ_LOG(3, (THIS_FILE, "Failure in initializing audio record."));
+                if (mic_source == 0) {
+                    status = PJMEDIA_EAUD_INIT;
+                    goto on_error;
+                }
+                mic_source = 0;
+                PJ_LOG(4, (THIS_FILE, "Trying the default audio source."));
+            }
+        } while (state == 0);
+        
+        stream->record = (*jni_env)->NewGlobalRef(jni_env, stream->record);
+        if (stream->record == 0) {
+            PJ_LOG(3, (THIS_FILE, "Unable to create audio record global ref."));
+            status = PJMEDIA_EAUD_INIT;
+            goto on_error;
+        }
+
+        status = pj_sem_create(stream->pool, NULL, 0, 1, &stream->rec_sem);
+        if (status != PJ_SUCCESS)
+            goto on_error;
+        
+        status = pj_thread_create(stream->pool, "android_recorder",
+                                  AndroidRecorderCallback, stream, 0, 0,
+                                  &stream->rec_thread);
+        if (status != PJ_SUCCESS)
+            goto on_error;
+
+        PJ_LOG(4, (THIS_FILE, "Audio record initialized successfully."));
+    }
+    
+    if (stream->dir & PJMEDIA_DIR_PLAYBACK) {
+        jthrowable exc;
+        
+        /* Get pointer to the constructor */
+        constructor_method = (*jni_env)->GetMethodID(jni_env,
+                                                     stream->track_class,
+                                                     "<init>", "(IIIIII)V");
+        if (constructor_method == 0) {
+            PJ_LOG(3, (THIS_FILE, "Unable to find audio track's constructor."));
+            status = PJMEDIA_EAUD_SYSERR;
+            goto on_error;
+        }
+        
+        stream->track = (*jni_env)->NewObject(jni_env,
+                                              stream->track_class,
+                                              constructor_method,
+                                              0, /* STREAM_VOICE_CALL */
+                                              param->clock_rate,
+                                              channelOutCfg,
+                                              sampleFormat,
+                                              inputBuffSizePlay,
+                                              1 /* MODE_STREAM */);
+        if (stream->track == 0) {
+            PJ_LOG(3, (THIS_FILE, "Unable to create audio track object."));
+            status = PJMEDIA_EAUD_INIT;
+            goto on_error;
+        }
+        
+        exc = (*jni_env)->ExceptionOccurred(jni_env);
+        if (exc) {
+            (*jni_env)->ExceptionDescribe(jni_env);
+            (*jni_env)->ExceptionClear(jni_env);
+            PJ_LOG(3, (THIS_FILE, "Failure in audio track's constructor"));
+            status = PJMEDIA_EAUD_INIT;
+            goto on_error;
+        }
+        
+        stream->track = (*jni_env)->NewGlobalRef(jni_env, stream->track);
+        if (stream->track == 0) {
+            PJ_LOG(3, (THIS_FILE, "Unable to create audio track's global ref"));
+            status = PJMEDIA_EAUD_INIT;
+            goto on_error;
+        }
+        
+        /* Check state */
+        method_id = (*jni_env)->GetMethodID(jni_env, stream->track_class,
+                                            "getState", "()I");
+        if (method_id == 0) {
+            PJ_LOG(3, (THIS_FILE, "Unable to find audio track getState() "
+                                  "method"));
+            status = PJMEDIA_EAUD_SYSERR;
+            goto on_error;
+        }
+        state = (*jni_env)->CallIntMethod(jni_env, stream->track,
+                                          method_id);
+        if (state == 0) { /* STATE_UNINITIALIZED */
+            PJ_LOG(3, (THIS_FILE, "Failure in initializing audio track."));
+            status = PJMEDIA_EAUD_INIT;
+            goto on_error;
+        }
+
+        status = pj_sem_create(stream->pool, NULL, 0, 1, &stream->play_sem);
+        if (status != PJ_SUCCESS)
+            goto on_error;
+        
+        status = pj_thread_create(stream->pool, "android_track",
+                                  AndroidTrackCallback, stream, 0, 0,
+                                  &stream->play_thread);
+        if (status != PJ_SUCCESS)
+            goto on_error;
+        
+        PJ_LOG(4, (THIS_FILE, "Audio track initialized successfully."));
+    }
+
+    if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING) {
+	strm_set_cap(&stream->base, PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING,
+                     &param->output_vol);
+    }
+    
+    /* Done */
+    stream->base.op = &android_strm_op;
+    *p_aud_strm = &stream->base;
+    
+    detach_jvm(attached);
+    
+    return PJ_SUCCESS;
+    
+on_error:
+    detach_jvm(attached);
+    strm_destroy(&stream->base);
+    return status;
+}
+
+/* API: Get stream parameters */
+static pj_status_t strm_get_param(pjmedia_aud_stream *s,
+                                  pjmedia_aud_param *pi)
+{
+    struct android_aud_stream *strm = (struct android_aud_stream*)s;
+    PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
+    pj_memcpy(pi, &strm->param, sizeof(*pi));
+
+    if (strm_get_cap(s, PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING,
+                     &pi->output_vol) == PJ_SUCCESS)
+    {
+        pi->flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING;
+    }    
+    
+    return PJ_SUCCESS;
+}
+
+/* API: get capability */
+static pj_status_t strm_get_cap(pjmedia_aud_stream *s,
+                                pjmedia_aud_dev_cap cap,
+                                void *pval)
+{
+    struct android_aud_stream *strm = (struct android_aud_stream*)s;
+    pj_status_t status = PJMEDIA_EAUD_INVCAP;
+    
+    PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+    
+    if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING &&
+	(strm->param.dir & PJMEDIA_DIR_PLAYBACK))
+    {
+    }
+    
+    return status;
+}
+
+/* API: set capability */
+static pj_status_t strm_set_cap(pjmedia_aud_stream *s,
+                                pjmedia_aud_dev_cap cap,
+                                const void *value)
+{
+    struct android_aud_stream *stream = (struct android_aud_stream*)s;
+    JNIEnv *jni_env = 0;
+    pj_bool_t attached;
+    
+    PJ_ASSERT_RETURN(s && value, PJ_EINVAL);
+    
+    if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING &&
+	(stream->param.dir & PJMEDIA_DIR_PLAYBACK))
+    {
+        if (stream->track) {
+            jmethodID vol_method = 0;
+            int retval;
+            float vol = *(int *)value;
+            
+            attached = attach_jvm(&jni_env);
+            
+            vol_method = (*jni_env)->GetMethodID(jni_env, stream->track_class,
+                                                 "setStereoVolume", "(FF)I");
+            if (vol_method) {
+                retval = (*jni_env)->CallIntMethod(jni_env, stream->track,
+                                                   vol_method,
+                                                   vol/100, vol/100);
+            }
+            
+            detach_jvm(attached);
+            
+            if (vol_method && retval == 0)
+                return PJ_SUCCESS;
+        }
+    }
+    
+    return PJMEDIA_EAUD_INVCAP;
+}
+
+/* API: start stream. */
+static pj_status_t strm_start(pjmedia_aud_stream *s)
+{
+    struct android_aud_stream *stream = (struct android_aud_stream*)s;    
+    
+    if (!stream->running) {
+        stream->running = PJ_TRUE;
+        if (stream->record)
+            pj_sem_post(stream->rec_sem);
+        if (stream->track)
+            pj_sem_post(stream->play_sem);
+    }
+    
+    PJ_LOG(4, (THIS_FILE, "Android JNI stream started"));
+    
+    return PJ_SUCCESS;
+}
+
+/* API: stop stream. */
+static pj_status_t strm_stop(pjmedia_aud_stream *s)
+{
+    struct android_aud_stream *stream = (struct android_aud_stream*)s;
+
+    if (!stream->running)
+        return PJ_SUCCESS;
+    
+    stream->running = PJ_FALSE;
+    PJ_LOG(4,(THIS_FILE, "Android JNI stream stopped"));
+    
+    return PJ_SUCCESS;
+}
+
+/* API: destroy stream. */
+static pj_status_t strm_destroy(pjmedia_aud_stream *s)
+{
+    struct android_aud_stream *stream = (struct android_aud_stream*)s;
+    JNIEnv *jni_env = 0;
+    jmethodID release_method=0;
+    pj_bool_t attached;
+    
+    PJ_LOG(4,(THIS_FILE, "Destroying Android JNI stream..."));
+    
+    stream->quit_flag = PJ_TRUE;
+    
+    /* Stop the stream */
+    strm_stop(s);
+    
+    attached = attach_jvm(&jni_env);
+    
+    if (stream->record){
+        if (stream->rec_thread) {
+            pj_sem_post(stream->rec_sem);
+            pj_thread_join(stream->rec_thread);
+            pj_thread_destroy(stream->rec_thread);
+            stream->rec_thread = NULL;
+        }
+        
+        if (stream->rec_sem) {
+            pj_sem_destroy(stream->rec_sem);
+            stream->rec_sem = NULL;
+        }
+        
+        release_method = (*jni_env)->GetMethodID(jni_env, stream->record_class,
+                                                 "release", "()V");
+        (*jni_env)->CallVoidMethod(jni_env, stream->record, release_method);
+        
+        (*jni_env)->DeleteGlobalRef(jni_env, stream->record);
+        (*jni_env)->DeleteGlobalRef(jni_env, stream->record_class);
+        stream->record = NULL;
+        stream->record_class = NULL;
+        PJ_LOG(4, (THIS_FILE, "Audio record released"));
+    }
+    
+    if (stream->track) {
+        if (stream->play_thread) {
+            pj_sem_post(stream->play_sem);
+            pj_thread_join(stream->play_thread);
+            pj_thread_destroy(stream->play_thread);
+            stream->play_thread = NULL;
+        }
+        
+        if (stream->play_sem) {
+            pj_sem_destroy(stream->play_sem);
+            stream->play_sem = NULL;
+        }
+        
+        release_method = (*jni_env)->GetMethodID(jni_env, stream->track_class,
+                                                 "release", "()V");
+        (*jni_env)->CallVoidMethod(jni_env, stream->track, release_method);
+        
+        (*jni_env)->DeleteGlobalRef(jni_env, stream->track);
+        (*jni_env)->DeleteGlobalRef(jni_env, stream->track_class);
+        stream->track = NULL;
+        stream->track_class = NULL;
+        PJ_LOG(3, (THIS_FILE, "Audio track released"));
+    }
+
+    pj_pool_release(stream->pool);
+    PJ_LOG(4, (THIS_FILE, "Android JNI stream destroyed"));
+    
+    detach_jvm(attached);
+    return PJ_SUCCESS;
+}
+
+#endif	/* PJMEDIA_AUDIO_DEV_HAS_ANDROID_JNI */