/* $Id$ */ /* * 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 #include #include #include #include #include #if defined(PJMEDIA_AUDIO_DEV_HAS_ANDROID_JNI) && \ PJMEDIA_AUDIO_DEV_HAS_ANDROID_JNI != 0 #include #include #include #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, "", "(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, "", "(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, ¶m->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 */