blob: 6025c8ccf33646d0009374cc0804b7f20c44d2a5 [file] [log] [blame]
/* $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 */