summaryrefslogtreecommitdiff
path: root/pjmedia
diff options
context:
space:
mode:
authorNanang Izzuddin <nanang@teluu.com>2013-03-11 06:32:58 +0000
committerNanang Izzuddin <nanang@teluu.com>2013-03-11 06:32:58 +0000
commit92de554adbf95a303f1051d85bef05abac9f1e8a (patch)
tree17019c03731e797280398e58b99044862341834a /pjmedia
parent2c2752f536e55bff7055eed2ed8e558163e1f9b6 (diff)
Re #1639: merged android branch into trunk (except sample apps, i.e: apjsua & apjloader)
git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@4435 74dad513-b988-da41-8d7b-12977e46ad98
Diffstat (limited to 'pjmedia')
-rw-r--r--pjmedia/build/Makefile3
-rw-r--r--pjmedia/include/pjmedia-audiodev/config.h16
-rw-r--r--pjmedia/src/pjmedia-audiodev/android_jni_dev.c1034
-rw-r--r--pjmedia/src/pjmedia-audiodev/audiodev.c14
-rw-r--r--pjmedia/src/pjmedia-audiodev/opensl_dev.c949
5 files changed, 2015 insertions, 1 deletions
diff --git a/pjmedia/build/Makefile b/pjmedia/build/Makefile
index d30e6bca..39b8d65a 100644
--- a/pjmedia/build/Makefile
+++ b/pjmedia/build/Makefile
@@ -81,7 +81,8 @@ export PJMEDIA_CFLAGS += $(_CFLAGS)
export PJMEDIA_AUDIODEV_SRCDIR = ../src/pjmedia-audiodev
export PJMEDIA_AUDIODEV_OBJS += audiodev.o audiotest.o errno.o \
coreaudio_dev.o legacy_dev.o null_dev.o pa_dev.o wmme_dev.o \
- alsa_dev.o bb10_dev.o bdimad_dev.o
+ alsa_dev.o bb10_dev.o bdimad_dev.o \
+ android_jni_dev.o opensl_dev.o
export PJMEDIA_AUDIODEV_CFLAGS += $(_CFLAGS)
diff --git a/pjmedia/include/pjmedia-audiodev/config.h b/pjmedia/include/pjmedia-audiodev/config.h
index 6ba4605a..66f45a72 100644
--- a/pjmedia/include/pjmedia-audiodev/config.h
+++ b/pjmedia/include/pjmedia-audiodev/config.h
@@ -58,6 +58,22 @@ PJ_BEGIN_DECL
#endif
/**
+ * This setting controls whether Android OpenSL audio support should be
+ * included.
+ */
+#ifndef PJMEDIA_AUDIO_DEV_HAS_OPENSL
+# define PJMEDIA_AUDIO_DEV_HAS_OPENSL 0
+#endif
+
+/**
+ * This setting controls whether Android JNI audio support should be
+ * included.
+ */
+#ifndef PJMEDIA_AUDIO_DEV_HAS_ANDROID_JNI
+# define PJMEDIA_AUDIO_DEV_HAS_ANDROID_JNI 0
+#endif
+
+/**
* This setting controls whether BlackBerry 10 (BB10) audio support
* should be included.
*/
diff --git a/pjmedia/src/pjmedia-audiodev/android_jni_dev.c b/pjmedia/src/pjmedia-audiodev/android_jni_dev.c
new file mode 100644
index 00000000..255663a4
--- /dev/null
+++ b/pjmedia/src/pjmedia-audiodev/android_jni_dev.c
@@ -0,0 +1,1034 @@
+/* $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 <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 */
diff --git a/pjmedia/src/pjmedia-audiodev/audiodev.c b/pjmedia/src/pjmedia-audiodev/audiodev.c
index f9ee4175..4ccce9f8 100644
--- a/pjmedia/src/pjmedia-audiodev/audiodev.c
+++ b/pjmedia/src/pjmedia-audiodev/audiodev.c
@@ -78,6 +78,14 @@ pjmedia_aud_dev_factory* pjmedia_coreaudio_factory(pj_pool_factory *pf);
pjmedia_aud_dev_factory* pjmedia_alsa_factory(pj_pool_factory *pf);
#endif
+#if PJMEDIA_AUDIO_DEV_HAS_OPENSL
+pjmedia_aud_dev_factory* pjmedia_opensl_factory(pj_pool_factory *pf);
+#endif
+
+#if PJMEDIA_AUDIO_DEV_HAS_ANDROID_JNI
+pjmedia_aud_dev_factory* pjmedia_android_factory(pj_pool_factory *pf);
+#endif
+
#if PJMEDIA_AUDIO_DEV_HAS_BB10
pjmedia_aud_dev_factory* pjmedia_bb10_factory(pj_pool_factory *pf);
#endif
@@ -395,6 +403,12 @@ PJ_DEF(pj_status_t) pjmedia_aud_subsys_init(pj_pool_factory *pf)
aud_subsys.dev_cnt = 0;
/* Register creation functions */
+#if PJMEDIA_AUDIO_DEV_HAS_OPENSL
+ aud_subsys.drv[aud_subsys.drv_cnt++].create = &pjmedia_opensl_factory;
+#endif
+#if PJMEDIA_AUDIO_DEV_HAS_ANDROID_JNI
+ aud_subsys.drv[aud_subsys.drv_cnt++].create = &pjmedia_android_factory;
+#endif
#if PJMEDIA_AUDIO_DEV_HAS_BB10
aud_subsys.drv[aud_subsys.drv_cnt++].create = &pjmedia_bb10_factory;
#endif
diff --git a/pjmedia/src/pjmedia-audiodev/opensl_dev.c b/pjmedia/src/pjmedia-audiodev/opensl_dev.c
new file mode 100644
index 00000000..a77340b4
--- /dev/null
+++ b/pjmedia/src/pjmedia-audiodev/opensl_dev.c
@@ -0,0 +1,949 @@
+/* $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 OpenSL ES 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_OPENSL) && PJMEDIA_AUDIO_DEV_HAS_OPENSL != 0
+
+#include <SLES/OpenSLES.h>
+
+#ifdef __ANDROID__
+ #include <SLES/OpenSLES_Android.h>
+ #include <SLES/OpenSLES_AndroidConfiguration.h>
+ #include <sys/system_properties.h>
+ #include <android/api-level.h>
+
+ #define W_SLBufferQueueItf SLAndroidSimpleBufferQueueItf
+ #define W_SLBufferQueueState SLAndroidSimpleBufferQueueState
+ #define W_SL_IID_BUFFERQUEUE SL_IID_ANDROIDSIMPLEBUFFERQUEUE
+#else
+ #define W_SLBufferQueueItf SLBufferQueueItf
+ #define W_SLBufferQueueState SLBufferQueueState
+ #define W_SL_IID_BUFFERQUEUE SL_IID_BUFFERQUEUE
+#endif
+
+#define THIS_FILE "opensl_dev.c"
+#define DRIVER_NAME "OpenSL"
+
+#define NUM_BUFFERS 2
+
+struct opensl_aud_factory
+{
+ pjmedia_aud_dev_factory base;
+ pj_pool_factory *pf;
+ pj_pool_t *pool;
+
+ SLObjectItf engineObject;
+ SLEngineItf engineEngine;
+ SLObjectItf outputMixObject;
+};
+
+/*
+ * Sound stream descriptor.
+ * This struct may be used for both unidirectional or bidirectional sound
+ * streams.
+ */
+struct opensl_aud_stream
+{
+ pjmedia_aud_stream base;
+ pj_pool_t *pool;
+ pj_str_t name;
+ pjmedia_dir dir;
+ pjmedia_aud_param param;
+
+ void *user_data;
+ pj_bool_t quit_flag;
+ pjmedia_aud_rec_cb rec_cb;
+ pjmedia_aud_play_cb play_cb;
+
+ pj_timestamp play_timestamp;
+ pj_timestamp rec_timestamp;
+
+ pj_bool_t rec_thread_initialized;
+ pj_thread_desc rec_thread_desc;
+ pj_thread_t *rec_thread;
+
+ pj_bool_t play_thread_initialized;
+ pj_thread_desc play_thread_desc;
+ pj_thread_t *play_thread;
+
+ /* Player */
+ SLObjectItf playerObj;
+ SLPlayItf playerPlay;
+ SLVolumeItf playerVol;
+ unsigned playerBufferSize;
+ char *playerBuffer[NUM_BUFFERS];
+ int playerBufIdx;
+
+ /* Recorder */
+ SLObjectItf recordObj;
+ SLRecordItf recordRecord;
+ unsigned recordBufferSize;
+ char *recordBuffer[NUM_BUFFERS];
+ int recordBufIdx;
+
+ W_SLBufferQueueItf playerBufQ;
+ W_SLBufferQueueItf recordBufQ;
+};
+
+/* Factory prototypes */
+static pj_status_t opensl_init(pjmedia_aud_dev_factory *f);
+static pj_status_t opensl_destroy(pjmedia_aud_dev_factory *f);
+static pj_status_t opensl_refresh(pjmedia_aud_dev_factory *f);
+static unsigned opensl_get_dev_count(pjmedia_aud_dev_factory *f);
+static pj_status_t opensl_get_dev_info(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_dev_info *info);
+static pj_status_t opensl_default_param(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_param *param);
+static pj_status_t opensl_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 opensl_op =
+{
+ &opensl_init,
+ &opensl_destroy,
+ &opensl_get_dev_count,
+ &opensl_get_dev_info,
+ &opensl_default_param,
+ &opensl_create_stream,
+ &opensl_refresh
+};
+
+static pjmedia_aud_stream_op opensl_strm_op =
+{
+ &strm_get_param,
+ &strm_get_cap,
+ &strm_set_cap,
+ &strm_start,
+ &strm_stop,
+ &strm_destroy
+};
+
+/* This callback is called every time a buffer finishes playing. */
+void bqPlayerCallback(W_SLBufferQueueItf bq, void *context)
+{
+ struct opensl_aud_stream *stream = (struct opensl_aud_stream*) context;
+ SLresult result;
+ int status;
+
+ pj_assert(context != NULL);
+ pj_assert(bq == stream->playerBufQ);
+
+ if (stream->play_thread_initialized == 0 || !pj_thread_is_registered())
+ {
+ pj_bzero(stream->play_thread_desc, sizeof(pj_thread_desc));
+ status = pj_thread_register("opensl_play", stream->play_thread_desc,
+ &stream->play_thread);
+ stream->play_thread_initialized = 1;
+ PJ_LOG(5, (THIS_FILE, "Player thread started"));
+ }
+
+ if (!stream->quit_flag) {
+ pjmedia_frame frame;
+ char * buf;
+
+ frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
+ frame.buf = buf = stream->playerBuffer[stream->playerBufIdx++];
+ frame.size = stream->playerBufferSize;
+ frame.timestamp.u64 = stream->play_timestamp.u64;
+ frame.bit_info = 0;
+
+ status = (*stream->play_cb)(stream->user_data, &frame);
+ if (status != PJ_SUCCESS || frame.type != PJMEDIA_FRAME_TYPE_AUDIO)
+ pj_bzero(buf, stream->playerBufferSize);
+
+ stream->play_timestamp.u64 += stream->param.samples_per_frame /
+ stream->param.channel_count;
+
+ result = (*bq)->Enqueue(bq, buf, stream->playerBufferSize);
+ if (result != SL_RESULT_SUCCESS) {
+ PJ_LOG(3, (THIS_FILE, "Unable to enqueue next player buffer !!! %d",
+ result));
+ }
+
+ stream->playerBufIdx %= NUM_BUFFERS;
+ }
+}
+
+/* This callback handler is called every time a buffer finishes recording */
+void bqRecorderCallback(W_SLBufferQueueItf bq, void *context)
+{
+ struct opensl_aud_stream *stream = (struct opensl_aud_stream*) context;
+ SLresult result;
+ int status;
+
+ pj_assert(context != NULL);
+ pj_assert(bq == stream->recordBufQ);
+
+ if (stream->rec_thread_initialized == 0 || !pj_thread_is_registered())
+ {
+ pj_bzero(stream->rec_thread_desc, sizeof(pj_thread_desc));
+ status = pj_thread_register("opensl_rec", stream->rec_thread_desc,
+ &stream->rec_thread);
+ stream->rec_thread_initialized = 1;
+ PJ_LOG(5, (THIS_FILE, "Recorder thread started"));
+ }
+
+ if (!stream->quit_flag) {
+ pjmedia_frame frame;
+ char *buf;
+
+ frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
+ frame.buf = buf = stream->recordBuffer[stream->recordBufIdx++];
+ frame.size = stream->recordBufferSize;
+ frame.timestamp.u64 = stream->rec_timestamp.u64;
+ frame.bit_info = 0;
+
+ status = (*stream->rec_cb)(stream->user_data, &frame);
+
+ stream->rec_timestamp.u64 += stream->param.samples_per_frame /
+ stream->param.channel_count;
+
+ /* And now enqueue next buffer */
+ result = (*bq)->Enqueue(bq, buf, stream->recordBufferSize);
+ if (result != SL_RESULT_SUCCESS) {
+ PJ_LOG(3, (THIS_FILE, "Unable to enqueue next record buffer !!! %d",
+ result));
+ }
+
+ stream->recordBufIdx %= NUM_BUFFERS;
+ }
+}
+
+pj_status_t opensl_to_pj_error(SLresult code)
+{
+ switch(code) {
+ case SL_RESULT_SUCCESS:
+ return PJ_SUCCESS;
+ case SL_RESULT_PRECONDITIONS_VIOLATED:
+ case SL_RESULT_PARAMETER_INVALID:
+ case SL_RESULT_CONTENT_CORRUPTED:
+ case SL_RESULT_FEATURE_UNSUPPORTED:
+ return PJMEDIA_EAUD_INVOP;
+ case SL_RESULT_MEMORY_FAILURE:
+ case SL_RESULT_BUFFER_INSUFFICIENT:
+ return PJ_ENOMEM;
+ case SL_RESULT_RESOURCE_ERROR:
+ case SL_RESULT_RESOURCE_LOST:
+ case SL_RESULT_CONTROL_LOST:
+ return PJMEDIA_EAUD_NOTREADY;
+ case SL_RESULT_CONTENT_UNSUPPORTED:
+ return PJ_ENOTSUP;
+ default:
+ return PJMEDIA_EAUD_ERR;
+ }
+}
+
+/* Init Android audio driver. */
+pjmedia_aud_dev_factory* pjmedia_opensl_factory(pj_pool_factory *pf)
+{
+ struct opensl_aud_factory *f;
+ pj_pool_t *pool;
+
+ pool = pj_pool_create(pf, "opensles", 256, 256, NULL);
+ f = PJ_POOL_ZALLOC_T(pool, struct opensl_aud_factory);
+ f->pf = pf;
+ f->pool = pool;
+ f->base.op = &opensl_op;
+
+ return &f->base;
+}
+
+/* API: Init factory */
+static pj_status_t opensl_init(pjmedia_aud_dev_factory *f)
+{
+ struct opensl_aud_factory *pa = (struct opensl_aud_factory*)f;
+ SLresult result;
+
+ /* Create engine */
+ result = slCreateEngine(&pa->engineObject, 0, NULL, 0, NULL, NULL);
+ if (result != SL_RESULT_SUCCESS) {
+ PJ_LOG(3, (THIS_FILE, "Cannot create engine %d ", result));
+ return opensl_to_pj_error(result);
+ }
+
+ /* Realize the engine */
+ result = (*pa->engineObject)->Realize(pa->engineObject, SL_BOOLEAN_FALSE);
+ if (result != SL_RESULT_SUCCESS) {
+ PJ_LOG(3, (THIS_FILE, "Cannot realize engine"));
+ opensl_destroy(f);
+ return opensl_to_pj_error(result);
+ }
+
+ /* Get the engine interface, which is needed in order to create
+ * other objects.
+ */
+ result = (*pa->engineObject)->GetInterface(pa->engineObject,
+ SL_IID_ENGINE,
+ &pa->engineEngine);
+ if (result != SL_RESULT_SUCCESS) {
+ PJ_LOG(3, (THIS_FILE, "Cannot get engine interface"));
+ opensl_destroy(f);
+ return opensl_to_pj_error(result);
+ }
+
+ /* Create output mix */
+ result = (*pa->engineEngine)->CreateOutputMix(pa->engineEngine,
+ &pa->outputMixObject,
+ 0, NULL, NULL);
+ if (result != SL_RESULT_SUCCESS) {
+ PJ_LOG(3, (THIS_FILE, "Cannot create output mix"));
+ opensl_destroy(f);
+ return opensl_to_pj_error(result);
+ }
+
+ /* Realize the output mix */
+ result = (*pa->outputMixObject)->Realize(pa->outputMixObject,
+ SL_BOOLEAN_FALSE);
+ if (result != SL_RESULT_SUCCESS) {
+ PJ_LOG(3, (THIS_FILE, "Cannot realize output mix"));
+ opensl_destroy(f);
+ return opensl_to_pj_error(result);
+ }
+
+ PJ_LOG(4,(THIS_FILE, "OpenSL sound library initialized"));
+ return PJ_SUCCESS;
+}
+
+/* API: Destroy factory */
+static pj_status_t opensl_destroy(pjmedia_aud_dev_factory *f)
+{
+ struct opensl_aud_factory *pa = (struct opensl_aud_factory*)f;
+ pj_pool_t *pool;
+
+ PJ_LOG(4,(THIS_FILE, "OpenSL sound library shutting down.."));
+
+ /* Destroy Output Mix object */
+ if (pa->outputMixObject) {
+ (*pa->outputMixObject)->Destroy(pa->outputMixObject);
+ pa->outputMixObject = NULL;
+ }
+
+ /* Destroy engine object, and invalidate all associated interfaces */
+ if (pa->engineObject) {
+ (*pa->engineObject)->Destroy(pa->engineObject);
+ pa->engineObject = NULL;
+ pa->engineEngine = NULL;
+ }
+
+ pool = pa->pool;
+ pa->pool = NULL;
+ pj_pool_release(pool);
+
+ return PJ_SUCCESS;
+}
+
+/* API: refresh the list of devices */
+static pj_status_t opensl_refresh(pjmedia_aud_dev_factory *f)
+{
+ PJ_UNUSED_ARG(f);
+ return PJ_SUCCESS;
+}
+
+/* API: Get device count. */
+static unsigned opensl_get_dev_count(pjmedia_aud_dev_factory *f)
+{
+ PJ_UNUSED_ARG(f);
+ return 1;
+}
+
+/* API: Get device info. */
+static pj_status_t opensl_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, "OpenSL ES Audio");
+ 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 opensl_default_param(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_param *param)
+{
+
+ pjmedia_aud_dev_info adi;
+ pj_status_t status;
+
+ status = opensl_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 opensl_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)
+{
+ /* Audio sink for recorder and audio source for player */
+#ifdef __ANDROID__
+ SLDataLocator_AndroidSimpleBufferQueue loc_bq =
+ { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, NUM_BUFFERS };
+#else
+ SLDataLocator_BufferQueue loc_bq =
+ { SL_DATALOCATOR_BUFFERQUEUE, NUM_BUFFERS };
+#endif
+ struct opensl_aud_factory *pa = (struct opensl_aud_factory*)f;
+ pj_pool_t *pool;
+ struct opensl_aud_stream *stream;
+ pj_status_t status = PJ_SUCCESS;
+ int i, bufferSize;
+ SLresult result;
+ SLDataFormat_PCM format_pcm;
+
+ /* Only supports for mono channel for now */
+ PJ_ASSERT_RETURN(param->channel_count == 1, PJ_EINVAL);
+ PJ_ASSERT_RETURN(play_cb && rec_cb && p_aud_strm, PJ_EINVAL);
+
+ PJ_LOG(4,(THIS_FILE, "Creating OpenSL stream"));
+
+ pool = pj_pool_create(pa->pf, "openslstrm", 1024, 1024, NULL);
+ if (!pool)
+ return PJ_ENOMEM;
+
+ stream = PJ_POOL_ZALLOC_T(pool, struct opensl_aud_stream);
+ stream->pool = pool;
+ pj_strdup2_with_null(pool, &stream->name, "OpenSL");
+ 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;
+ bufferSize = param->samples_per_frame * param->bits_per_sample / 8;
+
+ /* Configure audio PCM format */
+ format_pcm.formatType = SL_DATAFORMAT_PCM;
+ format_pcm.numChannels = param->channel_count;
+ /* Here samples per sec should be supported else we will get an error */
+ format_pcm.samplesPerSec = (SLuint32) param->clock_rate * 1000;
+ format_pcm.bitsPerSample = (SLuint16) param->bits_per_sample;
+ format_pcm.containerSize = (SLuint16) param->bits_per_sample;
+ format_pcm.channelMask = SL_SPEAKER_FRONT_CENTER;
+ format_pcm.endianness = SL_BYTEORDER_LITTLEENDIAN;
+
+ if (stream->dir & PJMEDIA_DIR_PLAYBACK) {
+ /* Audio source */
+ SLDataSource audioSrc = {&loc_bq, &format_pcm};
+ /* Audio sink */
+ SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX,
+ pa->outputMixObject};
+ SLDataSink audioSnk = {&loc_outmix, NULL};
+ /* Audio interface */
+#ifdef __ANDROID__
+ int numIface = 3;
+ const SLInterfaceID ids[3] = {SL_IID_BUFFERQUEUE,
+ SL_IID_VOLUME,
+ SL_IID_ANDROIDCONFIGURATION};
+ const SLboolean req[3] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE,
+ SL_BOOLEAN_TRUE};
+ SLAndroidConfigurationItf playerConfig;
+ SLint32 streamType = SL_ANDROID_STREAM_VOICE;
+#else
+ int numIface = 2;
+ const SLInterfaceID ids[2] = {SL_IID_BUFFERQUEUE,
+ SL_IID_VOLUME};
+ const SLboolean req[2] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
+#endif
+
+ /* Create audio player */
+ result = (*pa->engineEngine)->CreateAudioPlayer(pa->engineEngine,
+ &stream->playerObj,
+ &audioSrc, &audioSnk,
+ numIface, ids, req);
+ if (result != SL_RESULT_SUCCESS) {
+ PJ_LOG(3, (THIS_FILE, "Cannot create audio player: %d", result));
+ goto on_error;
+ }
+
+#ifdef __ANDROID__
+ /* Set Android configuration */
+ result = (*stream->playerObj)->GetInterface(stream->playerObj,
+ SL_IID_ANDROIDCONFIGURATION,
+ &playerConfig);
+ if (result == SL_RESULT_SUCCESS && playerConfig) {
+ result = (*playerConfig)->SetConfiguration(
+ playerConfig, SL_ANDROID_KEY_STREAM_TYPE,
+ &streamType, sizeof(SLint32));
+ }
+ if (result != SL_RESULT_SUCCESS) {
+ PJ_LOG(4, (THIS_FILE, "Warning: Unable to set android "
+ "player configuration"));
+ }
+#endif
+
+ /* Realize the player */
+ result = (*stream->playerObj)->Realize(stream->playerObj,
+ SL_BOOLEAN_FALSE);
+ if (result != SL_RESULT_SUCCESS) {
+ PJ_LOG(3, (THIS_FILE, "Cannot realize player : %d", result));
+ goto on_error;
+ }
+
+ /* Get the play interface */
+ result = (*stream->playerObj)->GetInterface(stream->playerObj,
+ SL_IID_PLAY,
+ &stream->playerPlay);
+ if (result != SL_RESULT_SUCCESS) {
+ PJ_LOG(3, (THIS_FILE, "Cannot get play interface"));
+ goto on_error;
+ }
+
+ /* Get the buffer queue interface */
+ result = (*stream->playerObj)->GetInterface(stream->playerObj,
+ SL_IID_BUFFERQUEUE,
+ &stream->playerBufQ);
+ if (result != SL_RESULT_SUCCESS) {
+ PJ_LOG(3, (THIS_FILE, "Cannot get buffer queue interface"));
+ goto on_error;
+ }
+
+ /* Get the volume interface */
+ result = (*stream->playerObj)->GetInterface(stream->playerObj,
+ SL_IID_VOLUME,
+ &stream->playerVol);
+
+ /* Register callback on the buffer queue */
+ result = (*stream->playerBufQ)->RegisterCallback(stream->playerBufQ,
+ bqPlayerCallback,
+ (void *)stream);
+ if (result != SL_RESULT_SUCCESS) {
+ PJ_LOG(3, (THIS_FILE, "Cannot register player callback"));
+ goto on_error;
+ }
+
+ stream->playerBufferSize = bufferSize;
+ for (i = 0; i < NUM_BUFFERS; i++) {
+ stream->playerBuffer[i] = (char *)
+ pj_pool_alloc(stream->pool,
+ stream->playerBufferSize);
+ }
+ }
+
+ if (stream->dir & PJMEDIA_DIR_CAPTURE) {
+ /* Audio source */
+ SLDataLocator_IODevice loc_dev = {SL_DATALOCATOR_IODEVICE,
+ SL_IODEVICE_AUDIOINPUT,
+ SL_DEFAULTDEVICEID_AUDIOINPUT,
+ NULL};
+ SLDataSource audioSrc = {&loc_dev, NULL};
+ /* Audio sink */
+ SLDataSink audioSnk = {&loc_bq, &format_pcm};
+ /* Audio interface */
+#ifdef __ANDROID__
+ int numIface = 2;
+ const SLInterfaceID ids[2] = {W_SL_IID_BUFFERQUEUE,
+ SL_IID_ANDROIDCONFIGURATION};
+ const SLboolean req[2] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
+ SLAndroidConfigurationItf recorderConfig;
+#else
+ int numIface = 1;
+ const SLInterfaceID ids[1] = {W_SL_IID_BUFFERQUEUE};
+ const SLboolean req[1] = {SL_BOOLEAN_TRUE};
+#endif
+
+ /* Create audio recorder
+ * (requires the RECORD_AUDIO permission)
+ */
+ result = (*pa->engineEngine)->CreateAudioRecorder(pa->engineEngine,
+ &stream->recordObj,
+ &audioSrc, &audioSnk,
+ numIface, ids, req);
+ if (result != SL_RESULT_SUCCESS) {
+ PJ_LOG(3, (THIS_FILE, "Cannot create recorder: %d", result));
+ goto on_error;
+ }
+
+#ifdef __ANDROID__
+ /* Set Android configuration */
+ result = (*stream->recordObj)->GetInterface(stream->recordObj,
+ SL_IID_ANDROIDCONFIGURATION,
+ &recorderConfig);
+ if (result == SL_RESULT_SUCCESS) {
+ SLint32 streamType = SL_ANDROID_RECORDING_PRESET_GENERIC;
+#if __ANDROID_API__ >= 14
+ 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 >= 14)
+ streamType = SL_ANDROID_RECORDING_PRESET_VOICE_COMMUNICATION;
+ PJ_LOG(4, (THIS_FILE, "Recording stream type %d, SDK : %d",
+ streamType, sdk_v));
+#endif
+ result = (*recorderConfig)->SetConfiguration(
+ recorderConfig, SL_ANDROID_KEY_RECORDING_PRESET,
+ &streamType, sizeof(SLint32));
+ }
+ if (result != SL_RESULT_SUCCESS) {
+ PJ_LOG(4, (THIS_FILE, "Warning: Unable to set android "
+ "recorder configuration"));
+ }
+#endif
+
+ /* Realize the recorder */
+ result = (*stream->recordObj)->Realize(stream->recordObj,
+ SL_BOOLEAN_FALSE);
+ if (result != SL_RESULT_SUCCESS) {
+ PJ_LOG(3, (THIS_FILE, "Cannot realize recorder : %d", result));
+ goto on_error;
+ }
+
+ /* Get the record interface */
+ result = (*stream->recordObj)->GetInterface(stream->recordObj,
+ SL_IID_RECORD,
+ &stream->recordRecord);
+ if (result != SL_RESULT_SUCCESS) {
+ PJ_LOG(3, (THIS_FILE, "Cannot get record interface"));
+ goto on_error;
+ }
+
+ /* Get the buffer queue interface */
+ result = (*stream->recordObj)->GetInterface(
+ stream->recordObj, W_SL_IID_BUFFERQUEUE,
+ &stream->recordBufQ);
+ if (result != SL_RESULT_SUCCESS) {
+ PJ_LOG(3, (THIS_FILE, "Cannot get recorder buffer queue iface"));
+ goto on_error;
+ }
+
+ /* Register callback on the buffer queue */
+ result = (*stream->recordBufQ)->RegisterCallback(stream->recordBufQ,
+ bqRecorderCallback,
+ (void *) stream);
+ if (result != SL_RESULT_SUCCESS) {
+ PJ_LOG(3, (THIS_FILE, "Cannot register recorder callback"));
+ goto on_error;
+ }
+
+ stream->recordBufferSize = bufferSize;
+ for (i = 0; i < NUM_BUFFERS; i++) {
+ stream->recordBuffer[i] = (char *)
+ pj_pool_alloc(stream->pool,
+ stream->recordBufferSize);
+ }
+
+ }
+
+ 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 = &opensl_strm_op;
+ *p_aud_strm = &stream->base;
+ return PJ_SUCCESS;
+
+on_error:
+ 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 opensl_aud_stream *strm = (struct opensl_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 opensl_aud_stream *strm = (struct opensl_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))
+ {
+ if (strm->playerVol) {
+ SLresult res;
+ SLmillibel vol, mvol;
+
+ res = (*strm->playerVol)->GetMaxVolumeLevel(strm->playerVol,
+ &mvol);
+ if (res == SL_RESULT_SUCCESS) {
+ res = (*strm->playerVol)->GetVolumeLevel(strm->playerVol,
+ &vol);
+ if (res == SL_RESULT_SUCCESS) {
+ *(int *)pval = ((int)vol - SL_MILLIBEL_MIN) * 100 /
+ ((int)mvol - SL_MILLIBEL_MIN);
+ return PJ_SUCCESS;
+ }
+ }
+ }
+ }
+
+ 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 opensl_aud_stream *strm = (struct opensl_aud_stream*)s;
+
+ PJ_ASSERT_RETURN(s && value, PJ_EINVAL);
+
+ if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING &&
+ (strm->param.dir & PJMEDIA_DIR_PLAYBACK))
+ {
+ if (strm->playerVol) {
+ SLresult res;
+ SLmillibel vol, mvol;
+
+ res = (*strm->playerVol)->GetMaxVolumeLevel(strm->playerVol,
+ &mvol);
+ if (res == SL_RESULT_SUCCESS) {
+ vol = (SLmillibel)(*(int *)value *
+ ((int)mvol - SL_MILLIBEL_MIN) / 100 + SL_MILLIBEL_MIN);
+ res = (*strm->playerVol)->SetVolumeLevel(strm->playerVol,
+ vol);
+ if (res == SL_RESULT_SUCCESS)
+ return PJ_SUCCESS;
+ }
+ }
+ }
+
+ return PJMEDIA_EAUD_INVCAP;
+}
+
+/* API: start stream. */
+static pj_status_t strm_start(pjmedia_aud_stream *s)
+{
+ struct opensl_aud_stream *stream = (struct opensl_aud_stream*)s;
+ int i;
+ SLresult result = SL_RESULT_SUCCESS;
+
+ PJ_LOG(4, (THIS_FILE, "Starting %s stream..", stream->name.ptr));
+ stream->quit_flag = 0;
+
+ if (stream->recordBufQ && stream->recordRecord) {
+ /* Enqueue an empty buffer to be filled by the recorder
+ * (for streaming recording, we need to enqueue at least 2 empty
+ * buffers to start things off)
+ */
+ for (i = 0; i < NUM_BUFFERS; i++) {
+ result = (*stream->recordBufQ)->Enqueue(stream->recordBufQ,
+ stream->recordBuffer[i],
+ stream->recordBufferSize);
+ /* The most likely other result is SL_RESULT_BUFFER_INSUFFICIENT,
+ * which for this code would indicate a programming error
+ */
+ pj_assert(result == SL_RESULT_SUCCESS);
+ }
+
+ result = (*stream->recordRecord)->SetRecordState(
+ stream->recordRecord, SL_RECORDSTATE_RECORDING);
+ if (result != SL_RESULT_SUCCESS) {
+ PJ_LOG(3, (THIS_FILE, "Cannot start recorder"));
+ goto on_error;
+ }
+ }
+
+ if (stream->playerPlay && stream->playerBufQ) {
+ /* Set the player's state to playing */
+ result = (*stream->playerPlay)->SetPlayState(stream->playerPlay,
+ SL_PLAYSTATE_PLAYING);
+ if (result != SL_RESULT_SUCCESS) {
+ PJ_LOG(3, (THIS_FILE, "Cannot start player"));
+ goto on_error;
+ }
+
+ for (i = 0; i < NUM_BUFFERS; i++) {
+ pj_bzero(stream->playerBuffer[i], stream->playerBufferSize/100);
+ result = (*stream->playerBufQ)->Enqueue(stream->playerBufQ,
+ stream->playerBuffer[i],
+ stream->playerBufferSize/100);
+ pj_assert(result == SL_RESULT_SUCCESS);
+ }
+ }
+
+ PJ_LOG(4, (THIS_FILE, "%s stream started", stream->name.ptr));
+ return PJ_SUCCESS;
+
+on_error:
+ if (result != SL_RESULT_SUCCESS)
+ strm_stop(&stream->base);
+ return opensl_to_pj_error(result);
+}
+
+/* API: stop stream. */
+static pj_status_t strm_stop(pjmedia_aud_stream *s)
+{
+ struct opensl_aud_stream *stream = (struct opensl_aud_stream*)s;
+
+ if (stream->quit_flag)
+ return PJ_SUCCESS;
+
+ PJ_LOG(4, (THIS_FILE, "Stopping stream"));
+
+ stream->quit_flag = 1;
+
+ if (stream->recordBufQ && stream->recordRecord) {
+ /* Stop recording and clear buffer queue */
+ (*stream->recordRecord)->SetRecordState(stream->recordRecord,
+ SL_RECORDSTATE_STOPPED);
+ (*stream->recordBufQ)->Clear(stream->recordBufQ);
+ }
+
+ if (stream->playerBufQ && stream->playerPlay) {
+ /* Wait until the PCM data is done playing, the buffer queue callback
+ * will continue to queue buffers until the entire PCM data has been
+ * played. This is indicated by waiting for the count member of the
+ * SLBufferQueueState to go to zero.
+ */
+/*
+ SLresult result;
+ W_SLBufferQueueState state;
+
+ result = (*stream->playerBufQ)->GetState(stream->playerBufQ, &state);
+ while (state.count) {
+ (*stream->playerBufQ)->GetState(stream->playerBufQ, &state);
+ } */
+ /* Stop player */
+ (*stream->playerPlay)->SetPlayState(stream->playerPlay,
+ SL_PLAYSTATE_STOPPED);
+ }
+
+ PJ_LOG(4,(THIS_FILE, "OpenSL stream stopped"));
+
+ return PJ_SUCCESS;
+
+}
+
+/* API: destroy stream. */
+static pj_status_t strm_destroy(pjmedia_aud_stream *s)
+{
+ struct opensl_aud_stream *stream = (struct opensl_aud_stream*)s;
+
+ /* Stop the stream */
+ strm_stop(s);
+
+ if (stream->playerObj) {
+ /* Destroy the player */
+ (*stream->playerObj)->Destroy(stream->playerObj);
+ /* Invalidate all associated interfaces */
+ stream->playerObj = NULL;
+ stream->playerPlay = NULL;
+ stream->playerBufQ = NULL;
+ stream->playerVol = NULL;
+ }
+
+ if (stream->recordObj) {
+ /* Destroy the recorder */
+ (*stream->recordObj)->Destroy(stream->recordObj);
+ /* Invalidate all associated interfaces */
+ stream->recordObj = NULL;
+ stream->recordRecord = NULL;
+ stream->recordBufQ = NULL;
+ }
+
+ pj_pool_release(stream->pool);
+ PJ_LOG(4, (THIS_FILE, "OpenSL stream destroyed"));
+
+ return PJ_SUCCESS;
+}
+
+#endif /* PJMEDIA_AUDIO_DEV_HAS_OPENSL */