Index: /pjproject/trunk/pjmedia/build/Makefile
===================================================================
--- /pjproject/trunk/pjmedia/build/Makefile	(revision 4993)
+++ /pjproject/trunk/pjmedia/build/Makefile	(revision 4994)
@@ -108,5 +108,5 @@
 export PJMEDIA_VIDEODEV_OBJS +=  errno.o videodev.o avi_dev.o ffmpeg_dev.o \
 				colorbar_dev.o v4l2_dev.o opengl_dev.o \
-				android_opengl.o
+				android_opengl.o android_dev.o
 export PJMEDIA_VIDEODEV_CFLAGS += $(_CFLAGS)
 export PJMEDIA_VIDEODEV_CXXFLAGS += $(_CXXFLAGS)
Index: /pjproject/trunk/pjmedia/include/pjmedia-videodev/config.h
===================================================================
--- /pjproject/trunk/pjmedia/include/pjmedia-videodev/config.h	(revision 4993)
+++ /pjproject/trunk/pjmedia/include/pjmedia-videodev/config.h	(revision 4994)
@@ -46,8 +46,8 @@
  * supported by a video device.
  *
- * Default: 16
+ * Default: 64
  */
 #ifndef PJMEDIA_VID_DEV_INFO_FMT_CNT
-#   define PJMEDIA_VID_DEV_INFO_FMT_CNT 16
+#   define PJMEDIA_VID_DEV_INFO_FMT_CNT 64
 #endif
 
@@ -193,4 +193,15 @@
 #endif
 
+
+/**
+ * This setting controls whether Android support should be included.
+ *
+ * Default: 0
+ */
+#ifndef PJMEDIA_VIDEO_DEV_HAS_ANDROID
+#   define PJMEDIA_VIDEO_DEV_HAS_ANDROID	0
+#endif
+
+
 /**
  * Specify the SDL library name to be linked with Visual Studio project. 
Index: /pjproject/trunk/pjmedia/include/pjmedia-videodev/videodev.h
===================================================================
--- /pjproject/trunk/pjmedia/include/pjmedia-videodev/videodev.h	(revision 4993)
+++ /pjproject/trunk/pjmedia/include/pjmedia-videodev/videodev.h	(revision 4994)
@@ -233,5 +233,5 @@
      * by setting this capability to PJ_TRUE or PJ_FALSE. Once the preview
      * is started, application may use PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW
-     * capability to query the vidow window.
+     * capability to query the video window.
      *
      * The value of this capability is a pj_bool_t containing boolean
Index: /pjproject/trunk/pjmedia/include/pjmedia/format.h
===================================================================
--- /pjproject/trunk/pjmedia/include/pjmedia/format.h	(revision 4993)
+++ /pjproject/trunk/pjmedia/include/pjmedia/format.h	(revision 4994)
@@ -177,4 +177,12 @@
     PJMEDIA_FORMAT_YV12	    = PJMEDIA_FORMAT_PACK('Y', 'V', '1', '2'),
 
+    /**
+     * This is planar 4:2:0/12bpp YUV format, the data can be treated as
+     * two planes of color components, where the first plane contains
+     * only the Y samples, the second plane contains interleaved
+     * V (Cr) - U (Cb) samples.
+     */
+    PJMEDIA_FORMAT_NV21	    = PJMEDIA_FORMAT_PACK('N', 'V', '2', '1'),
+    
     /**
      * This is planar 4:2:2/16bpp YUV format, the data can be treated as
Index: /pjproject/trunk/pjmedia/src/pjmedia-videodev/android_dev.c
===================================================================
--- /pjproject/trunk/pjmedia/src/pjmedia-videodev/android_dev.c	(revision 4994)
+++ /pjproject/trunk/pjmedia/src/pjmedia-videodev/android_dev.c	(revision 4994)
@@ -0,0 +1,1119 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2015 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/math.h>
+#include <pj/os.h>
+
+
+#if defined(PJMEDIA_HAS_VIDEO) && PJMEDIA_HAS_VIDEO != 0 && \
+    defined(PJMEDIA_VIDEO_DEV_HAS_ANDROID) && \
+    PJMEDIA_VIDEO_DEV_HAS_ANDROID != 0
+
+#include <jni.h>
+
+#define THIS_FILE		"android_dev.c"
+
+/* Default video params */
+#define DEFAULT_CLOCK_RATE	90000
+#define DEFAULT_WIDTH		352
+#define DEFAULT_HEIGHT		288
+#define DEFAULT_FPS		15
+#define ALIGN16(x)		((((x)+15) >> 4) << 4)
+
+
+/* Format map info */
+typedef struct and_fmt_map
+{
+    pjmedia_format_id   fmt_id;
+    pj_uint32_t		and_fmt_id;
+} and_fmt_map;
+
+
+/* Format map.
+ * Note: it seems that most of Android devices don't support I420, while
+ * unfortunately, our converter (libyuv based) only support I420 & RGBA,
+ * so in this case, we'd just pretend that we support I420 and we'll do
+ * the NV21/YV12 -> I420 conversion here.
+ */
+static and_fmt_map fmt_map[] =
+{
+    {PJMEDIA_FORMAT_NV21, 0x00000011},
+    {PJMEDIA_FORMAT_YV12, 0x32315659},
+    {PJMEDIA_FORMAT_I420, 0x00000023}, /* YUV_420_888 */
+};
+
+
+/* Device info */
+typedef struct and_dev_info
+{
+    pjmedia_vid_dev_info	 info;		/**< Base info         */
+    unsigned			 dev_idx;	/**< Original dev ID   */
+    unsigned			 sup_size_cnt;	/**< # of supp'd size  */
+    pjmedia_rect_size		*sup_size;	/**< Supported size    */
+    unsigned			 sup_fps_cnt;	/**< # of supp'd FPS   */
+    pjmedia_rect_size		*sup_fps;	/**< Supported FPS     */
+    pj_bool_t			 has_yv12;	/**< Support YV12?     */
+    pj_bool_t			 has_nv21;	/**< Support NV21?     */
+    pj_bool_t			 forced_i420;	/**< Support I420 with
+						     conversion		*/
+} and_dev_info;
+
+
+/* Video factory */
+typedef struct and_factory
+{
+    pjmedia_vid_dev_factory	 base;		/**< Base factory      */
+    pj_pool_t			*pool;		/**< Memory pool       */
+    pj_pool_factory		*pf;		/**< Pool factory      */
+
+    pj_pool_t			*dev_pool;	/**< Device list pool  */
+    unsigned			 dev_count;	/**< Device count      */
+    and_dev_info		*dev_info;	/**< Device info list  */
+} and_factory;
+
+
+/* Video stream. */
+typedef struct and_stream
+{
+    pjmedia_vid_dev_stream  base;		/**< Base stream       */
+    pjmedia_vid_dev_param   param;		/**< Settings	       */
+    pj_pool_t		   *pool;		/**< Memory pool       */
+    and_factory		   *factory;            /**< Factory           */
+    
+    pjmedia_vid_dev_cb	    vid_cb;		/**< Stream callback   */
+    void		   *user_data;          /**< Application data  */
+    pj_bool_t		    is_running;		/**< Stream running?   */
+    
+    jobject		    jcam;		/**< PjCamera instance */
+
+    pj_timestamp            frame_ts;		/**< Current timestamp */
+    unsigned                ts_inc;		/**< Timestamp interval*/
+    unsigned		    convert_to_i420;	/**< Need to convert to I420?
+						     1: from NV21
+						     2: from YV12	*/
+    
+    /** Capture thread info */
+    pj_bool_t		    thread_initialized;
+    pj_thread_desc	    thread_desc;
+    pj_thread_t		   *thread;
+
+    /** NV21/YV12 -> I420 Conversion buffer  */
+    pj_uint8_t		   *convert_buf;
+    
+    /** Frame format param for NV21/YV12 -> I420 conversion */
+    pjmedia_video_apply_fmt_param
+			    vafp;
+} and_stream;
+
+
+/* Prototypes */
+static pj_status_t and_factory_init(pjmedia_vid_dev_factory *f);
+static pj_status_t and_factory_destroy(pjmedia_vid_dev_factory *f);
+static pj_status_t and_factory_refresh(pjmedia_vid_dev_factory *f); 
+static unsigned    and_factory_get_dev_count(pjmedia_vid_dev_factory *f);
+static pj_status_t and_factory_get_dev_info(pjmedia_vid_dev_factory *f,
+					    unsigned index,
+					    pjmedia_vid_dev_info *info);
+static pj_status_t and_factory_default_param(pj_pool_t *pool,
+                                             pjmedia_vid_dev_factory *f,
+					     unsigned index,
+					     pjmedia_vid_dev_param *param);
+static pj_status_t and_factory_create_stream(
+					pjmedia_vid_dev_factory *f,
+					pjmedia_vid_dev_param *param,
+					const pjmedia_vid_dev_cb *cb,
+					void *user_data,
+					pjmedia_vid_dev_stream **p_vid_strm);
+
+
+static pj_status_t and_stream_get_param(pjmedia_vid_dev_stream *strm,
+                                        pjmedia_vid_dev_param *param);
+static pj_status_t and_stream_get_cap(pjmedia_vid_dev_stream *strm,
+                                      pjmedia_vid_dev_cap cap,
+                                      void *value);
+static pj_status_t and_stream_set_cap(pjmedia_vid_dev_stream *strm,
+                                      pjmedia_vid_dev_cap cap,
+                                      const void *value);
+static pj_status_t and_stream_start(pjmedia_vid_dev_stream *strm);
+static pj_status_t and_stream_stop(pjmedia_vid_dev_stream *strm);
+static pj_status_t and_stream_destroy(pjmedia_vid_dev_stream *strm);
+
+
+/* Operations */
+static pjmedia_vid_dev_factory_op factory_op =
+{
+    &and_factory_init,
+    &and_factory_destroy,
+    &and_factory_get_dev_count,
+    &and_factory_get_dev_info,
+    &and_factory_default_param,
+    &and_factory_create_stream,
+    &and_factory_refresh
+};
+
+static pjmedia_vid_dev_stream_op stream_op =
+{
+    &and_stream_get_param,
+    &and_stream_get_cap,
+    &and_stream_set_cap,
+    &and_stream_start,
+    NULL,
+    NULL,
+    &and_stream_stop,
+    &and_stream_destroy
+};
+
+
+/****************************************************************************
+ * JNI stuff
+ */
+extern JavaVM *pj_jni_jvm;
+#define PJ_CAMERA_CLASS_PATH		"org/pjsip/PjCamera"
+#define PJ_CAMERA_INFO_CLASS_PATH	"org/pjsip/PjCameraInfo"
+
+static struct jni_objs_t
+{
+    struct {
+	jclass		 cls;
+	jmethodID	 m_init;
+	jmethodID	 m_start;
+	jmethodID	 m_stop;
+	jmethodID	 m_switch;
+    } cam;
+
+    struct {
+	jclass		 cls;
+	jmethodID	 m_get_cnt;
+	jmethodID	 m_get_info;
+	jfieldID	 f_facing;
+	jfieldID	 f_orient;
+	jfieldID	 f_sup_size;
+	jfieldID	 f_sup_fmt;
+	jfieldID	 f_sup_fps;
+    } cam_info;
+
+} jobjs;
+
+
+static void JNICALL OnGetFrame(JNIEnv *env, jobject obj,
+                               jbyteArray data, jint length,
+			       jlong user_data);
+
+
+static pj_bool_t jni_get_env(JNIEnv **jni_env)
+{
+    pj_bool_t with_attach = PJ_FALSE;
+    if ((*pj_jni_jvm)->GetEnv(pj_jni_jvm, (void **)jni_env,
+                              JNI_VERSION_1_4) < 0)
+    {
+        if ((*pj_jni_jvm)->AttachCurrentThread(pj_jni_jvm, jni_env, NULL) < 0)
+        {
+            *jni_env = NULL;
+        } else {
+	    with_attach = PJ_TRUE;
+	}
+    }
+    
+    return with_attach;
+}
+
+
+static void jni_detach_env(pj_bool_t need_detach)
+{
+    if (need_detach)
+        (*pj_jni_jvm)->DetachCurrentThread(pj_jni_jvm);
+}
+
+
+/* Get Java object IDs (via FindClass, GetMethodID, GetFieldID, etc).
+ * Note that this function should be called from library-loader thread,
+ * otherwise FindClass, etc, may fail, see:
+ * http://developer.android.com/training/articles/perf-jni.html#faq_FindClass
+ */
+static pj_status_t jni_init_ids()
+{
+    JNIEnv *jni_env;
+    pj_status_t status = PJ_SUCCESS;
+    pj_bool_t with_attach = jni_get_env(&jni_env);
+
+#define GET_CLASS(class_path, class_name, cls) \
+    cls = (*jni_env)->FindClass(jni_env, class_path); \
+    if (cls == NULL || (*jni_env)->ExceptionCheck(jni_env)) { \
+	(*jni_env)->ExceptionClear(jni_env); \
+        PJ_LOG(3, (THIS_FILE, "[JNI] Unable to find class '" \
+			      class_name "'")); \
+        status = PJMEDIA_EVID_SYSERR; \
+        goto on_return; \
+    } else { \
+        jclass tmp = cls; \
+	cls = (jclass)(*jni_env)->NewGlobalRef(jni_env, tmp); \
+	(*jni_env)->DeleteLocalRef(jni_env, tmp); \
+	if (cls == NULL) { \
+	    PJ_LOG(3, (THIS_FILE, "[JNI] Unable to get global ref for " \
+				  "class '" class_name "'")); \
+	    status = PJMEDIA_EVID_SYSERR; \
+	    goto on_return; \
+	} \
+    }
+#define GET_METHOD_ID(cls, class_name, method_name, signature, id) \
+    id = (*jni_env)->GetMethodID(jni_env, cls, method_name, signature); \
+    if (id == 0) { \
+        PJ_LOG(3, (THIS_FILE, "[JNI] Unable to find method '" method_name \
+			      "' in class '" class_name "'")); \
+        status = PJMEDIA_EVID_SYSERR; \
+        goto on_return; \
+    }
+#define GET_SMETHOD_ID(cls, class_name, method_name, signature, id) \
+    id = (*jni_env)->GetStaticMethodID(jni_env, cls, method_name, signature); \
+    if (id == 0) { \
+        PJ_LOG(3, (THIS_FILE, "[JNI] Unable to find static method '" \
+			      method_name "' in class '" class_name "'")); \
+        status = PJMEDIA_EVID_SYSERR; \
+        goto on_return; \
+    }
+#define GET_FIELD_ID(cls, class_name, field_name, signature, id) \
+    id = (*jni_env)->GetFieldID(jni_env, cls, field_name, signature); \
+    if (id == 0) { \
+        PJ_LOG(3, (THIS_FILE, "[JNI] Unable to find field '" field_name \
+			      "' in class '" class_name "'")); \
+        status = PJMEDIA_EVID_SYSERR; \
+        goto on_return; \
+    }
+
+    /* PjCamera class info */
+    GET_CLASS(PJ_CAMERA_CLASS_PATH, "PjCamera", jobjs.cam.cls);
+    GET_METHOD_ID(jobjs.cam.cls, "PjCamera", "<init>",
+		  "(IIIIIJLandroid/view/SurfaceView;)V",
+		  jobjs.cam.m_init);
+    GET_METHOD_ID(jobjs.cam.cls, "PjCamera", "Start", "()I",
+		  jobjs.cam.m_start);
+    GET_METHOD_ID(jobjs.cam.cls, "PjCamera", "Stop", "()V",
+		  jobjs.cam.m_stop);
+    GET_METHOD_ID(jobjs.cam.cls, "PjCamera", "SwitchDevice", "(I)I",
+		  jobjs.cam.m_switch);
+
+    /* PjCameraInfo class info */
+    GET_CLASS(PJ_CAMERA_INFO_CLASS_PATH, "PjCameraInfo", jobjs.cam_info.cls);
+    GET_SMETHOD_ID(jobjs.cam_info.cls, "PjCameraInfo", "GetCameraCount", "()I",
+		   jobjs.cam_info.m_get_cnt);
+    GET_SMETHOD_ID(jobjs.cam_info.cls, "PjCameraInfo", "GetCameraInfo",
+		   "(I)L" PJ_CAMERA_INFO_CLASS_PATH ";",
+		   jobjs.cam_info.m_get_info);
+    GET_FIELD_ID(jobjs.cam_info.cls, "PjCameraInfo", "facing", "I",
+		 jobjs.cam_info.f_facing);
+    GET_FIELD_ID(jobjs.cam_info.cls, "PjCameraInfo", "orient", "I",
+		 jobjs.cam_info.f_orient);
+    GET_FIELD_ID(jobjs.cam_info.cls, "PjCameraInfo", "supportedSize", "[I",
+		 jobjs.cam_info.f_sup_size);
+    GET_FIELD_ID(jobjs.cam_info.cls, "PjCameraInfo", "supportedFormat", "[I",
+		 jobjs.cam_info.f_sup_fmt);
+    GET_FIELD_ID(jobjs.cam_info.cls, "PjCameraInfo", "supportedFps1000", "[I",
+		 jobjs.cam_info.f_sup_fps);
+
+#undef GET_CLASS_ID
+#undef GET_METHOD_ID
+#undef GET_SMETHOD_ID
+#undef GET_FIELD_ID
+
+    /* Register native function */
+    {
+	JNINativeMethod m = { "PushFrame", "([BIJ)V", (void*)&OnGetFrame };
+	if ((*jni_env)->RegisterNatives(jni_env, jobjs.cam.cls, &m, 1)) {
+	    PJ_LOG(3, (THIS_FILE, "[JNI] Failed in registering native "
+				  "function 'OnGetFrame()'"));
+	    status = PJMEDIA_EVID_SYSERR;
+	}
+    }
+
+on_return:
+    jni_detach_env(with_attach);
+    return status;
+}
+
+
+static void jni_deinit_ids()
+{
+    JNIEnv *jni_env;
+    pj_bool_t with_attach = jni_get_env(&jni_env);
+
+    if (jobjs.cam.cls) {
+	(*jni_env)->DeleteGlobalRef(jni_env, jobjs.cam.cls);
+	jobjs.cam.cls = NULL;
+    }
+
+    if (jobjs.cam_info.cls) {
+	(*jni_env)->DeleteGlobalRef(jni_env, jobjs.cam_info.cls);
+	jobjs.cam_info.cls = NULL;
+    }
+
+    jni_detach_env(with_attach);
+}
+
+
+/****************************************************************************
+ * Helper functions
+ */
+static pjmedia_format_id and_fmt_to_pj(pj_uint32_t fmt)
+{
+    unsigned i;
+    for (i = 0; i < PJ_ARRAY_SIZE(fmt_map); i++) {
+        if (fmt_map[i].and_fmt_id == fmt)
+            return fmt_map[i].fmt_id;
+    }
+    return 0;
+}
+
+static pj_uint32_t pj_fmt_to_and(pjmedia_format_id fmt)
+{
+    unsigned i;
+    for (i = 0; i < PJ_ARRAY_SIZE(fmt_map); i++) {
+        if (fmt_map[i].fmt_id == fmt)
+            return fmt_map[i].and_fmt_id;
+    }
+    return 0;
+}
+
+
+/****************************************************************************
+ * Factory operations
+ */
+/*
+ * Init and_ video driver.
+ */
+pjmedia_vid_dev_factory* pjmedia_and_factory(pj_pool_factory *pf)
+{
+    and_factory *f;
+    pj_pool_t *pool;
+
+    pool = pj_pool_create(pf, "and_video", 512, 512, NULL);
+    f = PJ_POOL_ZALLOC_T(pool, and_factory);
+    f->pf = pf;
+    f->pool = pool;
+    f->base.op = &factory_op;
+    f->dev_pool = pj_pool_create(pf, "and_video_dev", 512, 512, NULL);
+
+    return &f->base;
+}
+
+
+/* API: init factory */
+static pj_status_t and_factory_init(pjmedia_vid_dev_factory *ff)
+{
+    pj_status_t status;
+
+    status = jni_init_ids();
+    if (status != PJ_SUCCESS)
+	return status;
+
+    status = and_factory_refresh(ff);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    return PJ_SUCCESS;
+}
+
+
+/* API: destroy factory */
+static pj_status_t and_factory_destroy(pjmedia_vid_dev_factory *ff)
+{
+    and_factory *f = (and_factory*)ff;
+    pj_pool_t *pool;
+
+    jni_deinit_ids();
+    
+    pool = f->pool;
+    f->pool = NULL;
+    if (pool)
+	pj_pool_release(pool);
+    
+    pool = f->dev_pool;
+    f->dev_pool = NULL;
+    if (pool)
+	pj_pool_release(pool);
+
+    return PJ_SUCCESS;
+}
+
+
+/* API: refresh the list of devices */
+static pj_status_t and_factory_refresh(pjmedia_vid_dev_factory *ff)
+{
+    and_factory *f = (and_factory*)ff;
+    pj_status_t status = PJ_SUCCESS;
+
+    JNIEnv *jni_env;
+    pj_bool_t with_attach, found_front = PJ_FALSE;
+    int i, dev_count = 0;
+
+    /* Clean up device info and pool */
+    f->dev_count = 0;
+    pj_pool_reset(f->dev_pool);
+    
+    with_attach = jni_get_env(&jni_env);
+    
+    /* dev_count = PjCameraInfo::GetCameraCount() */
+    dev_count = (*jni_env)->CallStaticIntMethod(jni_env, jobjs.cam_info.cls,
+						jobjs.cam_info.m_get_cnt);
+    if (dev_count < 0) {
+        PJ_LOG(3, (THIS_FILE, "Failed to get camera count"));
+        status = PJMEDIA_EVID_SYSERR;
+        goto on_return;
+    }
+
+    /* Start querying device info */
+    f->dev_info = (and_dev_info*)
+ 		  pj_pool_calloc(f->dev_pool, dev_count,
+ 				 sizeof(and_dev_info));
+
+    for (i = 0; i < dev_count; i++) {
+	and_dev_info *adi = &f->dev_info[f->dev_count];
+	pjmedia_vid_dev_info *vdi = &adi->info;
+        jobject jdev_info;
+	jobject jtmp;
+	int facing, max_fmt_cnt = PJMEDIA_VID_DEV_INFO_FMT_CNT;
+
+	/* jdev_info = PjCameraInfo::GetCameraInfo(i) */
+	jdev_info = (*jni_env)->CallStaticObjectMethod(
+					    jni_env,
+					    jobjs.cam_info.cls,
+					    jobjs.cam_info.m_get_info,
+					    i);
+	if (jdev_info == NULL)
+	    continue;
+
+	/* Get camera facing: 0=back 1=front */
+	facing = (*jni_env)->GetIntField(jni_env, jdev_info,
+					 jobjs.cam_info.f_facing);
+	if (facing < 0)
+	    goto on_skip_dev;
+	
+	/* Set device ID, direction, and has_callback info */
+	adi->dev_idx = i;
+	vdi->id = f->dev_count;
+	vdi->dir = PJMEDIA_DIR_CAPTURE;
+	vdi->has_callback = PJ_TRUE;
+	vdi->caps = PJMEDIA_VID_DEV_CAP_SWITCH;
+
+	/* Set driver & name info */
+	pj_ansi_strncpy(vdi->driver, "Android", sizeof(vdi->driver));
+	if (facing == 0) {
+	    pj_ansi_strncpy(vdi->name, "Back camera", sizeof(vdi->name));
+	} else {
+	    pj_ansi_strncpy(vdi->name, "Front camera", sizeof(vdi->name));
+	}
+
+	/* Get supported sizes */
+	jtmp = (*jni_env)->GetObjectField(jni_env, jdev_info,
+					  jobjs.cam_info.f_sup_size);
+	if (jtmp) {
+	    jintArray jiarray = (jintArray*)jtmp;
+	    jint *sizes;
+	    jsize cnt, j;
+
+	    cnt = (*jni_env)->GetArrayLength(jni_env, jiarray);
+	    sizes = (*jni_env)->GetIntArrayElements(jni_env, jiarray, 0);
+	    
+	    adi->sup_size_cnt = cnt/2;
+	    adi->sup_size = pj_pool_calloc(f->dev_pool, adi->sup_size_cnt,
+					   sizeof(adi->sup_size[0]));
+	    for (j = 0; j < adi->sup_size_cnt; j++) {
+		adi->sup_size[j].w = sizes[j*2];
+		adi->sup_size[j].h = sizes[j*2+1];
+	    }
+	    (*jni_env)->ReleaseIntArrayElements(jni_env, jiarray, sizes, 0);
+	    (*jni_env)->DeleteLocalRef(jni_env, jtmp);
+	} else {
+	    goto on_skip_dev;
+	}
+
+	/* Get supported formats */
+	jtmp = (*jni_env)->GetObjectField(jni_env, jdev_info,
+					  jobjs.cam_info.f_sup_fmt);
+	if (jtmp) {
+	    jintArray jiarray = (jintArray*)jtmp;
+	    jint *fmts;
+	    jsize cnt, j;
+	    pj_bool_t has_i420 = PJ_FALSE;
+
+	    cnt = (*jni_env)->GetArrayLength(jni_env, jiarray);
+	    fmts = (*jni_env)->GetIntArrayElements(jni_env, jiarray, 0);
+	    for (j = 0; j < cnt; j++) {
+		int k;
+		pjmedia_format_id fmt = and_fmt_to_pj((pj_uint32_t)fmts[j]);
+		
+		/* Check for any duplicate */
+		for (k = 0; k < vdi->fmt_cnt; k++) {
+		    if (fmt == 0 || fmt == vdi->fmt[k].id) {
+			fmt = 0;
+			break;
+		    }
+		}
+
+		/* Make sure we recognize this format */
+		if (fmt == 0)
+		    continue;
+
+		/* Check formats for I420 conversion */
+		if (fmt == PJMEDIA_FORMAT_I420) has_i420 = PJ_TRUE;
+		else if (fmt == PJMEDIA_FORMAT_YV12) adi->has_yv12 = PJ_TRUE;
+		else if (fmt == PJMEDIA_FORMAT_NV21) adi->has_nv21 = PJ_TRUE;
+
+		for (k = 0; k < adi->sup_size_cnt &&
+			    vdi->fmt_cnt < max_fmt_cnt; k++)
+		{
+		    pjmedia_format_init_video(&vdi->fmt[vdi->fmt_cnt++],
+					      fmt,
+					      adi->sup_size[k].w,
+					      adi->sup_size[k].h,
+					      DEFAULT_FPS, 1);
+		}
+	    }
+	    (*jni_env)->ReleaseIntArrayElements(jni_env, jiarray, fmts,
+						JNI_ABORT);
+	    (*jni_env)->DeleteLocalRef(jni_env, jtmp);
+
+	    /* Pretend to support I420/IYUV, only if we support YV12/NV21 */
+	    if (!has_i420 && (adi->has_yv12 || adi->has_nv21) &&
+		vdi->fmt_cnt < PJ_ARRAY_SIZE(vdi->fmt))
+	    {
+		int k;
+		adi->forced_i420 = PJ_TRUE;
+		for (k = 0; k < adi->sup_size_cnt &&
+			    vdi->fmt_cnt < max_fmt_cnt; k++)
+		{
+		    pjmedia_format_init_video(&vdi->fmt[vdi->fmt_cnt++],
+					      PJMEDIA_FORMAT_I420,
+					      adi->sup_size[k].w,
+					      adi->sup_size[k].h,
+					      DEFAULT_FPS, 1);
+		}
+	    }
+	} else {
+	    goto on_skip_dev;
+	}
+	
+	/* If this is front camera, set it as first/default (if not yet) */
+	if (facing == 1) {
+	    if (!found_front && f->dev_count > 0) {
+		/* Swap this front cam info with one whose idx==0 */
+	        and_dev_info tmp_adi;
+		pj_memcpy(&tmp_adi, &f->dev_info[0], sizeof(tmp_adi));
+		pj_memcpy(&f->dev_info[0], adi, sizeof(tmp_adi));
+		pj_memcpy(adi, &tmp_adi, sizeof(tmp_adi));
+		f->dev_info[0].info.id = 0;
+		f->dev_info[f->dev_count].info.id = f->dev_count;
+	    }
+	    found_front = PJ_TRUE;
+	}
+	
+	f->dev_count++;
+
+    on_skip_dev:
+	(*jni_env)->DeleteLocalRef(jni_env, jdev_info);
+    }
+
+    PJ_LOG(4, (THIS_FILE,
+	       "Android video capture initialized with %d device(s):",
+	       f->dev_count));
+    for (i = 0; i < f->dev_count; i++) {
+	and_dev_info *adi = &f->dev_info[i];
+	char tmp_str[1024], *p;
+	int j, plen, slen;
+	PJ_LOG(4, (THIS_FILE, "%2d: %s", i, f->dev_info[i].info.name));
+
+	/* Print supported formats */
+	p = tmp_str;
+	plen = sizeof(tmp_str);
+	for (j = 0; j < adi->info.fmt_cnt; j++) {
+	    char tmp_str2[5];
+	    const pjmedia_video_format_detail *vfd =
+		pjmedia_format_get_video_format_detail(&adi->info.fmt[j], 0);
+	    pjmedia_fourcc_name(adi->info.fmt[j].id, tmp_str2);
+	    slen = pj_ansi_snprintf(p, plen, "%s/%dx%d ",
+				    tmp_str2, vfd->size.w, vfd->size.h);
+	    if (slen < 0 || slen >= plen) break;
+	    plen -= slen;
+	    p += slen;
+	}
+	PJ_LOG(4, (THIS_FILE, "     supported format = %s", tmp_str));
+    }
+
+on_return:
+    jni_detach_env(with_attach);
+    return status;
+}
+
+
+/* API: get number of devices */
+static unsigned and_factory_get_dev_count(pjmedia_vid_dev_factory *ff)
+{
+    and_factory *f = (and_factory*)ff;
+    return f->dev_count;
+}
+
+
+/* API: get device info */
+static pj_status_t and_factory_get_dev_info(pjmedia_vid_dev_factory *f,
+					    unsigned index,
+					    pjmedia_vid_dev_info *info)
+{
+    and_factory *cf = (and_factory*)f;
+
+    PJ_ASSERT_RETURN(index < cf->dev_count, PJMEDIA_EVID_INVDEV);
+
+    pj_memcpy(info, &cf->dev_info[index].info, sizeof(*info));
+
+    return PJ_SUCCESS;
+}
+
+
+/* API: create default device parameter */
+static pj_status_t and_factory_default_param(pj_pool_t *pool,
+                                             pjmedia_vid_dev_factory *f,
+					     unsigned index,
+					     pjmedia_vid_dev_param *param)
+{
+    and_factory *cf = (and_factory*)f;
+    and_dev_info *di = &cf->dev_info[index];
+
+    PJ_ASSERT_RETURN(index < cf->dev_count, PJMEDIA_EVID_INVDEV);
+
+    PJ_UNUSED_ARG(pool);
+
+    pj_bzero(param, sizeof(*param));
+    param->dir = PJMEDIA_DIR_CAPTURE;
+    param->cap_id = index;
+    param->rend_id = PJMEDIA_VID_INVALID_DEV;
+    param->flags = PJMEDIA_VID_DEV_CAP_FORMAT;
+    param->clock_rate = DEFAULT_CLOCK_RATE;
+    pj_memcpy(&param->fmt, &di->info.fmt[0], sizeof(param->fmt));
+
+    return PJ_SUCCESS;
+}
+
+
+/* API: create stream */
+static pj_status_t and_factory_create_stream(
+					pjmedia_vid_dev_factory *ff,
+					pjmedia_vid_dev_param *param,
+					const pjmedia_vid_dev_cb *cb,
+					void *user_data,
+					pjmedia_vid_dev_stream **p_vid_strm)
+{
+    and_factory *f = (and_factory*)ff;
+    pj_pool_t *pool;
+    and_stream *strm;
+    and_dev_info *adi;
+    const pjmedia_video_format_detail *vfd;
+    const pjmedia_video_format_info *vfi;
+    pjmedia_video_apply_fmt_param vafp;
+    pj_uint32_t and_fmt;
+    unsigned convert_to_i420 = 0;
+    pj_status_t status = PJ_SUCCESS;
+
+    JNIEnv *jni_env;
+    pj_bool_t with_attach;
+    jobject jcam;
+
+    PJ_ASSERT_RETURN(f && param && p_vid_strm, PJ_EINVAL);
+    PJ_ASSERT_RETURN(param->fmt.type == PJMEDIA_TYPE_VIDEO &&
+		     param->fmt.detail_type == PJMEDIA_FORMAT_DETAIL_VIDEO &&
+                     param->dir == PJMEDIA_DIR_CAPTURE,
+		     PJ_EINVAL);
+
+    pj_bzero(&vafp, sizeof(vafp));
+    adi = &f->dev_info[param->cap_id];
+    vfd = pjmedia_format_get_video_format_detail(&param->fmt, PJ_TRUE);
+    vfi = pjmedia_get_video_format_info(NULL, param->fmt.id);
+
+    if (param->fmt.id == PJMEDIA_FORMAT_I420 && adi->forced_i420) {
+	/* Not really support I420, need to convert it from YV12/NV21 */
+	if (adi->has_nv21) {
+	    and_fmt = pj_fmt_to_and(PJMEDIA_FORMAT_NV21);
+	    convert_to_i420 = 1;
+	} else if (adi->has_yv12) {
+	    and_fmt = pj_fmt_to_and(PJMEDIA_FORMAT_YV12);
+	    convert_to_i420 = 2;
+	} else
+	    pj_assert(!"Bug!");
+    } else {
+	and_fmt = pj_fmt_to_and(param->fmt.id);
+    }
+    if (!vfi || !and_fmt)
+        return PJMEDIA_EVID_BADFORMAT;
+
+    vafp.size = vfd->size;
+    if (vfi->apply_fmt(vfi, &vafp) != PJ_SUCCESS)
+        return PJMEDIA_EVID_BADFORMAT;
+
+    /* Create and Initialize stream descriptor */
+    pool = pj_pool_create(f->pf, "and-dev", 512, 512, NULL);
+    PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);
+
+    strm = PJ_POOL_ZALLOC_T(pool, and_stream);
+    pj_memcpy(&strm->param, param, sizeof(*param));
+    strm->pool = pool;
+    strm->factory = f;
+    pj_memcpy(&strm->vid_cb, cb, sizeof(*cb));
+    strm->user_data = user_data;
+    pj_memcpy(&strm->vafp, &vafp, sizeof(vafp));
+    strm->ts_inc = PJMEDIA_SPF2(param->clock_rate, &vfd->fps, 1);
+
+    /* Allocate buffer for YV12 -> I420 conversion */
+    if (convert_to_i420) {
+	pj_assert(vfi->plane_cnt > 1);
+	strm->convert_to_i420 = convert_to_i420;
+	strm->convert_buf = pj_pool_alloc(pool, vafp.plane_bytes[1]);
+    }
+
+    /* Native preview */
+    if (param->flags & PJMEDIA_VID_DEV_CAP_INPUT_PREVIEW) {
+    }
+
+    with_attach = jni_get_env(&jni_env);
+
+    /* Instantiate PjCamera */
+    jcam = (*jni_env)->NewObject(jni_env, jobjs.cam.cls, jobjs.cam.m_init,
+				 adi->dev_idx,		/* idx */
+				 vfd->size.w,		/* w */
+				 vfd->size.h,		/* h */
+				 and_fmt,		/* fmt */
+				 vfd->fps.num*1000/
+				 vfd->fps.denum,	/* fps */
+				 (jlong)(intptr_t)strm,	/* user data */
+				 NULL			/* SurfaceView */
+				 );	   
+    if (jcam == NULL) {
+        PJ_LOG(3, (THIS_FILE, "Unable to create PjCamera instance"));
+        status = PJMEDIA_EVID_SYSERR;
+	goto on_return;
+    }
+    strm->jcam = (jobject)(*jni_env)->NewGlobalRef(jni_env, jcam);
+    (*jni_env)->DeleteLocalRef(jni_env, jcam);
+    if (strm->jcam == NULL) {
+        PJ_LOG(3, (THIS_FILE, "Unable to create global ref to PjCamera"));
+        status = PJMEDIA_EVID_SYSERR;
+	goto on_return;
+    }
+
+on_return:
+    jni_detach_env(with_attach);
+
+    /* Success */
+    if (status == PJ_SUCCESS) {
+	strm->base.op = &stream_op;
+	*p_vid_strm = &strm->base;
+    }
+
+    return status;
+}
+
+
+/****************************************************************************
+ * Stream operations
+ */
+
+
+/* API: Get stream info. */
+static pj_status_t and_stream_get_param(pjmedia_vid_dev_stream *s,
+                                        pjmedia_vid_dev_param *pi)
+{
+    and_stream *strm = (and_stream*)s;
+    
+    PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
+    
+    pj_memcpy(pi, &strm->param, sizeof(*pi));
+
+    if (and_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 and_stream_get_cap(pjmedia_vid_dev_stream *s,
+                                      pjmedia_vid_dev_cap cap,
+                                      void *pval)
+{
+    and_stream *strm = (and_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;
+    }
+
+    return PJMEDIA_EVID_INVCAP;
+}
+
+
+/* API: set capability */
+static pj_status_t and_stream_set_cap(pjmedia_vid_dev_stream *s,
+                                      pjmedia_vid_dev_cap cap,
+                                      const void *pval)
+{
+    and_stream *strm = (and_stream*)s;
+    JNIEnv *jni_env;
+    pj_bool_t with_attach;
+    pj_status_t status = PJ_SUCCESS;
+    
+    PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+    switch (cap) {
+	case PJMEDIA_VID_DEV_CAP_SWITCH:
+	{
+	    pjmedia_vid_dev_switch_param *p = (pjmedia_vid_dev_switch_param*)
+					      pval;
+	    and_dev_info *adi;
+	    int res;
+            
+	    /* Just return if current and target device are the same */
+            if (strm->param.cap_id == p->target_id)
+                return PJ_SUCCESS;
+
+	    /* Verify target capture ID */
+	    if (p->target_id < 0 || p->target_id >= strm->factory->dev_count)
+		return PJ_EINVAL;
+
+	    /* Ok, let's do the switch */
+	    adi = &strm->factory->dev_info[p->target_id];
+	    PJ_LOG(4, (THIS_FILE, "Switching camera to %s..", adi->info.name));
+
+	    /* Call PjCamera::Start() method */
+	    with_attach = jni_get_env(&jni_env);
+	    res = (*jni_env)->CallIntMethod(jni_env, strm->jcam,
+					    jobjs.cam.m_switch, adi->dev_idx);
+	    if (res < 0) {
+		PJ_LOG(3, (THIS_FILE, "Failed to switch camera (err=%d)",
+			   res));
+		status = PJMEDIA_EVID_SYSERR;
+	    } else {
+		strm->param.cap_id = p->target_id;
+	    }
+	    jni_detach_env(with_attach);
+	    break;
+	}
+
+	default:
+	    status = PJMEDIA_EVID_INVCAP;
+	    break;
+    }
+    
+    return status;
+}
+
+
+/* API: Start stream. */
+static pj_status_t and_stream_start(pjmedia_vid_dev_stream *s)
+{
+    and_stream *strm = (and_stream*)s;
+    JNIEnv *jni_env;
+    pj_bool_t with_attach;
+    jint res;
+    pj_status_t status = PJ_SUCCESS;
+
+    PJ_LOG(4, (THIS_FILE, "Starting Android camera stream"));
+
+    with_attach = jni_get_env(&jni_env);
+
+    /* Call PjCamera::Start() method */
+    res = (*jni_env)->CallIntMethod(jni_env, strm->jcam, jobjs.cam.m_start);
+    if (res < 0) {
+        PJ_LOG(3, (THIS_FILE, "Failed to start camera (err=%d)", res));
+        status = PJMEDIA_EVID_SYSERR;
+        goto on_return;
+    }
+
+    strm->is_running = PJ_TRUE;
+
+on_return:
+    jni_detach_env(with_attach);
+    return status;
+}
+
+
+/* API: Stop stream. */
+static pj_status_t and_stream_stop(pjmedia_vid_dev_stream *s)
+{
+    and_stream *strm = (and_stream*)s;
+    JNIEnv *jni_env;
+    pj_bool_t with_attach;
+    pj_status_t status = PJ_SUCCESS;
+
+    PJ_ASSERT_RETURN(strm != NULL, PJ_EINVAL);
+    
+    PJ_LOG(4, (THIS_FILE, "Stopping Android camera stream"));
+
+    with_attach = jni_get_env(&jni_env);
+
+    /* Call PjCamera::Stop() method */
+    (*jni_env)->CallVoidMethod(jni_env, strm->jcam, jobjs.cam.m_stop);
+
+    strm->is_running = PJ_FALSE;
+
+    jni_detach_env(with_attach);
+    
+    return status;
+}
+
+
+/* API: Destroy stream. */
+static pj_status_t and_stream_destroy(pjmedia_vid_dev_stream *s)
+{
+    and_stream *strm = (and_stream*)s;
+    JNIEnv *jni_env;
+    pj_bool_t with_attach;
+    
+    PJ_ASSERT_RETURN(strm != NULL, PJ_EINVAL);
+    
+    with_attach = jni_get_env(&jni_env);
+
+    if (strm->is_running)
+	and_stream_stop(s);
+
+    if (strm->jcam) {
+	(*jni_env)->DeleteGlobalRef(jni_env, strm->jcam);
+	strm->jcam = NULL;
+    }
+    
+    jni_detach_env(with_attach);
+
+    PJ_LOG(4, (THIS_FILE, "Android camera stream destroyed"));
+
+    return PJ_SUCCESS;
+}
+
+static void JNICALL OnGetFrame(JNIEnv *env, jobject obj,
+                               jbyteArray data, jint length,
+			       jlong user_data)
+{
+    and_stream *strm = *(and_stream**)&user_data;
+    pjmedia_frame f;
+    pj_uint8_t *Y, *U, *V;
+
+    strm->frame_ts.u64 += strm->ts_inc;
+    if (!strm->vid_cb.capture_cb)
+	return;
+
+    if (strm->thread_initialized == 0 || !pj_thread_is_registered()) {
+	pj_status_t status;
+	pj_bzero(strm->thread_desc, sizeof(pj_thread_desc));
+	status = pj_thread_register("and_cam", strm->thread_desc,
+				    &strm->thread);
+	if (status != PJ_SUCCESS)
+	    return;
+	strm->thread_initialized = 1;
+	PJ_LOG(5,(THIS_FILE, "Android camera thread registered"));
+    }
+
+    f.type = PJMEDIA_FRAME_TYPE_VIDEO;
+    f.size = length;
+    f.timestamp.u64 = strm->frame_ts.u64;
+    f.buf = (*env)->GetByteArrayElements(env, data, 0);
+
+    Y = (pj_uint8_t*)f.buf;
+    U = Y + strm->vafp.plane_bytes[0];
+    V = U + strm->vafp.plane_bytes[1];
+
+    /* Convert NV21 -> I420, i.e: separate V/U interleaved data plane
+     * into U & V planes.
+     */
+    if (strm->convert_to_i420 == 1) {
+	pj_uint8_t *src = U;
+	pj_uint8_t *dst_u = U;
+	pj_uint8_t *end_u = U + strm->vafp.plane_bytes[1];
+	pj_uint8_t *dst_v = strm->convert_buf;
+	while (dst_u < end_u) {
+	    *dst_v++ = *src++;
+	    *dst_u++ = *src++;
+	}
+	pj_memcpy(V, strm->convert_buf, strm->vafp.plane_bytes[2]);
+    }
+
+    /* Convert YV12 -> I420, i.e: swap U & V planes. We also need to
+     * strip out padding, if any.
+     */
+    else if (strm->convert_to_i420 == 2) {
+	int y_stride  = ALIGN16(strm->vafp.size.w);
+	int uv_stride = ALIGN16(strm->vafp.size.w/2);
+
+	/* Strip out Y padding */
+	if (y_stride > strm->vafp.size.w) {
+	    int i;
+	    pj_uint8_t *src = Y + y_stride;
+	    pj_uint8_t *dst = Y + strm->vafp.size.w;
+
+	    for (i = 1; i < strm->vafp.size.h; ++i) {
+		memmove(dst, src, strm->vafp.size.w);
+		src += y_stride;
+		dst += strm->vafp.size.w;
+	    }
+	}
+
+	/* Swap U & V planes */
+	if (uv_stride == strm->vafp.size.w/2) {
+
+	    /* No padding, note Y plane should be no padding too! */
+	    pj_assert(y_stride == strm->vafp.size.w);
+	    pj_memcpy(strm->convert_buf, U, strm->vafp.plane_bytes[1]);
+	    pj_memmove(U, V, strm->vafp.plane_bytes[1]);
+	    pj_memcpy(V, strm->convert_buf, strm->vafp.plane_bytes[1]);
+
+	} else if (uv_stride > strm->vafp.size.w/2) {
+
+	    /* Strip & copy V plane into conversion buffer */
+	    pj_uint8_t *src = Y + y_stride*strm->vafp.size.h;
+	    pj_uint8_t *dst = strm->convert_buf;
+	    unsigned dst_stride = strm->vafp.size.w/2;
+	    int i;
+	    for (i = 0; i < strm->vafp.size.h/2; ++i) {
+		memmove(dst, src, dst_stride);
+		src += uv_stride;
+		dst += dst_stride;
+	    }
+
+	    /* Strip U plane */
+	    dst = U;
+	    for (i = 0; i < strm->vafp.size.h/2; ++i) {
+		memmove(dst, src, dst_stride);
+		src += uv_stride;
+		dst += dst_stride;
+	    }
+
+	    /* Get V plane data from conversion buffer */
+	    pj_memcpy(V, strm->convert_buf, strm->vafp.plane_bytes[2]);
+
+	}
+    }
+
+    (*strm->vid_cb.capture_cb)(&strm->base, strm->user_data, &f);
+    (*env)->ReleaseByteArrayElements(env, data, f.buf, JNI_ABORT);
+}
+
+#endif	/* PJMEDIA_VIDEO_DEV_HAS_ANDROID */
Index: /pjproject/trunk/pjmedia/src/pjmedia-videodev/videodev.c
===================================================================
--- /pjproject/trunk/pjmedia/src/pjmedia-videodev/videodev.c	(revision 4993)
+++ /pjproject/trunk/pjmedia/src/pjmedia-videodev/videodev.c	(revision 4994)
@@ -100,4 +100,8 @@
 #if PJMEDIA_VIDEO_DEV_HAS_OPENGL
 pjmedia_vid_dev_factory* pjmedia_opengl_factory(pj_pool_factory *pf);
+#endif
+
+#if PJMEDIA_VIDEO_DEV_HAS_ANDROID
+pjmedia_vid_dev_factory* pjmedia_and_factory(pj_pool_factory *pf);
 #endif
 
@@ -394,9 +398,15 @@
     vid_subsys.drv[vid_subsys.drv_cnt++].create = &pjmedia_ffmpeg_factory;
 #endif
-#if PJMEDIA_VIDEO_DEV_HAS_CBAR_SRC
-    vid_subsys.drv[vid_subsys.drv_cnt++].create = &pjmedia_cbar_factory;
-#endif
 #if PJMEDIA_VIDEO_DEV_HAS_SDL
     vid_subsys.drv[vid_subsys.drv_cnt++].create = &pjmedia_sdl_factory;
+#endif
+#if PJMEDIA_VIDEO_DEV_HAS_ANDROID
+    vid_subsys.drv[vid_subsys.drv_cnt++].create = &pjmedia_and_factory;
+#endif
+#if PJMEDIA_VIDEO_DEV_HAS_CBAR_SRC
+    /* Better put colorbar at the last, so the default capturer will be
+     * a real capturer, if any.
+     */
+    vid_subsys.drv[vid_subsys.drv_cnt++].create = &pjmedia_cbar_factory;
 #endif
 
Index: /pjproject/trunk/pjsip-apps/src/pjsua/android/src/org/pjsip/PjCamera.java
===================================================================
--- /pjproject/trunk/pjsip-apps/src/pjsua/android/src/org/pjsip/PjCamera.java	(revision 4994)
+++ /pjproject/trunk/pjsip-apps/src/pjsua/android/src/org/pjsip/PjCamera.java	(revision 4994)
@@ -0,0 +1,189 @@
+package org.pjsip;
+
+import android.graphics.SurfaceTexture;
+import android.hardware.Camera;
+import android.util.Log;
+import android.view.SurfaceView;
+import android.view.SurfaceHolder;
+
+import java.io.IOException;
+
+public class PjCamera implements Camera.PreviewCallback, SurfaceHolder.Callback
+{
+    private final String TAG = "PjCamera";
+
+    public class Param {
+	public int width;
+	public int height;
+	public int format;
+	public int fps1000;
+    }
+
+    private Camera camera = null;
+    private boolean isRunning = false;
+    private int camIdx;
+    private long userData;
+
+    private Param param = null;
+
+    private SurfaceView surfaceView = null;
+    private SurfaceHolder surfaceHolder = null;
+    private SurfaceTexture surfaceTexture = null;
+
+    public PjCamera(int idx, int w, int h, int fmt, int fps,
+	    	    long userData_, SurfaceView surface)
+    {
+	camIdx = idx;
+	userData = userData_;
+
+	param = new Param();
+	param.width = w;
+	param.height = h;
+	param.format = fmt;
+	param.fps1000 = fps;
+
+	SetSurfaceView(surface);
+    }
+
+    public void SetSurfaceView(SurfaceView surface)
+    {
+	boolean isCaptureRunning = isRunning;
+
+	if (isCaptureRunning)
+	    Stop();
+
+	if (surface != null) {
+	    surfaceView = surface;
+	    surfaceHolder = surfaceView.getHolder();
+	} else {
+	    // Create dummy texture
+	    surfaceHolder = null;
+	    surfaceView = null;
+	    if (surfaceTexture == null) {
+		surfaceTexture = new SurfaceTexture(10);
+	    }
+	}
+
+	if (isCaptureRunning)
+	    Start();
+    }
+
+    public int SwitchDevice(int idx)
+    {
+	boolean isCaptureRunning = isRunning;
+	int oldIdx = camIdx;
+
+	if (isCaptureRunning)
+	    Stop();
+
+	camIdx = idx;
+
+	if (isCaptureRunning) {
+	    int ret = Start();
+	    if (ret != 0) {
+		/* Try to revert back */
+		camIdx = oldIdx;
+		Start();
+		return ret; 
+	    }
+	}
+
+	return 0;
+    }
+
+    public int Start()
+    {
+	try {
+	    camera = Camera.open(camIdx);
+	} catch (Exception e) {
+	    Log.d("IOException", e.getMessage());
+	    return -10;
+	}
+
+	try {
+	    if (surfaceHolder != null) {
+		camera.setPreviewDisplay(surfaceHolder);
+		surfaceHolder.addCallback(this);
+	    } else {
+		camera.setPreviewTexture(surfaceTexture);
+	    }
+	} catch (IOException e) {
+	    Log.d("IOException", e.getMessage());
+	    return -20;
+	}
+
+	Camera.Parameters cp = camera.getParameters();
+	cp.setPreviewSize(param.width, param.height);
+	cp.setPreviewFormat(param.format);
+	cp.setPreviewFpsRange(param.fps1000, param.fps1000);
+	try {
+	    camera.setParameters(cp);
+	} catch (RuntimeException e) {
+	    Log.d("RuntimeException", e.getMessage());
+	    return -30;
+	}
+
+	camera.setPreviewCallback(this);
+	camera.startPreview();
+	isRunning = true;
+
+	return 0;
+    }
+
+    public void Stop()
+    {
+	isRunning = false;
+	if (camera == null)
+	    return;
+
+	if (surfaceHolder != null)
+	    surfaceHolder.removeCallback(this);
+
+	camera.setPreviewCallback(null);
+	camera.stopPreview();
+	camera.release();
+	camera = null;
+    }
+
+    native void PushFrame(byte[] data, int length, long userData_);
+
+    public void onPreviewFrame(byte[] data, Camera camera)
+    {
+	if (isRunning) {
+	    PushFrame(data, data.length, userData);
+	}
+    }
+
+    public void surfaceChanged(SurfaceHolder holder,
+	int format, int width, int height)
+    {
+	Log.d(TAG, "VideoCaptureAndroid::surfaceChanged");
+    }
+
+    public void surfaceCreated(SurfaceHolder holder)
+    {
+	Log.d(TAG, "VideoCaptureAndroid::surfaceCreated");
+	try {
+	    if(camera != null) {
+		camera.setPreviewDisplay(holder);
+	    }
+	} catch (IOException e) {
+	    Log.e(TAG, "Failed to set preview surface!", e);
+	}
+    }
+
+    public void surfaceDestroyed(SurfaceHolder holder)
+    {
+	Log.d(TAG, "VideoCaptureAndroid::surfaceDestroyed");
+	try {
+	    if(camera != null) {
+		camera.setPreviewDisplay(null);
+	    }
+	} catch (IOException e) {
+	    Log.e(TAG, "Failed to clear preview surface!", e);
+	} catch (RuntimeException e) {
+	    Log.w(TAG, "Clear preview surface useless", e);
+	}
+    }
+
+}
Index: /pjproject/trunk/pjsip-apps/src/pjsua/android/src/org/pjsip/PjCameraInfo.java
===================================================================
--- /pjproject/trunk/pjsip-apps/src/pjsua/android/src/org/pjsip/PjCameraInfo.java	(revision 4994)
+++ /pjproject/trunk/pjsip-apps/src/pjsua/android/src/org/pjsip/PjCameraInfo.java	(revision 4994)
@@ -0,0 +1,92 @@
+package org.pjsip;
+
+import java.util.List;
+
+import android.hardware.Camera;
+import android.util.Log;
+
+public class PjCameraInfo {
+    public int facing;
+    public int orient;
+    public int[] supportedSize;		// [w1, h1, w2, h2, ...]
+    public int[] supportedFps1000;	// [min1, max1, min2, max2, ...]
+    public int[] supportedFormat;	// [fmt1, fmt2, ...]
+
+    // convert Format list {fmt1, fmt2, ...} to [fmt1, fmt2, ...]
+    private static int[] IntegerListToIntArray(List<Integer> list)
+    {
+	int[] li = new int[list.size()];
+	int i = 0;
+	for (Integer e : list)  {
+	    li[i++] = e.intValue();
+	}
+	return li;
+    }
+
+    // convert Fps list {[min1, max1], [min2, max2], ...} to 
+    // [min1, max1, min2, max2, ...]
+    private static int[] IntArrayListToIntArray(List<int[]> list)
+    {
+	int[] li = new int[list.size() * 2];
+	int i = 0;
+	for (int[] e : list)  {
+	    li[i++] = e[0];
+	    li[i++] = e[1];
+	}
+	return li;
+    }
+
+    // convert Size list {{w1, h1}, {w2, h2}, ...} to [w1, h1, w2, h2, ...]
+    private static int[] CameraSizeListToIntArray(List<Camera.Size> list)
+    {
+	int[] li = new int[list.size() * 2];
+	int i = 0;
+	for (Camera.Size e : list)  {
+	    li[i++] = e.width;
+	    li[i++] = e.height;
+	}
+	return li;
+    }
+
+    public static int GetCameraCount()
+    {
+	return Camera.getNumberOfCameras();
+    }
+
+    // Get camera info: facing, orientation, supported size/fps/format.
+    // Camera must not be opened.
+    public static PjCameraInfo GetCameraInfo(int idx)
+    {
+	if (idx < 0 || idx >= GetCameraCount())
+	    return null;
+
+	Camera cam;
+	try {
+	    cam = Camera.open(idx);
+	} catch (Exception e) {
+	    Log.d("IOException", e.getMessage());
+	    return null;
+	}
+
+	PjCameraInfo pjci = new PjCameraInfo();
+
+	Camera.CameraInfo ci = new Camera.CameraInfo();
+	Camera.getCameraInfo(idx, ci);
+
+	pjci.facing = ci.facing;
+	pjci.orient = ci.orientation;
+
+	Camera.Parameters param = cam.getParameters();
+	cam.release();
+	cam = null;
+
+	pjci.supportedFormat = IntegerListToIntArray(
+				    param.getSupportedPreviewFormats());
+	pjci.supportedFps1000 = IntArrayListToIntArray(
+				    param.getSupportedPreviewFpsRange());
+	pjci.supportedSize = CameraSizeListToIntArray(
+				    param.getSupportedPreviewSizes());
+
+	return pjci;
+    }
+}
