Index: /pjproject/trunk/aconfigure
===================================================================
--- /pjproject/trunk/aconfigure	(revision 4927)
+++ /pjproject/trunk/aconfigure	(revision 4928)
@@ -668,4 +668,6 @@
 ac_ios_cflags
 ac_pjmedia_video_has_ios
+ac_android_cflags
+ac_pjmedia_video_has_android
 ac_pjmedia_video
 ac_pa_use_oss
@@ -6160,4 +6162,39 @@
 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
Index: /pjproject/trunk/aconfigure.ac
===================================================================
--- /pjproject/trunk/aconfigure.ac	(revision 4927)
+++ /pjproject/trunk/aconfigure.ac	(revision 4928)
@@ -706,4 +706,20 @@
 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
Index: /pjproject/trunk/pjmedia/build/Makefile
===================================================================
--- /pjproject/trunk/pjmedia/build/Makefile	(revision 4927)
+++ /pjproject/trunk/pjmedia/build/Makefile	(revision 4928)
@@ -107,5 +107,6 @@
 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)
Index: /pjproject/trunk/pjmedia/build/os-auto.mak.in
===================================================================
--- /pjproject/trunk/pjmedia/build/os-auto.mak.in	(revision 4927)
+++ /pjproject/trunk/pjmedia/build/os-auto.mak.in	(revision 4928)
@@ -26,4 +26,7 @@
 IOS_CFLAGS = @ac_ios_cflags@
 
+# Android
+ANDROID_CFLAGS = @ac_android_cflags@
+
 # libyuv
 LIBYUV_CFLAGS = @ac_libyuv_cflags@
@@ -34,5 +37,5 @@
 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)
Index: /pjproject/trunk/pjmedia/include/pjmedia-videodev/config.h
===================================================================
--- /pjproject/trunk/pjmedia/include/pjmedia-videodev/config.h	(revision 4927)
+++ /pjproject/trunk/pjmedia/include/pjmedia-videodev/config.h	(revision 4928)
@@ -72,4 +72,20 @@
 
 /**
+ * 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.
  *
Index: /pjproject/trunk/pjmedia/src/pjmedia-videodev/android_opengl.c
===================================================================
--- /pjproject/trunk/pjmedia/src/pjmedia-videodev/android_opengl.c	(revision 4928)
+++ /pjproject/trunk/pjmedia/src/pjmedia-videodev/android_opengl.c	(revision 4928)
@@ -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 */
Index: /pjproject/trunk/pjsip-apps/src/pjsua/android/AndroidManifest.xml
===================================================================
--- /pjproject/trunk/pjsip-apps/src/pjsua/android/AndroidManifest.xml	(revision 4927)
+++ /pjproject/trunk/pjsip-apps/src/pjsua/android/AndroidManifest.xml	(revision 4928)
@@ -23,4 +23,6 @@
     <uses-permission android:name="android.permission.CAMERA" />
 
+    <uses-feature android:glEsVersion="0x00020000" android:required="false" />
+    
     <uses-feature
         android:name="android.hardware.microphone"
Index: /pjproject/trunk/pjsip-apps/src/swig/java/android/AndroidManifest.xml
===================================================================
--- /pjproject/trunk/pjsip-apps/src/swig/java/android/AndroidManifest.xml	(revision 4927)
+++ /pjproject/trunk/pjsip-apps/src/swig/java/android/AndroidManifest.xml	(revision 4928)
@@ -21,4 +21,6 @@
     <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
