summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLiong Sauw Ming <ming@teluu.com>2014-09-24 04:01:34 +0000
committerLiong Sauw Ming <ming@teluu.com>2014-09-24 04:01:34 +0000
commit4d9808a6a48bd7ad4617709a2a1dbb6891deb963 (patch)
treec371e06b95eaabd5ddce3a5a96b5b47d8a7ee388
parent76631c2519ae0dc653828d2eda184271cedd5aae (diff)
Re #1790: Initial basic implementation of Android OpenGL renderer
To use it, app has to specify PJMEDIA_HAS_VIDEO to 1 git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@4928 74dad513-b988-da41-8d7b-12977e46ad98
-rwxr-xr-xaconfigure37
-rw-r--r--aconfigure.ac16
-rw-r--r--pjmedia/build/Makefile3
-rw-r--r--pjmedia/build/os-auto.mak.in5
-rw-r--r--pjmedia/include/pjmedia-videodev/config.h16
-rw-r--r--pjmedia/src/pjmedia-videodev/android_opengl.c612
-rw-r--r--pjsip-apps/src/pjsua/android/AndroidManifest.xml2
-rw-r--r--pjsip-apps/src/swig/java/android/AndroidManifest.xml2
8 files changed, 691 insertions, 2 deletions
diff --git a/aconfigure b/aconfigure
index ba0e6d25..c1e5cc1a 100755
--- a/aconfigure
+++ b/aconfigure
@@ -667,6 +667,8 @@ ac_qt_cflags
ac_pjmedia_video_has_qt
ac_ios_cflags
ac_pjmedia_video_has_ios
+ac_android_cflags
+ac_pjmedia_video_has_android
ac_pjmedia_video
ac_pa_use_oss
ac_pa_use_alsa
@@ -6159,6 +6161,41 @@ if test "$enable_video" = "no"; then
true;
else
case $target in
+ *android*)
+ ac_pjmedia_video=android_os
+
+
+ SAVED_LIBS="$LIBS"
+ LIBS="-lGLESv2 -lEGL -landroid"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+int
+main ()
+{
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_pjmedia_video_has_android=yes
+else
+ ac_pjmedia_video_has_android=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+ LIBS="$SAVED_LIBS"
+ if test "$ac_pjmedia_video_has_android" = "yes"; then
+ ac_android_cflags="-DPJMEDIA_VIDEO_DEV_HAS_ANDROID_OPENGL=1"
+ LIBS="$LIBS -lGLESv2 -lEGL -landroid"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: Checking if OpenGL ES 2 is available... yes" >&5
+$as_echo "Checking if OpenGL ES 2 is available... yes" >&6; }
+ else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: Checking if OpenGL ES 2 is available... no" >&5
+$as_echo "Checking if OpenGL ES 2 is available... no" >&6; }
+ fi
+ ;;
arm-apple-darwin*)
ac_pjmedia_video=iphone_os
diff --git a/aconfigure.ac b/aconfigure.ac
index b7bf92e0..83386423 100644
--- a/aconfigure.ac
+++ b/aconfigure.ac
@@ -705,6 +705,22 @@ if test "$enable_video" = "no"; then
true;
else
case $target in
+ *android*)
+ ac_pjmedia_video=android_os
+ AC_SUBST(ac_pjmedia_video_has_android)
+ AC_SUBST(ac_android_cflags)
+ SAVED_LIBS="$LIBS"
+ LIBS="-lGLESv2 -lEGL -landroid"
+ AC_LINK_IFELSE([AC_LANG_PROGRAM([[]], [])],[ac_pjmedia_video_has_android=yes],[ac_pjmedia_video_has_android=no])
+ LIBS="$SAVED_LIBS"
+ if test "$ac_pjmedia_video_has_android" = "yes"; then
+ ac_android_cflags="-DPJMEDIA_VIDEO_DEV_HAS_ANDROID_OPENGL=1"
+ LIBS="$LIBS -lGLESv2 -lEGL -landroid"
+ AC_MSG_RESULT([Checking if OpenGL ES 2 is available... yes])
+ else
+ AC_MSG_RESULT([Checking if OpenGL ES 2 is available... no])
+ fi
+ ;;
arm-apple-darwin*)
ac_pjmedia_video=iphone_os
AC_SUBST(ac_pjmedia_video_has_ios)
diff --git a/pjmedia/build/Makefile b/pjmedia/build/Makefile
index 54395c38..ba2b8187 100644
--- a/pjmedia/build/Makefile
+++ b/pjmedia/build/Makefile
@@ -106,7 +106,8 @@ export PJMEDIA_AUDIODEV_LDFLAGS += $(PJLIB_LDLIB) \
#
export PJMEDIA_VIDEODEV_SRCDIR = ../src/pjmedia-videodev
export PJMEDIA_VIDEODEV_OBJS += errno.o videodev.o avi_dev.o ffmpeg_dev.o \
- colorbar_dev.o v4l2_dev.o opengl_dev.o
+ colorbar_dev.o v4l2_dev.o opengl_dev.o \
+ android_opengl.o
export PJMEDIA_VIDEODEV_CFLAGS += $(_CFLAGS)
export PJMEDIA_VIDEODEV_CXXFLAGS += $(_CXXFLAGS)
export PJMEDIA_VIDEODEV_LDFLAGS += $(PJLIB_LDLIB) \
diff --git a/pjmedia/build/os-auto.mak.in b/pjmedia/build/os-auto.mak.in
index 91b14aa4..2e8d24d7 100644
--- a/pjmedia/build/os-auto.mak.in
+++ b/pjmedia/build/os-auto.mak.in
@@ -25,6 +25,9 @@ QT_CFLAGS = @ac_qt_cflags@
# iOS
IOS_CFLAGS = @ac_ios_cflags@
+# Android
+ANDROID_CFLAGS = @ac_android_cflags@
+
# libyuv
LIBYUV_CFLAGS = @ac_libyuv_cflags@
LIBYUV_LDFLAGS = @ac_libyuv_ldflags@
@@ -33,7 +36,7 @@ LIBYUV_LDFLAGS = @ac_libyuv_ldflags@
# PJMEDIA features exclusion
export CFLAGS += @ac_no_small_filter@ @ac_no_large_filter@ @ac_no_speex_aec@ \
$(SDL_CFLAGS) $(FFMPEG_CFLAGS) $(V4L2_CFLAGS) $(QT_CFLAGS) \
- $(IOS_CFLAGS) $(LIBYUV_CFLAGS)
+ $(IOS_CFLAGS) $(ANDROID_CFLAGS) $(LIBYUV_CFLAGS)
export LDFLAGS += $(SDL_LDFLAGS) $(FFMPEG_LDFLAGS) $(V4L2_LDFLAGS) \
$(LIBYUV_LDFLAGS)
diff --git a/pjmedia/include/pjmedia-videodev/config.h b/pjmedia/include/pjmedia-videodev/config.h
index f11485f0..85c7415e 100644
--- a/pjmedia/include/pjmedia-videodev/config.h
+++ b/pjmedia/include/pjmedia-videodev/config.h
@@ -71,6 +71,22 @@ PJ_BEGIN_DECL
/**
+ * This setting controls whether OpenGL for Android should be included.
+ *
+ * Default: 0 (or detected by configure)
+ */
+#ifndef PJMEDIA_VIDEO_DEV_HAS_ANDROID_OPENGL
+# define PJMEDIA_VIDEO_DEV_HAS_ANDROID_OPENGL 0
+#else
+# if defined(PJMEDIA_VIDEO_DEV_HAS_ANDROID_OPENGL) && \
+ PJMEDIA_VIDEO_DEV_HAS_ANDROID_OPENGL != 0
+# undef PJMEDIA_VIDEO_DEV_HAS_OPENGL_ES
+# define PJMEDIA_VIDEO_DEV_HAS_OPENGL_ES 1
+# endif
+#endif
+
+
+/**
* This setting controls whether OpenGL ES support should be included.
*
* Default: 0 (or detected by configure)
diff --git a/pjmedia/src/pjmedia-videodev/android_opengl.c b/pjmedia/src/pjmedia-videodev/android_opengl.c
new file mode 100644
index 00000000..f494e004
--- /dev/null
+++ b/pjmedia/src/pjmedia-videodev/android_opengl.c
@@ -0,0 +1,612 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2013-2014 Teluu Inc. (http://www.teluu.com)
+ *
+ * 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
+ */
+#include <pjmedia-videodev/videodev_imp.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/os.h>
+
+#if defined(PJMEDIA_HAS_VIDEO) && PJMEDIA_HAS_VIDEO != 0 && \
+ defined(PJMEDIA_VIDEO_DEV_HAS_ANDROID_OPENGL) && \
+ PJMEDIA_VIDEO_DEV_HAS_ANDROID_OPENGL != 0
+
+#include <pjmedia-videodev/opengl_dev.h>
+#include <jni.h>
+#include <android/native_window_jni.h>
+#include <EGL/egl.h>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+
+#define THIS_FILE "android_opengl.cpp"
+
+#define MAX_JOBS 1
+
+extern JavaVM *pj_jni_jvm;
+
+typedef struct andgl_fmt_info
+{
+ pjmedia_format_id pjmedia_format;
+} andgl_fmt_info;
+
+/* Supported formats */
+static andgl_fmt_info andgl_fmts[] =
+{
+ {PJMEDIA_FORMAT_BGRA}
+};
+
+typedef pj_status_t (*job_func_ptr)(void *data);
+
+typedef struct job {
+ job_func_ptr func;
+ void *data;
+ unsigned flags;
+ pj_status_t retval;
+} job;
+
+typedef struct job_queue {
+ job *jobs[MAX_JOBS];
+ pj_sem_t *job_sem[MAX_JOBS];
+ pj_mutex_t *mutex;
+ pj_thread_t *thread;
+ pj_sem_t *sem;
+
+ unsigned size;
+ unsigned head, tail;
+ pj_bool_t is_quitting;
+} job_queue;
+
+/* Video stream. */
+struct andgl_stream
+{
+ pjmedia_vid_dev_stream base; /**< Base stream */
+ pjmedia_vid_dev_param param; /**< Settings */
+ pj_pool_t *pool; /**< Memory pool */
+
+ pjmedia_vid_dev_cb vid_cb; /**< Stream callback */
+ void *user_data; /**< Application data */
+
+ pj_timestamp frame_ts;
+ unsigned ts_inc;
+ pjmedia_rect_size vid_size;
+
+ job_queue *jq;
+ pj_bool_t is_running;
+ const pjmedia_frame *frame;
+
+ gl_buffers *gl_buf;
+ EGLDisplay display;
+ EGLSurface surface;
+ EGLContext context;
+ ANativeWindow *window;
+};
+
+
+/* Prototypes */
+static pj_status_t andgl_stream_get_param(pjmedia_vid_dev_stream *strm,
+ pjmedia_vid_dev_param *param);
+static pj_status_t andgl_stream_get_cap(pjmedia_vid_dev_stream *strm,
+ pjmedia_vid_dev_cap cap,
+ void *value);
+static pj_status_t andgl_stream_set_cap(pjmedia_vid_dev_stream *strm,
+ pjmedia_vid_dev_cap cap,
+ const void *value);
+static pj_status_t andgl_stream_start(pjmedia_vid_dev_stream *strm);
+static pj_status_t andgl_stream_put_frame(pjmedia_vid_dev_stream *strm,
+ const pjmedia_frame *frame);
+static pj_status_t andgl_stream_stop(pjmedia_vid_dev_stream *strm);
+static pj_status_t andgl_stream_destroy(pjmedia_vid_dev_stream *strm);
+
+/* Job queue prototypes */
+static pj_status_t job_queue_create(pj_pool_t *pool, job_queue **pjq);
+static pj_status_t job_queue_post_job(job_queue *jq, job_func_ptr func,
+ void *data, unsigned flags,
+ pj_status_t *retval);
+static pj_status_t job_queue_destroy(job_queue *jq);
+
+/* Operations */
+static pjmedia_vid_dev_stream_op stream_op =
+{
+ &andgl_stream_get_param,
+ &andgl_stream_get_cap,
+ &andgl_stream_set_cap,
+ &andgl_stream_start,
+ NULL,
+ &andgl_stream_put_frame,
+ &andgl_stream_stop,
+ &andgl_stream_destroy
+};
+
+ANativeWindow *def_native_win = NULL;
+
+static void get_jvm(JNIEnv **jni_env)
+{
+ (*pj_jni_jvm)->GetEnv(pj_jni_jvm, (void **)jni_env, JNI_VERSION_1_4);
+}
+
+void android_opengl_set_surface(jobject surface)
+{
+ JNIEnv *env = 0;
+
+ PJ_LOG(4, (THIS_FILE, "Setting default OpenGL surface"));
+
+ if (!surface) {
+ def_native_win = NULL;
+ } else {
+ get_jvm(&env);
+ if (env) def_native_win = ANativeWindow_fromSurface(env, surface);
+ }
+}
+
+int pjmedia_vid_dev_opengl_imp_get_cap(void)
+{
+ return PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW |
+ PJMEDIA_VID_DEV_CAP_OUTPUT_RESIZE;
+}
+
+static andgl_fmt_info* get_andgl_format_info(pjmedia_format_id id)
+{
+ unsigned i;
+
+ for (i = 0; i < PJ_ARRAY_SIZE(andgl_fmts); i++) {
+ if (andgl_fmts[i].pjmedia_format == id)
+ return &andgl_fmts[i];
+ }
+
+ return NULL;
+}
+
+#define EGL_ERR(str) \
+ { \
+ PJ_LOG(3, (THIS_FILE, "Unable to %s %d", str, eglGetError())); \
+ return PJMEDIA_EVID_SYSERR; \
+ }
+
+static pj_status_t init_opengl(void * data)
+{
+ struct andgl_stream *strm = (struct andgl_stream *)data;
+ const EGLint attr[] =
+ {
+ EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_SURFACE_TYPE,
+ EGL_WINDOW_BIT, EGL_BLUE_SIZE, 8, EGL_GREEN_SIZE, 8,
+ EGL_RED_SIZE, 8, EGL_DEPTH_SIZE, 8, EGL_NONE
+ };
+ EGLint context_attr[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };
+ EGLConfig config;
+ EGLint numConfigs;
+ EGLint format;
+ EGLint width;
+ EGLint height;
+
+ strm->display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+ if (strm->display == EGL_NO_DISPLAY ||
+ !eglInitialize(strm->display, NULL, NULL))
+ {
+ EGL_ERR("initialize OpenGL display");
+ }
+
+ if (!eglChooseConfig(strm->display, attr, &config, 1, &numConfigs) ||
+ (!eglGetConfigAttrib(strm->display, config, EGL_NATIVE_VISUAL_ID,
+ &format)))
+ {
+ EGL_ERR("configure OpenGL display");
+ }
+
+ if (ANativeWindow_setBuffersGeometry(strm->window, strm->param.disp_size.w,
+ strm->param.disp_size.h, format) != 0)
+ {
+ EGL_ERR("set window geometry");
+ }
+
+ strm->surface = eglCreateWindowSurface(strm->display, config,
+ strm->window, 0);
+ if (strm->surface == EGL_NO_SURFACE)
+ EGL_ERR("create window surface");
+
+ strm->context = eglCreateContext(strm->display, config, EGL_NO_CONTEXT,
+ context_attr);
+ if (strm->context == EGL_NO_CONTEXT)
+ EGL_ERR("create OpenGL context");
+
+ if (!eglMakeCurrent(strm->display, strm->surface, strm->surface,
+ strm->context))
+ {
+ EGL_ERR("make OpenGL as current context");
+ }
+
+ if (!eglQuerySurface(strm->display, strm->surface, EGL_WIDTH, &width) ||
+ !eglQuerySurface(strm->display, strm->surface, EGL_HEIGHT, &height))
+ {
+ EGL_ERR("query surface");
+ }
+
+ /* Create GL buffers */
+ pjmedia_vid_dev_opengl_create_buffers(strm->pool, PJ_TRUE, &strm->gl_buf);
+
+ /* Init GL buffers */
+ return pjmedia_vid_dev_opengl_init_buffers(strm->gl_buf);
+}
+
+static pj_status_t render(void * data)
+{
+ struct andgl_stream *stream = (struct andgl_stream *)data;
+
+ pjmedia_vid_dev_opengl_draw(stream->gl_buf, stream->vid_size.w,
+ stream->vid_size.h, stream->frame->buf);
+
+ return eglSwapBuffers(stream->display, stream->surface);
+}
+
+static pj_status_t deinit_opengl(void * data)
+{
+ struct andgl_stream *stream = (struct andgl_stream *)data;
+
+ pjmedia_vid_dev_opengl_destroy_buffers(stream->gl_buf);
+
+ eglMakeCurrent(stream->display, EGL_NO_SURFACE, EGL_NO_SURFACE,
+ EGL_NO_CONTEXT);
+ eglDestroyContext(stream->display, stream->context);
+ eglDestroySurface(stream->display, stream->surface);
+ eglTerminate(stream->display);
+
+ stream->display = EGL_NO_DISPLAY;
+ stream->surface = EGL_NO_SURFACE;
+ stream->context = EGL_NO_CONTEXT;
+
+ return PJ_SUCCESS;
+}
+
+/* API: create stream */
+pj_status_t
+pjmedia_vid_dev_opengl_imp_create_stream(pj_pool_t *pool,
+ pjmedia_vid_dev_param *param,
+ const pjmedia_vid_dev_cb *cb,
+ void *user_data,
+ pjmedia_vid_dev_stream **p_vid_strm)
+{
+ struct andgl_stream *strm;
+ const pjmedia_video_format_detail *vfd;
+ pj_status_t status = PJ_SUCCESS;
+
+ strm = PJ_POOL_ZALLOC_T(pool, struct andgl_stream);
+ pj_memcpy(&strm->param, param, sizeof(*param));
+ strm->pool = pool;
+ pj_memcpy(&strm->vid_cb, cb, sizeof(*cb));
+ strm->user_data = user_data;
+
+ vfd = pjmedia_format_get_video_format_detail(&strm->param.fmt, PJ_TRUE);
+ strm->ts_inc = PJMEDIA_SPF2(param->clock_rate, &vfd->fps, 1);
+
+ /* If OUTPUT_RESIZE flag is not used, set display size to default */
+ if (!(param->flags & PJMEDIA_VID_DEV_CAP_OUTPUT_RESIZE)) {
+ pj_bzero(&strm->param.disp_size, sizeof(strm->param.disp_size));
+ }
+
+ if (param->flags & PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW) {
+ andgl_stream_set_cap(&strm->base, PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW,
+ param->window.info.android.window);
+ } else {
+ strm->window = def_native_win;
+ }
+
+ if (!strm->window) {
+ PJ_LOG(3, (THIS_FILE, "Unable to find output window nor surface"));
+ status = PJ_EINVAL;
+ goto on_error;
+ }
+
+ /* Set video format */
+ status = andgl_stream_set_cap(&strm->base, PJMEDIA_VID_DEV_CAP_FORMAT,
+ &param->fmt);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ status = job_queue_create(pool, &strm->jq);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ job_queue_post_job(strm->jq, init_opengl, strm, 0, &status);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Apply the remaining settings */
+/* if (param->flags & PJMEDIA_VID_DEV_CAP_ORIENTATION) {
+ andgl_stream_set_cap(&strm->base, PJMEDIA_VID_DEV_CAP_ORIENTATION,
+ &param->orient);
+ }
+*/
+ PJ_LOG(4, (THIS_FILE, "Android OpenGL ES renderer successfully created"));
+
+ /* Done */
+ strm->base.op = &stream_op;
+ *p_vid_strm = &strm->base;
+
+ return PJ_SUCCESS;
+
+on_error:
+ andgl_stream_destroy((pjmedia_vid_dev_stream *)strm);
+
+ return status;
+}
+
+/* API: Get stream info. */
+static pj_status_t andgl_stream_get_param(pjmedia_vid_dev_stream *s,
+ pjmedia_vid_dev_param *pi)
+{
+ struct andgl_stream *strm = (struct andgl_stream*)s;
+
+ PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
+
+ pj_memcpy(pi, &strm->param, sizeof(*pi));
+
+ if (andgl_stream_get_cap(s, PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW,
+ &pi->window) == PJ_SUCCESS)
+ {
+ pi->flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW;
+ }
+
+ return PJ_SUCCESS;
+}
+
+/* API: get capability */
+static pj_status_t andgl_stream_get_cap(pjmedia_vid_dev_stream *s,
+ pjmedia_vid_dev_cap cap,
+ void *pval)
+{
+ struct andgl_stream *strm = (struct andgl_stream*)s;
+
+ PJ_UNUSED_ARG(strm);
+
+ PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+ if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW) {
+ pjmedia_vid_dev_hwnd *wnd = (pjmedia_vid_dev_hwnd *)pval;
+ wnd->info.android.window = strm->window;
+ return PJ_SUCCESS;
+ } else {
+ return PJMEDIA_EVID_INVCAP;
+ }
+}
+
+/* API: set capability */
+static pj_status_t andgl_stream_set_cap(pjmedia_vid_dev_stream *s,
+ pjmedia_vid_dev_cap cap,
+ const void *pval)
+{
+ struct andgl_stream *strm = (struct andgl_stream*)s;
+
+ PJ_UNUSED_ARG(strm);
+
+ PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+ if (cap==PJMEDIA_VID_DEV_CAP_FORMAT) {
+ const pjmedia_video_format_info *vfi;
+ pjmedia_video_format_detail *vfd;
+ pjmedia_format *fmt = (pjmedia_format *)pval;
+ andgl_fmt_info *ifi;
+
+ if (!(ifi = get_andgl_format_info(fmt->id)))
+ return PJMEDIA_EVID_BADFORMAT;
+
+ vfi = pjmedia_get_video_format_info(pjmedia_video_format_mgr_instance(),
+ fmt->id);
+ if (!vfi)
+ return PJMEDIA_EVID_BADFORMAT;
+
+ pjmedia_format_copy(&strm->param.fmt, fmt);
+
+ vfd = pjmedia_format_get_video_format_detail(fmt, PJ_TRUE);
+ pj_memcpy(&strm->vid_size, &vfd->size, sizeof(vfd->size));
+ if (strm->param.disp_size.w == 0 || strm->param.disp_size.h == 0)
+ pj_memcpy(&strm->param.disp_size, &vfd->size, sizeof(vfd->size));
+
+ return PJ_SUCCESS;
+ } else if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW) {
+ strm->window = (ANativeWindow *)pval;
+ strm->param.window.info.android.window = (void *)pval;
+ return PJ_SUCCESS;
+ } else if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_RESIZE) {
+ pj_memcpy(&strm->param.disp_size, pval, sizeof(strm->param.disp_size));
+ return PJ_SUCCESS;
+ } else if (cap == PJMEDIA_VID_DEV_CAP_ORIENTATION) {
+ pj_memcpy(&strm->param.orient, pval, sizeof(strm->param.orient));
+ if (strm->param.orient == PJMEDIA_ORIENT_UNKNOWN)
+ return PJ_SUCCESS;
+ return PJ_SUCCESS;
+ }
+
+ return PJMEDIA_EVID_INVCAP;
+}
+
+/* API: Start stream. */
+static pj_status_t andgl_stream_start(pjmedia_vid_dev_stream *strm)
+{
+ struct andgl_stream *stream = (struct andgl_stream*)strm;
+
+ stream->is_running = PJ_TRUE;
+ PJ_LOG(4, (THIS_FILE, "Starting Android opengl stream"));
+
+ return PJ_SUCCESS;
+}
+
+/* API: Put frame from stream */
+static pj_status_t andgl_stream_put_frame(pjmedia_vid_dev_stream *strm,
+ const pjmedia_frame *frame)
+{
+ struct andgl_stream *stream = (struct andgl_stream*)strm;
+ pj_status_t status;
+
+ if (!stream->is_running)
+ return PJ_EINVALIDOP;
+
+ stream->frame = frame;
+ job_queue_post_job(stream->jq, render, strm, 0, &status);
+
+ return status;
+}
+
+/* API: Stop stream. */
+static pj_status_t andgl_stream_stop(pjmedia_vid_dev_stream *strm)
+{
+ struct andgl_stream *stream = (struct andgl_stream*)strm;
+
+ stream->is_running = PJ_FALSE;
+ PJ_LOG(4, (THIS_FILE, "Stopping Android opengl stream"));
+
+ return PJ_SUCCESS;
+}
+
+
+/* API: Destroy stream. */
+static pj_status_t andgl_stream_destroy(pjmedia_vid_dev_stream *strm)
+{
+ struct andgl_stream *stream = (struct andgl_stream*)strm;
+
+ PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
+
+ andgl_stream_stop(strm);
+
+ job_queue_post_job(stream->jq, deinit_opengl, strm, 0, NULL);
+
+ if (stream->jq) {
+ job_queue_destroy(stream->jq);
+ stream->jq = NULL;
+ }
+
+ pj_pool_release(stream->pool);
+
+ return PJ_SUCCESS;
+}
+
+static int job_thread(void * data)
+{
+ job_queue *jq = (job_queue *)data;
+
+ while (1) {
+ job *jb;
+
+ /* Wait until there is a job. */
+ pj_sem_wait(jq->sem);
+
+ /* Make sure there is no pending jobs before we quit. */
+ if (jq->is_quitting && jq->head == jq->tail)
+ break;
+
+ jb = jq->jobs[jq->head];
+ jb->retval = (*jb->func)(jb->data);
+ pj_sem_post(jq->job_sem[jq->head]);
+ jq->head = (jq->head + 1) % jq->size;
+ }
+
+ return 0;
+}
+
+static pj_status_t job_queue_create(pj_pool_t *pool, job_queue **pjq)
+{
+ unsigned i;
+ pj_status_t status;
+
+ job_queue *jq = PJ_POOL_ZALLOC_T(pool, job_queue);
+ jq->size = MAX_JOBS;
+ status = pj_sem_create(pool, "thread_sem", 0, jq->size + 1, &jq->sem);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ for (i = 0; i < jq->size; i++) {
+ status = pj_sem_create(pool, "job_sem", 0, 1, &jq->job_sem[i]);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+ }
+
+ status = pj_mutex_create_recursive(pool, "job_mutex", &jq->mutex);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ status = pj_thread_create(pool, "job_th", job_thread, jq, 0, 0,
+ &jq->thread);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ *pjq = jq;
+ return PJ_SUCCESS;
+
+on_error:
+ job_queue_destroy(jq);
+ return status;
+}
+
+static pj_status_t job_queue_post_job(job_queue *jq, job_func_ptr func,
+ void *data, unsigned flags,
+ pj_status_t *retval)
+{
+ job jb;
+ int tail;
+
+ if (jq->is_quitting)
+ return PJ_EBUSY;
+
+ jb.func = func;
+ jb.data = data;
+ jb.flags = flags;
+
+ pj_mutex_lock(jq->mutex);
+ jq->jobs[jq->tail] = &jb;
+ tail = jq->tail;
+ jq->tail = (jq->tail + 1) % jq->size;
+
+ pj_sem_post(jq->sem);
+ /* Wait until our posted job is completed. */
+ pj_sem_wait(jq->job_sem[tail]);
+ pj_mutex_unlock(jq->mutex);
+
+ if (retval) *retval = jb.retval;
+
+ return PJ_SUCCESS;
+}
+
+static pj_status_t job_queue_destroy(job_queue *jq)
+{
+ unsigned i;
+
+ jq->is_quitting = PJ_TRUE;
+
+ if (jq->thread) {
+ pj_sem_post(jq->sem);
+ pj_thread_join(jq->thread);
+ pj_thread_destroy(jq->thread);
+ }
+
+ if (jq->sem) {
+ pj_sem_destroy(jq->sem);
+ jq->sem = NULL;
+ }
+ for (i = 0; i < jq->size; i++) {
+ if (jq->job_sem[i]) {
+ pj_sem_destroy(jq->job_sem[i]);
+ jq->job_sem[i] = NULL;
+ }
+ }
+
+ if (jq->mutex) {
+ pj_mutex_destroy(jq->mutex);
+ jq->mutex = NULL;
+ }
+
+ return PJ_SUCCESS;
+}
+
+#endif /* PJMEDIA_VIDEO_DEV_HAS_ANDROID_OPENGL */
diff --git a/pjsip-apps/src/pjsua/android/AndroidManifest.xml b/pjsip-apps/src/pjsua/android/AndroidManifest.xml
index e5a0088a..97da16d3 100644
--- a/pjsip-apps/src/pjsua/android/AndroidManifest.xml
+++ b/pjsip-apps/src/pjsua/android/AndroidManifest.xml
@@ -22,6 +22,8 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
+ <uses-feature android:glEsVersion="0x00020000" android:required="false" />
+
<uses-feature
android:name="android.hardware.microphone"
android:required="true" />
diff --git a/pjsip-apps/src/swig/java/android/AndroidManifest.xml b/pjsip-apps/src/swig/java/android/AndroidManifest.xml
index 5a9b0aef..63986c42 100644
--- a/pjsip-apps/src/swig/java/android/AndroidManifest.xml
+++ b/pjsip-apps/src/swig/java/android/AndroidManifest.xml
@@ -20,6 +20,8 @@
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.READ_LOGS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
+ <uses-feature android:glEsVersion="0x00020000" android:required="false" />
<application
android:allowBackup="true"