* #27232: jni: added pjproject checkout as regular git content

We will remove it once the next release of pjsip (with Android support)
comes out and is merged into SFLphone.
diff --git a/jni/pjproject-android/pjmedia/src/pjmedia-videodev/avi_dev.c b/jni/pjproject-android/pjmedia/src/pjmedia-videodev/avi_dev.c
new file mode 100644
index 0000000..e31e703
--- /dev/null
+++ b/jni/pjproject-android/pjmedia/src/pjmedia-videodev/avi_dev.c
@@ -0,0 +1,678 @@
+/* $Id: avi_dev.c 4537 2013-06-19 06:47:43Z riza $ */
+/*
+ * Copyright (C) 2008-2011 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 <pjmedia-videodev/avi_dev.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/rand.h>
+#include <pjmedia/vid_codec.h>
+
+#if defined(PJMEDIA_VIDEO_DEV_HAS_AVI) && PJMEDIA_VIDEO_DEV_HAS_AVI != 0 && \
+    defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
+
+#define THIS_FILE		"avi_dev.c"
+#define DRIVER_NAME		"AVIDev"
+#define DEFAULT_CLOCK_RATE	90000
+#define DEFAULT_WIDTH		640
+#define DEFAULT_HEIGHT		480
+#define DEFAULT_FPS		25
+
+typedef struct avi_dev_strm avi_dev_strm;
+
+/* avi_ device info */
+struct avi_dev_info
+{
+    pjmedia_vid_dev_info	 info;
+
+    pj_pool_t			*pool;
+    pj_str_t			 fpath;
+    pj_str_t			 title;
+    pjmedia_avi_streams		*avi;
+    pjmedia_port                *vid;
+    avi_dev_strm		*strm;
+    pjmedia_vid_codec           *codec;
+    pj_uint8_t                  *enc_buf;
+    pj_size_t                    enc_buf_size;
+};
+
+/* avi_ factory */
+struct avi_factory
+{
+    pjmedia_vid_dev_factory	 base;
+    pj_pool_t			*pool;
+    pj_pool_factory		*pf;
+
+    unsigned			 dev_count;
+    struct avi_dev_info		*dev_info;
+};
+
+/* Video stream. */
+struct avi_dev_strm
+{
+    pjmedia_vid_dev_stream	     base;	    /**< Base stream	    */
+    pjmedia_vid_dev_param	     param;	    /**< Settings	    */
+    pj_pool_t			    *pool;          /**< Memory pool.       */
+    struct avi_dev_info		    *adi;
+
+    pjmedia_vid_dev_cb		     vid_cb;	    /**< Stream callback.   */
+    void			    *user_data;	    /**< Application data.  */
+};
+
+
+/* Prototypes */
+static pj_status_t avi_factory_init(pjmedia_vid_dev_factory *f);
+static pj_status_t avi_factory_destroy(pjmedia_vid_dev_factory *f);
+static pj_status_t avi_factory_refresh(pjmedia_vid_dev_factory *f);
+static unsigned    avi_factory_get_dev_count(pjmedia_vid_dev_factory *f);
+static pj_status_t avi_factory_get_dev_info(pjmedia_vid_dev_factory *f,
+					     unsigned index,
+					     pjmedia_vid_dev_info *info);
+static pj_status_t avi_factory_default_param(pj_pool_t *pool,
+                                              pjmedia_vid_dev_factory *f,
+					      unsigned index,
+					      pjmedia_vid_dev_param *param);
+static pj_status_t avi_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 avi_dev_strm_get_param(pjmedia_vid_dev_stream *strm,
+					  pjmedia_vid_dev_param *param);
+static pj_status_t avi_dev_strm_get_cap(pjmedia_vid_dev_stream *strm,
+				        pjmedia_vid_dev_cap cap,
+				        void *value);
+static pj_status_t avi_dev_strm_set_cap(pjmedia_vid_dev_stream *strm,
+				        pjmedia_vid_dev_cap cap,
+				        const void *value);
+static pj_status_t avi_dev_strm_get_frame(pjmedia_vid_dev_stream *strm,
+                                          pjmedia_frame *frame);
+static pj_status_t avi_dev_strm_start(pjmedia_vid_dev_stream *strm);
+static pj_status_t avi_dev_strm_stop(pjmedia_vid_dev_stream *strm);
+static pj_status_t avi_dev_strm_destroy(pjmedia_vid_dev_stream *strm);
+
+static void reset_dev_info(struct avi_dev_info *adi);
+
+/* Operations */
+static pjmedia_vid_dev_factory_op factory_op =
+{
+    &avi_factory_init,
+    &avi_factory_destroy,
+    &avi_factory_get_dev_count,
+    &avi_factory_get_dev_info,
+    &avi_factory_default_param,
+    &avi_factory_create_stream,
+    &avi_factory_refresh
+};
+
+static pjmedia_vid_dev_stream_op stream_op =
+{
+    &avi_dev_strm_get_param,
+    &avi_dev_strm_get_cap,
+    &avi_dev_strm_set_cap,
+    &avi_dev_strm_start,
+    &avi_dev_strm_get_frame,
+    NULL,
+    &avi_dev_strm_stop,
+    &avi_dev_strm_destroy
+};
+
+
+/****************************************************************************
+ * Factory operations
+ */
+
+/* API */
+PJ_DEF(pj_status_t) pjmedia_avi_dev_create_factory(
+				    pj_pool_factory *pf,
+				    unsigned max_dev,
+				    pjmedia_vid_dev_factory **p_ret)
+{
+    struct avi_factory *cf;
+    pj_pool_t *pool;
+    pj_status_t status;
+
+    pool = pj_pool_create(pf, "avidevfc%p", 512, 512, NULL);
+    cf = PJ_POOL_ZALLOC_T(pool, struct avi_factory);
+    cf->pf = pf;
+    cf->pool = pool;
+    cf->dev_count = max_dev;
+    cf->base.op = &factory_op;
+
+    cf->dev_info = (struct avi_dev_info*)
+ 		   pj_pool_calloc(cf->pool, cf->dev_count,
+ 				  sizeof(struct avi_dev_info));
+
+    if (p_ret) {
+	*p_ret = &cf->base;
+    }
+
+    status = pjmedia_vid_register_factory(NULL, &cf->base);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    PJ_LOG(4, (THIS_FILE, "AVI dev factory created with %d virtual device(s)",
+	       cf->dev_count));
+
+    return PJ_SUCCESS;
+}
+
+/* API: init factory */
+static pj_status_t avi_factory_init(pjmedia_vid_dev_factory *f)
+{
+    struct avi_factory *cf = (struct avi_factory*)f;
+    unsigned i;
+
+    for (i=0; i<cf->dev_count; ++i) {
+	reset_dev_info(&cf->dev_info[i]);
+    }
+
+    return PJ_SUCCESS;
+}
+
+/* API: destroy factory */
+static pj_status_t avi_factory_destroy(pjmedia_vid_dev_factory *f)
+{
+    struct avi_factory *cf = (struct avi_factory*)f;
+    pj_pool_t *pool = cf->pool;
+
+    cf->pool = NULL;
+    pj_pool_release(pool);
+
+    return PJ_SUCCESS;
+}
+
+/* API: refresh the list of devices */
+static pj_status_t avi_factory_refresh(pjmedia_vid_dev_factory *f)
+{
+    PJ_UNUSED_ARG(f);
+    return PJ_SUCCESS;
+}
+
+/* API: get number of devices */
+static unsigned avi_factory_get_dev_count(pjmedia_vid_dev_factory *f)
+{
+    struct avi_factory *cf = (struct avi_factory*)f;
+    return cf->dev_count;
+}
+
+/* API: get device info */
+static pj_status_t avi_factory_get_dev_info(pjmedia_vid_dev_factory *f,
+					     unsigned index,
+					     pjmedia_vid_dev_info *info)
+{
+    struct avi_factory *cf = (struct avi_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 avi_factory_default_param(pj_pool_t *pool,
+                                              pjmedia_vid_dev_factory *f,
+					      unsigned index,
+					      pjmedia_vid_dev_param *param)
+{
+    struct avi_factory *cf = (struct avi_factory*)f;
+    struct avi_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;
+}
+
+/* reset dev info */
+static void reset_dev_info(struct avi_dev_info *adi)
+{
+    /* Close avi streams */
+    if (adi->avi) {
+	unsigned i, cnt;
+
+	cnt = pjmedia_avi_streams_get_num_streams(adi->avi);
+	for (i=0; i<cnt; ++i) {
+	    pjmedia_avi_stream *as;
+
+	    as = pjmedia_avi_streams_get_stream(adi->avi, i);
+	    if (as) {
+		pjmedia_port *port;
+		port = pjmedia_avi_stream_get_port(as);
+		pjmedia_port_destroy(port);
+	    }
+	}
+	adi->avi = NULL;
+    }
+
+    if (adi->codec) {
+        pjmedia_vid_codec_close(adi->codec);
+        adi->codec = NULL;
+    }
+
+    if (adi->pool)
+	pj_pool_release(adi->pool);
+
+    pj_bzero(adi, sizeof(*adi));
+
+    /* Fill up with *dummy" device info */
+    pj_ansi_strncpy(adi->info.name, "AVI Player", sizeof(adi->info.name)-1);
+    pj_ansi_strncpy(adi->info.driver, DRIVER_NAME, sizeof(adi->info.driver)-1);
+    adi->info.dir = PJMEDIA_DIR_CAPTURE;
+    adi->info.has_callback = PJ_FALSE;
+}
+
+/* API: release resources */
+PJ_DEF(pj_status_t) pjmedia_avi_dev_free(pjmedia_vid_dev_index id)
+{
+    pjmedia_vid_dev_factory *f;
+    struct avi_factory *cf;
+    unsigned local_idx;
+    struct avi_dev_info *adi;
+    pj_status_t status;
+
+    /* Lookup the factory and local device index */
+    status = pjmedia_vid_dev_get_local_index(id, &f, &local_idx);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    /* The factory must be AVI factory */
+    PJ_ASSERT_RETURN(f->op->init == &avi_factory_init, PJMEDIA_EVID_INVDEV);
+    cf = (struct avi_factory*)f;
+
+    /* Device index should be valid */
+    PJ_ASSERT_RETURN(local_idx <= cf->dev_count, PJ_EBUG);
+    adi = &cf->dev_info[local_idx];
+
+    /* Cannot configure if stream is running */
+    if (adi->strm)
+	return PJ_EBUSY;
+
+    /* Reset */
+    reset_dev_info(adi);
+    return PJ_SUCCESS;
+}
+
+/* API: get param */
+PJ_DEF(pj_status_t) pjmedia_avi_dev_get_param(pjmedia_vid_dev_index id,
+                                              pjmedia_avi_dev_param *prm)
+{
+    pjmedia_vid_dev_factory *f;
+    struct avi_factory *cf;
+    unsigned local_idx;
+    struct avi_dev_info *adi;
+    pj_status_t status;
+
+    /* Lookup the factory and local device index */
+    status = pjmedia_vid_dev_get_local_index(id, &f, &local_idx);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    /* The factory must be factory */
+    PJ_ASSERT_RETURN(f->op->init == &avi_factory_init, PJMEDIA_EVID_INVDEV);
+    cf = (struct avi_factory*)f;
+
+    /* Device index should be valid */
+    PJ_ASSERT_RETURN(local_idx <= cf->dev_count, PJ_EBUG);
+    adi = &cf->dev_info[local_idx];
+
+    pj_bzero(prm, sizeof(*prm));
+    prm->path = adi->fpath;
+    prm->title = adi->title;
+    prm->avi_streams = adi->avi;
+
+    return PJ_SUCCESS;
+}
+
+PJ_DEF(void) pjmedia_avi_dev_param_default(pjmedia_avi_dev_param *p)
+{
+    pj_bzero(p, sizeof(*p));
+}
+
+/* API: configure the AVI */
+PJ_DEF(pj_status_t) pjmedia_avi_dev_alloc( pjmedia_vid_dev_factory *f,
+                                           pjmedia_avi_dev_param *p,
+                                           pjmedia_vid_dev_index *p_id)
+{
+    pjmedia_vid_dev_index id;
+    struct avi_factory *cf = (struct avi_factory*)f;
+    unsigned local_idx;
+    struct avi_dev_info *adi = NULL;
+    pjmedia_format avi_fmt;
+    const pjmedia_video_format_info *vfi;
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(f && p && p_id, PJ_EINVAL);
+
+    if (p_id)
+	*p_id = PJMEDIA_VID_INVALID_DEV;
+
+    /* Get a free dev */
+    for (local_idx=0; local_idx<cf->dev_count; ++local_idx) {
+	if (cf->dev_info[local_idx].avi == NULL) {
+	    adi = &cf->dev_info[local_idx];
+	    break;
+	}
+    }
+
+    if (!adi)
+	return PJ_ETOOMANY;
+
+    /* Convert local ID to global id */
+    status = pjmedia_vid_dev_get_global_index(&cf->base, local_idx, &id);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    /* Reset */
+    if (adi->pool) {
+	pj_pool_release(adi->pool);
+    }
+    pj_bzero(adi, sizeof(*adi));
+
+    /* Reinit */
+    PJ_ASSERT_RETURN(p->path.slen, PJ_EINVAL);
+    adi->pool = pj_pool_create(cf->pf, "avidi%p", 512, 512, NULL);
+
+
+    /* Open the AVI */
+    pj_strdup_with_null(adi->pool, &adi->fpath, &p->path);
+    status = pjmedia_avi_player_create_streams(adi->pool, adi->fpath.ptr, 0,
+                                               &adi->avi);
+    if (status != PJ_SUCCESS) {
+	goto on_error;
+    }
+
+    adi->vid = pjmedia_avi_streams_get_stream_by_media(adi->avi, 0,
+                                                       PJMEDIA_TYPE_VIDEO);
+    if (!adi->vid) {
+	status = PJMEDIA_EVID_BADFORMAT;
+	PJ_LOG(4,(THIS_FILE, "Error: cannot find video in AVI %s",
+		adi->fpath.ptr));
+	goto on_error;
+    }
+
+    pjmedia_format_copy(&avi_fmt, &adi->vid->info.fmt);
+    vfi = pjmedia_get_video_format_info(NULL, avi_fmt.id);
+    /* Check whether the frame is encoded. */
+    if (!vfi || vfi->bpp == 0) {
+        /* Yes, prepare codec */
+        const pjmedia_vid_codec_info *codec_info;
+        pjmedia_vid_codec_param codec_param;
+	pjmedia_video_apply_fmt_param vafp;
+
+        /* Lookup codec */
+        status = pjmedia_vid_codec_mgr_get_codec_info2(NULL,
+                                                       avi_fmt.id,
+                                                       &codec_info);
+        if (status != PJ_SUCCESS || !codec_info)
+            goto on_error;
+
+        status = pjmedia_vid_codec_mgr_get_default_param(NULL, codec_info,
+                                                         &codec_param);
+        if (status != PJ_SUCCESS)
+            goto on_error;
+
+        /* Open codec */
+        status = pjmedia_vid_codec_mgr_alloc_codec(NULL, codec_info,
+                                                   &adi->codec);
+        if (status != PJ_SUCCESS)
+            goto on_error;
+
+        status = pjmedia_vid_codec_init(adi->codec, adi->pool);
+        if (status != PJ_SUCCESS)
+            goto on_error;
+
+        codec_param.dir = PJMEDIA_DIR_DECODING;
+        codec_param.packing = PJMEDIA_VID_PACKING_WHOLE;
+        status = pjmedia_vid_codec_open(adi->codec, &codec_param);
+        if (status != PJ_SUCCESS)
+            goto on_error;
+
+	/* Allocate buffer */
+        avi_fmt.id = codec_info->dec_fmt_id[0];
+        vfi = pjmedia_get_video_format_info(NULL, avi_fmt.id);
+	pj_bzero(&vafp, sizeof(vafp));
+	vafp.size = avi_fmt.det.vid.size;
+	status = vfi->apply_fmt(vfi, &vafp);
+	if (status != PJ_SUCCESS)
+	    goto on_error;
+
+	adi->enc_buf = pj_pool_alloc(adi->pool, vafp.framebytes);
+	adi->enc_buf_size = vafp.framebytes;
+    }
+
+    /* Calculate title */
+    if (p->title.slen) {
+	pj_strdup_with_null(adi->pool, &adi->title, &p->title);
+    } else {
+	char *start = p->path.ptr + p->path.slen;
+	pj_str_t tmp;
+
+	while (start >= p->path.ptr) {
+	    if (*start == '/' || *start == '\\')
+		break;
+	    --start;
+	}
+	tmp.ptr = start + 1;
+	tmp.slen = p->path.ptr + p->path.slen - tmp.ptr;
+	pj_strdup_with_null(adi->pool, &adi->title, &tmp);
+    }
+
+    /* Init device info */
+    pj_ansi_strncpy(adi->info.name, adi->title.ptr, sizeof(adi->info.name)-1);
+    pj_ansi_strncpy(adi->info.driver, DRIVER_NAME, sizeof(adi->info.driver)-1);
+    adi->info.dir = PJMEDIA_DIR_CAPTURE;
+    adi->info.has_callback = PJ_FALSE;
+
+    adi->info.caps = PJMEDIA_VID_DEV_CAP_FORMAT;
+    adi->info.fmt_cnt = 1;
+    pjmedia_format_copy(&adi->info.fmt[0], &avi_fmt);
+
+    /* Set out vars */
+    if (p_id)
+	*p_id = id;
+    p->avi_streams = adi->avi;
+    if (p->title.slen == 0)
+	p->title = adi->title;
+
+    return PJ_SUCCESS;
+
+on_error:
+    if (adi->codec) {
+        pjmedia_vid_codec_close(adi->codec);
+        adi->codec = NULL;
+    }
+    if (adi->pool) {
+	pj_pool_release(adi->pool);
+	adi->pool = NULL;
+    }
+    pjmedia_avi_dev_free(id);
+    return status;
+}
+
+
+/* API: create stream */
+static pj_status_t avi_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)
+{
+    struct avi_factory *cf = (struct avi_factory*)f;
+    pj_pool_t *pool = NULL;
+    struct avi_dev_info *adi;
+    struct avi_dev_strm *strm;
+
+    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);
+
+    /* Device must have been configured with pjmedia_avi_dev_set_param() */
+    adi = &cf->dev_info[param->cap_id];
+    PJ_ASSERT_RETURN(adi->avi != NULL, PJ_EINVALIDOP);
+
+    /* Cannot create while stream is already active */
+    PJ_ASSERT_RETURN(adi->strm==NULL, PJ_EINVALIDOP);
+
+    /* Create and initialize basic stream descriptor */
+    pool = pj_pool_create(cf->pf, "avidev%p", 512, 512, NULL);
+    PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);
+
+    strm = PJ_POOL_ZALLOC_T(pool, struct avi_dev_strm);
+    pj_memcpy(&strm->param, param, sizeof(*param));
+    strm->pool = pool;
+    pj_memcpy(&strm->vid_cb, cb, sizeof(*cb));
+    strm->user_data = user_data;
+    strm->adi = adi;
+
+    pjmedia_format_copy(&param->fmt, &adi->info.fmt[0]);
+
+    /* Done */
+    strm->base.op = &stream_op;
+    adi->strm = strm;
+    *p_vid_strm = &strm->base;
+
+    return PJ_SUCCESS;
+}
+
+/* API: Get stream info. */
+static pj_status_t avi_dev_strm_get_param(pjmedia_vid_dev_stream *s,
+					 pjmedia_vid_dev_param *pi)
+{
+    struct avi_dev_strm *strm = (struct avi_dev_strm*)s;
+
+    PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
+
+    pj_memcpy(pi, &strm->param, sizeof(*pi));
+
+    return PJ_SUCCESS;
+}
+
+/* API: get capability */
+static pj_status_t avi_dev_strm_get_cap(pjmedia_vid_dev_stream *s,
+				       pjmedia_vid_dev_cap cap,
+				       void *pval)
+{
+    struct avi_dev_strm *strm = (struct avi_dev_strm*)s;
+
+    PJ_UNUSED_ARG(strm);
+    PJ_UNUSED_ARG(cap);
+    PJ_UNUSED_ARG(pval);
+
+    PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+    return PJMEDIA_EVID_INVCAP;
+}
+
+/* API: set capability */
+static pj_status_t avi_dev_strm_set_cap(pjmedia_vid_dev_stream *s,
+				       pjmedia_vid_dev_cap cap,
+				       const void *pval)
+{
+    struct avi_dev_strm *strm = (struct avi_dev_strm*)s;
+
+    PJ_UNUSED_ARG(strm);
+    PJ_UNUSED_ARG(cap);
+    PJ_UNUSED_ARG(pval);
+
+    PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+    return PJMEDIA_EVID_INVCAP;
+}
+
+/* API: Get frame from stream */
+static pj_status_t avi_dev_strm_get_frame(pjmedia_vid_dev_stream *strm,
+                                         pjmedia_frame *frame)
+{
+    struct avi_dev_strm *stream = (struct avi_dev_strm*)strm;
+    
+    if (stream->adi->codec) {
+        pjmedia_frame enc_frame;
+        pj_status_t status;
+
+        enc_frame.buf = stream->adi->enc_buf;
+        enc_frame.size = stream->adi->enc_buf_size;
+        status = pjmedia_port_get_frame(stream->adi->vid, &enc_frame);
+        if (status != PJ_SUCCESS)
+            return status;
+
+        return pjmedia_vid_codec_decode(stream->adi->codec, 1, &enc_frame,
+                                        (unsigned)frame->size, frame);
+    } else {
+        return pjmedia_port_get_frame(stream->adi->vid, frame);
+    }
+}
+
+/* API: Start stream. */
+static pj_status_t avi_dev_strm_start(pjmedia_vid_dev_stream *strm)
+{
+    struct avi_dev_strm *stream = (struct avi_dev_strm*)strm;
+
+    PJ_UNUSED_ARG(stream);
+
+    PJ_LOG(4, (THIS_FILE, "Starting avi video stream"));
+
+    return PJ_SUCCESS;
+}
+
+/* API: Stop stream. */
+static pj_status_t avi_dev_strm_stop(pjmedia_vid_dev_stream *strm)
+{
+    struct avi_dev_strm *stream = (struct avi_dev_strm*)strm;
+
+    PJ_UNUSED_ARG(stream);
+
+    PJ_LOG(4, (THIS_FILE, "Stopping avi video stream"));
+
+    return PJ_SUCCESS;
+}
+
+
+/* API: Destroy stream. */
+static pj_status_t avi_dev_strm_destroy(pjmedia_vid_dev_stream *strm)
+{
+    struct avi_dev_strm *stream = (struct avi_dev_strm*)strm;
+
+    PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
+
+    avi_dev_strm_stop(strm);
+
+    stream->adi->strm = NULL;
+    stream->adi = NULL;
+    pj_pool_release(stream->pool);
+
+    return PJ_SUCCESS;
+}
+
+#endif	/* PJMEDIA_VIDEO_DEV_HAS_AVI */
diff --git a/jni/pjproject-android/pjmedia/src/pjmedia-videodev/colorbar_dev.c b/jni/pjproject-android/pjmedia/src/pjmedia-videodev/colorbar_dev.c
new file mode 100644
index 0000000..a3bb4eb
--- /dev/null
+++ b/jni/pjproject-android/pjmedia/src/pjmedia-videodev/colorbar_dev.c
@@ -0,0 +1,631 @@
+/* $Id: colorbar_dev.c 4158 2012-06-06 09:56:14Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 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>
+#include <pj/rand.h>
+
+
+#if defined(PJMEDIA_VIDEO_DEV_HAS_CBAR_SRC) && \
+    PJMEDIA_VIDEO_DEV_HAS_CBAR_SRC != 0
+
+
+#define THIS_FILE		"colorbar_dev.c"
+#define DEFAULT_CLOCK_RATE	90000
+#define DEFAULT_WIDTH		352 //640
+#define DEFAULT_HEIGHT		288 //480
+#define DEFAULT_FPS		25
+
+/* cbar_ device info */
+struct cbar_dev_info
+{
+    pjmedia_vid_dev_info	 info;
+};
+
+/* cbar_ factory */
+struct cbar_factory
+{
+    pjmedia_vid_dev_factory	 base;
+    pj_pool_t			*pool;
+    pj_pool_factory		*pf;
+
+    unsigned			 dev_count;
+    struct cbar_dev_info	*dev_info;
+};
+
+struct cbar_fmt_info {
+    pjmedia_format_id            fmt_id;        /* Format ID                */
+
+    /* Info for packed formats. */
+    unsigned                     c_offset[3];   /* Color component offset, 
+                                                   in bytes                 */
+    unsigned                     c_stride[3];   /* Color component stride,
+                                                   or distance between two 
+                                                   consecutive same color
+                                                   components, in bytes     */
+};
+
+/* Colorbar video source supports */
+static struct cbar_fmt_info cbar_fmts[] =
+{
+    /* Packed formats */
+    { PJMEDIA_FORMAT_YUY2,      {0, 1, 3}, {2, 4, 4} },
+    { PJMEDIA_FORMAT_UYVY,      {1, 0, 2}, {2, 4, 4} },
+    { PJMEDIA_FORMAT_YVYU,      {0, 3, 1}, {2, 4, 4} },
+    { PJMEDIA_FORMAT_RGBA,      {0, 1, 2}, {4, 4, 4} },
+    { PJMEDIA_FORMAT_RGB24,     {0, 1, 2}, {3, 3, 3} },
+    { PJMEDIA_FORMAT_BGRA,      {2, 1, 0}, {4, 4, 4} },
+
+    /* Planar formats */
+    { PJMEDIA_FORMAT_YV12 },
+    { PJMEDIA_FORMAT_I420 },
+    { PJMEDIA_FORMAT_I422 },
+    { PJMEDIA_FORMAT_I420JPEG },
+    { PJMEDIA_FORMAT_I422JPEG },
+};
+
+/* Video stream. */
+struct cbar_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.  */
+
+    const struct cbar_fmt_info      *cbfi;
+    const pjmedia_video_format_info *vfi;
+    pjmedia_video_apply_fmt_param    vafp;
+    pj_uint8_t                      *first_line[PJMEDIA_MAX_VIDEO_PLANES];
+    pj_timestamp		     ts;
+    unsigned			     ts_inc;
+};
+
+
+/* Prototypes */
+static pj_status_t cbar_factory_init(pjmedia_vid_dev_factory *f);
+static pj_status_t cbar_factory_destroy(pjmedia_vid_dev_factory *f);
+static pj_status_t cbar_factory_refresh(pjmedia_vid_dev_factory *f); 
+static unsigned    cbar_factory_get_dev_count(pjmedia_vid_dev_factory *f);
+static pj_status_t cbar_factory_get_dev_info(pjmedia_vid_dev_factory *f,
+					     unsigned index,
+					     pjmedia_vid_dev_info *info);
+static pj_status_t cbar_factory_default_param(pj_pool_t *pool,
+                                              pjmedia_vid_dev_factory *f,
+					      unsigned index,
+					      pjmedia_vid_dev_param *param);
+static pj_status_t cbar_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 cbar_stream_get_param(pjmedia_vid_dev_stream *strm,
+					 pjmedia_vid_dev_param *param);
+static pj_status_t cbar_stream_get_cap(pjmedia_vid_dev_stream *strm,
+				       pjmedia_vid_dev_cap cap,
+				       void *value);
+static pj_status_t cbar_stream_set_cap(pjmedia_vid_dev_stream *strm,
+				       pjmedia_vid_dev_cap cap,
+				       const void *value);
+static pj_status_t cbar_stream_get_frame(pjmedia_vid_dev_stream *strm,
+                                         pjmedia_frame *frame);
+static pj_status_t cbar_stream_start(pjmedia_vid_dev_stream *strm);
+static pj_status_t cbar_stream_stop(pjmedia_vid_dev_stream *strm);
+static pj_status_t cbar_stream_destroy(pjmedia_vid_dev_stream *strm);
+
+/* Operations */
+static pjmedia_vid_dev_factory_op factory_op =
+{
+    &cbar_factory_init,
+    &cbar_factory_destroy,
+    &cbar_factory_get_dev_count,
+    &cbar_factory_get_dev_info,
+    &cbar_factory_default_param,
+    &cbar_factory_create_stream,
+    &cbar_factory_refresh
+};
+
+static pjmedia_vid_dev_stream_op stream_op =
+{
+    &cbar_stream_get_param,
+    &cbar_stream_get_cap,
+    &cbar_stream_set_cap,
+    &cbar_stream_start,
+    &cbar_stream_get_frame,
+    NULL,
+    &cbar_stream_stop,
+    &cbar_stream_destroy
+};
+
+
+/****************************************************************************
+ * Factory operations
+ */
+/*
+ * Init cbar_ video driver.
+ */
+pjmedia_vid_dev_factory* pjmedia_cbar_factory(pj_pool_factory *pf)
+{
+    struct cbar_factory *f;
+    pj_pool_t *pool;
+
+    pool = pj_pool_create(pf, "cbar video", 512, 512, NULL);
+    f = PJ_POOL_ZALLOC_T(pool, struct cbar_factory);
+    f->pf = pf;
+    f->pool = pool;
+    f->base.op = &factory_op;
+
+    return &f->base;
+}
+
+
+/* API: init factory */
+static pj_status_t cbar_factory_init(pjmedia_vid_dev_factory *f)
+{
+    struct cbar_factory *cf = (struct cbar_factory*)f;
+    struct cbar_dev_info *ddi;
+    unsigned i;
+
+    cf->dev_count = 1;
+    cf->dev_info = (struct cbar_dev_info*)
+ 		   pj_pool_calloc(cf->pool, cf->dev_count,
+ 				  sizeof(struct cbar_dev_info));
+
+    ddi = &cf->dev_info[0];
+    pj_bzero(ddi, sizeof(*ddi));
+    pj_ansi_strncpy(ddi->info.name, "Colorbar generator",
+		    sizeof(ddi->info.name));
+    ddi->info.driver[sizeof(ddi->info.driver)-1] = '\0';
+    pj_ansi_strncpy(ddi->info.driver, "Colorbar", sizeof(ddi->info.driver));
+    ddi->info.driver[sizeof(ddi->info.driver)-1] = '\0';
+    ddi->info.dir = PJMEDIA_DIR_CAPTURE;
+    ddi->info.has_callback = PJ_FALSE;
+
+    ddi->info.caps = PJMEDIA_VID_DEV_CAP_FORMAT;
+    ddi->info.fmt_cnt = sizeof(cbar_fmts)/sizeof(cbar_fmts[0]);
+    for (i = 0; i < ddi->info.fmt_cnt; i++) {
+        pjmedia_format *fmt = &ddi->info.fmt[i];
+        pjmedia_format_init_video(fmt, cbar_fmts[i].fmt_id,
+				  DEFAULT_WIDTH, DEFAULT_HEIGHT,
+				  DEFAULT_FPS, 1);
+    }
+
+    PJ_LOG(4, (THIS_FILE, "Colorbar video src initialized with %d device(s):",
+	       cf->dev_count));
+    for (i = 0; i < cf->dev_count; i++) {
+	PJ_LOG(4, (THIS_FILE, "%2d: %s", i, cf->dev_info[i].info.name));
+    }
+
+    return PJ_SUCCESS;
+}
+
+/* API: destroy factory */
+static pj_status_t cbar_factory_destroy(pjmedia_vid_dev_factory *f)
+{
+    struct cbar_factory *cf = (struct cbar_factory*)f;
+    pj_pool_t *pool = cf->pool;
+
+    cf->pool = NULL;
+    pj_pool_release(pool);
+
+    return PJ_SUCCESS;
+}
+
+/* API: refresh the list of devices */
+static pj_status_t cbar_factory_refresh(pjmedia_vid_dev_factory *f)
+{
+    PJ_UNUSED_ARG(f);
+    return PJ_SUCCESS;
+}
+
+/* API: get number of devices */
+static unsigned cbar_factory_get_dev_count(pjmedia_vid_dev_factory *f)
+{
+    struct cbar_factory *cf = (struct cbar_factory*)f;
+    return cf->dev_count;
+}
+
+/* API: get device info */
+static pj_status_t cbar_factory_get_dev_info(pjmedia_vid_dev_factory *f,
+					     unsigned index,
+					     pjmedia_vid_dev_info *info)
+{
+    struct cbar_factory *cf = (struct cbar_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 cbar_factory_default_param(pj_pool_t *pool,
+                                              pjmedia_vid_dev_factory *f,
+					      unsigned index,
+					      pjmedia_vid_dev_param *param)
+{
+    struct cbar_factory *cf = (struct cbar_factory*)f;
+    struct cbar_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;
+}
+
+static const struct cbar_fmt_info* get_cbar_fmt_info(pjmedia_format_id id)
+{
+    unsigned i;
+
+    for (i = 0; i < sizeof(cbar_fmts)/sizeof(cbar_fmts[0]); i++) {
+        if (cbar_fmts[i].fmt_id == id)
+            return &cbar_fmts[i];
+    }
+
+    return NULL;
+}
+
+static void fill_first_line(pj_uint8_t *first_lines[], 
+                            const struct cbar_fmt_info *cbfi,
+                            const pjmedia_video_format_info *vfi,
+                            const pjmedia_video_apply_fmt_param *vafp)
+{
+    typedef pj_uint8_t color_comp_t[3];
+    color_comp_t rgb_colors[] = 
+    { 
+        {255,255,255}, {255,255,0}, {0,255,255}, {0,255,0},
+        {255,0,255}, {255,0,0}, {0,0,255}, {0,0,0}
+    };
+    color_comp_t yuv_colors[] = 
+    { 
+        //{235,128,128}, {162,44,142}, {131,156,44}, {112,72,58},
+        //{84,184,198}, {65,100,212}, {35,212,114}, {16,128,128}
+        {235,128,128}, {210,16,146}, {170,166,16}, {145,54,34},
+        {106,202,222}, {81,90,240}, {41,240,110}, {16,128,128}
+    };
+
+    unsigned i, j, k;
+
+    if (vfi->plane_cnt == 1) {
+        /* Packed */
+
+        for (i = 0; i < 8; ++i) {
+            /* iterate bars */
+            for (j = 0; j < 3; ++j) {
+                /* iterate color components */
+                pj_uint8_t *p = NULL, c;
+                unsigned bar_width, inc_p;
+
+                if (vfi->color_model == PJMEDIA_COLOR_MODEL_RGB)
+                    c = rgb_colors[i][j];
+                else
+                    c = yuv_colors[i][j];
+
+                bar_width = vafp->size.w/8;
+                bar_width /= (cbfi->c_stride[j] * 8 / vfi->bpp);
+                inc_p = cbfi->c_stride[j];
+                p = first_lines[0] + bar_width*i*inc_p + cbfi->c_offset[j];
+
+                /* draw this color */
+                for (k = 0; k < bar_width; ++k) {
+                    *p = c;
+                    p += inc_p;
+                }
+            }
+        }
+
+    } else if (vfi->plane_cnt == 3) {
+
+        for (i = 0; i < 8; ++i) {
+            /* iterate bars */
+            for (j = 0; j < 3; ++j) {
+                /* iterate planes/color components */
+                pj_uint8_t *p = NULL, c;
+                unsigned bar_width;
+
+                if (vfi->color_model == PJMEDIA_COLOR_MODEL_RGB)
+                    c = rgb_colors[i][j];
+		else {
+		    if (vfi->id == PJMEDIA_FORMAT_YV12 && j > 0)
+			c = yuv_colors[i][3-j];
+		    else
+			c = yuv_colors[i][j];
+		}
+
+                bar_width = vafp->strides[j]/8;
+                p = first_lines[j] + bar_width*i;
+
+                /* draw this plane/color */
+                for (k = 0; k < bar_width; ++k)
+                    *p++ = c;
+            }
+        }
+    }
+}
+
+/* API: create stream */
+static pj_status_t cbar_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)
+{
+    struct cbar_factory *cf = (struct cbar_factory*)f;
+    pj_pool_t *pool;
+    struct cbar_stream *strm;
+    const pjmedia_video_format_detail *vfd;
+    const pjmedia_video_format_info *vfi;
+    pjmedia_video_apply_fmt_param vafp;
+    const struct cbar_fmt_info *cbfi;
+    unsigned i;
+
+    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));
+
+    vfd = pjmedia_format_get_video_format_detail(&param->fmt, PJ_TRUE);
+    vfi = pjmedia_get_video_format_info(NULL, param->fmt.id);
+    cbfi = get_cbar_fmt_info(param->fmt.id);
+    if (!vfi || !cbfi)
+        return PJMEDIA_EVID_BADFORMAT;
+
+    vafp.size = param->fmt.det.vid.size;
+    if (vfi->apply_fmt(vfi, &vafp) != PJ_SUCCESS)
+        return PJMEDIA_EVID_BADFORMAT;
+
+    /* Create and Initialize stream descriptor */
+    pool = pj_pool_create(cf->pf, "cbar-dev", 512, 512, NULL);
+    PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);
+
+    strm = PJ_POOL_ZALLOC_T(pool, struct cbar_stream);
+    pj_memcpy(&strm->param, param, sizeof(*param));
+    strm->pool = pool;
+    pj_memcpy(&strm->vid_cb, cb, sizeof(*cb));
+    strm->user_data = user_data;
+    strm->vfi = vfi;
+    strm->cbfi = cbfi;
+    pj_memcpy(&strm->vafp, &vafp, sizeof(vafp));
+    strm->ts_inc = PJMEDIA_SPF2(param->clock_rate, &vfd->fps, 1);
+
+    for (i = 0; i < vfi->plane_cnt; ++i) {
+        strm->first_line[i] = pj_pool_alloc(pool, vafp.strides[i]);
+        pj_memset(strm->first_line[i], 255, vafp.strides[i]);
+    }
+
+    fill_first_line(strm->first_line, strm->cbfi, vfi, &strm->vafp);
+
+    /* Apply the remaining settings */
+/*    if (param->flags & PJMEDIA_VID_DEV_CAP_INPUT_SCALE) {
+	cbar_stream_set_cap(&strm->base,
+		            PJMEDIA_VID_DEV_CAP_INPUT_SCALE,
+                            &param->fmt);
+    }
+*/
+    /* Done */
+    strm->base.op = &stream_op;
+    *p_vid_strm = &strm->base;
+
+    return PJ_SUCCESS;
+}
+
+/* API: Get stream info. */
+static pj_status_t cbar_stream_get_param(pjmedia_vid_dev_stream *s,
+					 pjmedia_vid_dev_param *pi)
+{
+    struct cbar_stream *strm = (struct cbar_stream*)s;
+
+    PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
+
+    pj_memcpy(pi, &strm->param, sizeof(*pi));
+
+/*    if (cbar_stream_get_cap(s, PJMEDIA_VID_DEV_CAP_INPUT_SCALE,
+                            &pi->fmt.info_size) == PJ_SUCCESS)
+    {
+        pi->flags |= PJMEDIA_VID_DEV_CAP_INPUT_SCALE;
+    }
+*/
+    return PJ_SUCCESS;
+}
+
+/* API: get capability */
+static pj_status_t cbar_stream_get_cap(pjmedia_vid_dev_stream *s,
+				       pjmedia_vid_dev_cap cap,
+				       void *pval)
+{
+    struct cbar_stream *strm = (struct cbar_stream*)s;
+
+    PJ_UNUSED_ARG(strm);
+
+    PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+    if (cap==PJMEDIA_VID_DEV_CAP_INPUT_SCALE)
+    {
+        return PJMEDIA_EVID_INVCAP;
+//	return PJ_SUCCESS;
+    } else {
+	return PJMEDIA_EVID_INVCAP;
+    }
+}
+
+/* API: set capability */
+static pj_status_t cbar_stream_set_cap(pjmedia_vid_dev_stream *s,
+				       pjmedia_vid_dev_cap cap,
+				       const void *pval)
+{
+    struct cbar_stream *strm = (struct cbar_stream*)s;
+
+    PJ_UNUSED_ARG(strm);
+
+    PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+    if (cap==PJMEDIA_VID_DEV_CAP_INPUT_SCALE)
+    {
+	return PJ_SUCCESS;
+    }
+
+    return PJMEDIA_EVID_INVCAP;
+}
+
+static pj_status_t spectrum_run(struct cbar_stream *d, pj_uint8_t *p,
+                                pj_size_t size)
+{
+    unsigned i;
+    pj_uint8_t *ptr = p;
+    pj_time_val tv;
+
+    PJ_UNUSED_ARG(size);
+
+    /* Subsequent lines */
+    for (i=0; i<d->vfi->plane_cnt; ++i) {
+        pj_uint8_t *plane_end;
+
+        plane_end = ptr + d->vafp.plane_bytes[i];
+        while (ptr < plane_end) {
+            pj_memcpy(ptr, d->first_line[i], d->vafp.strides[i]);
+            ptr += d->vafp.strides[i]; 
+        }
+    }
+
+    /* blinking dot */
+    pj_gettimeofday(&tv);
+    if (tv.msec < 660) {
+	enum { DOT_SIZE = 8 };
+        pj_uint8_t dot_clr_rgb[3] = {255, 255, 255};
+        pj_uint8_t dot_clr_yuv[3] = {235, 128, 128};
+
+        if (d->vfi->plane_cnt == 1) {
+            for (i = 0; i < 3; ++i) {
+                pj_uint8_t *ptr;
+                unsigned j, k, inc_ptr;
+                pj_size_t dot_size = DOT_SIZE;
+
+                dot_size /= (d->cbfi->c_stride[i] * 8 / d->vfi->bpp);
+                inc_ptr = d->cbfi->c_stride[i];
+                for (j = 0; j < dot_size; ++j) {
+                    ptr = p + d->vafp.strides[0]*(dot_size+j+1) - 
+                          2*dot_size*inc_ptr + d->cbfi->c_offset[i];
+                    for (k = 0; k < dot_size; ++k) {
+                        if (d->vfi->color_model == PJMEDIA_COLOR_MODEL_RGB)
+                            *ptr = dot_clr_rgb[i];
+                        else
+                            *ptr = dot_clr_yuv[i];
+                        ptr += inc_ptr;
+                    }
+                }
+            }
+        } else {
+            pj_size_t offset_p = 0;
+
+            for (i = 0; i < 3; ++i) {
+                pj_uint8_t *ptr, c;
+                unsigned j;
+                pj_size_t dot_size = DOT_SIZE;
+
+                if (d->vfi->color_model == PJMEDIA_COLOR_MODEL_RGB)
+                    c = dot_clr_rgb[i];
+                else
+                    c = dot_clr_yuv[i];
+
+                dot_size /= (d->vafp.size.w / d->vafp.strides[i]);
+                ptr = p + offset_p + d->vafp.strides[i]*(dot_size+1) - 
+                      2*dot_size;
+                for (j = 0; j < dot_size; ++j) {
+                    pj_memset(ptr, c, dot_size);
+                    ptr += d->vafp.strides[i];
+                }
+                offset_p += d->vafp.plane_bytes[i];
+            }
+        }
+    }
+
+    return PJ_SUCCESS;
+}
+
+/* API: Get frame from stream */
+static pj_status_t cbar_stream_get_frame(pjmedia_vid_dev_stream *strm,
+                                         pjmedia_frame *frame)
+{
+    struct cbar_stream *stream = (struct cbar_stream*)strm;
+
+    frame->type = PJMEDIA_FRAME_TYPE_VIDEO;
+    frame->bit_info = 0;
+    frame->timestamp = stream->ts;
+    stream->ts.u64 += stream->ts_inc;
+    return spectrum_run(stream, frame->buf, frame->size);
+}
+
+/* API: Start stream. */
+static pj_status_t cbar_stream_start(pjmedia_vid_dev_stream *strm)
+{
+    struct cbar_stream *stream = (struct cbar_stream*)strm;
+
+    PJ_UNUSED_ARG(stream);
+
+    PJ_LOG(4, (THIS_FILE, "Starting cbar video stream"));
+
+    return PJ_SUCCESS;
+}
+
+/* API: Stop stream. */
+static pj_status_t cbar_stream_stop(pjmedia_vid_dev_stream *strm)
+{
+    struct cbar_stream *stream = (struct cbar_stream*)strm;
+
+    PJ_UNUSED_ARG(stream);
+
+    PJ_LOG(4, (THIS_FILE, "Stopping cbar video stream"));
+
+    return PJ_SUCCESS;
+}
+
+
+/* API: Destroy stream. */
+static pj_status_t cbar_stream_destroy(pjmedia_vid_dev_stream *strm)
+{
+    struct cbar_stream *stream = (struct cbar_stream*)strm;
+
+    PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
+
+    cbar_stream_stop(strm);
+
+    pj_pool_release(stream->pool);
+
+    return PJ_SUCCESS;
+}
+
+#endif	/* PJMEDIA_VIDEO_DEV_HAS_CBAR_SRC */
diff --git a/jni/pjproject-android/pjmedia/src/pjmedia-videodev/dshow_dev.c b/jni/pjproject-android/pjmedia/src/pjmedia-videodev/dshow_dev.c
new file mode 100644
index 0000000..1684f2f
--- /dev/null
+++ b/jni/pjproject-android/pjmedia/src/pjmedia-videodev/dshow_dev.c
@@ -0,0 +1,1067 @@
+/* $Id: dshow_dev.c 4537 2013-06-19 06:47:43Z riza $ */
+/*
+ * Copyright (C) 2008-2011 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>
+#include <pj/unicode.h>
+
+
+#if defined(PJMEDIA_VIDEO_DEV_HAS_DSHOW) && PJMEDIA_VIDEO_DEV_HAS_DSHOW != 0
+
+
+#ifdef _MSC_VER
+#   pragma warning(push, 3)
+#endif
+
+#include <windows.h>
+#define COBJMACROS
+#include <DShow.h>
+#include <wmsdkidl.h>
+
+#ifdef _MSC_VER
+#   pragma warning(pop)
+#endif
+
+#pragma comment(lib, "Strmiids.lib")
+#pragma comment(lib, "Rpcrt4.lib")
+#pragma comment(lib, "Quartz.lib")
+
+#define THIS_FILE		"dshow_dev.c"
+#define DEFAULT_CLOCK_RATE	90000
+#define DEFAULT_WIDTH		640
+#define DEFAULT_HEIGHT		480
+#define DEFAULT_FPS		25
+
+/* Temporarily disable DirectShow renderer (VMR) */
+#define HAS_VMR			0
+
+typedef void (*input_callback)(void *user_data, IMediaSample *pMediaSample);
+typedef struct NullRenderer NullRenderer;
+IBaseFilter* NullRenderer_Create(input_callback input_cb,
+                                 void *user_data);
+typedef struct SourceFilter SourceFilter;
+IBaseFilter* SourceFilter_Create(SourceFilter **pSrc);
+HRESULT SourceFilter_Deliver(SourceFilter *src, void *buf, long size);
+void SourceFilter_SetMediaType(SourceFilter *src, AM_MEDIA_TYPE *pmt);
+
+typedef struct dshow_fmt_info
+{
+    pjmedia_format_id    pjmedia_format;
+    const GUID          *dshow_format;
+    pj_bool_t            enabled;
+} dshow_fmt_info;
+
+static dshow_fmt_info dshow_fmts[] =
+{
+    {PJMEDIA_FORMAT_YUY2, &MEDIASUBTYPE_YUY2, PJ_FALSE} ,
+    {PJMEDIA_FORMAT_RGB24, &MEDIASUBTYPE_RGB24, PJ_FALSE} ,
+    {PJMEDIA_FORMAT_RGB32, &MEDIASUBTYPE_RGB32, PJ_FALSE} ,
+    {PJMEDIA_FORMAT_IYUV, &MEDIASUBTYPE_IYUV, PJ_FALSE} ,
+    {PJMEDIA_FORMAT_I420, &WMMEDIASUBTYPE_I420, PJ_FALSE}
+};
+
+/* dshow_ device info */
+struct dshow_dev_info
+{
+    pjmedia_vid_dev_info	 info;
+    unsigned			 dev_id;
+    WCHAR                        display_name[192];
+};
+
+/* dshow_ factory */
+struct dshow_factory
+{
+    pjmedia_vid_dev_factory	 base;
+    pj_pool_t			*pool;
+    pj_pool_t			*dev_pool;
+    pj_pool_factory		*pf;
+
+    unsigned			 dev_count;
+    struct dshow_dev_info	*dev_info;
+};
+
+/* Video stream. */
+struct dshow_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_bool_t		     quit_flag;
+    pj_bool_t		     rend_thread_exited;
+    pj_bool_t		     cap_thread_exited;
+    pj_bool_t		     cap_thread_initialized;
+    pj_thread_desc	     cap_thread_desc;
+    pj_thread_t		    *cap_thread;
+    void                    *frm_buf;
+    unsigned                 frm_buf_size;
+
+    struct dshow_graph
+    {
+        IFilterGraph        *filter_graph;
+        IMediaFilter        *media_filter;
+        SourceFilter        *csource_filter;
+        IBaseFilter         *source_filter;
+        IBaseFilter         *rend_filter;
+        AM_MEDIA_TYPE       *mediatype;
+    } dgraph;
+
+    pj_timestamp	     cap_ts;
+    unsigned		     cap_ts_inc;
+};
+
+
+/* Prototypes */
+static pj_status_t dshow_factory_init(pjmedia_vid_dev_factory *f);
+static pj_status_t dshow_factory_destroy(pjmedia_vid_dev_factory *f);
+static pj_status_t dshow_factory_refresh(pjmedia_vid_dev_factory *f);
+static unsigned    dshow_factory_get_dev_count(pjmedia_vid_dev_factory *f);
+static pj_status_t dshow_factory_get_dev_info(pjmedia_vid_dev_factory *f,
+					      unsigned index,
+					      pjmedia_vid_dev_info *info);
+static pj_status_t dshow_factory_default_param(pj_pool_t *pool,
+                                               pjmedia_vid_dev_factory *f,
+					       unsigned index,
+					       pjmedia_vid_dev_param *param);
+static pj_status_t dshow_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 dshow_stream_get_param(pjmedia_vid_dev_stream *strm,
+					  pjmedia_vid_dev_param *param);
+static pj_status_t dshow_stream_get_cap(pjmedia_vid_dev_stream *strm,
+				        pjmedia_vid_dev_cap cap,
+				        void *value);
+static pj_status_t dshow_stream_set_cap(pjmedia_vid_dev_stream *strm,
+				        pjmedia_vid_dev_cap cap,
+				        const void *value);
+static pj_status_t dshow_stream_start(pjmedia_vid_dev_stream *strm);
+static pj_status_t dshow_stream_put_frame(pjmedia_vid_dev_stream *strm,
+                                          const pjmedia_frame *frame);
+static pj_status_t dshow_stream_stop(pjmedia_vid_dev_stream *strm);
+static pj_status_t dshow_stream_destroy(pjmedia_vid_dev_stream *strm);
+
+/* Operations */
+static pjmedia_vid_dev_factory_op factory_op =
+{
+    &dshow_factory_init,
+    &dshow_factory_destroy,
+    &dshow_factory_get_dev_count,
+    &dshow_factory_get_dev_info,
+    &dshow_factory_default_param,
+    &dshow_factory_create_stream,
+    &dshow_factory_refresh
+};
+
+static pjmedia_vid_dev_stream_op stream_op =
+{
+    &dshow_stream_get_param,
+    &dshow_stream_get_cap,
+    &dshow_stream_set_cap,
+    &dshow_stream_start,
+    NULL,
+    &dshow_stream_put_frame,
+    &dshow_stream_stop,
+    &dshow_stream_destroy
+};
+
+
+/****************************************************************************
+ * Factory operations
+ */
+/*
+ * Init dshow_ video driver.
+ */
+pjmedia_vid_dev_factory* pjmedia_dshow_factory(pj_pool_factory *pf)
+{
+    struct dshow_factory *f;
+    pj_pool_t *pool;
+
+    pool = pj_pool_create(pf, "dshow video", 1000, 1000, NULL);
+    f = PJ_POOL_ZALLOC_T(pool, struct dshow_factory);
+    f->pf = pf;
+    f->pool = pool;
+    f->base.op = &factory_op;
+
+    return &f->base;
+}
+
+/* API: init factory */
+static pj_status_t dshow_factory_init(pjmedia_vid_dev_factory *f)
+{
+    HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
+    if (hr == RPC_E_CHANGED_MODE) {
+        PJ_LOG(4,(THIS_FILE, "Failed initializing DShow: "
+                             "COM library already initialized with "
+                             "incompatible concurrency model"));
+        return PJMEDIA_EVID_INIT;
+    }
+
+    return dshow_factory_refresh(f);
+}
+
+/* API: destroy factory */
+static pj_status_t dshow_factory_destroy(pjmedia_vid_dev_factory *f)
+{
+    struct dshow_factory *df = (struct dshow_factory*)f;
+    pj_pool_t *pool = df->pool;
+
+    df->pool = NULL;
+    if (df->dev_pool)
+        pj_pool_release(df->dev_pool);
+    if (pool)
+        pj_pool_release(pool);
+
+    CoUninitialize();
+
+    return PJ_SUCCESS;
+}
+
+static HRESULT get_cap_device(struct dshow_factory *df,
+			      unsigned id,
+			      IBaseFilter **filter)
+{
+    IBindCtx *pbc;
+    HRESULT hr;
+
+    hr = CreateBindCtx(0, &pbc);
+    if (SUCCEEDED (hr)) {
+	IMoniker *moniker;
+	DWORD pchEaten;
+
+	hr = MkParseDisplayName(pbc, df->dev_info[id].display_name,
+				&pchEaten, &moniker);
+	if (SUCCEEDED(hr)) {
+	    hr = IMoniker_BindToObject(moniker, pbc, NULL,
+				       &IID_IBaseFilter,
+				       (LPVOID *)filter);
+	    IMoniker_Release(moniker);
+	}
+	IBindCtx_Release(pbc);
+    }
+
+    return hr;
+}
+
+static void enum_dev_cap(IBaseFilter *filter,
+			 pjmedia_dir dir,
+			 const GUID *dshow_fmt,
+			 AM_MEDIA_TYPE **pMediatype,
+			 IPin **pSrcpin,
+			 pj_bool_t *sup_fmt)
+{
+    IEnumPins *pEnum;
+    AM_MEDIA_TYPE *mediatype = NULL;
+    HRESULT hr;
+
+    if (pSrcpin)
+	*pSrcpin = NULL;
+    hr = IBaseFilter_EnumPins(filter, &pEnum);
+    if (SUCCEEDED(hr)) {
+        /* Loop through all the pins. */
+	IPin *pPin = NULL;
+
+        while (IEnumPins_Next(pEnum, 1, &pPin, NULL) == S_OK) {
+            PIN_DIRECTION pindirtmp;
+
+            hr = IPin_QueryDirection(pPin, &pindirtmp);
+            if (hr != S_OK || pindirtmp != PINDIR_OUTPUT) {
+                if (SUCCEEDED(hr))
+                    IPin_Release(pPin);
+                continue;
+            }
+
+            if (dir == PJMEDIA_DIR_CAPTURE) {
+                IAMStreamConfig *streamcaps;
+
+                hr = IPin_QueryInterface(pPin, &IID_IAMStreamConfig,
+                                         (LPVOID *)&streamcaps);
+                if (SUCCEEDED(hr)) {
+                    VIDEO_STREAM_CONFIG_CAPS vscc;
+                    int i, isize, icount;
+
+                    IAMStreamConfig_GetNumberOfCapabilities(streamcaps,
+                                                            &icount, &isize);
+
+                    for (i = 0; i < icount; i++) {
+			unsigned j, nformat;
+                        RPC_STATUS rpcstatus, rpcstatus2;
+
+                        hr = IAMStreamConfig_GetStreamCaps(streamcaps, i,
+                                                           &mediatype,
+                                                           (BYTE *)&vscc);
+                        if (FAILED (hr))
+                            continue;
+
+			nformat = (dshow_fmt? 1:
+				   sizeof(dshow_fmts)/sizeof(dshow_fmts[0]));
+			for (j = 0; j < nformat; j++) {
+			    const GUID *dshow_format = dshow_fmt;
+                            
+			    if (!dshow_format)
+				dshow_format = dshow_fmts[j].dshow_format;
+			    if (UuidCompare(&mediatype->subtype, 
+					    (UUID*)dshow_format,
+					    &rpcstatus) == 0 && 
+				rpcstatus == RPC_S_OK &&
+				UuidCompare(&mediatype->formattype,
+					    (UUID*)&FORMAT_VideoInfo,
+					    &rpcstatus2) == 0 &&
+				rpcstatus2 == RPC_S_OK)
+			    {
+                                if (!dshow_fmt)
+                                    dshow_fmts[j].enabled = PJ_TRUE;
+				if (sup_fmt)
+				    sup_fmt[j] = PJ_TRUE;
+				if (pSrcpin) {
+				    *pSrcpin = pPin;
+				    *pMediatype = mediatype;
+				}
+			    }
+			}
+			if (pSrcpin && *pSrcpin)
+			    break;
+                    }
+                    IAMStreamConfig_Release(streamcaps);
+                }
+            } else {
+                *pSrcpin = pPin;
+            }
+            if (pSrcpin && *pSrcpin)
+                break;
+            IPin_Release(pPin);
+	}
+        IEnumPins_Release(pEnum);
+    }
+}
+
+/* API: refresh the list of devices */
+static pj_status_t dshow_factory_refresh(pjmedia_vid_dev_factory *f)
+{
+    struct dshow_factory *df = (struct dshow_factory*)f;
+    struct dshow_dev_info *ddi;
+    int dev_count = 0;
+    unsigned c;
+    ICreateDevEnum *dev_enum = NULL;
+    IEnumMoniker *enum_cat = NULL;
+    IMoniker *moniker = NULL;
+    HRESULT hr;
+    ULONG fetched;
+
+    if (df->dev_pool) {
+        pj_pool_release(df->dev_pool);
+        df->dev_pool = NULL;
+    }
+
+    df->dev_count = 0;
+    df->dev_pool = pj_pool_create(df->pf, "dshow video", 500, 500, NULL);
+
+    hr = CoCreateInstance(&CLSID_SystemDeviceEnum, NULL,
+                          CLSCTX_INPROC_SERVER, &IID_ICreateDevEnum,
+                          (void**)&dev_enum);
+    if (FAILED(hr) ||
+        ICreateDevEnum_CreateClassEnumerator(dev_enum,
+            &CLSID_VideoInputDeviceCategory, &enum_cat, 0) != S_OK) 
+    {
+	PJ_LOG(4,(THIS_FILE, "Windows found no video input devices"));
+        if (dev_enum)
+            ICreateDevEnum_Release(dev_enum);
+	dev_count = 0;
+    } else {
+        while (IEnumMoniker_Next(enum_cat, 1, &moniker, &fetched) == S_OK) {
+            dev_count++;
+        }
+    }
+
+    /* Add renderer device */
+    dev_count += 1;
+    df->dev_info = (struct dshow_dev_info*)
+ 		   pj_pool_calloc(df->dev_pool, dev_count,
+ 				  sizeof(struct dshow_dev_info));
+
+    if (dev_count > 1) {
+        IEnumMoniker_Reset(enum_cat);
+        while (IEnumMoniker_Next(enum_cat, 1, &moniker, &fetched) == S_OK) {
+            IPropertyBag *prop_bag;
+
+            hr = IMoniker_BindToStorage(moniker, 0, 0, &IID_IPropertyBag,
+                                        (void**)&prop_bag);
+            if (SUCCEEDED(hr)) {
+                VARIANT var_name;
+
+                VariantInit(&var_name);
+                hr = IPropertyBag_Read(prop_bag, L"FriendlyName",
+                                       &var_name, NULL);
+                if (SUCCEEDED(hr) && var_name.bstrVal) {
+                    WCHAR *wszDisplayName = NULL;
+		    IBaseFilter *filter;
+
+                    ddi = &df->dev_info[df->dev_count++];
+                    pj_bzero(ddi, sizeof(*ddi));
+                    pj_unicode_to_ansi(var_name.bstrVal,
+                                       wcslen(var_name.bstrVal),
+                                       ddi->info.name,
+                                       sizeof(ddi->info.name));
+
+                    hr = IMoniker_GetDisplayName(moniker, NULL, NULL,
+                                                 &wszDisplayName);
+                    if (hr == S_OK && wszDisplayName) {
+                        pj_memcpy(ddi->display_name, wszDisplayName,
+                                  (wcslen(wszDisplayName)+1) * sizeof(WCHAR));
+                        CoTaskMemFree(wszDisplayName);
+                    }
+
+                    strncpy(ddi->info.driver, "dshow", 
+                            sizeof(ddi->info.driver));
+                    ddi->info.driver[sizeof(ddi->info.driver)-1] = '\0';
+                    ddi->info.dir = PJMEDIA_DIR_CAPTURE;
+                    ddi->info.has_callback = PJ_TRUE;
+
+                    /* Set the device capabilities here */
+                    ddi->info.caps = PJMEDIA_VID_DEV_CAP_FORMAT;
+
+		    hr = get_cap_device(df, df->dev_count-1, &filter);
+		    if (SUCCEEDED(hr)) {
+			unsigned j;
+			pj_bool_t sup_fmt[sizeof(dshow_fmts)/sizeof(dshow_fmts[0])];
+
+			pj_bzero(sup_fmt, sizeof(sup_fmt));
+			enum_dev_cap(filter, ddi->info.dir, NULL, NULL, NULL, sup_fmt);
+
+			ddi->info.fmt_cnt = 0;
+			for (j = 0;
+			     j < sizeof(dshow_fmts)/sizeof(dshow_fmts[0]);
+			     j++)
+			{
+			    if (!sup_fmt[j])
+				continue;
+			    pjmedia_format_init_video(
+				&ddi->info.fmt[ddi->info.fmt_cnt++],
+				dshow_fmts[j].pjmedia_format, 
+				DEFAULT_WIDTH, DEFAULT_HEIGHT, 
+				DEFAULT_FPS, 1);
+			}
+		    }
+                }
+                VariantClear(&var_name);
+
+                IPropertyBag_Release(prop_bag);
+            }
+            IMoniker_Release(moniker);
+        }
+
+        IEnumMoniker_Release(enum_cat);
+        ICreateDevEnum_Release(dev_enum);
+    }
+
+#if HAS_VMR
+    ddi = &df->dev_info[df->dev_count++];
+    pj_bzero(ddi, sizeof(*ddi));
+    pj_ansi_strncpy(ddi->info.name,  "Video Mixing Renderer",
+                    sizeof(ddi->info.name));
+    ddi->info.name[sizeof(ddi->info.name)-1] = '\0';
+    pj_ansi_strncpy(ddi->info.driver, "dshow", sizeof(ddi->info.driver));
+    ddi->info.driver[sizeof(ddi->info.driver)-1] = '\0';
+    ddi->info.dir = PJMEDIA_DIR_RENDER;
+    ddi->info.has_callback = PJ_FALSE;
+    ddi->info.caps = PJMEDIA_VID_DEV_CAP_FORMAT;
+//    TODO:
+//    ddi->info.caps |= PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW;
+
+    ddi->info.fmt_cnt = 1;
+    pjmedia_format_init_video(&ddi->info.fmt[0], dshow_fmts[0].pjmedia_format, 
+			      DEFAULT_WIDTH, DEFAULT_HEIGHT, 
+			      DEFAULT_FPS, 1);
+#endif
+
+    PJ_LOG(4, (THIS_FILE, "DShow has %d devices:", 
+	       df->dev_count));
+    for (c = 0; c < df->dev_count; ++c) {
+	PJ_LOG(4, (THIS_FILE, " dev_id %d: %s (%s)", 
+	       c,
+	       df->dev_info[c].info.name,
+	       df->dev_info[c].info.dir & PJMEDIA_DIR_CAPTURE ?
+               "capture" : "render"));
+    }
+
+    return PJ_SUCCESS;
+}
+
+/* API: get number of devices */
+static unsigned dshow_factory_get_dev_count(pjmedia_vid_dev_factory *f)
+{
+    struct dshow_factory *df = (struct dshow_factory*)f;
+    return df->dev_count;
+}
+
+/* API: get device info */
+static pj_status_t dshow_factory_get_dev_info(pjmedia_vid_dev_factory *f,
+					      unsigned index,
+					      pjmedia_vid_dev_info *info)
+{
+    struct dshow_factory *df = (struct dshow_factory*)f;
+
+    PJ_ASSERT_RETURN(index < df->dev_count, PJMEDIA_EVID_INVDEV);
+
+    pj_memcpy(info, &df->dev_info[index].info, sizeof(*info));
+
+    return PJ_SUCCESS;
+}
+
+/* API: create default device parameter */
+static pj_status_t dshow_factory_default_param(pj_pool_t *pool,
+                                               pjmedia_vid_dev_factory *f,
+					       unsigned index,
+					       pjmedia_vid_dev_param *param)
+{
+    struct dshow_factory *df = (struct dshow_factory*)f;
+    struct dshow_dev_info *di = &df->dev_info[index];
+
+    PJ_ASSERT_RETURN(index < df->dev_count, PJMEDIA_EVID_INVDEV);
+
+    PJ_UNUSED_ARG(pool);
+
+    pj_bzero(param, sizeof(*param));
+    if (di->info.dir & PJMEDIA_DIR_CAPTURE) {
+	param->dir = PJMEDIA_DIR_CAPTURE;
+	param->cap_id = index;
+	param->rend_id = PJMEDIA_VID_INVALID_DEV;
+    } else if (di->info.dir & PJMEDIA_DIR_RENDER) {
+	param->dir = PJMEDIA_DIR_RENDER;
+	param->rend_id = index;
+	param->cap_id = PJMEDIA_VID_INVALID_DEV;
+    } else {
+	return PJMEDIA_EVID_INVDEV;
+    }
+
+    /* Set the device capabilities here */
+    param->clock_rate = DEFAULT_CLOCK_RATE;
+    param->flags = PJMEDIA_VID_DEV_CAP_FORMAT;
+
+    pjmedia_format_copy(&param->fmt, &di->info.fmt[0]);
+
+    return PJ_SUCCESS;
+}
+
+static void input_cb(void *user_data, IMediaSample *pMediaSample)
+{
+    struct dshow_stream *strm = (struct dshow_stream*)user_data;
+    pjmedia_frame frame = {0};
+
+    if (strm->quit_flag) {
+        strm->cap_thread_exited = PJ_TRUE;
+        return;
+    }
+
+    if (strm->cap_thread_initialized == 0 || !pj_thread_is_registered())
+    {
+        pj_status_t status;
+
+	status = pj_thread_register("ds_cap", strm->cap_thread_desc, 
+				    &strm->cap_thread);
+        if (status != PJ_SUCCESS)
+            return;
+	strm->cap_thread_initialized = 1;
+	PJ_LOG(5,(THIS_FILE, "Capture thread started"));
+    }
+
+    frame.type = PJMEDIA_FRAME_TYPE_VIDEO;
+    IMediaSample_GetPointer(pMediaSample, (BYTE **)&frame.buf);
+    frame.size = IMediaSample_GetActualDataLength(pMediaSample);
+    frame.bit_info = 0;
+    frame.timestamp = strm->cap_ts;
+    strm->cap_ts.u64 += strm->cap_ts_inc;
+
+    if (strm->frm_buf_size) {
+        unsigned i, stride;
+        BYTE *src_buf, *dst_buf;
+        pjmedia_video_format_detail *vfd;
+        
+        /* Image is bottom-up, convert it to top-down. */
+        src_buf = dst_buf = (BYTE *)frame.buf;
+        stride = strm->frm_buf_size;
+        vfd = pjmedia_format_get_video_format_detail(&strm->param.fmt,
+                                                     PJ_TRUE);
+        src_buf += (vfd->size.h - 1) * stride;
+
+        for (i = vfd->size.h / 2; i > 0; i--) {
+            memcpy(strm->frm_buf, dst_buf, stride);
+            memcpy(dst_buf, src_buf, stride);
+            memcpy(src_buf, strm->frm_buf, stride);
+            dst_buf += stride;
+            src_buf -= stride;
+        }
+    }
+
+    if (strm->vid_cb.capture_cb)
+        (*strm->vid_cb.capture_cb)(&strm->base, strm->user_data, &frame);
+}
+
+/* API: Put frame from stream */
+static pj_status_t dshow_stream_put_frame(pjmedia_vid_dev_stream *strm,
+                                          const pjmedia_frame *frame)
+{
+    struct dshow_stream *stream = (struct dshow_stream*)strm;
+    HRESULT hr;
+
+    if (stream->quit_flag) {
+        stream->rend_thread_exited = PJ_TRUE;
+        return PJ_SUCCESS;
+    }
+
+    hr = SourceFilter_Deliver(stream->dgraph.csource_filter,
+                              frame->buf, (long)frame->size);
+    if (FAILED(hr))
+        return hr;
+
+    return PJ_SUCCESS;
+}
+
+static dshow_fmt_info* get_dshow_format_info(pjmedia_format_id id)
+{
+    unsigned i;
+
+    for (i = 0; i < sizeof(dshow_fmts)/sizeof(dshow_fmts[0]); i++) {
+        if (dshow_fmts[i].pjmedia_format == id && dshow_fmts[i].enabled)
+            return &dshow_fmts[i];
+    }
+
+    return NULL;
+}
+
+static pj_status_t create_filter_graph(pjmedia_dir dir,
+                                       unsigned id,
+                                       pj_bool_t use_def_size,
+                                       pj_bool_t use_def_fps,
+                                       struct dshow_factory *df,
+                                       struct dshow_stream *strm,
+                                       struct dshow_graph *graph)
+{
+    HRESULT hr;
+    IEnumPins *pEnum;
+    IPin *srcpin = NULL;
+    IPin *sinkpin = NULL;
+    AM_MEDIA_TYPE *mediatype= NULL, mtype;
+    VIDEOINFOHEADER *video_info, *vi = NULL;
+    pjmedia_video_format_detail *vfd;
+    const pjmedia_video_format_info *vfi;
+
+    vfi = pjmedia_get_video_format_info(pjmedia_video_format_mgr_instance(),
+                                        strm->param.fmt.id);
+    if (!vfi)
+        return PJMEDIA_EVID_BADFORMAT;
+
+    hr = CoCreateInstance(&CLSID_FilterGraph, NULL, CLSCTX_INPROC,
+                          &IID_IFilterGraph, (LPVOID *)&graph->filter_graph);
+    if (FAILED(hr)) {
+        goto on_error;
+    }
+
+    hr = IFilterGraph_QueryInterface(graph->filter_graph, &IID_IMediaFilter,
+                                     (LPVOID *)&graph->media_filter);
+    if (FAILED(hr)) {
+        goto on_error;
+    }
+
+    if (dir == PJMEDIA_DIR_CAPTURE) {
+	hr = get_cap_device(df, id, &graph->source_filter);
+	if (FAILED(hr)) {
+            goto on_error;
+        }
+    } else {
+        graph->source_filter = SourceFilter_Create(&graph->csource_filter);
+    }
+
+    hr = IFilterGraph_AddFilter(graph->filter_graph, graph->source_filter,
+                                L"capture");
+    if (FAILED(hr)) {
+        goto on_error;
+    }
+
+    if (dir == PJMEDIA_DIR_CAPTURE) {
+        graph->rend_filter = NullRenderer_Create(input_cb, strm);
+    } else {
+        hr = CoCreateInstance(&CLSID_VideoMixingRenderer, NULL,
+                              CLSCTX_INPROC, &IID_IBaseFilter,
+                              (LPVOID *)&graph->rend_filter);
+        if (FAILED (hr)) {
+            goto on_error;
+        }
+    }
+
+    IBaseFilter_EnumPins(graph->rend_filter, &pEnum);
+    if (SUCCEEDED(hr)) {
+        // Loop through all the pins
+	IPin *pPin = NULL;
+
+        while (IEnumPins_Next(pEnum, 1, &pPin, NULL) == S_OK) {
+            PIN_DIRECTION pindirtmp;
+
+            hr = IPin_QueryDirection(pPin, &pindirtmp);
+            if (hr == S_OK && pindirtmp == PINDIR_INPUT) {
+                sinkpin = pPin;
+                break;
+            }
+	    IPin_Release(pPin);
+        }
+        IEnumPins_Release(pEnum);
+    }
+
+    vfd = pjmedia_format_get_video_format_detail(&strm->param.fmt, PJ_TRUE);
+
+    enum_dev_cap(graph->source_filter, dir,
+		 get_dshow_format_info(strm->param.fmt.id)->dshow_format,
+		 &mediatype, &srcpin, NULL);
+    graph->mediatype = mediatype;
+
+    if (srcpin && dir == PJMEDIA_DIR_RENDER) {
+	mediatype = graph->mediatype = &mtype;
+
+	memset (mediatype, 0, sizeof(AM_MEDIA_TYPE));
+	mediatype->majortype = MEDIATYPE_Video;
+	mediatype->subtype = *(get_dshow_format_info(strm->param.fmt.id)->
+			       dshow_format);
+	mediatype->bFixedSizeSamples = TRUE;
+	mediatype->bTemporalCompression = FALSE;
+
+	vi = (VIDEOINFOHEADER *)
+	    CoTaskMemAlloc(sizeof(VIDEOINFOHEADER));
+	memset (vi, 0, sizeof(VIDEOINFOHEADER));
+	mediatype->formattype = FORMAT_VideoInfo;
+	mediatype->cbFormat = sizeof(VIDEOINFOHEADER);
+	mediatype->pbFormat = (BYTE *)vi;
+
+	vi->rcSource.bottom = vfd->size.h;
+	vi->rcSource.right = vfd->size.w;
+	vi->rcTarget.bottom = vfd->size.h;
+	vi->rcTarget.right = vfd->size.w;
+
+	vi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
+	vi->bmiHeader.biPlanes = 1;
+	vi->bmiHeader.biBitCount = vfi->bpp;
+	vi->bmiHeader.biCompression = strm->param.fmt.id;
+    }
+
+    if (!srcpin || !sinkpin || !mediatype) {
+        hr = VFW_E_TYPE_NOT_ACCEPTED;
+        goto on_error;
+    }
+    video_info = (VIDEOINFOHEADER *) mediatype->pbFormat;
+    if (!use_def_size) {
+        video_info->bmiHeader.biWidth = vfd->size.w;
+        video_info->bmiHeader.biHeight = vfd->size.h;
+    }
+    if (video_info->AvgTimePerFrame == 0 ||
+        (!use_def_fps && vfd->fps.num != 0))
+    {
+        video_info->AvgTimePerFrame = (LONGLONG) (10000000 * 
+						  (double)vfd->fps.denum /
+						  vfd->fps.num);
+    }
+    video_info->bmiHeader.biSizeImage = DIBSIZE(video_info->bmiHeader);
+    mediatype->lSampleSize = DIBSIZE(video_info->bmiHeader);
+    if (graph->csource_filter)
+        SourceFilter_SetMediaType(graph->csource_filter,
+                                  mediatype);
+
+    hr = IFilterGraph_AddFilter(graph->filter_graph,
+                                (IBaseFilter *)graph->rend_filter,
+                                L"renderer");
+    if (FAILED(hr))
+        goto on_error;
+
+    hr = IFilterGraph_ConnectDirect(graph->filter_graph, srcpin, sinkpin,
+                                    mediatype);
+    if (SUCCEEDED(hr)) {
+        if (use_def_size || use_def_fps) {
+            pjmedia_format_init_video(&strm->param.fmt, strm->param.fmt.id,
+                                      video_info->bmiHeader.biWidth,
+                                      video_info->bmiHeader.biHeight,
+                                      10000000,
+                                      (unsigned)video_info->AvgTimePerFrame);
+        }
+
+        strm->frm_buf_size = 0;
+        if (dir == PJMEDIA_DIR_CAPTURE &&
+            video_info->bmiHeader.biCompression == BI_RGB &&
+            video_info->bmiHeader.biHeight > 0)
+        {
+            /* Allocate buffer to flip the captured image. */
+            strm->frm_buf_size = (video_info->bmiHeader.biBitCount >> 3) *
+                                 video_info->bmiHeader.biWidth;
+            strm->frm_buf = pj_pool_alloc(strm->pool, strm->frm_buf_size);
+        }
+    }
+
+on_error:
+    if (srcpin)
+        IPin_Release(srcpin);
+    if (sinkpin)
+        IPin_Release(sinkpin);
+    if (vi)
+        CoTaskMemFree(vi);
+    if (FAILED(hr)) {
+	char msg[80];
+	if (AMGetErrorText(hr, msg, sizeof(msg))) {
+	    PJ_LOG(4,(THIS_FILE, "Error creating filter graph: %s (hr=0x%x)", 
+		      msg, hr));
+	}
+        return PJ_EUNKNOWN;
+    }
+
+    return PJ_SUCCESS;
+}
+
+static void destroy_filter_graph(struct dshow_stream * stream)
+{
+    if (stream->dgraph.source_filter) {
+        IBaseFilter_Release(stream->dgraph.source_filter);
+        stream->dgraph.source_filter = NULL;
+    }
+    if (stream->dgraph.rend_filter) {
+        IBaseFilter_Release(stream->dgraph.rend_filter);
+        stream->dgraph.rend_filter = NULL;
+    }
+    if (stream->dgraph.media_filter) {
+        IMediaFilter_Release(stream->dgraph.media_filter);
+        stream->dgraph.media_filter = NULL;
+    }
+    if (stream->dgraph.filter_graph) {
+        IFilterGraph_Release(stream->dgraph.filter_graph);
+        stream->dgraph.filter_graph = NULL;
+    }
+}
+
+/* API: create stream */
+static pj_status_t dshow_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)
+{
+    struct dshow_factory *df = (struct dshow_factory*)f;
+    pj_pool_t *pool;
+    struct dshow_stream *strm;
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(param->dir == PJMEDIA_DIR_CAPTURE ||
+                     param->dir == PJMEDIA_DIR_RENDER, PJ_EINVAL);
+
+    if (!get_dshow_format_info(param->fmt.id))
+        return PJMEDIA_EVID_BADFORMAT;
+
+    /* Create and Initialize stream descriptor */
+    pool = pj_pool_create(df->pf, "dshow-dev", 1000, 1000, NULL);
+    PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);
+
+    strm = PJ_POOL_ZALLOC_T(pool, struct dshow_stream);
+    pj_memcpy(&strm->param, param, sizeof(*param));
+    strm->pool = pool;
+    pj_memcpy(&strm->vid_cb, cb, sizeof(*cb));
+    strm->user_data = user_data;
+
+    if (param->dir & PJMEDIA_DIR_CAPTURE) {
+	const pjmedia_video_format_detail *vfd;
+
+        /* Create capture stream here */
+        status = create_filter_graph(PJMEDIA_DIR_CAPTURE, param->cap_id,
+                                     PJ_FALSE, PJ_FALSE, df, strm,
+                                     &strm->dgraph);
+        if (status != PJ_SUCCESS) {
+            destroy_filter_graph(strm);
+            /* Try to use default fps */
+            PJ_LOG(4,(THIS_FILE, "Trying to open dshow dev with default fps"));
+            status = create_filter_graph(PJMEDIA_DIR_CAPTURE, param->cap_id,
+                                         PJ_FALSE, PJ_TRUE, df, strm,
+                                         &strm->dgraph);
+
+            if (status != PJ_SUCCESS) {
+                /* Still failed, now try to use default fps and size */
+                destroy_filter_graph(strm);
+                /* Try to use default fps */
+                PJ_LOG(4,(THIS_FILE, "Trying to open dshow dev with default "
+                                     "size & fps"));
+                status = create_filter_graph(PJMEDIA_DIR_CAPTURE,
+                                             param->cap_id,
+                                             PJ_TRUE, PJ_TRUE, df, strm,
+                                             &strm->dgraph);
+            }
+
+            if (status != PJ_SUCCESS)
+                goto on_error;
+            pj_memcpy(param, &strm->param, sizeof(*param));
+        }
+	
+	vfd = pjmedia_format_get_video_format_detail(&param->fmt, PJ_TRUE);
+	strm->cap_ts_inc = PJMEDIA_SPF2(param->clock_rate, &vfd->fps, 1);
+    } else if (param->dir & PJMEDIA_DIR_RENDER) {
+        /* Create render stream here */
+        status = create_filter_graph(PJMEDIA_DIR_RENDER, param->rend_id,
+                                     PJ_FALSE, PJ_FALSE, df, strm,
+                                     &strm->dgraph);
+        if (status != PJ_SUCCESS)
+            goto on_error;
+    }
+
+    /* Apply the remaining settings */
+    if (param->flags & PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW) {
+	dshow_stream_set_cap(&strm->base,
+		            PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW,
+		            &param->window);
+    }
+
+    /* Done */
+    strm->base.op = &stream_op;
+    *p_vid_strm = &strm->base;
+
+    return PJ_SUCCESS;
+ 
+on_error:
+    dshow_stream_destroy((pjmedia_vid_dev_stream *)strm);
+    return status;
+}
+
+/* API: Get stream info. */
+static pj_status_t dshow_stream_get_param(pjmedia_vid_dev_stream *s,
+					  pjmedia_vid_dev_param *pi)
+{
+    struct dshow_stream *strm = (struct dshow_stream*)s;
+
+    PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
+
+    pj_memcpy(pi, &strm->param, sizeof(*pi));
+
+    if (dshow_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 dshow_stream_get_cap(pjmedia_vid_dev_stream *s,
+				        pjmedia_vid_dev_cap cap,
+				        void *pval)
+{
+    struct dshow_stream *strm = (struct dshow_stream*)s;
+
+    PJ_UNUSED_ARG(strm);
+
+    PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+    if (cap==PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW)
+    {
+	*(unsigned*)pval = 0;
+	return PJ_SUCCESS;
+    } else {
+	return PJMEDIA_EVID_INVCAP;
+    }
+}
+
+/* API: set capability */
+static pj_status_t dshow_stream_set_cap(pjmedia_vid_dev_stream *s,
+				        pjmedia_vid_dev_cap cap,
+				        const void *pval)
+{
+    struct dshow_stream *strm = (struct dshow_stream*)s;
+
+    PJ_UNUSED_ARG(strm);
+
+    PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+    if (cap==PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW)
+    {
+	// set renderer's window here
+	return PJ_SUCCESS;
+    }
+
+    return PJMEDIA_EVID_INVCAP;
+}
+
+/* API: Start stream. */
+static pj_status_t dshow_stream_start(pjmedia_vid_dev_stream *strm)
+{
+    struct dshow_stream *stream = (struct dshow_stream*)strm;
+    HRESULT hr;
+
+    stream->quit_flag = PJ_FALSE;
+    stream->cap_thread_exited = PJ_FALSE;
+    stream->rend_thread_exited = PJ_FALSE;
+
+    hr = IMediaFilter_Run(stream->dgraph.media_filter, 0);
+    if (FAILED(hr)) {
+        char msg[80];
+        if (AMGetErrorText(hr, msg, sizeof(msg))) {
+            PJ_LOG(4,(THIS_FILE, "Error starting media: %s", msg));
+        }
+        return PJ_EUNKNOWN;
+    }
+
+    PJ_LOG(4, (THIS_FILE, "Starting dshow video stream"));
+
+    return PJ_SUCCESS;
+}
+
+/* API: Stop stream. */
+static pj_status_t dshow_stream_stop(pjmedia_vid_dev_stream *strm)
+{
+    struct dshow_stream *stream = (struct dshow_stream*)strm;
+    unsigned i;
+
+    stream->quit_flag = PJ_TRUE;
+    if (stream->cap_thread) {
+        for (i=0; !stream->cap_thread_exited && i<100; ++i)
+	    pj_thread_sleep(10);
+    }
+    for (i=0; !stream->rend_thread_exited && i<100; ++i)
+	pj_thread_sleep(10);
+
+    IMediaFilter_Stop(stream->dgraph.media_filter);
+
+    PJ_LOG(4, (THIS_FILE, "Stopping dshow video stream"));
+
+    return PJ_SUCCESS;
+}
+
+/* API: Destroy stream. */
+static pj_status_t dshow_stream_destroy(pjmedia_vid_dev_stream *strm)
+{
+    struct dshow_stream *stream = (struct dshow_stream*)strm;
+
+    PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
+
+    dshow_stream_stop(strm);
+    destroy_filter_graph(stream);
+
+    pj_pool_release(stream->pool);
+
+    return PJ_SUCCESS;
+}
+
+#endif	/* PJMEDIA_VIDEO_DEV_HAS_DSHOW */
diff --git a/jni/pjproject-android/pjmedia/src/pjmedia-videodev/dshowclasses.cpp b/jni/pjproject-android/pjmedia/src/pjmedia-videodev/dshowclasses.cpp
new file mode 100644
index 0000000..5affe60
--- /dev/null
+++ b/jni/pjproject-android/pjmedia/src/pjmedia-videodev/dshowclasses.cpp
@@ -0,0 +1,242 @@
+/* $Id: dshowclasses.cpp 4062 2012-04-19 06:36:57Z ming $ */
+/*
+ * Copyright (C) 2008-2011 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/config.h>
+
+
+#if defined(PJMEDIA_VIDEO_DEV_HAS_DSHOW) && PJMEDIA_VIDEO_DEV_HAS_DSHOW != 0
+
+
+#include <assert.h>
+#include <streams.h>
+
+typedef void (*input_callback)(void *user_data, IMediaSample *pMediaSample);
+
+const GUID CLSID_NullRenderer = {0xF9168C5E, 0xCEB2, 0x4FAA, {0xB6, 0xBF,
+                                 0x32, 0x9B, 0xF3, 0x9F, 0xA1, 0xE4}};
+
+const GUID CLSID_SourceFilter = {0xF9168C5E, 0xCEB2, 0x4FAA, {0xB6, 0xBF,
+                                 0x32, 0x9B, 0xF3, 0x9F, 0xA1, 0xE5}};
+
+class NullRenderer: public CBaseRenderer
+{
+public:
+    NullRenderer(HRESULT *pHr);
+    virtual ~NullRenderer();
+
+    virtual HRESULT CheckMediaType(const CMediaType *pmt);
+    virtual HRESULT DoRenderSample(IMediaSample *pMediaSample);
+
+    input_callback  input_cb;
+    void           *user_data;
+};
+
+class OutputPin: public CBaseOutputPin
+{
+public:
+    OutputPin(CBaseFilter *pFilter, CCritSec *pLock, HRESULT *pHr);
+    ~OutputPin();
+
+    HRESULT Push(void *buf, long size);
+
+    virtual HRESULT CheckMediaType(const CMediaType *pmt);
+    virtual HRESULT DecideBufferSize(IMemAllocator *pAlloc, 
+                                     ALLOCATOR_PROPERTIES *ppropInputRequest);
+
+    CMediaType mediaType;
+    long bufSize;
+};
+
+class SourceFilter: public CBaseFilter
+{
+public:
+    SourceFilter();
+    ~SourceFilter();
+
+    int GetPinCount();
+    CBasePin* GetPin(int n);
+
+protected:
+    CCritSec lock;
+    OutputPin* outPin;
+};
+
+OutputPin::OutputPin(CBaseFilter *pFilter, CCritSec *pLock, HRESULT *pHr):
+    CBaseOutputPin("OutputPin", pFilter, pLock, pHr, L"OutputPin")
+{
+}
+
+OutputPin::~OutputPin()
+{
+}
+
+HRESULT OutputPin::CheckMediaType(const CMediaType *pmt)
+{
+    return S_OK;
+}
+
+HRESULT OutputPin::DecideBufferSize(IMemAllocator *pAlloc, 
+                                    ALLOCATOR_PROPERTIES *ppropInputRequest)
+{
+    ALLOCATOR_PROPERTIES properties;
+
+    ppropInputRequest->cbBuffer = bufSize;
+    ppropInputRequest->cBuffers = 1;
+
+    /* First set the buffer descriptions we're interested in */
+    pAlloc->SetProperties(ppropInputRequest, &properties);
+
+    return S_OK;
+}
+
+HRESULT OutputPin::Push(void *buf, long size)
+{
+    HRESULT hr;
+    IMediaSample *pSample;
+    VIDEOINFOHEADER *vi;
+    AM_MEDIA_TYPE *pmt;
+    BYTE *dst_buf;
+
+    /**
+     * Hold the critical section here as the pin might get disconnected
+     * during the Deliver() method call.
+     */
+    m_pLock->Lock();
+
+    hr = GetDeliveryBuffer(&pSample, NULL, NULL, 0);
+    if (FAILED(hr))
+        goto on_error;
+
+    pSample->GetMediaType(&pmt);
+    if (pmt) {
+        mediaType.Set(*pmt);
+        bufSize = pmt->lSampleSize;
+    }
+
+    pSample->GetPointer(&dst_buf);
+    vi = (VIDEOINFOHEADER *)mediaType.pbFormat;
+    if (vi->rcSource.right == vi->bmiHeader.biWidth) {
+        assert(pSample->GetSize() >= size);
+        memcpy(dst_buf, buf, size);
+    } else {
+        unsigned i, bpp;
+        unsigned dststride, srcstride;
+        BYTE *src_buf = (BYTE *)buf;
+
+        bpp = size / abs(vi->bmiHeader.biHeight) / vi->rcSource.right;
+        dststride = vi->bmiHeader.biWidth * bpp;
+        srcstride = vi->rcSource.right * bpp;
+        for (i = abs(vi->bmiHeader.biHeight); i > 0; i--) {
+            memcpy(dst_buf, src_buf, srcstride);
+            dst_buf += dststride;
+            src_buf += srcstride;
+        }
+    }
+    pSample->SetActualDataLength(size);
+
+    hr = Deliver(pSample);
+
+    pSample->Release();
+
+on_error:
+    m_pLock->Unlock();
+    return hr;
+}
+
+SourceFilter::SourceFilter(): CBaseFilter("SourceFilter", NULL, &lock, 
+                                          CLSID_SourceFilter)
+{
+    HRESULT hr;
+    outPin = new OutputPin(this, &lock, &hr);
+}
+
+SourceFilter::~SourceFilter()
+{
+}
+
+int SourceFilter::GetPinCount()
+{
+    return 1;
+}
+
+CBasePin* SourceFilter::GetPin(int n)
+{
+    return outPin;
+}
+
+NullRenderer::NullRenderer(HRESULT *pHr): CBaseRenderer(CLSID_NullRenderer,
+                                                        "NullRenderer",
+                                                        NULL, pHr)
+{
+    input_cb = NULL;
+}
+
+NullRenderer::~NullRenderer()
+{
+}
+
+HRESULT NullRenderer::CheckMediaType(const CMediaType *pmt)
+{
+    return S_OK;
+}
+
+HRESULT NullRenderer::DoRenderSample(IMediaSample *pMediaSample)
+{
+    if (input_cb)
+        input_cb(user_data, pMediaSample);
+
+    return S_OK;
+}
+
+extern "C" IBaseFilter* NullRenderer_Create(input_callback input_cb,
+                                             void *user_data)
+{
+    HRESULT hr;
+    NullRenderer *renderer = new NullRenderer(&hr);
+    renderer->AddRef();
+    renderer->input_cb = input_cb;
+    renderer->user_data = user_data;
+
+    return (CBaseFilter *)renderer;
+}
+
+extern "C" IBaseFilter* SourceFilter_Create(SourceFilter **pSrc)
+{
+    SourceFilter *src = new SourceFilter();
+    src->AddRef();
+    *pSrc = src;
+
+    return (CBaseFilter *)src;
+}
+
+extern "C" HRESULT SourceFilter_Deliver(SourceFilter *src,
+                                        void *buf, long size)
+{
+    return ((OutputPin *)src->GetPin(0))->Push(buf, size);
+}
+
+extern "C" void SourceFilter_SetMediaType(SourceFilter *src,
+                                          AM_MEDIA_TYPE *pmt)
+{
+    ((OutputPin *)src->GetPin(0))->mediaType.Set(*pmt);
+    ((OutputPin *)src->GetPin(0))->bufSize = pmt->lSampleSize;
+}
+
+
+#endif	/* PJMEDIA_VIDEO_DEV_HAS_DSHOW */
diff --git a/jni/pjproject-android/pjmedia/src/pjmedia-videodev/errno.c b/jni/pjproject-android/pjmedia/src/pjmedia-videodev/errno.c
new file mode 100644
index 0000000..ffcccf7
--- /dev/null
+++ b/jni/pjproject-android/pjmedia/src/pjmedia-videodev/errno.c
@@ -0,0 +1,119 @@
+/* $Id: errno.c 3715 2011-08-19 09:35:25Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 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/errno.h>
+#include <pj/string.h>
+#include <pj/unicode.h>
+
+/* PJMEDIA-videodev's own error codes/messages
+ * MUST KEEP THIS ARRAY SORTED!!
+ * Message must be limited to 64 chars!
+ */
+
+
+#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
+
+
+#if defined(PJ_HAS_ERROR_STRING) && (PJ_HAS_ERROR_STRING != 0)
+
+static const struct
+{
+    int code;
+    const char *msg;
+} err_str[] =
+{
+    PJ_BUILD_ERR( PJMEDIA_EVID_ERR,	    "Unspecified video device error" ),
+    PJ_BUILD_ERR( PJMEDIA_EVID_SYSERR,	    "Unknown error from video driver" ),
+    PJ_BUILD_ERR( PJMEDIA_EVID_INIT,	    "video subsystem not initialized" ),
+    PJ_BUILD_ERR( PJMEDIA_EVID_INVDEV,	    "Invalid video device" ),
+    PJ_BUILD_ERR( PJMEDIA_EVID_NODEV,	    "Found no video devices" ),
+    PJ_BUILD_ERR( PJMEDIA_EVID_NODEFDEV,    "Unable to find default video device" ),
+    PJ_BUILD_ERR( PJMEDIA_EVID_NOTREADY,    "video device not ready" ),
+    PJ_BUILD_ERR( PJMEDIA_EVID_INVCAP,	    "Invalid or unsupported video capability" ),
+    PJ_BUILD_ERR( PJMEDIA_EVID_INVOP,	    "Invalid or unsupported video device operation" ),
+    PJ_BUILD_ERR( PJMEDIA_EVID_BADFORMAT,   "Bad or invalid video device format" ),
+    PJ_BUILD_ERR( PJMEDIA_EVID_SAMPFORMAT,  "Invalid video device sample format"),
+    PJ_BUILD_ERR( PJMEDIA_EVID_BADLATENCY,  "Bad video latency setting")
+
+};
+
+#endif	/* PJ_HAS_ERROR_STRING */
+
+
+
+/*
+ * pjmedia_videodev_strerror()
+ */
+PJ_DEF(pj_str_t) pjmedia_videodev_strerror(pj_status_t statcode,
+					   char *buf, pj_size_t bufsize )
+{
+    pj_str_t errstr;
+
+#if defined(PJ_HAS_ERROR_STRING) && (PJ_HAS_ERROR_STRING != 0)
+
+    /* videodev error */
+    if (statcode >= PJMEDIA_VIDEODEV_ERRNO_START &&
+	statcode < PJMEDIA_VIDEODEV_ERRNO_END)
+    {
+	/* Find the error in the table.
+	 * Use binary search!
+	 */
+	int first = 0;
+	int n = PJ_ARRAY_SIZE(err_str);
+
+	while (n > 0) {
+	    int half = n/2;
+	    int mid = first + half;
+
+	    if (err_str[mid].code < statcode) {
+		first = mid+1;
+		n -= (half+1);
+	    } else if (err_str[mid].code > statcode) {
+		n = half;
+	    } else {
+		first = mid;
+		break;
+	    }
+	}
+
+
+	if (PJ_ARRAY_SIZE(err_str) && err_str[first].code == statcode) {
+	    pj_str_t msg;
+
+	    msg.ptr = (char*)err_str[first].msg;
+	    msg.slen = pj_ansi_strlen(err_str[first].msg);
+
+	    errstr.ptr = buf;
+	    pj_strncpy_with_null(&errstr, &msg, bufsize);
+	    return errstr;
+
+	}
+    }
+#endif	/* PJ_HAS_ERROR_STRING */
+
+    /* Error not found. */
+    errstr.ptr = buf;
+    errstr.slen = pj_ansi_snprintf(buf, bufsize,
+				   "Unknown pjmedia-videodev error %d",
+				   statcode);
+
+    return errstr;
+}
+
+
+#endif /* PJMEDIA_HAS_VIDEO */
diff --git a/jni/pjproject-android/pjmedia/src/pjmedia-videodev/ffmpeg_dev.c b/jni/pjproject-android/pjmedia/src/pjmedia-videodev/ffmpeg_dev.c
new file mode 100644
index 0000000..1b65737
--- /dev/null
+++ b/jni/pjproject-android/pjmedia/src/pjmedia-videodev/ffmpeg_dev.c
@@ -0,0 +1,517 @@
+/* $Id: ffmpeg_dev.c 4537 2013-06-19 06:47:43Z riza $ */
+/*
+ * Copyright (C) 2008-2011 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
+ */
+
+/* Video device with ffmpeg backend, currently only capture devices are
+ * implemented.
+ *
+ * Issues:
+ * - no device enumeration (ffmpeg limitation), so this uses "host API" enum
+ *   instead
+ * - need stricter filter on "host API" enum, currently audio capture devs are
+ *   still listed.
+ * - no format enumeration, currently hardcoded to PJMEDIA_FORMAT_RGB24 only
+ * - tested on Vista only (vfw backend) with virtual cam
+ * - vfw backend produce bottom up pictures
+ * - using VS IDE, this cannot run under debugger!
+ */
+
+#include <pjmedia-videodev/videodev_imp.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/unicode.h>
+
+
+#if defined(PJMEDIA_VIDEO_DEV_HAS_FFMPEG) && PJMEDIA_VIDEO_DEV_HAS_FFMPEG != 0
+
+
+#define THIS_FILE		"ffmpeg.c"
+
+#include "../pjmedia/ffmpeg_util.h"
+#include <libavdevice/avdevice.h>
+#include <libavformat/avformat.h>
+
+#define MAX_DEV_CNT     8
+
+typedef struct ffmpeg_dev_info
+{
+    pjmedia_vid_dev_info         base;
+    AVInputFormat               *host_api;
+    const char                  *def_devname;
+} ffmpeg_dev_info;
+
+
+typedef struct ffmpeg_factory
+{
+    pjmedia_vid_dev_factory	 base;
+    pj_pool_factory		*pf;
+    pj_pool_t                   *pool;
+    pj_pool_t                   *dev_pool;
+    unsigned                     dev_count;
+    ffmpeg_dev_info              dev_info[MAX_DEV_CNT];
+} ffmpeg_factory;
+
+
+typedef struct ffmpeg_stream
+{
+    pjmedia_vid_dev_stream       base;
+    ffmpeg_factory              *factory;
+    pj_pool_t                   *pool;
+    pjmedia_vid_dev_param        param;
+    AVFormatContext             *ff_fmt_ctx;
+} ffmpeg_stream;
+
+
+/* Prototypes */
+static pj_status_t ffmpeg_factory_init(pjmedia_vid_dev_factory *f);
+static pj_status_t ffmpeg_factory_destroy(pjmedia_vid_dev_factory *f);
+static pj_status_t ffmpeg_factory_refresh(pjmedia_vid_dev_factory *f);
+static unsigned    ffmpeg_factory_get_dev_count(pjmedia_vid_dev_factory *f);
+static pj_status_t ffmpeg_factory_get_dev_info(pjmedia_vid_dev_factory *f,
+					       unsigned index,
+					       pjmedia_vid_dev_info *info);
+static pj_status_t ffmpeg_factory_default_param(pj_pool_t *pool,
+                                                pjmedia_vid_dev_factory *f,
+					        unsigned index,
+					        pjmedia_vid_dev_param *param);
+static pj_status_t ffmpeg_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 ffmpeg_stream_get_param(pjmedia_vid_dev_stream *strm,
+					   pjmedia_vid_dev_param *param);
+static pj_status_t ffmpeg_stream_get_cap(pjmedia_vid_dev_stream *strm,
+				         pjmedia_vid_dev_cap cap,
+				         void *value);
+static pj_status_t ffmpeg_stream_set_cap(pjmedia_vid_dev_stream *strm,
+				         pjmedia_vid_dev_cap cap,
+				         const void *value);
+static pj_status_t ffmpeg_stream_start(pjmedia_vid_dev_stream *strm);
+static pj_status_t ffmpeg_stream_get_frame(pjmedia_vid_dev_stream *s,
+                                           pjmedia_frame *frame);
+static pj_status_t ffmpeg_stream_stop(pjmedia_vid_dev_stream *strm);
+static pj_status_t ffmpeg_stream_destroy(pjmedia_vid_dev_stream *strm);
+
+/* Operations */
+static pjmedia_vid_dev_factory_op factory_op =
+{
+    &ffmpeg_factory_init,
+    &ffmpeg_factory_destroy,
+    &ffmpeg_factory_get_dev_count,
+    &ffmpeg_factory_get_dev_info,
+    &ffmpeg_factory_default_param,
+    &ffmpeg_factory_create_stream,
+    &ffmpeg_factory_refresh
+};
+
+static pjmedia_vid_dev_stream_op stream_op =
+{
+    &ffmpeg_stream_get_param,
+    &ffmpeg_stream_get_cap,
+    &ffmpeg_stream_set_cap,
+    &ffmpeg_stream_start,
+    &ffmpeg_stream_get_frame,
+    NULL,
+    &ffmpeg_stream_stop,
+    &ffmpeg_stream_destroy
+};
+
+
+static void print_ffmpeg_err(int err)
+{
+    char errbuf[512];
+    if (av_strerror(err, errbuf, sizeof(errbuf)) >= 0)
+        PJ_LOG(1, (THIS_FILE, "ffmpeg err %d: %s", err, errbuf));
+
+}
+
+static void print_ffmpeg_log(void* ptr, int level, const char* fmt, va_list vl)
+{
+    PJ_UNUSED_ARG(ptr);
+    PJ_UNUSED_ARG(level);
+    vfprintf(stdout, fmt, vl);
+}
+
+
+static pj_status_t ffmpeg_capture_open(AVFormatContext **ctx,
+                                       AVInputFormat *ifmt,
+                                       const char *dev_name,
+                                       const pjmedia_vid_dev_param *param)
+{
+    AVFormatParameters fp;
+    pjmedia_video_format_detail *vfd;
+    int err;
+
+    PJ_ASSERT_RETURN(ctx && ifmt && dev_name && param, PJ_EINVAL);
+    PJ_ASSERT_RETURN(param->fmt.detail_type == PJMEDIA_FORMAT_DETAIL_VIDEO,
+                     PJ_EINVAL);
+
+    vfd = pjmedia_format_get_video_format_detail(&param->fmt, PJ_TRUE);
+
+    /* Init ffmpeg format context */
+    *ctx = avformat_alloc_context();
+
+    /* Init ffmpeg format param */
+    pj_bzero(&fp, sizeof(fp));
+    fp.prealloced_context = 1;
+    fp.width = vfd->size.w;
+    fp.height = vfd->size.h;
+    fp.pix_fmt = PIX_FMT_BGR24;
+    fp.time_base.num = vfd->fps.denum;
+    fp.time_base.den = vfd->fps.num;
+
+    /* Open capture stream */
+    err = av_open_input_stream(ctx, NULL, dev_name, ifmt, &fp);
+    if (err < 0) {
+        *ctx = NULL; /* ffmpeg freed its states on failure, do we must too */
+        print_ffmpeg_err(err);
+        return PJ_EUNKNOWN;
+    }
+
+    return PJ_SUCCESS;
+}
+
+static void ffmpeg_capture_close(AVFormatContext *ctx)
+{
+    if (ctx)
+        av_close_input_stream(ctx);
+}
+
+
+/****************************************************************************
+ * Factory operations
+ */
+/*
+ * Init ffmpeg_ video driver.
+ */
+pjmedia_vid_dev_factory* pjmedia_ffmpeg_factory(pj_pool_factory *pf)
+{
+    ffmpeg_factory *f;
+    pj_pool_t *pool;
+
+    pool = pj_pool_create(pf, "ffmpeg_cap_dev", 1000, 1000, NULL);
+    f = PJ_POOL_ZALLOC_T(pool, ffmpeg_factory);
+
+    f->pool = pool;
+    f->pf = pf;
+    f->base.op = &factory_op;
+
+    avdevice_register_all();
+
+    return &f->base;
+}
+
+
+/* API: init factory */
+static pj_status_t ffmpeg_factory_init(pjmedia_vid_dev_factory *f)
+{
+    return ffmpeg_factory_refresh(f);
+}
+
+/* API: destroy factory */
+static pj_status_t ffmpeg_factory_destroy(pjmedia_vid_dev_factory *f)
+{
+    ffmpeg_factory *ff = (ffmpeg_factory*)f;
+    pj_pool_t *pool = ff->pool;
+
+    ff->dev_count = 0;
+    ff->pool = NULL;
+    if (ff->dev_pool)
+        pj_pool_release(ff->dev_pool);
+    if (pool)
+        pj_pool_release(pool);
+
+    return PJ_SUCCESS;
+}
+
+/* API: refresh the list of devices */
+static pj_status_t ffmpeg_factory_refresh(pjmedia_vid_dev_factory *f)
+{
+    ffmpeg_factory *ff = (ffmpeg_factory*)f;
+    AVInputFormat *p;
+    ffmpeg_dev_info *info;
+
+    av_log_set_callback(&print_ffmpeg_log);
+    av_log_set_level(AV_LOG_DEBUG);
+
+    if (ff->dev_pool) {
+        pj_pool_release(ff->dev_pool);
+        ff->dev_pool = NULL;
+    }
+
+    /* TODO: this should enumerate devices, now it enumerates host APIs */
+    ff->dev_count = 0;
+    ff->dev_pool = pj_pool_create(ff->pf, "ffmpeg_cap_dev", 500, 500, NULL);
+
+    p = av_iformat_next(NULL);
+    while (p) {
+        if (p->flags & AVFMT_NOFILE) {
+            unsigned i;
+
+            info = &ff->dev_info[ff->dev_count++];
+            pj_bzero(info, sizeof(*info));
+            pj_ansi_strncpy(info->base.name, "default", 
+                            sizeof(info->base.name));
+            pj_ansi_snprintf(info->base.driver, sizeof(info->base.driver),
+                             "%s (ffmpeg)", p->name);
+            info->base.dir = PJMEDIA_DIR_CAPTURE;
+            info->base.has_callback = PJ_FALSE;
+
+            info->host_api = p;
+
+#if (defined(PJ_WIN32) && PJ_WIN32!=0) || \
+    (defined(PJ_WIN64) && PJ_WIN64!=0)
+            info->def_devname = "0";
+#elif defined(PJ_LINUX) && PJ_LINUX!=0
+            info->def_devname = "/dev/video0";
+#endif
+
+            /* Set supported formats, currently hardcoded to RGB24 only */
+            info->base.caps = PJMEDIA_VID_DEV_CAP_FORMAT;
+            info->base.fmt_cnt = 1;
+            for (i = 0; i < info->base.fmt_cnt; ++i) {
+                pjmedia_format *fmt = &info->base.fmt[i];
+
+                fmt->id = PJMEDIA_FORMAT_RGB24;
+                fmt->type = PJMEDIA_TYPE_VIDEO;
+                fmt->detail_type = PJMEDIA_FORMAT_DETAIL_NONE;
+            }
+        }
+        p = av_iformat_next(p);
+    }
+
+    return PJ_SUCCESS;
+}
+
+/* API: get number of devices */
+static unsigned ffmpeg_factory_get_dev_count(pjmedia_vid_dev_factory *f)
+{
+    ffmpeg_factory *ff = (ffmpeg_factory*)f;
+    return ff->dev_count;
+}
+
+/* API: get device info */
+static pj_status_t ffmpeg_factory_get_dev_info(pjmedia_vid_dev_factory *f,
+					       unsigned index,
+					       pjmedia_vid_dev_info *info)
+{
+    ffmpeg_factory *ff = (ffmpeg_factory*)f;
+
+    PJ_ASSERT_RETURN(index < ff->dev_count, PJMEDIA_EVID_INVDEV);
+
+    pj_memcpy(info, &ff->dev_info[index].base, sizeof(*info));
+
+    return PJ_SUCCESS;
+}
+
+/* API: create default device parameter */
+static pj_status_t ffmpeg_factory_default_param(pj_pool_t *pool,
+                                                pjmedia_vid_dev_factory *f,
+					        unsigned index,
+					        pjmedia_vid_dev_param *param)
+{
+    ffmpeg_factory *ff = (ffmpeg_factory*)f;
+    ffmpeg_dev_info *info;
+
+    PJ_ASSERT_RETURN(index < ff->dev_count, PJMEDIA_EVID_INVDEV);
+
+    PJ_UNUSED_ARG(pool);
+
+    info = &ff->dev_info[index];
+
+    pj_bzero(param, sizeof(*param));
+    param->dir = PJMEDIA_DIR_CAPTURE;
+    param->cap_id = index;
+    param->rend_id = PJMEDIA_VID_INVALID_DEV;
+    param->clock_rate = 0;
+
+    /* Set the device capabilities here */
+    param->flags = PJMEDIA_VID_DEV_CAP_FORMAT;
+    param->clock_rate = 90000;
+    pjmedia_format_init_video(&param->fmt, 0, 320, 240, 25, 1);
+    param->fmt.id = info->base.fmt[0].id;
+
+    return PJ_SUCCESS;
+}
+
+
+
+/* API: create stream */
+static pj_status_t ffmpeg_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)
+{
+    ffmpeg_factory *ff = (ffmpeg_factory*)f;
+    pj_pool_t *pool;
+    ffmpeg_stream *strm;
+
+    PJ_ASSERT_RETURN(f && param && p_vid_strm, PJ_EINVAL);
+    PJ_ASSERT_RETURN(param->dir == PJMEDIA_DIR_CAPTURE, PJ_EINVAL);
+    PJ_ASSERT_RETURN((unsigned)param->cap_id < ff->dev_count, PJ_EINVAL);
+    PJ_ASSERT_RETURN(param->fmt.detail_type == PJMEDIA_FORMAT_DETAIL_VIDEO,
+		     PJ_EINVAL);
+
+    PJ_UNUSED_ARG(cb);
+    PJ_UNUSED_ARG(user_data);
+
+    /* Create and Initialize stream descriptor */
+    pool = pj_pool_create(ff->pf, "ffmpeg-dev", 1000, 1000, NULL);
+    PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);
+
+    strm = PJ_POOL_ZALLOC_T(pool, struct ffmpeg_stream);
+    strm->factory = (ffmpeg_factory*)f;
+    strm->pool = pool;
+    pj_memcpy(&strm->param, param, sizeof(*param));
+
+    /* Done */
+    strm->base.op = &stream_op;
+    *p_vid_strm = &strm->base;
+
+    return PJ_SUCCESS;
+}
+
+/* API: Get stream info. */
+static pj_status_t ffmpeg_stream_get_param(pjmedia_vid_dev_stream *s,
+					   pjmedia_vid_dev_param *pi)
+{
+    ffmpeg_stream *strm = (ffmpeg_stream*)s;
+
+    PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
+
+    pj_memcpy(pi, &strm->param, sizeof(*pi));
+
+    return PJ_SUCCESS;
+}
+
+/* API: get capability */
+static pj_status_t ffmpeg_stream_get_cap(pjmedia_vid_dev_stream *s,
+				         pjmedia_vid_dev_cap cap,
+				         void *pval)
+{
+    ffmpeg_stream *strm = (ffmpeg_stream*)s;
+
+    PJ_UNUSED_ARG(strm);
+    PJ_UNUSED_ARG(cap);
+    PJ_UNUSED_ARG(pval);
+
+    return PJMEDIA_EVID_INVCAP;
+}
+
+/* API: set capability */
+static pj_status_t ffmpeg_stream_set_cap(pjmedia_vid_dev_stream *s,
+				         pjmedia_vid_dev_cap cap,
+				         const void *pval)
+{
+    ffmpeg_stream *strm = (ffmpeg_stream*)s;
+
+    PJ_UNUSED_ARG(strm);
+    PJ_UNUSED_ARG(cap);
+    PJ_UNUSED_ARG(pval);
+
+    return PJMEDIA_EVID_INVCAP;
+}
+
+
+/* API: Start stream. */
+static pj_status_t ffmpeg_stream_start(pjmedia_vid_dev_stream *s)
+{
+    ffmpeg_stream *strm = (ffmpeg_stream*)s;
+    ffmpeg_dev_info *info;
+    pj_status_t status;
+
+    info = &strm->factory->dev_info[strm->param.cap_id];
+
+    PJ_LOG(4, (THIS_FILE, "Starting ffmpeg capture stream"));
+
+    status = ffmpeg_capture_open(&strm->ff_fmt_ctx, info->host_api,
+                                 info->def_devname, &strm->param);
+    if (status != PJ_SUCCESS) {
+        /* must set ffmpeg states to NULL on any failure */
+        strm->ff_fmt_ctx = NULL;
+    }
+
+    return status;
+}
+
+
+/* API: Get frame from stream */
+static pj_status_t ffmpeg_stream_get_frame(pjmedia_vid_dev_stream *s,
+                                           pjmedia_frame *frame)
+{
+    ffmpeg_stream *strm = (ffmpeg_stream*)s;
+    AVPacket p;
+    int err;
+
+    err = av_read_frame(strm->ff_fmt_ctx, &p);
+    if (err < 0) {
+        print_ffmpeg_err(err);
+        return PJ_EUNKNOWN;
+    }
+
+    pj_bzero(frame, sizeof(*frame));
+    frame->type = PJMEDIA_FRAME_TYPE_VIDEO;
+    frame->buf = p.data;
+    frame->size = p.size;
+
+    return PJ_SUCCESS;
+}
+
+
+/* API: Stop stream. */
+static pj_status_t ffmpeg_stream_stop(pjmedia_vid_dev_stream *s)
+{
+    ffmpeg_stream *strm = (ffmpeg_stream*)s;
+
+    PJ_LOG(4, (THIS_FILE, "Stopping ffmpeg capture stream"));
+
+    ffmpeg_capture_close(strm->ff_fmt_ctx);
+    strm->ff_fmt_ctx = NULL;
+
+    return PJ_SUCCESS;
+}
+
+
+/* API: Destroy stream. */
+static pj_status_t ffmpeg_stream_destroy(pjmedia_vid_dev_stream *s)
+{
+    ffmpeg_stream *strm = (ffmpeg_stream*)s;
+
+    PJ_ASSERT_RETURN(strm != NULL, PJ_EINVAL);
+
+    ffmpeg_stream_stop(s);
+
+    pj_pool_release(strm->pool);
+
+    return PJ_SUCCESS;
+}
+
+#ifdef _MSC_VER
+#   pragma comment( lib, "avdevice.lib")
+#   pragma comment( lib, "avformat.lib")
+#   pragma comment( lib, "avutil.lib")
+#endif
+
+
+#endif	/* PJMEDIA_VIDEO_DEV_HAS_FFMPEG */
diff --git a/jni/pjproject-android/pjmedia/src/pjmedia-videodev/ios_dev.m b/jni/pjproject-android/pjmedia/src/pjmedia-videodev/ios_dev.m
new file mode 100644
index 0000000..9a20778
--- /dev/null
+++ b/jni/pjproject-android/pjmedia/src/pjmedia-videodev/ios_dev.m
@@ -0,0 +1,703 @@
+/* $Id: ios_dev.m 4519 2013-05-16 10:24:34Z bennylp $ */
+/*
+ * Copyright (C) 2008-2011 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 PJMEDIA_VIDEO_DEV_HAS_IOS && PJMEDIA_HAS_VIDEO
+#include "Availability.h"
+#ifdef __IPHONE_4_0
+
+#import <UIKit/UIKit.h>
+#import <AVFoundation/AVFoundation.h>
+
+#define THIS_FILE		"ios_dev.c"
+#define DEFAULT_CLOCK_RATE	90000
+#define DEFAULT_WIDTH		480
+#define DEFAULT_HEIGHT		360
+#define DEFAULT_FPS		15
+
+typedef struct ios_fmt_info
+{
+    pjmedia_format_id   pjmedia_format;
+    UInt32		ios_format;
+} ios_fmt_info;
+
+static ios_fmt_info ios_fmts[] =
+{
+    {PJMEDIA_FORMAT_BGRA, kCVPixelFormatType_32BGRA} ,
+};
+
+/* qt device info */
+struct ios_dev_info
+{
+    pjmedia_vid_dev_info	 info;
+};
+
+/* qt factory */
+struct ios_factory
+{
+    pjmedia_vid_dev_factory	 base;
+    pj_pool_t			*pool;
+    pj_pool_factory		*pf;
+
+    unsigned			 dev_count;
+    struct ios_dev_info		*dev_info;
+};
+
+@interface VOutDelegate: NSObject 
+			 <AVCaptureVideoDataOutputSampleBufferDelegate>
+{
+@public
+    struct ios_stream *stream;
+}
+@end
+
+/* Video stream. */
+struct ios_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  */
+
+    pjmedia_rect_size	    size;
+    pj_uint8_t		    bpp;
+    unsigned		    bytes_per_row;
+    unsigned		    frame_size;
+    
+    AVCaptureSession		*cap_session;
+    AVCaptureDeviceInput	*dev_input;
+    AVCaptureVideoDataOutput	*video_output;
+    VOutDelegate		*vout_delegate;
+    
+    UIImageView		*imgView;
+    void		*buf;
+    dispatch_queue_t     render_queue;
+    
+    pj_timestamp	 frame_ts;
+    unsigned		 ts_inc;
+};
+
+
+/* Prototypes */
+static pj_status_t ios_factory_init(pjmedia_vid_dev_factory *f);
+static pj_status_t ios_factory_destroy(pjmedia_vid_dev_factory *f);
+static pj_status_t ios_factory_refresh(pjmedia_vid_dev_factory *f);
+static unsigned    ios_factory_get_dev_count(pjmedia_vid_dev_factory *f);
+static pj_status_t ios_factory_get_dev_info(pjmedia_vid_dev_factory *f,
+					    unsigned index,
+					    pjmedia_vid_dev_info *info);
+static pj_status_t ios_factory_default_param(pj_pool_t *pool,
+					     pjmedia_vid_dev_factory *f,
+					     unsigned index,
+					     pjmedia_vid_dev_param *param);
+static pj_status_t ios_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 ios_stream_get_param(pjmedia_vid_dev_stream *strm,
+				        pjmedia_vid_dev_param *param);
+static pj_status_t ios_stream_get_cap(pjmedia_vid_dev_stream *strm,
+				      pjmedia_vid_dev_cap cap,
+				      void *value);
+static pj_status_t ios_stream_set_cap(pjmedia_vid_dev_stream *strm,
+				      pjmedia_vid_dev_cap cap,
+				      const void *value);
+static pj_status_t ios_stream_start(pjmedia_vid_dev_stream *strm);
+static pj_status_t ios_stream_put_frame(pjmedia_vid_dev_stream *strm,
+					const pjmedia_frame *frame);
+static pj_status_t ios_stream_stop(pjmedia_vid_dev_stream *strm);
+static pj_status_t ios_stream_destroy(pjmedia_vid_dev_stream *strm);
+
+/* Operations */
+static pjmedia_vid_dev_factory_op factory_op =
+{
+    &ios_factory_init,
+    &ios_factory_destroy,
+    &ios_factory_get_dev_count,
+    &ios_factory_get_dev_info,
+    &ios_factory_default_param,
+    &ios_factory_create_stream,
+    &ios_factory_refresh
+};
+
+static pjmedia_vid_dev_stream_op stream_op =
+{
+    &ios_stream_get_param,
+    &ios_stream_get_cap,
+    &ios_stream_set_cap,
+    &ios_stream_start,
+    NULL,
+    &ios_stream_put_frame,
+    &ios_stream_stop,
+    &ios_stream_destroy
+};
+
+
+/****************************************************************************
+ * Factory operations
+ */
+/*
+ * Init ios_ video driver.
+ */
+pjmedia_vid_dev_factory* pjmedia_ios_factory(pj_pool_factory *pf)
+{
+    struct ios_factory *f;
+    pj_pool_t *pool;
+
+    pool = pj_pool_create(pf, "ios video", 512, 512, NULL);
+    f = PJ_POOL_ZALLOC_T(pool, struct ios_factory);
+    f->pf = pf;
+    f->pool = pool;
+    f->base.op = &factory_op;
+
+    return &f->base;
+}
+
+
+/* API: init factory */
+static pj_status_t ios_factory_init(pjmedia_vid_dev_factory *f)
+{
+    struct ios_factory *qf = (struct ios_factory*)f;
+    struct ios_dev_info *qdi;
+    unsigned i, l;
+    
+    /* Initialize input and output devices here */
+    qf->dev_info = (struct ios_dev_info*)
+		   pj_pool_calloc(qf->pool, 2,
+				  sizeof(struct ios_dev_info));
+    
+    qf->dev_count = 0;
+    qdi = &qf->dev_info[qf->dev_count++];
+    pj_bzero(qdi, sizeof(*qdi));
+    strcpy(qdi->info.name, "iOS UIView");
+    strcpy(qdi->info.driver, "iOS");	    
+    qdi->info.dir = PJMEDIA_DIR_RENDER;
+    qdi->info.has_callback = PJ_FALSE;
+    qdi->info.caps = PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW;
+    
+    if (NSClassFromString(@"AVCaptureSession")) {
+	qdi = &qf->dev_info[qf->dev_count++];
+	pj_bzero(qdi, sizeof(*qdi));
+	strcpy(qdi->info.name, "iOS AVCapture");
+	strcpy(qdi->info.driver, "iOS");	    
+	qdi->info.dir = PJMEDIA_DIR_CAPTURE;
+	qdi->info.has_callback = PJ_TRUE;
+    }
+
+    for (i = 0; i < qf->dev_count; i++) {
+	qdi = &qf->dev_info[i];
+	qdi->info.fmt_cnt = PJ_ARRAY_SIZE(ios_fmts);	    
+	qdi->info.caps |= PJMEDIA_VID_DEV_CAP_FORMAT;
+	
+	for (l = 0; l < PJ_ARRAY_SIZE(ios_fmts); l++) {
+	    pjmedia_format *fmt = &qdi->info.fmt[l];
+	    pjmedia_format_init_video(fmt,
+				      ios_fmts[l].pjmedia_format,
+				      DEFAULT_WIDTH,
+				      DEFAULT_HEIGHT,
+				      DEFAULT_FPS, 1);	
+	}
+    }
+    
+    PJ_LOG(4, (THIS_FILE, "iOS video initialized with %d devices",
+	       qf->dev_count));
+    
+    return PJ_SUCCESS;
+}
+
+/* API: destroy factory */
+static pj_status_t ios_factory_destroy(pjmedia_vid_dev_factory *f)
+{
+    struct ios_factory *qf = (struct ios_factory*)f;
+    pj_pool_t *pool = qf->pool;
+
+    qf->pool = NULL;
+    pj_pool_release(pool);
+
+    return PJ_SUCCESS;
+}
+
+/* API: refresh the list of devices */
+static pj_status_t ios_factory_refresh(pjmedia_vid_dev_factory *f)
+{
+    PJ_UNUSED_ARG(f);
+    return PJ_SUCCESS;
+}
+
+/* API: get number of devices */
+static unsigned ios_factory_get_dev_count(pjmedia_vid_dev_factory *f)
+{
+    struct ios_factory *qf = (struct ios_factory*)f;
+    return qf->dev_count;
+}
+
+/* API: get device info */
+static pj_status_t ios_factory_get_dev_info(pjmedia_vid_dev_factory *f,
+					    unsigned index,
+					    pjmedia_vid_dev_info *info)
+{
+    struct ios_factory *qf = (struct ios_factory*)f;
+
+    PJ_ASSERT_RETURN(index < qf->dev_count, PJMEDIA_EVID_INVDEV);
+
+    pj_memcpy(info, &qf->dev_info[index].info, sizeof(*info));
+
+    return PJ_SUCCESS;
+}
+
+/* API: create default device parameter */
+static pj_status_t ios_factory_default_param(pj_pool_t *pool,
+					     pjmedia_vid_dev_factory *f,
+					     unsigned index,
+					     pjmedia_vid_dev_param *param)
+{
+    struct ios_factory *qf = (struct ios_factory*)f;
+    struct ios_dev_info *di = &qf->dev_info[index];
+
+    PJ_ASSERT_RETURN(index < qf->dev_count, PJMEDIA_EVID_INVDEV);
+
+    PJ_UNUSED_ARG(pool);
+
+    pj_bzero(param, sizeof(*param));
+    if (di->info.dir & PJMEDIA_DIR_CAPTURE) {
+	param->dir = PJMEDIA_DIR_CAPTURE;
+	param->cap_id = index;
+	param->rend_id = PJMEDIA_VID_INVALID_DEV;
+    } else if (di->info.dir & PJMEDIA_DIR_RENDER) {
+	param->dir = PJMEDIA_DIR_RENDER;
+	param->rend_id = index;
+	param->cap_id = PJMEDIA_VID_INVALID_DEV;
+    } else {
+	return PJMEDIA_EVID_INVDEV;
+    }
+    
+    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;
+}
+
+@implementation VOutDelegate
+- (void)update_image
+{    
+    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+    
+    /* Create a device-dependent RGB color space */
+    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 
+    
+    /* Create a bitmap graphics context with the sample buffer data */
+    CGContextRef context = 
+	CGBitmapContextCreate(stream->buf, stream->size.w, stream->size.h, 8,
+			      stream->bytes_per_row, colorSpace,
+			      kCGBitmapByteOrder32Little |
+			      kCGImageAlphaPremultipliedFirst);
+    
+    /**
+     * Create a Quartz image from the pixel data in the bitmap graphics
+     * context
+     */
+    CGImageRef quartzImage = CGBitmapContextCreateImage(context); 
+    
+    /* Free up the context and color space */
+    CGContextRelease(context); 
+    CGColorSpaceRelease(colorSpace);
+    
+    /* Create an image object from the Quartz image */
+    UIImage *image = [UIImage imageWithCGImage:quartzImage scale:1.0 
+			      orientation:UIImageOrientationRight];
+    
+    /* Release the Quartz image */
+    CGImageRelease(quartzImage);
+    
+    dispatch_async(dispatch_get_main_queue(),
+                   ^{[stream->imgView setImage:image];});
+    /*
+    [stream->imgView performSelectorOnMainThread:@selector(setImage:)
+		     withObject:image waitUntilDone:NO];
+     */
+    
+    [pool release];
+}    
+
+- (void)captureOutput:(AVCaptureOutput *)captureOutput 
+		      didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
+		      fromConnection:(AVCaptureConnection *)connection
+{
+    pjmedia_frame frame;
+    CVImageBufferRef imageBuffer;
+
+    if (!sampleBuffer)
+	return;
+    
+    /* Get a CMSampleBuffer's Core Video image buffer for the media data */
+    imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); 
+    
+    /* Lock the base address of the pixel buffer */
+    CVPixelBufferLockBaseAddress(imageBuffer, 0); 
+    
+    frame.type = PJMEDIA_FRAME_TYPE_VIDEO;
+    frame.buf = CVPixelBufferGetBaseAddress(imageBuffer);
+    frame.size = stream->frame_size;
+    frame.bit_info = 0;
+    frame.timestamp.u64 = stream->frame_ts.u64;
+    
+    if (stream->vid_cb.capture_cb)
+        (*stream->vid_cb.capture_cb)(&stream->base, stream->user_data, &frame);
+
+    stream->frame_ts.u64 += stream->ts_inc;
+    
+    /* Unlock the pixel buffer */
+    CVPixelBufferUnlockBaseAddress(imageBuffer,0);
+}
+@end
+
+static ios_fmt_info* get_ios_format_info(pjmedia_format_id id)
+{
+    unsigned i;
+    
+    for (i = 0; i < PJ_ARRAY_SIZE(ios_fmts); i++) {
+        if (ios_fmts[i].pjmedia_format == id)
+            return &ios_fmts[i];
+    }
+    
+    return NULL;
+}
+
+/* API: create stream */
+static pj_status_t ios_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)
+{
+    struct ios_factory *qf = (struct ios_factory*)f;
+    pj_pool_t *pool;
+    struct ios_stream *strm;
+    const pjmedia_video_format_detail *vfd;
+    const pjmedia_video_format_info *vfi;
+    pj_status_t status = PJ_SUCCESS;
+    ios_fmt_info *ifi = get_ios_format_info(param->fmt.id);
+    NSError *error;
+
+    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 ||
+                     param->dir == PJMEDIA_DIR_RENDER),
+		     PJ_EINVAL);
+
+    if (!(ifi = get_ios_format_info(param->fmt.id)))
+        return PJMEDIA_EVID_BADFORMAT;
+    
+    vfi = pjmedia_get_video_format_info(NULL, param->fmt.id);
+    if (!vfi)
+        return PJMEDIA_EVID_BADFORMAT;
+
+    /* Create and Initialize stream descriptor */
+    pool = pj_pool_create(qf->pf, "ios-dev", 4000, 4000, NULL);
+    PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);
+
+    strm = PJ_POOL_ZALLOC_T(pool, struct ios_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);
+    pj_memcpy(&strm->size, &vfd->size, sizeof(vfd->size));
+    strm->bpp = vfi->bpp;
+    strm->bytes_per_row = strm->size.w * strm->bpp / 8;
+    strm->frame_size = strm->bytes_per_row * strm->size.h;
+    strm->ts_inc = PJMEDIA_SPF2(param->clock_rate, &vfd->fps, 1);
+
+    if (param->dir & PJMEDIA_DIR_CAPTURE) {
+        /* Create capture stream here */
+	strm->cap_session = [[AVCaptureSession alloc] init];
+	if (!strm->cap_session) {
+	    status = PJ_ENOMEM;
+	    goto on_error;
+	}
+	strm->cap_session.sessionPreset = AVCaptureSessionPresetMedium;
+	
+	/* Open video device */
+	AVCaptureDevice *videoDevice = 
+	    [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
+	if (!videoDevice) {
+	    status = PJMEDIA_EVID_SYSERR;
+	    goto on_error;
+	}
+	
+	/* Add the video device to the session as a device input */	
+	strm->dev_input = [AVCaptureDeviceInput 
+			   deviceInputWithDevice:videoDevice
+			   error: &error];
+	if (!strm->dev_input) {
+	    status = PJMEDIA_EVID_SYSERR;
+	    goto on_error;
+	}
+	[strm->cap_session addInput:strm->dev_input];
+	
+	strm->video_output = [[[AVCaptureVideoDataOutput alloc] init]
+			      autorelease];
+	if (!strm->video_output) {
+	    status = PJMEDIA_EVID_SYSERR;
+	    goto on_error;
+	}
+	[strm->cap_session addOutput:strm->video_output];
+	
+	/* Configure the video output */
+	strm->vout_delegate = [VOutDelegate alloc];
+	strm->vout_delegate->stream = strm;
+	dispatch_queue_t queue = dispatch_queue_create("myQueue", NULL);
+	[strm->video_output setSampleBufferDelegate:strm->vout_delegate
+			    queue:queue];
+	dispatch_release(queue);	
+	
+	strm->video_output.videoSettings =
+	    [NSDictionary dictionaryWithObjectsAndKeys:
+			  [NSNumber numberWithInt:ifi->ios_format],
+			  kCVPixelBufferPixelFormatTypeKey,
+			  [NSNumber numberWithInt: vfd->size.w],
+			  kCVPixelBufferWidthKey,
+			  [NSNumber numberWithInt: vfd->size.h],
+			  kCVPixelBufferHeightKey, nil];
+	strm->video_output.minFrameDuration = CMTimeMake(vfd->fps.denum,
+							 vfd->fps.num);	
+    } else if (param->dir & PJMEDIA_DIR_RENDER) {
+        /* Create renderer stream here */
+	/* Get the main window */
+	UIWindow *window = [[UIApplication sharedApplication] keyWindow];
+	
+	if (param->flags & PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW &&
+            param->window.info.ios.window)
+	    window = (UIWindow *)param->window.info.ios.window;
+	
+	pj_assert(window);
+	strm->imgView = [[UIImageView alloc] initWithFrame:[window bounds]];
+	if (!strm->imgView) {
+	    status = PJ_ENOMEM;
+	    goto on_error;
+	}
+	[window addSubview:strm->imgView];
+	
+	if (!strm->vout_delegate) {
+	    strm->vout_delegate = [VOutDelegate alloc];
+	    strm->vout_delegate->stream = strm;
+	}
+        
+        strm->render_queue = dispatch_queue_create("com.pjsip.render_queue",
+                                                   NULL);
+        if (!strm->render_queue)
+            goto on_error;
+	
+	strm->buf = pj_pool_alloc(pool, strm->frame_size);
+    }    
+    
+    /* Apply the remaining settings */
+    /*    
+     if (param->flags & PJMEDIA_VID_DEV_CAP_INPUT_SCALE) {
+	ios_stream_set_cap(&strm->base,
+			  PJMEDIA_VID_DEV_CAP_INPUT_SCALE,
+			  &param->fmt);
+     }
+     */
+    /* Done */
+    strm->base.op = &stream_op;
+    *p_vid_strm = &strm->base;
+    
+    return PJ_SUCCESS;
+    
+on_error:
+    ios_stream_destroy((pjmedia_vid_dev_stream *)strm);
+    
+    return status;
+}
+
+/* API: Get stream info. */
+static pj_status_t ios_stream_get_param(pjmedia_vid_dev_stream *s,
+				        pjmedia_vid_dev_param *pi)
+{
+    struct ios_stream *strm = (struct ios_stream*)s;
+
+    PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
+
+    pj_memcpy(pi, &strm->param, sizeof(*pi));
+
+/*    if (ios_stream_get_cap(s, PJMEDIA_VID_DEV_CAP_INPUT_SCALE,
+                            &pi->fmt.info_size) == PJ_SUCCESS)
+    {
+        pi->flags |= PJMEDIA_VID_DEV_CAP_INPUT_SCALE;
+    }
+*/
+    return PJ_SUCCESS;
+}
+
+/* API: get capability */
+static pj_status_t ios_stream_get_cap(pjmedia_vid_dev_stream *s,
+				      pjmedia_vid_dev_cap cap,
+				      void *pval)
+{
+    struct ios_stream *strm = (struct ios_stream*)s;
+
+    PJ_UNUSED_ARG(strm);
+
+    PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+    if (cap==PJMEDIA_VID_DEV_CAP_INPUT_SCALE)
+    {
+        return PJMEDIA_EVID_INVCAP;
+//	return PJ_SUCCESS;
+    } else {
+	return PJMEDIA_EVID_INVCAP;
+    }
+}
+
+/* API: set capability */
+static pj_status_t ios_stream_set_cap(pjmedia_vid_dev_stream *s,
+				      pjmedia_vid_dev_cap cap,
+				      const void *pval)
+{
+    struct ios_stream *strm = (struct ios_stream*)s;
+
+    PJ_UNUSED_ARG(strm);
+
+    PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+    if (cap==PJMEDIA_VID_DEV_CAP_INPUT_SCALE)
+    {
+	return PJ_SUCCESS;
+    }
+
+    return PJMEDIA_EVID_INVCAP;
+}
+
+/* API: Start stream. */
+static pj_status_t ios_stream_start(pjmedia_vid_dev_stream *strm)
+{
+    struct ios_stream *stream = (struct ios_stream*)strm;
+
+    PJ_UNUSED_ARG(stream);
+
+    PJ_LOG(4, (THIS_FILE, "Starting ios video stream"));
+
+    if (stream->cap_session) {
+	[stream->cap_session startRunning];
+    
+	if (![stream->cap_session isRunning])
+	    return PJ_EUNKNOWN;
+    }
+    
+    return PJ_SUCCESS;
+}
+
+
+/* API: Put frame from stream */
+static pj_status_t ios_stream_put_frame(pjmedia_vid_dev_stream *strm,
+					const pjmedia_frame *frame)
+{
+    struct ios_stream *stream = (struct ios_stream*)strm;
+    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+    
+    pj_assert(stream->frame_size >= frame->size);
+    pj_memcpy(stream->buf, frame->buf, frame->size);
+    /* Perform video display in a background thread */
+/*   
+    [stream->vout_delegate update_image];
+    [NSThread detachNewThreadSelector:@selector(update_image)
+	      toTarget:stream->vout_delegate withObject:nil];
+*/
+    dispatch_async(stream->render_queue,
+                   ^{[stream->vout_delegate update_image];});
+    
+    [pool release];
+    
+    return PJ_SUCCESS;
+}
+
+/* API: Stop stream. */
+static pj_status_t ios_stream_stop(pjmedia_vid_dev_stream *strm)
+{
+    struct ios_stream *stream = (struct ios_stream*)strm;
+
+    PJ_UNUSED_ARG(stream);
+
+    PJ_LOG(4, (THIS_FILE, "Stopping ios video stream"));
+
+    if (stream->cap_session && [stream->cap_session isRunning])
+	[stream->cap_session stopRunning];
+    
+    return PJ_SUCCESS;
+}
+
+
+/* API: Destroy stream. */
+static pj_status_t ios_stream_destroy(pjmedia_vid_dev_stream *strm)
+{
+    struct ios_stream *stream = (struct ios_stream*)strm;
+
+    PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
+
+    ios_stream_stop(strm);
+    
+    if (stream->imgView) {
+	[stream->imgView removeFromSuperview];
+	[stream->imgView release];
+	stream->imgView = NULL;
+    }
+
+    if (stream->cap_session) {
+	[stream->cap_session release];
+	stream->cap_session = NULL;
+    }    
+/*    if (stream->dev_input) {
+	[stream->dev_input release];
+	stream->dev_input = NULL;
+    }
+*/ 
+    if (stream->vout_delegate) {
+	[stream->vout_delegate release];
+	stream->vout_delegate = NULL;
+    }
+/*    if (stream->video_output) {
+	[stream->video_output release];
+	stream->video_output = NULL;
+    }
+*/
+    if (stream->render_queue) {
+        dispatch_release(stream->render_queue);
+        stream->render_queue = NULL;
+    }
+
+    pj_pool_release(stream->pool);
+
+    return PJ_SUCCESS;
+}
+
+#endif
+#endif	/* PJMEDIA_VIDEO_DEV_HAS_IOS */
diff --git a/jni/pjproject-android/pjmedia/src/pjmedia-videodev/qt_dev.m b/jni/pjproject-android/pjmedia/src/pjmedia-videodev/qt_dev.m
new file mode 100644
index 0000000..10dfe2e
--- /dev/null
+++ b/jni/pjproject-android/pjmedia/src/pjmedia-videodev/qt_dev.m
@@ -0,0 +1,697 @@
+/* $Id: qt_dev.m 3979 2012-03-20 08:55:33Z ming $ */
+/*
+ * Copyright (C) 2008-2011 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 PJMEDIA_VIDEO_DEV_HAS_QT
+
+#include <Foundation/NSAutoreleasePool.h>
+#include <QTKit/QTKit.h>
+
+#define THIS_FILE		"qt_dev.c"
+#define DEFAULT_CLOCK_RATE	90000
+#define DEFAULT_WIDTH		640
+#define DEFAULT_HEIGHT		480
+#define DEFAULT_FPS		15
+
+#define kCVPixelFormatType_422YpCbCr8_yuvs 'yuvs'
+
+typedef struct qt_fmt_info
+{
+    pjmedia_format_id   pjmedia_format;
+    unsigned		qt_format;
+} qt_fmt_info;
+
+static qt_fmt_info qt_fmts[] =
+{
+    {PJMEDIA_FORMAT_YUY2, kCVPixelFormatType_422YpCbCr8_yuvs},
+    {PJMEDIA_FORMAT_UYVY, kCVPixelFormatType_422YpCbCr8},
+};
+
+/* qt device info */
+struct qt_dev_info
+{
+    pjmedia_vid_dev_info	 info;
+    char			 dev_id[192];
+};
+
+/* qt factory */
+struct qt_factory
+{
+    pjmedia_vid_dev_factory	 base;
+    pj_pool_t			*pool;
+    pj_pool_t			*dev_pool;
+    pj_pool_factory		*pf;
+
+    unsigned			 dev_count;
+    struct qt_dev_info		*dev_info;
+};
+
+struct qt_stream;
+typedef void (*func_ptr)(struct qt_stream *strm);
+
+@interface QTDelegate: NSObject
+{
+@public
+    struct qt_stream *strm;
+    func_ptr          func;
+}
+
+- (void)run_func;
+@end
+
+/* Video stream. */
+struct qt_stream
+{
+    pjmedia_vid_dev_stream  base;	    /**< Base stream	       */
+    pjmedia_vid_dev_param   param;	    /**< Settings	       */
+    pj_pool_t		   *pool;           /**< Memory pool.          */
+
+    pj_timestamp	    cap_frame_ts;   /**< Captured frame tstamp */
+    unsigned		    cap_ts_inc;	    /**< Increment	       */
+    
+    pjmedia_vid_dev_cb	    vid_cb;         /**< Stream callback.      */
+    void		   *user_data;      /**< Application data.     */
+
+    pj_bool_t		    cap_thread_exited;
+    pj_bool_t		    cap_thread_initialized;
+    pj_thread_desc	    cap_thread_desc;
+    pj_thread_t		   *cap_thread;
+    
+    struct qt_factory      *qf;
+    pj_status_t             status;
+    pj_bool_t               is_running;
+    pj_bool_t               cap_exited;
+    
+    QTCaptureSession			*cap_session;
+    QTCaptureDeviceInput		*dev_input;
+    QTCaptureDecompressedVideoOutput	*video_output;
+    QTDelegate                          *qt_delegate;
+};
+
+
+/* Prototypes */
+static pj_status_t qt_factory_init(pjmedia_vid_dev_factory *f);
+static pj_status_t qt_factory_destroy(pjmedia_vid_dev_factory *f);
+static pj_status_t qt_factory_refresh(pjmedia_vid_dev_factory *f);
+static unsigned    qt_factory_get_dev_count(pjmedia_vid_dev_factory *f);
+static pj_status_t qt_factory_get_dev_info(pjmedia_vid_dev_factory *f,
+					   unsigned index,
+					   pjmedia_vid_dev_info *info);
+static pj_status_t qt_factory_default_param(pj_pool_t *pool,
+					    pjmedia_vid_dev_factory *f,
+					    unsigned index,
+					    pjmedia_vid_dev_param *param);
+static pj_status_t qt_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 qt_stream_get_param(pjmedia_vid_dev_stream *strm,
+				       pjmedia_vid_dev_param *param);
+static pj_status_t qt_stream_get_cap(pjmedia_vid_dev_stream *strm,
+				     pjmedia_vid_dev_cap cap,
+				     void *value);
+static pj_status_t qt_stream_set_cap(pjmedia_vid_dev_stream *strm,
+				     pjmedia_vid_dev_cap cap,
+				     const void *value);
+static pj_status_t qt_stream_start(pjmedia_vid_dev_stream *strm);
+static pj_status_t qt_stream_stop(pjmedia_vid_dev_stream *strm);
+static pj_status_t qt_stream_destroy(pjmedia_vid_dev_stream *strm);
+
+/* Operations */
+static pjmedia_vid_dev_factory_op factory_op =
+{
+    &qt_factory_init,
+    &qt_factory_destroy,
+    &qt_factory_get_dev_count,
+    &qt_factory_get_dev_info,
+    &qt_factory_default_param,
+    &qt_factory_create_stream,
+    &qt_factory_refresh
+};
+
+static pjmedia_vid_dev_stream_op stream_op =
+{
+    &qt_stream_get_param,
+    &qt_stream_get_cap,
+    &qt_stream_set_cap,
+    &qt_stream_start,
+    NULL,
+    NULL,
+    &qt_stream_stop,
+    &qt_stream_destroy
+};
+
+
+/****************************************************************************
+ * Factory operations
+ */
+/*
+ * Init qt_ video driver.
+ */
+pjmedia_vid_dev_factory* pjmedia_qt_factory(pj_pool_factory *pf)
+{
+    struct qt_factory *f;
+    pj_pool_t *pool;
+
+    pool = pj_pool_create(pf, "qt video", 4000, 4000, NULL);
+    f = PJ_POOL_ZALLOC_T(pool, struct qt_factory);
+    f->pf = pf;
+    f->pool = pool;
+    f->base.op = &factory_op;
+
+    return &f->base;
+}
+
+
+/* API: init factory */
+static pj_status_t qt_factory_init(pjmedia_vid_dev_factory *f)
+{
+    return qt_factory_refresh(f);
+}
+
+/* API: destroy factory */
+static pj_status_t qt_factory_destroy(pjmedia_vid_dev_factory *f)
+{
+    struct qt_factory *qf = (struct qt_factory*)f;
+    pj_pool_t *pool = qf->pool;
+
+    if (qf->dev_pool)
+        pj_pool_release(qf->dev_pool);
+    qf->pool = NULL;
+    if (pool)
+        pj_pool_release(pool);
+
+    return PJ_SUCCESS;
+}
+
+/* API: refresh the list of devices */
+static pj_status_t qt_factory_refresh(pjmedia_vid_dev_factory *f)
+{
+    struct qt_factory *qf = (struct qt_factory*)f;
+    struct qt_dev_info *qdi;
+    unsigned i, dev_count = 0;
+    NSAutoreleasePool *apool = [[NSAutoreleasePool alloc]init];
+    NSArray *dev_array;
+    
+    if (qf->dev_pool) {
+        pj_pool_release(qf->dev_pool);
+        qf->dev_pool = NULL;
+    }
+    
+    dev_array = [QTCaptureDevice inputDevices];
+    for (i = 0; i < [dev_array count]; i++) {
+	QTCaptureDevice *dev = [dev_array objectAtIndex:i];
+	if ([dev hasMediaType:QTMediaTypeVideo] ||
+	    [dev hasMediaType:QTMediaTypeMuxed])
+	{
+	    dev_count++;
+	}
+    }
+    
+    /* Initialize input and output devices here */
+    qf->dev_count = 0;
+    qf->dev_pool = pj_pool_create(qf->pf, "qt video", 500, 500, NULL);
+    
+    qf->dev_info = (struct qt_dev_info*)
+    pj_pool_calloc(qf->dev_pool, dev_count,
+                   sizeof(struct qt_dev_info));
+    for (i = 0; i < [dev_array count]; i++) {
+	QTCaptureDevice *dev = [dev_array objectAtIndex:i];
+	if ([dev hasMediaType:QTMediaTypeVideo] ||
+	    [dev hasMediaType:QTMediaTypeMuxed])
+	{
+	    unsigned k;
+	    
+	    qdi = &qf->dev_info[qf->dev_count++];
+	    pj_bzero(qdi, sizeof(*qdi));
+	    [[dev localizedDisplayName] getCString:qdi->info.name
+                                        maxLength:sizeof(qdi->info.name)
+                                        encoding:
+                                        [NSString defaultCStringEncoding]];
+	    [[dev uniqueID] getCString:qdi->dev_id
+                            maxLength:sizeof(qdi->dev_id)
+                            encoding:[NSString defaultCStringEncoding]];
+	    strcpy(qdi->info.driver, "QT");	    
+	    qdi->info.dir = PJMEDIA_DIR_CAPTURE;
+	    qdi->info.has_callback = PJ_TRUE;
+            
+	    qdi->info.fmt_cnt = 0;
+	    qdi->info.caps = PJMEDIA_VID_DEV_CAP_FORMAT;
+	    for (k = 0; k < [[dev formatDescriptions] count]; k++) {
+		unsigned l;
+		QTFormatDescription *desc = [[dev formatDescriptions]
+					     objectAtIndex:k];
+		for (l = 0; l < PJ_ARRAY_SIZE(qt_fmts); l++) {
+		    if ([desc formatType] == qt_fmts[l].qt_format) {
+			pjmedia_format *fmt = 
+                            &qdi->info.fmt[qdi->info.fmt_cnt++];
+			pjmedia_format_init_video(fmt,
+						  qt_fmts[l].pjmedia_format,
+						  DEFAULT_WIDTH,
+						  DEFAULT_HEIGHT,
+						  DEFAULT_FPS, 1);
+			break;
+		    }
+		}
+	    }
+            
+	    PJ_LOG(4, (THIS_FILE, " dev_id %d: %s", i, qdi->info.name));    
+	}
+    }
+    
+    [apool release];
+    
+    PJ_LOG(4, (THIS_FILE, "qt video has %d devices",
+	       qf->dev_count));
+    
+    return PJ_SUCCESS;
+}
+
+/* API: get number of devices */
+static unsigned qt_factory_get_dev_count(pjmedia_vid_dev_factory *f)
+{
+    struct qt_factory *qf = (struct qt_factory*)f;
+    return qf->dev_count;
+}
+
+/* API: get device info */
+static pj_status_t qt_factory_get_dev_info(pjmedia_vid_dev_factory *f,
+					   unsigned index,
+					   pjmedia_vid_dev_info *info)
+{
+    struct qt_factory *qf = (struct qt_factory*)f;
+
+    PJ_ASSERT_RETURN(index < qf->dev_count, PJMEDIA_EVID_INVDEV);
+
+    pj_memcpy(info, &qf->dev_info[index].info, sizeof(*info));
+
+    return PJ_SUCCESS;
+}
+
+/* API: create default device parameter */
+static pj_status_t qt_factory_default_param(pj_pool_t *pool,
+					    pjmedia_vid_dev_factory *f,
+					    unsigned index,
+					    pjmedia_vid_dev_param *param)
+{
+    struct qt_factory *qf = (struct qt_factory*)f;
+    struct qt_dev_info *di = &qf->dev_info[index];
+
+    PJ_ASSERT_RETURN(index < qf->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;
+}
+
+static qt_fmt_info* get_qt_format_info(pjmedia_format_id id)
+{
+    unsigned i;
+    
+    for (i = 0; i < PJ_ARRAY_SIZE(qt_fmts); i++) {
+        if (qt_fmts[i].pjmedia_format == id)
+            return &qt_fmts[i];
+    }
+    
+    return NULL;
+}
+
+@implementation QTDelegate
+- (void)captureOutput:(QTCaptureOutput *)captureOutput
+		      didOutputVideoFrame:(CVImageBufferRef)videoFrame
+		      withSampleBuffer:(QTSampleBuffer *)sampleBuffer
+		      fromConnection:(QTCaptureConnection *)connection
+{
+    unsigned size = [sampleBuffer lengthForAllSamples];
+    pjmedia_frame frame;
+
+    if (!strm->is_running) {
+        strm->cap_exited = PJ_TRUE;
+        return;
+    }
+    
+    if (strm->cap_thread_initialized == 0 || !pj_thread_is_registered())
+    {
+	pj_thread_register("qt_cap", strm->cap_thread_desc,
+			   &strm->cap_thread);
+	strm->cap_thread_initialized = 1;
+	PJ_LOG(5,(THIS_FILE, "Capture thread started"));
+    }
+    
+    if (!videoFrame)
+	return;
+    
+    frame.type = PJMEDIA_FRAME_TYPE_VIDEO;
+    frame.buf = [sampleBuffer bytesForAllSamples];
+    frame.size = size;
+    frame.bit_info = 0;
+    frame.timestamp.u64 = strm->cap_frame_ts.u64;
+    
+    if (strm->vid_cb.capture_cb)
+        (*strm->vid_cb.capture_cb)(&strm->base, strm->user_data, &frame);
+    
+    strm->cap_frame_ts.u64 += strm->cap_ts_inc;
+}
+
+- (void)run_func
+{
+    (*func)(strm);
+}
+
+@end
+
+static void init_qt(struct qt_stream *strm)
+{
+    const pjmedia_video_format_detail *vfd;
+    qt_fmt_info *qfi = get_qt_format_info(strm->param.fmt.id);
+    BOOL success = NO;
+    NSError *error;
+    
+    if (!qfi) {
+        strm->status = PJMEDIA_EVID_BADFORMAT;
+        return;
+    }
+    
+    strm->cap_session = [[QTCaptureSession alloc] init];
+    if (!strm->cap_session) {
+        strm->status = PJ_ENOMEM;
+        return;
+    }
+    
+    /* Open video device */
+    QTCaptureDevice *videoDevice = 
+        [QTCaptureDevice deviceWithUniqueID:
+                         [NSString stringWithCString:
+                                   strm->qf->dev_info[strm->param.cap_id].dev_id
+                                   encoding:
+                                   [NSString defaultCStringEncoding]]];
+    if (!videoDevice || ![videoDevice open:&error]) {
+        strm->status = PJMEDIA_EVID_SYSERR;
+        return;
+    }
+    
+    /* Add the video device to the session as a device input */	
+    strm->dev_input = [[QTCaptureDeviceInput alloc] 
+                       initWithDevice:videoDevice];
+    success = [strm->cap_session addInput:strm->dev_input error:&error];
+    if (!success) {
+        strm->status = PJMEDIA_EVID_SYSERR;
+        return;
+    }
+    
+    strm->video_output = [[QTCaptureDecompressedVideoOutput alloc] init];
+    success = [strm->cap_session addOutput:strm->video_output
+                                 error:&error];
+    if (!success) {
+        strm->status = PJMEDIA_EVID_SYSERR;
+        return;
+    }
+    
+    vfd = pjmedia_format_get_video_format_detail(&strm->param.fmt,
+                                                 PJ_TRUE);
+    [strm->video_output setPixelBufferAttributes:
+                        [NSDictionary dictionaryWithObjectsAndKeys:
+                                      [NSNumber numberWithInt:qfi->qt_format],
+                                      kCVPixelBufferPixelFormatTypeKey,
+                                      [NSNumber numberWithInt:vfd->size.w],
+                                      kCVPixelBufferWidthKey,
+                                      [NSNumber numberWithInt:vfd->size.h],
+                                      kCVPixelBufferHeightKey, nil]];
+    
+    pj_assert(vfd->fps.num);
+    strm->cap_ts_inc = PJMEDIA_SPF2(strm->param.clock_rate, &vfd->fps, 1);
+    
+    if ([strm->video_output
+         respondsToSelector:@selector(setMinimumVideoFrameInterval)])
+    {
+        [strm->video_output setMinimumVideoFrameInterval:
+                            (1.0f * vfd->fps.denum / (double)vfd->fps.num)];
+    }
+    
+    strm->qt_delegate = [[QTDelegate alloc]init];
+    strm->qt_delegate->strm = strm;
+    [strm->video_output setDelegate:strm->qt_delegate];
+}    
+
+static void run_func_on_main_thread(struct qt_stream *strm, func_ptr func)
+{
+    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+    QTDelegate *delg = [[QTDelegate alloc] init];
+    
+    delg->strm = strm;
+    delg->func = func;
+    [delg performSelectorOnMainThread:@selector(run_func)
+                           withObject:nil waitUntilDone:YES];
+    
+    CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false);
+
+    [delg release];
+    [pool release];    
+}
+
+/* API: create stream */
+static pj_status_t qt_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)
+{
+    struct qt_factory *qf = (struct qt_factory*)f;
+    pj_pool_t *pool;
+    struct qt_stream *strm;
+    const pjmedia_video_format_info *vfi;
+    pj_status_t status = PJ_SUCCESS;
+
+    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);
+
+    vfi = pjmedia_get_video_format_info(NULL, param->fmt.id);
+    if (!vfi)
+        return PJMEDIA_EVID_BADFORMAT;
+
+    /* Create and Initialize stream descriptor */
+    pool = pj_pool_create(qf->pf, "qt-dev", 4000, 4000, NULL);
+    PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);
+
+    strm = PJ_POOL_ZALLOC_T(pool, struct qt_stream);
+    pj_memcpy(&strm->param, param, sizeof(*param));
+    strm->pool = pool;
+    pj_memcpy(&strm->vid_cb, cb, sizeof(*cb));
+    strm->user_data = user_data;
+    strm->qf = qf;
+
+    /* Create capture stream here */
+    if (param->dir & PJMEDIA_DIR_CAPTURE) {        
+        strm->status = PJ_SUCCESS;
+        run_func_on_main_thread(strm, init_qt);
+        if ((status = strm->status) != PJ_SUCCESS)
+            goto on_error;
+    }
+    
+    /* Apply the remaining settings */
+    /*    
+     if (param->flags & PJMEDIA_VID_DEV_CAP_INPUT_SCALE) {
+	qt_stream_set_cap(&strm->base,
+			  PJMEDIA_VID_DEV_CAP_INPUT_SCALE,
+			  &param->fmt);
+     }
+     */
+    /* Done */
+    strm->base.op = &stream_op;
+    *p_vid_strm = &strm->base;
+    
+    return PJ_SUCCESS;
+    
+on_error:
+    qt_stream_destroy((pjmedia_vid_dev_stream *)strm);
+    
+    return status;
+}
+
+/* API: Get stream info. */
+static pj_status_t qt_stream_get_param(pjmedia_vid_dev_stream *s,
+				       pjmedia_vid_dev_param *pi)
+{
+    struct qt_stream *strm = (struct qt_stream*)s;
+
+    PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
+
+    pj_memcpy(pi, &strm->param, sizeof(*pi));
+
+/*    if (qt_stream_get_cap(s, PJMEDIA_VID_DEV_CAP_INPUT_SCALE,
+                            &pi->fmt.info_size) == PJ_SUCCESS)
+    {
+        pi->flags |= PJMEDIA_VID_DEV_CAP_INPUT_SCALE;
+    }
+*/
+    return PJ_SUCCESS;
+}
+
+/* API: get capability */
+static pj_status_t qt_stream_get_cap(pjmedia_vid_dev_stream *s,
+				     pjmedia_vid_dev_cap cap,
+				     void *pval)
+{
+    struct qt_stream *strm = (struct qt_stream*)s;
+
+    PJ_UNUSED_ARG(strm);
+
+    PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+    if (cap==PJMEDIA_VID_DEV_CAP_INPUT_SCALE)
+    {
+        return PJMEDIA_EVID_INVCAP;
+//	return PJ_SUCCESS;
+    } else {
+	return PJMEDIA_EVID_INVCAP;
+    }
+}
+
+/* API: set capability */
+static pj_status_t qt_stream_set_cap(pjmedia_vid_dev_stream *s,
+				     pjmedia_vid_dev_cap cap,
+				     const void *pval)
+{
+    struct qt_stream *strm = (struct qt_stream*)s;
+
+    PJ_UNUSED_ARG(strm);
+
+    PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+    if (cap==PJMEDIA_VID_DEV_CAP_INPUT_SCALE)
+    {
+	return PJ_SUCCESS;
+    }
+
+    return PJMEDIA_EVID_INVCAP;
+}
+
+static void start_qt(struct qt_stream *strm)
+{
+    [strm->cap_session startRunning];
+}
+
+static void stop_qt(struct qt_stream *strm)
+{
+    [strm->cap_session stopRunning];
+}
+
+/* API: Start stream. */
+static pj_status_t qt_stream_start(pjmedia_vid_dev_stream *strm)
+{
+    struct qt_stream *stream = (struct qt_stream*)strm;
+
+    PJ_UNUSED_ARG(stream);
+
+    PJ_LOG(4, (THIS_FILE, "Starting qt video stream"));
+
+    if (stream->cap_session) {
+        run_func_on_main_thread(stream, start_qt);
+    
+	if (![stream->cap_session isRunning])
+	    return PJMEDIA_EVID_NOTREADY;
+        
+        stream->is_running = PJ_TRUE;
+    }
+
+    return PJ_SUCCESS;
+}
+
+/* API: Stop stream. */
+static pj_status_t qt_stream_stop(pjmedia_vid_dev_stream *strm)
+{
+    struct qt_stream *stream = (struct qt_stream*)strm;
+
+    PJ_UNUSED_ARG(stream);
+
+    PJ_LOG(4, (THIS_FILE, "Stopping qt video stream"));
+
+    if (stream->cap_session && [stream->cap_session isRunning]) {
+        int i;
+        
+        stream->cap_exited = PJ_FALSE;
+        run_func_on_main_thread(stream, stop_qt);
+        
+        stream->is_running = PJ_FALSE;
+        for (i = 50; i >= 0 && !stream->cap_exited; i--) {
+            pj_thread_sleep(10);
+        }
+    }
+    
+    return PJ_SUCCESS;
+}
+
+static void destroy_qt(struct qt_stream *strm)
+{
+    if (strm->dev_input && [[strm->dev_input device] isOpen])
+	[[strm->dev_input device] close];
+    
+    if (strm->cap_session) {
+	[strm->cap_session release];
+	strm->cap_session = NULL;
+    }
+    if (strm->dev_input) {
+	[strm->dev_input release];
+	strm->dev_input = NULL;
+    }
+    if (strm->qt_delegate) {
+	[strm->qt_delegate release];
+	strm->qt_delegate = NULL;
+    }
+    if (strm->video_output) {
+	[strm->video_output release];
+	strm->video_output = NULL;
+    }
+}
+
+/* API: Destroy stream. */
+static pj_status_t qt_stream_destroy(pjmedia_vid_dev_stream *strm)
+{
+    struct qt_stream *stream = (struct qt_stream*)strm;
+
+    PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
+
+    qt_stream_stop(strm);
+
+    run_func_on_main_thread(stream, destroy_qt);
+    
+    pj_pool_release(stream->pool);
+
+    return PJ_SUCCESS;
+}
+
+#endif	/* PJMEDIA_VIDEO_DEV_HAS_QT */
diff --git a/jni/pjproject-android/pjmedia/src/pjmedia-videodev/sdl_dev.c b/jni/pjproject-android/pjmedia/src/pjmedia-videodev/sdl_dev.c
new file mode 100644
index 0000000..d1ad99c
--- /dev/null
+++ b/jni/pjproject-android/pjmedia/src/pjmedia-videodev/sdl_dev.c
@@ -0,0 +1,1453 @@
+/* $Id: sdl_dev.c 4535 2013-06-13 09:36:49Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 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_VIDEO_DEV_HAS_SDL) && PJMEDIA_VIDEO_DEV_HAS_SDL != 0
+#include <SDL.h>
+#include <SDL_syswm.h>
+#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL
+#   include "SDL_opengl.h"
+#   define OPENGL_DEV_IDX 1
+#endif /* PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL */
+
+#if !(SDL_VERSION_ATLEAST(1,3,0))
+#   error "SDL 1.3 or later is required"
+#endif
+
+#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0
+#   include "TargetConditionals.h"
+#   include <Foundation/Foundation.h>
+#endif
+
+#define THIS_FILE		"sdl_dev.c"
+#define DEFAULT_CLOCK_RATE	90000
+#define DEFAULT_WIDTH		640
+#define DEFAULT_HEIGHT		480
+#define DEFAULT_FPS		25
+
+typedef struct sdl_fmt_info
+{
+    pjmedia_format_id   fmt_id;
+    Uint32              sdl_format;
+    Uint32              Rmask;
+    Uint32              Gmask;
+    Uint32              Bmask;
+    Uint32              Amask;
+} sdl_fmt_info;
+
+static sdl_fmt_info sdl_fmts[] =
+{
+#if PJ_IS_BIG_ENDIAN
+    {PJMEDIA_FORMAT_RGBA,  (Uint32)SDL_PIXELFORMAT_RGBA8888,
+     0xFF000000, 0xFF0000, 0xFF00, 0xFF} ,
+    {PJMEDIA_FORMAT_RGB24, (Uint32)SDL_PIXELFORMAT_RGB24,
+     0xFF0000, 0xFF00, 0xFF, 0} ,
+    {PJMEDIA_FORMAT_BGRA,  (Uint32)SDL_PIXELFORMAT_BGRA8888,
+     0xFF00, 0xFF0000, 0xFF000000, 0xFF} ,
+#else /* PJ_IS_BIG_ENDIAN */
+    {PJMEDIA_FORMAT_RGBA,  (Uint32)SDL_PIXELFORMAT_ABGR8888,
+     0xFF, 0xFF00, 0xFF0000, 0xFF000000} ,
+    {PJMEDIA_FORMAT_RGB24, (Uint32)SDL_PIXELFORMAT_BGR24,
+     0xFF, 0xFF00, 0xFF0000, 0} ,
+    {PJMEDIA_FORMAT_BGRA,  (Uint32)SDL_PIXELFORMAT_ARGB8888,
+     0xFF0000, 0xFF00, 0xFF, 0xFF000000} ,
+#endif /* PJ_IS_BIG_ENDIAN */
+
+    {PJMEDIA_FORMAT_DIB , (Uint32)SDL_PIXELFORMAT_RGB24,
+     0xFF0000, 0xFF00, 0xFF, 0} ,
+
+    {PJMEDIA_FORMAT_YUY2, SDL_PIXELFORMAT_YUY2, 0, 0, 0, 0} ,
+    {PJMEDIA_FORMAT_UYVY, SDL_PIXELFORMAT_UYVY, 0, 0, 0, 0} ,
+    {PJMEDIA_FORMAT_YVYU, SDL_PIXELFORMAT_YVYU, 0, 0, 0, 0} ,
+    {PJMEDIA_FORMAT_I420, SDL_PIXELFORMAT_IYUV, 0, 0, 0, 0} ,
+    {PJMEDIA_FORMAT_YV12, SDL_PIXELFORMAT_YV12, 0, 0, 0, 0} ,
+    {PJMEDIA_FORMAT_I420JPEG, SDL_PIXELFORMAT_IYUV, 0, 0, 0, 0} ,
+    {PJMEDIA_FORMAT_I422JPEG, SDL_PIXELFORMAT_YV12, 0, 0, 0, 0}
+};
+
+/* sdl_ device info */
+struct sdl_dev_info
+{
+    pjmedia_vid_dev_info	 info;
+};
+
+/* Linked list of streams */
+struct stream_list
+{
+    PJ_DECL_LIST_MEMBER(struct stream_list);
+    struct sdl_stream	*stream;
+};
+
+#define INITIAL_MAX_JOBS 64
+#define JOB_QUEUE_INC_FACTOR 2
+
+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;
+
+#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0
+@interface JQDelegate: NSObject
+{
+    @public
+    job *pjob;
+}
+
+- (void)run_job;
+@end
+
+@implementation JQDelegate
+- (void)run_job
+{
+    pjob->retval = (*pjob->func)(pjob->data);
+}
+@end
+#endif /* PJ_DARWINOS */
+
+typedef struct job_queue {
+    pj_pool_t      *pool;
+    job           **jobs;
+    pj_sem_t      **job_sem;
+    pj_sem_t      **old_sem;
+    pj_mutex_t     *mutex;
+    pj_thread_t    *thread;
+    pj_sem_t       *sem;
+
+    unsigned        size;
+    unsigned        head, tail;
+    pj_bool_t	    is_full;
+    pj_bool_t       is_quitting;
+} job_queue;
+
+/* sdl_ factory */
+struct sdl_factory
+{
+    pjmedia_vid_dev_factory	 base;
+    pj_pool_t			*pool;
+    pj_pool_factory		*pf;
+
+    unsigned			 dev_count;
+    struct sdl_dev_info	        *dev_info;
+    job_queue                   *jq;
+
+    pj_thread_t			*sdl_thread;        /**< SDL thread.        */
+    pj_sem_t                    *sem;
+    pj_mutex_t			*mutex;
+    struct stream_list		 streams;
+    pj_bool_t                    is_quitting;
+    pj_thread_desc 		 thread_desc;
+    pj_thread_t 		*ev_thread;
+};
+
+/* Video stream. */
+struct sdl_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.  */
+
+    struct sdl_factory          *sf;
+    const pjmedia_frame         *frame;
+    pj_bool_t			 is_running;
+    pj_timestamp		 last_ts;
+    struct stream_list		 list_entry;
+
+    SDL_Window                  *window;            /**< Display window.    */
+    SDL_Renderer                *renderer;          /**< Display renderer.  */
+    SDL_Texture                 *scr_tex;           /**< Screen texture.    */
+    int                          pitch;             /**< Pitch value.       */
+    SDL_Rect			 rect;              /**< Frame rectangle.   */
+    SDL_Rect			 dstrect;           /**< Display rectangle. */
+#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL
+    SDL_GLContext               *gl_context;
+    GLuint			 texture;
+#endif /* PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL */
+
+    pjmedia_video_apply_fmt_param vafp;
+};
+
+/* Prototypes */
+static pj_status_t sdl_factory_init(pjmedia_vid_dev_factory *f);
+static pj_status_t sdl_factory_destroy(pjmedia_vid_dev_factory *f);
+static pj_status_t sdl_factory_refresh(pjmedia_vid_dev_factory *f);
+static unsigned    sdl_factory_get_dev_count(pjmedia_vid_dev_factory *f);
+static pj_status_t sdl_factory_get_dev_info(pjmedia_vid_dev_factory *f,
+					    unsigned index,
+					    pjmedia_vid_dev_info *info);
+static pj_status_t sdl_factory_default_param(pj_pool_t *pool,
+                                             pjmedia_vid_dev_factory *f,
+					     unsigned index,
+					     pjmedia_vid_dev_param *param);
+static pj_status_t sdl_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 sdl_stream_get_param(pjmedia_vid_dev_stream *strm,
+					pjmedia_vid_dev_param *param);
+static pj_status_t sdl_stream_get_cap(pjmedia_vid_dev_stream *strm,
+				      pjmedia_vid_dev_cap cap,
+				      void *value);
+static pj_status_t sdl_stream_set_cap(pjmedia_vid_dev_stream *strm,
+				      pjmedia_vid_dev_cap cap,
+				      const void *value);
+static pj_status_t sdl_stream_put_frame(pjmedia_vid_dev_stream *strm,
+                                        const pjmedia_frame *frame);
+static pj_status_t sdl_stream_start(pjmedia_vid_dev_stream *strm);
+static pj_status_t sdl_stream_stop(pjmedia_vid_dev_stream *strm);
+static pj_status_t sdl_stream_destroy(pjmedia_vid_dev_stream *strm);
+
+static pj_status_t resize_disp(struct sdl_stream *strm,
+                               pjmedia_rect_size *new_disp_size);
+static pj_status_t sdl_destroy_all(void *data);
+
+/* 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_factory_op factory_op =
+{
+    &sdl_factory_init,
+    &sdl_factory_destroy,
+    &sdl_factory_get_dev_count,
+    &sdl_factory_get_dev_info,
+    &sdl_factory_default_param,
+    &sdl_factory_create_stream,
+    &sdl_factory_refresh
+};
+
+static pjmedia_vid_dev_stream_op stream_op =
+{
+    &sdl_stream_get_param,
+    &sdl_stream_get_cap,
+    &sdl_stream_set_cap,
+    &sdl_stream_start,
+    NULL,
+    &sdl_stream_put_frame,
+    &sdl_stream_stop,
+    &sdl_stream_destroy
+};
+
+/*
+ * Util
+ */
+static void sdl_log_err(const char *op)
+{
+    PJ_LOG(1,(THIS_FILE, "%s error: %s", op, SDL_GetError()));
+}
+
+/****************************************************************************
+ * Factory operations
+ */
+/*
+ * Init sdl_ video driver.
+ */
+pjmedia_vid_dev_factory* pjmedia_sdl_factory(pj_pool_factory *pf)
+{
+    struct sdl_factory *f;
+    pj_pool_t *pool;
+
+    pool = pj_pool_create(pf, "sdl video", 1000, 1000, NULL);
+    f = PJ_POOL_ZALLOC_T(pool, struct sdl_factory);
+    f->pf = pf;
+    f->pool = pool;
+    f->base.op = &factory_op;
+
+    return &f->base;
+}
+
+static pj_status_t sdl_init(void * data)
+{
+    PJ_UNUSED_ARG(data);
+
+    if (SDL_Init(SDL_INIT_VIDEO)) {
+        sdl_log_err("SDL_Init()");
+        return PJMEDIA_EVID_INIT;
+    }
+
+    return PJ_SUCCESS;
+}
+
+static struct sdl_stream* find_stream(struct sdl_factory *sf,
+                                      Uint32 windowID,
+                                      pjmedia_event *pevent)
+{
+    struct stream_list *it, *itBegin;
+    struct sdl_stream *strm = NULL;
+
+    itBegin = &sf->streams;
+    for (it = itBegin->next; it != itBegin; it = it->next) {
+        if (SDL_GetWindowID(it->stream->window) == windowID)
+        {
+            strm = it->stream;
+            break;
+        }
+    }
+ 
+    if (strm)
+        pjmedia_event_init(pevent, PJMEDIA_EVENT_NONE, &strm->last_ts,
+		           strm);
+
+    return strm;
+}
+
+static pj_status_t handle_event(void *data)
+{
+    struct sdl_factory *sf = (struct sdl_factory*)data;
+    SDL_Event sevent;
+
+    if (!pj_thread_is_registered())
+	pj_thread_register("sdl_ev", sf->thread_desc, &sf->ev_thread);
+
+    while (SDL_PollEvent(&sevent)) {
+        struct sdl_stream *strm = NULL;
+        pjmedia_event pevent;
+
+        pj_mutex_lock(sf->mutex);
+        pevent.type = PJMEDIA_EVENT_NONE;
+	switch(sevent.type) {
+        case SDL_MOUSEBUTTONDOWN:
+            strm = find_stream(sf, sevent.button.windowID, &pevent);
+            pevent.type = PJMEDIA_EVENT_MOUSE_BTN_DOWN;
+            break;
+        case SDL_WINDOWEVENT:
+            strm = find_stream(sf, sevent.window.windowID, &pevent);
+            switch (sevent.window.event) {
+            case SDL_WINDOWEVENT_RESIZED:
+                pevent.type = PJMEDIA_EVENT_WND_RESIZED;
+                pevent.data.wnd_resized.new_size.w =
+                    sevent.window.data1;
+                pevent.data.wnd_resized.new_size.h =
+                    sevent.window.data2;
+                break;
+            case SDL_WINDOWEVENT_CLOSE:
+                pevent.type = PJMEDIA_EVENT_WND_CLOSING;
+                break;
+            }
+            break;
+        default:
+            break;
+	}
+
+        if (strm && pevent.type != PJMEDIA_EVENT_NONE) {
+            pj_status_t status;
+
+	    pjmedia_event_publish(NULL, strm, &pevent, 0);
+
+	    switch (pevent.type) {
+	    case PJMEDIA_EVENT_WND_RESIZED:
+                status = resize_disp(strm, &pevent.data.wnd_resized.new_size);
+                if (status != PJ_SUCCESS)
+                    PJ_LOG(3, (THIS_FILE, "Failed resizing the display."));
+		break;
+	    case PJMEDIA_EVENT_WND_CLOSING:
+		if (pevent.data.wnd_closing.cancel) {
+		    /* Cancel the closing operation */
+		    break;
+		}
+
+		/* Proceed to cleanup SDL. App must still call
+		 * pjmedia_dev_stream_destroy() when getting WND_CLOSED
+		 * event
+		 */
+		sdl_stream_stop(&strm->base);
+                sdl_destroy_all(strm);
+                pjmedia_event_init(&pevent, PJMEDIA_EVENT_WND_CLOSED,
+                                   &strm->last_ts, strm);
+                pjmedia_event_publish(NULL, strm, &pevent, 0);
+
+                /*
+                 * Note: don't access the stream after this point, it
+                 * might have been destroyed
+                 */
+                break;
+	    default:
+		/* Just to prevent gcc warning about unused enums */
+		break;
+	    }
+        }
+
+        pj_mutex_unlock(sf->mutex);
+    }
+
+    return PJ_SUCCESS;
+}
+
+static int sdl_ev_thread(void *data)
+{
+    struct sdl_factory *sf = (struct sdl_factory*)data;
+
+    while(1) {
+        pj_status_t status;
+
+        pj_mutex_lock(sf->mutex);
+        if (pj_list_empty(&sf->streams)) {
+            pj_mutex_unlock(sf->mutex);
+            /* Wait until there is any stream. */
+            pj_sem_wait(sf->sem);
+        } else
+            pj_mutex_unlock(sf->mutex);
+
+        if (sf->is_quitting)
+            break;
+
+        job_queue_post_job(sf->jq, handle_event, sf, 0, &status);
+
+        pj_thread_sleep(50);
+    }
+
+    return 0;
+}
+
+static pj_status_t sdl_quit(void *data)
+{
+    PJ_UNUSED_ARG(data);
+    SDL_Quit();
+    return PJ_SUCCESS;
+}
+
+/* API: init factory */
+static pj_status_t sdl_factory_init(pjmedia_vid_dev_factory *f)
+{
+    struct sdl_factory *sf = (struct sdl_factory*)f;
+    struct sdl_dev_info *ddi;
+    unsigned i, j;
+    pj_status_t status;
+    SDL_version version;
+
+    pj_list_init(&sf->streams);
+
+    status = job_queue_create(sf->pool, &sf->jq);
+    if (status != PJ_SUCCESS)
+        return PJMEDIA_EVID_INIT;
+
+    job_queue_post_job(sf->jq, sdl_init, NULL, 0, &status);
+    if (status != PJ_SUCCESS)
+        return status;
+
+    status = pj_mutex_create_recursive(sf->pool, "sdl_factory",
+				       &sf->mutex);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    status = pj_sem_create(sf->pool, NULL, 0, 1, &sf->sem);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    /* Create event handler thread. */
+    status = pj_thread_create(sf->pool, "sdl_thread", sdl_ev_thread,
+			      sf, 0, 0, &sf->sdl_thread);
+    if (status != PJ_SUCCESS)
+        return status;
+
+    sf->dev_count = 1;
+#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL
+    sf->dev_count++;
+#endif /* PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL */
+    sf->dev_info = (struct sdl_dev_info*)
+		   pj_pool_calloc(sf->pool, sf->dev_count,
+				  sizeof(struct sdl_dev_info));
+
+    ddi = &sf->dev_info[0];
+    pj_bzero(ddi, sizeof(*ddi));
+    strncpy(ddi->info.name, "SDL renderer", sizeof(ddi->info.name));
+    ddi->info.name[sizeof(ddi->info.name)-1] = '\0';
+    ddi->info.fmt_cnt = PJ_ARRAY_SIZE(sdl_fmts);
+
+#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL
+    ddi = &sf->dev_info[OPENGL_DEV_IDX];
+    pj_bzero(ddi, sizeof(*ddi));
+    strncpy(ddi->info.name, "SDL openGL renderer", sizeof(ddi->info.name));
+    ddi->info.name[sizeof(ddi->info.name)-1] = '\0';
+    ddi->info.fmt_cnt = 1;
+#endif /* PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL */
+
+    for (i = 0; i < sf->dev_count; i++) {
+        ddi = &sf->dev_info[i];
+        strncpy(ddi->info.driver, "SDL", sizeof(ddi->info.driver));
+        ddi->info.driver[sizeof(ddi->info.driver)-1] = '\0';
+        ddi->info.dir = PJMEDIA_DIR_RENDER;
+        ddi->info.has_callback = PJ_FALSE;
+        ddi->info.caps = PJMEDIA_VID_DEV_CAP_FORMAT |
+                         PJMEDIA_VID_DEV_CAP_OUTPUT_RESIZE;
+        ddi->info.caps |= PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW;
+        ddi->info.caps |= PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW_FLAGS;
+
+        for (j = 0; j < ddi->info.fmt_cnt; j++) {
+            pjmedia_format *fmt = &ddi->info.fmt[j];
+            pjmedia_format_init_video(fmt, sdl_fmts[j].fmt_id,
+                                      DEFAULT_WIDTH, DEFAULT_HEIGHT,
+                                      DEFAULT_FPS, 1);
+        }
+    }
+
+    SDL_VERSION(&version);
+    PJ_LOG(4, (THIS_FILE, "SDL %d.%d initialized",
+			  version.major, version.minor));
+
+    return PJ_SUCCESS;
+}
+
+/* API: destroy factory */
+static pj_status_t sdl_factory_destroy(pjmedia_vid_dev_factory *f)
+{
+    struct sdl_factory *sf = (struct sdl_factory*)f;
+    pj_pool_t *pool = sf->pool;
+    pj_status_t status;
+
+    pj_assert(pj_list_empty(&sf->streams));
+
+    sf->is_quitting = PJ_TRUE;
+    if (sf->sdl_thread) {
+        pj_sem_post(sf->sem);
+#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0
+        /* To prevent pj_thread_join() of getting stuck if we are in
+         * the main thread and we haven't finished processing the job
+         * posted by sdl_thread.
+         */
+        CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false);
+#endif
+        pj_thread_join(sf->sdl_thread);
+        pj_thread_destroy(sf->sdl_thread);
+    }
+
+    if (sf->mutex) {
+	pj_mutex_destroy(sf->mutex);
+	sf->mutex = NULL;
+    }
+
+    if (sf->sem) {
+        pj_sem_destroy(sf->sem);
+        sf->sem = NULL;
+    }
+
+    job_queue_post_job(sf->jq, sdl_quit, NULL, 0, &status);
+    job_queue_destroy(sf->jq);
+
+    sf->pool = NULL;
+    pj_pool_release(pool);
+
+    return PJ_SUCCESS;
+}
+
+/* API: refresh the list of devices */
+static pj_status_t sdl_factory_refresh(pjmedia_vid_dev_factory *f)
+{
+    PJ_UNUSED_ARG(f);
+    return PJ_SUCCESS;
+}
+
+/* API: get number of devices */
+static unsigned sdl_factory_get_dev_count(pjmedia_vid_dev_factory *f)
+{
+    struct sdl_factory *sf = (struct sdl_factory*)f;
+    return sf->dev_count;
+}
+
+/* API: get device info */
+static pj_status_t sdl_factory_get_dev_info(pjmedia_vid_dev_factory *f,
+					    unsigned index,
+					    pjmedia_vid_dev_info *info)
+{
+    struct sdl_factory *sf = (struct sdl_factory*)f;
+
+    PJ_ASSERT_RETURN(index < sf->dev_count, PJMEDIA_EVID_INVDEV);
+
+    pj_memcpy(info, &sf->dev_info[index].info, sizeof(*info));
+
+    return PJ_SUCCESS;
+}
+
+/* API: create default device parameter */
+static pj_status_t sdl_factory_default_param(pj_pool_t *pool,
+                                             pjmedia_vid_dev_factory *f,
+					     unsigned index,
+					     pjmedia_vid_dev_param *param)
+{
+    struct sdl_factory *sf = (struct sdl_factory*)f;
+    struct sdl_dev_info *di = &sf->dev_info[index];
+
+    PJ_ASSERT_RETURN(index < sf->dev_count, PJMEDIA_EVID_INVDEV);
+    
+    PJ_UNUSED_ARG(pool);
+
+    pj_bzero(param, sizeof(*param));
+    param->dir = PJMEDIA_DIR_RENDER;
+    param->rend_id = index;
+    param->cap_id = PJMEDIA_VID_INVALID_DEV;
+
+    /* Set the device capabilities here */
+    param->flags = PJMEDIA_VID_DEV_CAP_FORMAT;
+    param->fmt.type = PJMEDIA_TYPE_VIDEO;
+    param->clock_rate = DEFAULT_CLOCK_RATE;
+    pj_memcpy(&param->fmt, &di->info.fmt[0], sizeof(param->fmt));
+
+    return PJ_SUCCESS;
+}
+
+static sdl_fmt_info* get_sdl_format_info(pjmedia_format_id id)
+{
+    unsigned i;
+
+    for (i = 0; i < sizeof(sdl_fmts)/sizeof(sdl_fmts[0]); i++) {
+        if (sdl_fmts[i].fmt_id == id)
+            return &sdl_fmts[i];
+    }
+
+    return NULL;
+}
+
+static pj_status_t sdl_destroy(void *data)
+{
+    struct sdl_stream *strm = (struct sdl_stream *)data;
+
+#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL
+    if (strm->texture) {
+	glDeleteTextures(1, &strm->texture);
+	strm->texture = 0;
+    }
+    if (strm->gl_context) {
+        SDL_GL_DeleteContext(strm->gl_context);
+        strm->gl_context = NULL;
+    }
+#endif /* PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL */
+    if (strm->scr_tex) {
+        SDL_DestroyTexture(strm->scr_tex);
+        strm->scr_tex = NULL;
+    }
+    if (strm->renderer) {
+        SDL_DestroyRenderer(strm->renderer);
+        strm->renderer = NULL;
+    }
+
+    return PJ_SUCCESS;
+}
+
+static pj_status_t sdl_destroy_all(void *data)
+{
+    struct sdl_stream *strm = (struct sdl_stream *)data;
+
+    sdl_destroy(data);
+#if !defined(TARGET_OS_IPHONE) || TARGET_OS_IPHONE == 0
+    if (strm->window &&
+        !(strm->param.flags & PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW))
+    {
+        SDL_DestroyWindow(strm->window);
+    }
+    strm->window = NULL;
+#endif /* TARGET_OS_IPHONE */
+
+    return PJ_SUCCESS;
+}
+
+static pj_status_t sdl_create_rend(struct sdl_stream * strm,
+                                   pjmedia_format *fmt)
+{
+    sdl_fmt_info *sdl_info;
+    const pjmedia_video_format_info *vfi;
+    pjmedia_video_format_detail *vfd;
+
+    sdl_info = get_sdl_format_info(fmt->id);
+    vfi = pjmedia_get_video_format_info(pjmedia_video_format_mgr_instance(),
+                                        fmt->id);
+    if (!vfi || !sdl_info)
+        return PJMEDIA_EVID_BADFORMAT;
+
+    strm->vafp.size = fmt->det.vid.size;
+    strm->vafp.buffer = NULL;
+    if (vfi->apply_fmt(vfi, &strm->vafp) != PJ_SUCCESS)
+        return PJMEDIA_EVID_BADFORMAT;
+
+    vfd = pjmedia_format_get_video_format_detail(fmt, PJ_TRUE);
+    strm->rect.x = strm->rect.y = 0;
+    strm->rect.w = (Uint16)vfd->size.w;
+    strm->rect.h = (Uint16)vfd->size.h;
+    if (strm->param.disp_size.w == 0)
+        strm->param.disp_size.w = strm->rect.w;
+    if (strm->param.disp_size.h == 0)
+        strm->param.disp_size.h = strm->rect.h;
+    strm->dstrect.x = strm->dstrect.y = 0;
+    strm->dstrect.w = (Uint16)strm->param.disp_size.w;
+    strm->dstrect.h = (Uint16)strm->param.disp_size.h;
+
+    sdl_destroy(strm);
+
+#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL
+    if (strm->param.rend_id == OPENGL_DEV_IDX) {
+	SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER,1);
+    }
+#endif /* PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL */
+
+    if (!strm->window) {
+        Uint32 flags = 0;
+        
+        if (strm->param.flags & PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW_FLAGS) {
+            if (!(strm->param.window_flags & PJMEDIA_VID_DEV_WND_BORDER))
+                flags |= SDL_WINDOW_BORDERLESS;
+            if (strm->param.window_flags & PJMEDIA_VID_DEV_WND_RESIZABLE)
+                flags |= SDL_WINDOW_RESIZABLE;
+        } else {
+            flags |= SDL_WINDOW_BORDERLESS;
+        }
+
+        if (!((strm->param.flags & PJMEDIA_VID_DEV_CAP_OUTPUT_HIDE) &&
+            strm->param.window_hide))
+        {
+            flags |= SDL_WINDOW_SHOWN;
+        } else {
+            flags &= ~SDL_WINDOW_SHOWN;
+            flags |= SDL_WINDOW_HIDDEN;
+        }
+
+#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL
+        if (strm->param.rend_id == OPENGL_DEV_IDX)
+            flags |= SDL_WINDOW_OPENGL;
+#endif /* PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL */
+
+        if (strm->param.flags & PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW) {
+            /* Use the window supplied by the application. */
+	    strm->window = SDL_CreateWindowFrom(
+                               strm->param.window.info.window);
+	    if (!strm->window) {
+		sdl_log_err("SDL_CreateWindowFrom()");
+		return PJMEDIA_EVID_SYSERR;
+	    }
+        } else {
+            int x, y;
+
+            x = y = SDL_WINDOWPOS_CENTERED;
+            if (strm->param.flags & PJMEDIA_VID_DEV_CAP_OUTPUT_POSITION) {
+                x = strm->param.window_pos.x;
+                y = strm->param.window_pos.y;
+            }
+
+            /* Create the window where we will draw. */
+            strm->window = SDL_CreateWindow("pjmedia-SDL video",
+                                            x, y,
+                                            strm->param.disp_size.w,
+                                            strm->param.disp_size.h,
+                                            flags);
+	    if (!strm->window) {
+		sdl_log_err("SDL_CreateWindow()");
+		return PJMEDIA_EVID_SYSERR;
+	    }
+        }
+    }
+
+    /**
+      * We must call SDL_CreateRenderer in order for draw calls to
+      * affect this window.
+      */
+    strm->renderer = SDL_CreateRenderer(strm->window, -1, 0);
+    if (!strm->renderer) {
+	sdl_log_err("SDL_CreateRenderer()");
+        return PJMEDIA_EVID_SYSERR;
+    }
+
+#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL
+    if (strm->param.rend_id == OPENGL_DEV_IDX) {
+        strm->gl_context = SDL_GL_CreateContext(strm->window);
+        if (!strm->gl_context) {
+            sdl_log_err("SDL_GL_CreateContext()");
+            return PJMEDIA_EVID_SYSERR;
+        }
+        SDL_GL_MakeCurrent(strm->window, strm->gl_context);
+
+        /* Init some OpenGL settings */
+	glDisable(GL_DEPTH_TEST);
+	glDisable(GL_CULL_FACE);
+	glEnable(GL_TEXTURE_2D);
+	
+	/* Init the viewport */
+	glViewport(0, 0, strm->param.disp_size.w, strm->param.disp_size.h);
+	glMatrixMode(GL_PROJECTION);
+	glLoadIdentity();
+	
+	glOrtho(0.0, (GLdouble)strm->param.disp_size.w,
+                (GLdouble)strm->param.disp_size.h, 0.0, 0.0, 1.0);
+	
+	glMatrixMode(GL_MODELVIEW);
+	glLoadIdentity();
+	
+	/* Create a texture */
+	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
+	glGenTextures(1, &strm->texture);
+
+        if (!strm->texture)
+            return PJMEDIA_EVID_SYSERR;
+    } else
+#endif /* PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL */
+    {    
+        strm->scr_tex = SDL_CreateTexture(strm->renderer, sdl_info->sdl_format,
+                                          SDL_TEXTUREACCESS_STREAMING,
+                                          strm->rect.w, strm->rect.h);
+        if (strm->scr_tex == NULL) {
+            sdl_log_err("SDL_CreateTexture()");
+            return PJMEDIA_EVID_SYSERR;
+        }
+    
+        strm->pitch = strm->rect.w * SDL_BYTESPERPIXEL(sdl_info->sdl_format);
+    }
+
+    return PJ_SUCCESS;
+}
+
+static pj_status_t sdl_create(void *data)
+{
+    struct sdl_stream *strm = (struct sdl_stream *)data;
+    return sdl_create_rend(strm, &strm->param.fmt);
+}
+
+static pj_status_t resize_disp(struct sdl_stream *strm,
+                               pjmedia_rect_size *new_disp_size)
+{
+    pj_memcpy(&strm->param.disp_size, new_disp_size,
+              sizeof(strm->param.disp_size));
+    
+    if (strm->scr_tex) {
+        strm->dstrect.x = strm->dstrect.y = 0;
+        strm->dstrect.w = (Uint16)strm->param.disp_size.w;
+	strm->dstrect.h = (Uint16)strm->param.disp_size.h;
+	SDL_RenderSetViewport(strm->renderer, &strm->dstrect);
+    }
+#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL
+    else if (strm->param.rend_id == OPENGL_DEV_IDX) {
+	sdl_create_rend(strm, &strm->param.fmt);
+    }
+#endif /* PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL */
+
+    return PJ_SUCCESS;
+}
+
+static pj_status_t change_format(struct sdl_stream *strm,
+                                 pjmedia_format *new_fmt)
+{
+    pj_status_t status;
+
+    /* Recreate SDL renderer */
+    status = sdl_create_rend(strm, (new_fmt? new_fmt :
+				   &strm->param.fmt));
+    if (status == PJ_SUCCESS && new_fmt)
+        pjmedia_format_copy(&strm->param.fmt, new_fmt);
+
+    return status;
+}
+
+static pj_status_t put_frame(void *data)
+{
+    struct sdl_stream *stream = (struct sdl_stream *)data;
+    const pjmedia_frame *frame = stream->frame;
+
+    if (stream->scr_tex) {
+        SDL_UpdateTexture(stream->scr_tex, NULL, frame->buf, stream->pitch);
+        SDL_RenderClear(stream->renderer);
+        SDL_RenderCopy(stream->renderer, stream->scr_tex,
+		       &stream->rect, &stream->dstrect);
+        SDL_RenderPresent(stream->renderer);
+    }
+#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL
+    else if (stream->param.rend_id == OPENGL_DEV_IDX && stream->texture) {
+	glBindTexture(GL_TEXTURE_2D, stream->texture);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
+		     stream->rect.w, stream->rect.h, 0,
+		     GL_RGBA, GL_UNSIGNED_BYTE, frame->buf);
+	glBegin(GL_TRIANGLE_STRIP);
+	glTexCoord2f(0, 0); glVertex2i(0, 0);
+	glTexCoord2f(1, 0); glVertex2i(stream->param.disp_size.w, 0);
+	glTexCoord2f(0, 1); glVertex2i(0, stream->param.disp_size.h);
+	glTexCoord2f(1, 1);
+        glVertex2i(stream->param.disp_size.w, stream->param.disp_size.h);
+	glEnd();
+        SDL_GL_SwapWindow(stream->window);
+    }
+#endif /* PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL */
+
+    return PJ_SUCCESS;
+}
+
+/* API: Put frame from stream */
+static pj_status_t sdl_stream_put_frame(pjmedia_vid_dev_stream *strm,
+					const pjmedia_frame *frame)
+{
+    struct sdl_stream *stream = (struct sdl_stream*)strm;
+    pj_status_t status;
+
+    stream->last_ts.u64 = frame->timestamp.u64;
+
+    if (!stream->is_running)
+	return PJ_EINVALIDOP;
+
+    if (frame->size==0 || frame->buf==NULL ||
+	frame->size < stream->vafp.framebytes)
+	return PJ_SUCCESS;
+
+    stream->frame = frame;
+    job_queue_post_job(stream->sf->jq, put_frame, strm, 0, &status);
+    
+    return status;
+}
+
+/* API: create stream */
+static pj_status_t sdl_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)
+{
+    struct sdl_factory *sf = (struct sdl_factory*)f;
+    pj_pool_t *pool;
+    struct sdl_stream *strm;
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(param->dir == PJMEDIA_DIR_RENDER, PJ_EINVAL);
+
+    /* Create and Initialize stream descriptor */
+    pool = pj_pool_create(sf->pf, "sdl-dev", 1000, 1000, NULL);
+    PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);
+
+    strm = PJ_POOL_ZALLOC_T(pool, struct sdl_stream);
+    pj_memcpy(&strm->param, param, sizeof(*param));
+    strm->pool = pool;
+    strm->sf = sf;
+    pj_memcpy(&strm->vid_cb, cb, sizeof(*cb));
+    pj_list_init(&strm->list_entry);
+    strm->list_entry.stream = strm;
+    strm->user_data = user_data;
+
+    /* Create render stream here */
+    job_queue_post_job(sf->jq, sdl_create, strm, 0, &status);
+    if (status != PJ_SUCCESS) {
+        goto on_error;
+    }
+    pj_mutex_lock(strm->sf->mutex);
+    if (pj_list_empty(&strm->sf->streams))
+        pj_sem_post(strm->sf->sem);
+    pj_list_insert_after(&strm->sf->streams, &strm->list_entry);
+    pj_mutex_unlock(strm->sf->mutex);
+
+    /* Done */
+    strm->base.op = &stream_op;
+    *p_vid_strm = &strm->base;
+
+    return PJ_SUCCESS;
+
+on_error:
+    sdl_stream_destroy(&strm->base);
+    return status;
+}
+
+/* API: Get stream info. */
+static pj_status_t sdl_stream_get_param(pjmedia_vid_dev_stream *s,
+					pjmedia_vid_dev_param *pi)
+{
+    struct sdl_stream *strm = (struct sdl_stream*)s;
+
+    PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
+
+    pj_memcpy(pi, &strm->param, sizeof(*pi));
+
+    if (sdl_stream_get_cap(s, PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW,
+			   &pi->window) == PJ_SUCCESS)
+    {
+	pi->flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW;
+    }
+    if (sdl_stream_get_cap(s, PJMEDIA_VID_DEV_CAP_OUTPUT_POSITION,
+			   &pi->window_pos) == PJ_SUCCESS)
+    {
+	pi->flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_POSITION;
+    }
+    if (sdl_stream_get_cap(s, PJMEDIA_VID_DEV_CAP_OUTPUT_RESIZE,
+			   &pi->disp_size) == PJ_SUCCESS)
+    {
+	pi->flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_RESIZE;
+    }
+    if (sdl_stream_get_cap(s, PJMEDIA_VID_DEV_CAP_OUTPUT_HIDE,
+			   &pi->window_hide) == PJ_SUCCESS)
+    {
+	pi->flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_HIDE;
+    }
+    if (sdl_stream_get_cap(s, PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW_FLAGS,
+			   &pi->window_flags) == PJ_SUCCESS)
+    {
+	pi->flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW_FLAGS;
+    }
+
+    return PJ_SUCCESS;
+}
+
+struct strm_cap {
+    struct sdl_stream   *strm;
+    pjmedia_vid_dev_cap  cap;
+    union {
+        void            *pval;
+        const void      *cpval;
+    } pval;
+};
+
+static pj_status_t get_cap(void *data)
+{
+    struct strm_cap *scap = (struct strm_cap *)data;
+    struct sdl_stream *strm = scap->strm;
+    pjmedia_vid_dev_cap cap = scap->cap;
+    void *pval = scap->pval.pval;
+
+    if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW)
+    {
+	SDL_SysWMinfo info;
+	SDL_VERSION(&info.version);
+
+	if (SDL_GetWindowWMInfo(strm->window, &info)) {
+	    pjmedia_vid_dev_hwnd *wnd = (pjmedia_vid_dev_hwnd *)pval;
+	    if (0) { }
+#if defined(SDL_VIDEO_DRIVER_WINDOWS)
+	    else if (info.subsystem == SDL_SYSWM_WINDOWS) {
+		wnd->type = PJMEDIA_VID_DEV_HWND_TYPE_WINDOWS;
+		wnd->info.win.hwnd = (void *)info.info.win.window;
+	    }
+#endif
+#if defined(SDL_VIDEO_DRIVER_X11)
+	    else if (info.subsystem == SDL_SYSWM_X11) {
+		wnd->info.x11.window = (void *)info.info.x11.window;
+		wnd->info.x11.display = (void *)info.info.x11.display;
+	    }
+#endif
+#if defined(SDL_VIDEO_DRIVER_COCOA)
+	    else if (info.subsystem == SDL_SYSWM_COCOA) {
+		wnd->info.cocoa.window = (void *)info.info.cocoa.window;
+	    }
+#endif
+#if defined(SDL_VIDEO_DRIVER_UIKIT)
+	    else if (info.subsystem == SDL_SYSWM_UIKIT) {
+		wnd->info.ios.window = (void *)info.info.uikit.window;
+	    }
+#endif
+	    else {
+		return PJMEDIA_EVID_INVCAP;
+	    }
+	    return PJ_SUCCESS;
+	} else
+	    return PJMEDIA_EVID_INVCAP;
+    } else if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_POSITION) {
+        SDL_GetWindowPosition(strm->window, &((pjmedia_coord *)pval)->x,
+                              &((pjmedia_coord *)pval)->y);
+	return PJ_SUCCESS;
+    } else if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_RESIZE) {
+        SDL_GetWindowSize(strm->window, (int *)&((pjmedia_rect_size *)pval)->w,
+                          (int *)&((pjmedia_rect_size *)pval)->h);
+	return PJ_SUCCESS;
+    } else if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_HIDE) {
+	Uint32 flag = SDL_GetWindowFlags(strm->window);
+	*((pj_bool_t *)pval) = (flag & SDL_WINDOW_HIDDEN)? PJ_TRUE: PJ_FALSE;
+	return PJ_SUCCESS;
+    } else if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW_FLAGS) {
+	Uint32 flag = SDL_GetWindowFlags(strm->window);
+        unsigned *wnd_flags = (unsigned *)pval;
+        if (!(flag & SDL_WINDOW_BORDERLESS))
+            *wnd_flags |= PJMEDIA_VID_DEV_WND_BORDER;
+        if (flag & SDL_WINDOW_RESIZABLE)
+            *wnd_flags |= PJMEDIA_VID_DEV_WND_RESIZABLE;
+	return PJ_SUCCESS;
+    }
+
+    return PJMEDIA_EVID_INVCAP;
+}
+
+/* API: get capability */
+static pj_status_t sdl_stream_get_cap(pjmedia_vid_dev_stream *s,
+				      pjmedia_vid_dev_cap cap,
+				      void *pval)
+{
+    struct sdl_stream *strm = (struct sdl_stream*)s;
+    struct strm_cap scap;
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+    scap.strm = strm;
+    scap.cap = cap;
+    scap.pval.pval = pval;
+
+    job_queue_post_job(strm->sf->jq, get_cap, &scap, 0, &status);
+
+    return status;
+}
+
+static pj_status_t set_cap(void *data)
+{
+    struct strm_cap *scap = (struct strm_cap *)data;
+    struct sdl_stream *strm = scap->strm;
+    pjmedia_vid_dev_cap cap = scap->cap;
+    const void *pval = scap->pval.cpval;
+
+    if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_POSITION) {
+        /**
+         * Setting window's position when the window is hidden also sets
+         * the window's flag to shown (while the window is, actually,
+         * still hidden). This causes problems later when setting/querying
+         * the window's visibility.
+         * See ticket #1429 (http://trac.pjsip.org/repos/ticket/1429)
+         */
+	Uint32 flag = SDL_GetWindowFlags(strm->window);
+	if (flag & SDL_WINDOW_HIDDEN)
+            SDL_ShowWindow(strm->window);
+        SDL_SetWindowPosition(strm->window, ((pjmedia_coord *)pval)->x,
+                              ((pjmedia_coord *)pval)->y);
+	if (flag & SDL_WINDOW_HIDDEN)
+            SDL_HideWindow(strm->window);
+	return PJ_SUCCESS;
+    } else if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_HIDE) {
+        if (*(pj_bool_t *)pval)
+            SDL_HideWindow(strm->window);
+        else
+            SDL_ShowWindow(strm->window);
+	return PJ_SUCCESS;
+    } else if (cap == PJMEDIA_VID_DEV_CAP_FORMAT) {
+        pj_status_t status;
+
+        status = change_format(strm, (pjmedia_format *)pval);
+	if (status != PJ_SUCCESS) {
+	    pj_status_t status_;
+	    
+	    /**
+	     * Failed to change the output format. Try to revert
+	     * to its original format.
+	     */
+            status_ = change_format(strm, &strm->param.fmt);
+	    if (status_ != PJ_SUCCESS) {
+		/**
+		 * This means that we failed to revert to our
+		 * original state!
+		 */
+		status = PJMEDIA_EVID_ERR;
+	    }
+	}
+	
+	return status;
+    } else if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_RESIZE) {
+	pjmedia_rect_size *new_size = (pjmedia_rect_size *)pval;
+
+	SDL_SetWindowSize(strm->window, new_size->w, new_size->h);
+        return resize_disp(strm, new_size);
+    }
+
+    return PJMEDIA_EVID_INVCAP;
+}
+
+/* API: set capability */
+static pj_status_t sdl_stream_set_cap(pjmedia_vid_dev_stream *s,
+				      pjmedia_vid_dev_cap cap,
+				      const void *pval)
+{
+    struct sdl_stream *strm = (struct sdl_stream*)s;
+    struct strm_cap scap;
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+    scap.strm = strm;
+    scap.cap = cap;
+    scap.pval.cpval = pval;
+
+    job_queue_post_job(strm->sf->jq, set_cap, &scap, 0, &status);
+
+    return status;
+}
+
+/* API: Start stream. */
+static pj_status_t sdl_stream_start(pjmedia_vid_dev_stream *strm)
+{
+    struct sdl_stream *stream = (struct sdl_stream*)strm;
+
+    PJ_LOG(4, (THIS_FILE, "Starting sdl video stream"));
+
+    stream->is_running = PJ_TRUE;
+
+    return PJ_SUCCESS;
+}
+
+
+/* API: Stop stream. */
+static pj_status_t sdl_stream_stop(pjmedia_vid_dev_stream *strm)
+{
+    struct sdl_stream *stream = (struct sdl_stream*)strm;
+
+    PJ_LOG(4, (THIS_FILE, "Stopping sdl video stream"));
+
+    stream->is_running = PJ_FALSE;
+
+    return PJ_SUCCESS;
+}
+
+
+/* API: Destroy stream. */
+static pj_status_t sdl_stream_destroy(pjmedia_vid_dev_stream *strm)
+{
+    struct sdl_stream *stream = (struct sdl_stream*)strm;
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
+
+    sdl_stream_stop(strm);
+
+    job_queue_post_job(stream->sf->jq, sdl_destroy_all, strm, 0, &status);
+    if (status != PJ_SUCCESS)
+        return status;
+
+    pj_mutex_lock(stream->sf->mutex);
+    if (!pj_list_empty(&stream->list_entry))
+	pj_list_erase(&stream->list_entry);
+    pj_mutex_unlock(stream->sf->mutex);
+
+    pj_pool_release(stream->pool);
+
+    return PJ_SUCCESS;
+}
+
+/****************************************************************************
+ * Job queue implementation
+ */
+#if PJ_DARWINOS==0
+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 && !jq->is_full)
+            break;
+
+        jb = jq->jobs[jq->head];
+        jb->retval = (*jb->func)(jb->data);
+        /* If job queue is full and we already finish all the pending
+         * jobs, increase the size.
+         */
+        if (jq->is_full && ((jq->head + 1) % jq->size == jq->tail)) {
+            unsigned i, head;
+            pj_status_t status;
+
+            if (jq->old_sem) {
+                for (i = 0; i < jq->size / JOB_QUEUE_INC_FACTOR; i++) {
+                    pj_sem_destroy(jq->old_sem[i]);
+                }
+            }
+            jq->old_sem = jq->job_sem;
+
+            /* Double the job queue size. */
+            jq->size *= JOB_QUEUE_INC_FACTOR;
+            pj_sem_destroy(jq->sem);
+            status = pj_sem_create(jq->pool, "thread_sem", 0, jq->size + 1,
+                                   &jq->sem);
+            if (status != PJ_SUCCESS) {
+                PJ_LOG(3, (THIS_FILE, "Failed growing SDL job queue size."));
+                return 0;
+            }
+            jq->jobs = (job **)pj_pool_calloc(jq->pool, jq->size,
+                                              sizeof(job *));
+            jq->job_sem = (pj_sem_t **) pj_pool_calloc(jq->pool, jq->size,
+                                                       sizeof(pj_sem_t *));
+            for (i = 0; i < jq->size; i++) {
+                status = pj_sem_create(jq->pool, "job_sem", 0, 1,
+                                       &jq->job_sem[i]);
+                if (status != PJ_SUCCESS) {
+                    PJ_LOG(3, (THIS_FILE, "Failed growing SDL job "
+                                          "queue size."));
+                    return 0;
+                }
+            }
+            jq->is_full = PJ_FALSE;
+            head = jq->head;
+            jq->head = jq->tail = 0;
+            pj_sem_post(jq->old_sem[head]);
+        } else {
+            pj_sem_post(jq->job_sem[jq->head]);
+            jq->head = (jq->head + 1) % jq->size;
+        }
+    }
+
+    return 0;
+}
+#endif
+
+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->pool = pool;
+    jq->size = INITIAL_MAX_JOBS;
+    status = pj_sem_create(pool, "thread_sem", 0, jq->size + 1, &jq->sem);
+    if (status != PJ_SUCCESS)
+        goto on_error;
+    jq->jobs = (job **)pj_pool_calloc(pool, jq->size, sizeof(job *));
+    jq->job_sem = (pj_sem_t **) pj_pool_calloc(pool, jq->size,
+                                               sizeof(pj_sem_t *));
+    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;
+
+#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0
+    PJ_UNUSED_ARG(status);
+#else
+    status = pj_thread_create(pool, "job_th", job_thread, jq, 0, 0,
+                              &jq->thread);
+    if (status != PJ_SUCCESS)
+        goto on_error;
+#endif /* PJ_DARWINOS */
+
+    *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;
+
+#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0
+    PJ_UNUSED_ARG(tail);
+    NSAutoreleasePool *apool = [[NSAutoreleasePool alloc]init];
+    JQDelegate *jqd = [[JQDelegate alloc]init];
+    jqd->pjob = &jb;
+    [jqd performSelectorOnMainThread:@selector(run_job)
+ 	 withObject:nil waitUntilDone:YES];
+    [jqd release];
+    [apool release];
+#else /* PJ_DARWINOS */
+    pj_mutex_lock(jq->mutex);
+    jq->jobs[jq->tail] = &jb;
+    tail = jq->tail;
+    jq->tail = (jq->tail + 1) % jq->size;
+    if (jq->tail == jq->head) {
+	jq->is_full = PJ_TRUE;
+        PJ_LOG(4, (THIS_FILE, "SDL job queue is full, increasing "
+                              "the queue 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);
+    } else {
+        pj_mutex_unlock(jq->mutex);
+        pj_sem_post(jq->sem);
+        /* Wait until our posted job is completed. */
+        pj_sem_wait(jq->job_sem[tail]);
+    }
+#endif /* PJ_DARWINOS */
+
+    *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->old_sem) {
+        for (i = 0; i < jq->size / JOB_QUEUE_INC_FACTOR; i++) {
+            if (jq->old_sem[i]) {
+                pj_sem_destroy(jq->old_sem[i]);
+                jq->old_sem[i] = NULL;
+            }
+        }
+    }
+    if (jq->mutex) {
+        pj_mutex_destroy(jq->mutex);
+        jq->mutex = NULL;
+    }
+
+    return PJ_SUCCESS;
+}
+
+#ifdef _MSC_VER
+#   if defined(PJMEDIA_SDL_LIB)
+#	pragma comment( lib, PJMEDIA_SDL_LIB)
+#   elif SDL_VERSION_ATLEAST(2,0,0)
+#	pragma comment( lib, "sdl2.lib")
+#   elif SDL_VERSION_ATLEAST(1,3,0)
+#	pragma comment( lib, "sdl.lib")
+#   endif
+#   if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL
+#	pragma comment(lib, "OpenGL32.lib")
+#   endif /* PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL */
+#endif /* _MSC_VER */
+
+
+#endif	/* PJMEDIA_VIDEO_DEV_HAS_SDL */
diff --git a/jni/pjproject-android/pjmedia/src/pjmedia-videodev/sdl_dev_m.m b/jni/pjproject-android/pjmedia/src/pjmedia-videodev/sdl_dev_m.m
new file mode 100644
index 0000000..f6b5d0e
--- /dev/null
+++ b/jni/pjproject-android/pjmedia/src/pjmedia-videodev/sdl_dev_m.m
@@ -0,0 +1,20 @@
+/* $Id: sdl_dev_m.m 3664 2011-07-19 03:42:28Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 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 "sdl_dev.c"
diff --git a/jni/pjproject-android/pjmedia/src/pjmedia-videodev/v4l2_dev.c b/jni/pjproject-android/pjmedia/src/pjmedia-videodev/v4l2_dev.c
new file mode 100644
index 0000000..5b9c5e0
--- /dev/null
+++ b/jni/pjproject-android/pjmedia/src/pjmedia-videodev/v4l2_dev.c
@@ -0,0 +1,822 @@
+/* $Id: v4l2_dev.c 4310 2012-12-19 05:38:28Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 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 <pjmedia/errno.h>
+#include <pj/assert.h>
+#include <pj/errno.h>
+#include <pj/file_access.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/rand.h>
+
+#if PJMEDIA_VIDEO_DEV_HAS_V4L2
+
+#include <linux/videodev2.h>
+#include <libv4l2.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/mman.h>
+
+#define THIS_FILE		"v4l2_dev.c"
+#define DRIVER_NAME		"v4l2"
+#define V4L2_MAX_DEVS		4
+#define DEFAULT_WIDTH		640
+#define DEFAULT_HEIGHT		480
+#define DEFAULT_FPS		25
+#define DEFAULT_CLOCK_RATE	90000
+#define INVALID_FD		-1
+#define BUFFER_CNT		2
+#define MAX_IOCTL_RETRY		20
+
+
+/* mapping between pjmedia_fmt_id and v4l2 pixel format */
+typedef struct vid4lin_fmt_map
+{
+    pj_uint32_t pjmedia_fmt_id;
+    pj_uint32_t	v4l2_fmt_id;
+} vid4lin_fmt_map;
+
+/* I/O type being used */
+enum vid4lin_io_type
+{
+    IO_TYPE_NONE,
+    IO_TYPE_READ,
+    IO_TYPE_MMAP,
+    IO_TYPE_MMAP_USER
+};
+
+/* descriptor for each mmap-ed buffer */
+typedef struct vid4lin_buffer
+{
+    void   *start;
+    size_t  length;
+} vid4lin_buffer;
+
+/* v4l2 device info */
+typedef struct vid4lin_dev_info
+{
+    pjmedia_vid_dev_info	 info;
+    char			 dev_name[32];
+    struct v4l2_capability 	 v4l2_cap;
+} vid4lin_dev_info;
+
+/* v4l2 factory */
+typedef struct vid4lin_factory
+{
+    pjmedia_vid_dev_factory	 base;
+    pj_pool_t			*pool;
+    pj_pool_t			*dev_pool;
+    pj_pool_factory		*pf;
+
+    unsigned			 dev_count;
+    vid4lin_dev_info		*dev_info;
+} vid4lin_factory;
+
+/* Video stream. */
+typedef struct vid4lin_stream
+{
+    pjmedia_vid_dev_stream	 base;		/**< Base stream	*/
+    pjmedia_vid_dev_param	 param;		/**< Settings		*/
+    pj_pool_t           	*pool;		/**< Memory pool.	*/
+
+    int			 	 fd;		/**< Video fd.		*/
+    char			 name[64];	/**< Name for log	*/
+    enum vid4lin_io_type	 io_type;	/**< I/O method.	*/
+    unsigned			 buf_cnt;	/**< MMap buf cnt.  	*/
+    vid4lin_buffer		*buffers;	/**< MMap buffers.  	*/
+    pj_time_val			 start_time;	/**< Time when started	*/
+
+    pjmedia_vid_dev_cb       	 vid_cb;	/**< Stream callback  	*/
+    void                	*user_data;	/**< Application data 	*/
+} vid4lin_stream;
+
+/* Use this to convert between pjmedia_format_id and V4L2 fourcc */
+static vid4lin_fmt_map v4l2_fmt_maps[] =
+{
+    { PJMEDIA_FORMAT_RGB24,	V4L2_PIX_FMT_BGR24 },
+    { PJMEDIA_FORMAT_RGBA,	V4L2_PIX_FMT_BGR32 },
+    { PJMEDIA_FORMAT_RGB32,	V4L2_PIX_FMT_BGR32 },
+    { PJMEDIA_FORMAT_AYUV,	V4L2_PIX_FMT_YUV32 },
+    { PJMEDIA_FORMAT_YUY2,	V4L2_PIX_FMT_YUYV },
+    { PJMEDIA_FORMAT_UYVY,	V4L2_PIX_FMT_UYVY }
+};
+
+/* Prototypes */
+static pj_status_t vid4lin_factory_init(pjmedia_vid_dev_factory *f);
+static pj_status_t vid4lin_factory_destroy(pjmedia_vid_dev_factory *f);
+static pj_status_t vid4lin_factory_refresh(pjmedia_vid_dev_factory *f);
+static unsigned    vid4lin_factory_get_dev_count(pjmedia_vid_dev_factory *f);
+static pj_status_t vid4lin_factory_get_dev_info(pjmedia_vid_dev_factory *f,
+					        unsigned index,
+					        pjmedia_vid_dev_info *info);
+static pj_status_t vid4lin_factory_default_param(pj_pool_t *pool,
+                                                 pjmedia_vid_dev_factory *f,
+					         unsigned index,
+					         pjmedia_vid_dev_param *param);
+static pj_status_t vid4lin_factory_create_stream(pjmedia_vid_dev_factory *f,
+						 pjmedia_vid_dev_param *prm,
+					         const pjmedia_vid_dev_cb *cb,
+					         void *user_data,
+					         pjmedia_vid_dev_stream **p);
+
+static pj_status_t vid4lin_stream_get_param(pjmedia_vid_dev_stream *strm,
+					    pjmedia_vid_dev_param *param);
+static pj_status_t vid4lin_stream_get_cap(pjmedia_vid_dev_stream *strm,
+				          pjmedia_vid_dev_cap cap,
+				          void *value);
+static pj_status_t vid4lin_stream_set_cap(pjmedia_vid_dev_stream *strm,
+				          pjmedia_vid_dev_cap cap,
+				          const void *value);
+static pj_status_t vid4lin_stream_get_frame(pjmedia_vid_dev_stream *strm,
+                                            pjmedia_frame *frame);
+static pj_status_t vid4lin_stream_start(pjmedia_vid_dev_stream *strm);
+static pj_status_t vid4lin_stream_stop(pjmedia_vid_dev_stream *strm);
+static pj_status_t vid4lin_stream_destroy(pjmedia_vid_dev_stream *strm);
+
+/* Operations */
+static pjmedia_vid_dev_factory_op factory_op =
+{
+    &vid4lin_factory_init,
+    &vid4lin_factory_destroy,
+    &vid4lin_factory_get_dev_count,
+    &vid4lin_factory_get_dev_info,
+    &vid4lin_factory_default_param,
+    &vid4lin_factory_create_stream,
+    &vid4lin_factory_refresh
+};
+
+static pjmedia_vid_dev_stream_op stream_op =
+{
+    &vid4lin_stream_get_param,
+    &vid4lin_stream_get_cap,
+    &vid4lin_stream_set_cap,
+    &vid4lin_stream_start,
+    &vid4lin_stream_get_frame,
+    NULL,
+    &vid4lin_stream_stop,
+    &vid4lin_stream_destroy
+};
+
+
+/****************************************************************************
+ * Factory operations
+ */
+/*
+ * Factory creation function.
+ */
+pjmedia_vid_dev_factory* pjmedia_v4l2_factory(pj_pool_factory *pf)
+{
+    vid4lin_factory *f;
+    pj_pool_t *pool;
+
+    pool = pj_pool_create(pf, DRIVER_NAME, 512, 512, NULL);
+    f = PJ_POOL_ZALLOC_T(pool, vid4lin_factory);
+    f->pf = pf;
+    f->pool = pool;
+    f->base.op = &factory_op;
+
+    return &f->base;
+}
+
+/* util: ioctl that tries harder. */
+static pj_status_t xioctl(int fh, int request, void *arg)
+{
+    enum { RETRY = MAX_IOCTL_RETRY };
+    int r, c=0;
+
+    do {
+	r = v4l2_ioctl(fh, request, arg);
+    } while (r==-1 && c++<RETRY && ((errno==EINTR) || (errno==EAGAIN)));
+
+    return (r == -1) ? pj_get_os_error() : PJ_SUCCESS;
+}
+
+/* Scan V4L2 devices */
+static pj_status_t v4l2_scan_devs(vid4lin_factory *f)
+{
+    vid4lin_dev_info vdi[V4L2_MAX_DEVS];
+    char dev_name[32];
+    unsigned i, old_count;
+    pj_status_t status;
+
+    if (f->dev_pool) {
+        pj_pool_release(f->dev_pool);
+        f->dev_pool = NULL;
+    }
+
+    pj_bzero(vdi, sizeof(vdi));
+    old_count = f->dev_count;
+    f->dev_count = 0;
+    f->dev_pool = pj_pool_create(f->pf, DRIVER_NAME, 500, 500, NULL);
+
+    for (i=0; i<V4L2_MAX_DEVS && f->dev_count < V4L2_MAX_DEVS; ++i) {
+	int fd;
+	vid4lin_dev_info *pdi;
+	pj_uint32_t fmt_cap[8];
+	int j, fmt_cnt=0;
+
+	pdi = &vdi[f->dev_count];
+
+	snprintf(dev_name, sizeof(dev_name), "/dev/video%d", i);
+	if (!pj_file_exists(dev_name))
+	    continue;
+
+	fd = v4l2_open(dev_name, O_RDWR, 0);
+	if (fd == -1)
+	    continue;
+
+	status = xioctl(fd, VIDIOC_QUERYCAP, &pdi->v4l2_cap);
+	if (status != PJ_SUCCESS) {
+	    PJ_PERROR(4,(THIS_FILE, status, "Error querying %s", dev_name));
+	    v4l2_close(fd);
+	    continue;
+	}
+
+	if ((pdi->v4l2_cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) == 0) {
+	    v4l2_close(fd);
+	    continue;
+	}
+
+	PJ_LOG(5,(THIS_FILE, "Found capture device %s", pdi->v4l2_cap.card));
+	PJ_LOG(5,(THIS_FILE, "  Enumerating formats:"));
+	for (j=0; fmt_cnt<PJ_ARRAY_SIZE(fmt_cap); ++j) {
+	    struct v4l2_fmtdesc fdesc;
+	    unsigned k;
+
+	    pj_bzero(&fdesc, sizeof(fdesc));
+	    fdesc.index = j;
+	    fdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+
+	    status = xioctl(fd, VIDIOC_ENUM_FMT, &fdesc);
+	    if (status != PJ_SUCCESS)
+		break;
+
+	    for (k=0; k<PJ_ARRAY_SIZE(v4l2_fmt_maps); ++k) {
+		if (v4l2_fmt_maps[k].v4l2_fmt_id == fdesc.pixelformat) {
+		    fmt_cap[fmt_cnt++] = v4l2_fmt_maps[k].pjmedia_fmt_id;
+		    PJ_LOG(5,(THIS_FILE, "   Supported: %s",
+			      fdesc.description));
+		    break;
+		}
+	    }
+	    if (k==PJ_ARRAY_SIZE(v4l2_fmt_maps)) {
+		PJ_LOG(5,(THIS_FILE, "   Unsupported: %s", fdesc.description));
+	    }
+	}
+
+	v4l2_close(fd);
+
+	if (fmt_cnt==0) {
+	    PJ_LOG(5,(THIS_FILE, "    Found no common format"));
+	    continue;
+	}
+
+	strncpy(pdi->dev_name, dev_name, sizeof(pdi->dev_name));
+	pdi->dev_name[sizeof(pdi->dev_name)-1] = '\0';
+	strncpy(pdi->info.name, (char*)pdi->v4l2_cap.card,
+		sizeof(pdi->info.name));
+	pdi->info.name[sizeof(pdi->info.name)-1] = '\0';
+	strncpy(pdi->info.driver, DRIVER_NAME, sizeof(pdi->info.driver));
+	pdi->info.driver[sizeof(pdi->info.driver)-1] = '\0';
+	pdi->info.dir = PJMEDIA_DIR_CAPTURE;
+	pdi->info.has_callback = PJ_FALSE;
+	pdi->info.caps = PJMEDIA_VID_DEV_CAP_FORMAT;
+
+	pdi->info.fmt_cnt = fmt_cnt;
+	for (j=0; j<fmt_cnt; ++j) {
+	    pjmedia_format_init_video(&pdi->info.fmt[j],
+				      fmt_cap[j],
+				      DEFAULT_WIDTH,
+				      DEFAULT_HEIGHT,
+				      DEFAULT_FPS, 1);
+	}
+	if (j < fmt_cnt)
+	    continue;
+
+	f->dev_count++;
+    }
+
+    if (f->dev_count == 0)
+	return PJ_SUCCESS;
+
+    if (f->dev_count > old_count || f->dev_info == NULL) {
+	f->dev_info = (vid4lin_dev_info*)
+		      pj_pool_calloc(f->dev_pool,
+				     f->dev_count,
+				     sizeof(vid4lin_dev_info));
+    }
+    pj_memcpy(f->dev_info, vdi, f->dev_count * sizeof(vid4lin_dev_info));
+
+    return PJ_SUCCESS;
+}
+
+
+/* API: init factory */
+static pj_status_t vid4lin_factory_init(pjmedia_vid_dev_factory *f)
+{
+    return vid4lin_factory_refresh(f);
+}
+
+/* API: destroy factory */
+static pj_status_t vid4lin_factory_destroy(pjmedia_vid_dev_factory *f)
+{
+    vid4lin_factory *cf = (vid4lin_factory*)f;
+    pj_pool_t *pool = cf->pool;
+
+    if (cf->dev_pool)
+        pj_pool_release(cf->dev_pool);
+    if (cf->pool) {
+	cf->pool = NULL;
+	pj_pool_release(pool);
+    }
+
+    return PJ_SUCCESS;
+}
+
+/* API: refresh the list of devices */
+static pj_status_t vid4lin_factory_refresh(pjmedia_vid_dev_factory *f)
+{
+    vid4lin_factory *cf = (vid4lin_factory*)f;
+    pj_status_t status;
+
+    status = v4l2_scan_devs(cf);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    PJ_LOG(4, (THIS_FILE, "Video4Linux2 has %d devices",
+	       cf->dev_count));
+
+    return PJ_SUCCESS;
+}
+
+/* API: get number of devices */
+static unsigned vid4lin_factory_get_dev_count(pjmedia_vid_dev_factory *f)
+{
+    vid4lin_factory *cf = (vid4lin_factory*)f;
+    return cf->dev_count;
+}
+
+/* API: get device info */
+static pj_status_t vid4lin_factory_get_dev_info(pjmedia_vid_dev_factory *f,
+					     unsigned index,
+					     pjmedia_vid_dev_info *info)
+{
+    vid4lin_factory *cf = (vid4lin_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 vid4lin_factory_default_param(pj_pool_t *pool,
+                                                 pjmedia_vid_dev_factory *f,
+                                                 unsigned index,
+                                                 pjmedia_vid_dev_param *param)
+{
+    vid4lin_factory *cf = (vid4lin_factory*)f;
+
+    PJ_ASSERT_RETURN(index < cf->dev_count, PJMEDIA_EVID_INVDEV);
+
+    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;
+    pjmedia_format_copy(&param->fmt, &cf->dev_info[index].info.fmt[0]);
+
+    return PJ_SUCCESS;
+}
+
+static vid4lin_fmt_map* get_v4l2_format_info(pjmedia_format_id id)
+{
+    unsigned i;
+
+    for (i = 0; i < PJ_ARRAY_SIZE(v4l2_fmt_maps); i++) {
+        if (v4l2_fmt_maps[i].pjmedia_fmt_id == id)
+            return &v4l2_fmt_maps[i];
+    }
+
+    return NULL;
+}
+
+/* util: setup format */
+static pj_status_t vid4lin_stream_init_fmt(vid4lin_stream *stream,
+					const pjmedia_vid_dev_param *param,
+					pj_uint32_t pix_fmt)
+{
+    pjmedia_video_format_detail *vfd;
+    struct v4l2_format v4l2_fmt;
+    pj_status_t status;
+
+    vfd = pjmedia_format_get_video_format_detail(&param->fmt, PJ_TRUE);
+    if (vfd == NULL)
+	return PJMEDIA_EVID_BADFORMAT;
+
+    pj_bzero(&v4l2_fmt, sizeof(v4l2_fmt));
+    v4l2_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+    v4l2_fmt.fmt.pix.width       = vfd->size.w;
+    v4l2_fmt.fmt.pix.height      = vfd->size.h;
+    v4l2_fmt.fmt.pix.pixelformat = pix_fmt;
+    v4l2_fmt.fmt.pix.field       = V4L2_FIELD_INTERLACED;
+    status = xioctl(stream->fd, VIDIOC_S_FMT, &v4l2_fmt);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    if (v4l2_fmt.fmt.pix.pixelformat != pix_fmt) {
+	status = PJMEDIA_EVID_BADFORMAT;
+	return status;
+    }
+
+    if ((v4l2_fmt.fmt.pix.width != vfd->size.w) ||
+	(v4l2_fmt.fmt.pix.height != vfd->size.h))
+    {
+	/* Size has changed */
+	vfd->size.w = v4l2_fmt.fmt.pix.width;
+	vfd->size.h = v4l2_fmt.fmt.pix.height;
+    }
+
+    return PJ_SUCCESS;
+}
+
+/* Util: initiate v4l2 streaming via mmap */
+static pj_status_t vid4lin_stream_init_streaming(vid4lin_stream *stream)
+{
+    struct v4l2_requestbuffers req;
+    unsigned i;
+    pj_status_t status;
+
+    pj_bzero(&req, sizeof(req));
+    req.count = BUFFER_CNT;
+    req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+    req.memory = V4L2_MEMORY_MMAP;
+    status = xioctl(stream->fd, VIDIOC_REQBUFS, &req);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    stream->buffers = pj_pool_calloc(stream->pool, req.count,
+				     sizeof(*stream->buffers));
+    stream->buf_cnt = 0;
+
+    for (i = 0; i < req.count; ++i) {
+	struct v4l2_buffer buf;
+
+	pj_bzero(&buf, sizeof(buf));
+
+	buf.type        = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+	buf.memory      = V4L2_MEMORY_MMAP;
+	buf.index       = i;
+
+	status = xioctl(stream->fd, VIDIOC_QUERYBUF, &buf);
+	if (status != PJ_SUCCESS)
+	    goto on_error;
+
+	stream->buffers[i].length = buf.length;
+	stream->buffers[i].start = v4l2_mmap(NULL, buf.length,
+					     PROT_READ | PROT_WRITE,
+					     MAP_SHARED, stream->fd,
+					     buf.m.offset);
+
+	if (MAP_FAILED == stream->buffers[i].start) {
+	    status = pj_get_os_error();
+	    goto on_error;
+	}
+
+	stream->buf_cnt++;
+    }
+
+    PJ_LOG(5,(THIS_FILE, "  mmap streaming initialized"));
+
+    stream->io_type = IO_TYPE_MMAP;
+    return PJ_SUCCESS;
+
+on_error:
+    return status;
+}
+
+/* init streaming with user pointer */
+static pj_status_t vid4lin_stream_init_streaming_user(vid4lin_stream *stream)
+{
+    return PJ_ENOTSUP;
+}
+
+/* init streaming with read() */
+static pj_status_t vid4lin_stream_init_read_write(vid4lin_stream *stream)
+{
+    return PJ_ENOTSUP;
+}
+
+/* API: create stream */
+static pj_status_t vid4lin_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)
+{
+    vid4lin_factory *cf = (vid4lin_factory*)f;
+    pj_pool_t *pool;
+    vid4lin_stream *stream;
+    vid4lin_dev_info *vdi;
+    const vid4lin_fmt_map *fmt_map;
+    const pjmedia_video_format_info *fmt_info;
+    pjmedia_video_format_detail *vfd;
+    pj_status_t status = PJ_SUCCESS;
+
+
+    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_ASSERT_RETURN(param->cap_id >= 0 && param->cap_id < cf->dev_count,
+		     PJMEDIA_EVID_INVDEV);
+
+    fmt_info = pjmedia_get_video_format_info(NULL, param->fmt.id);
+    if (!fmt_info || (fmt_map=get_v4l2_format_info(param->fmt.id))==NULL)
+        return PJMEDIA_EVID_BADFORMAT;
+
+    vdi = &cf->dev_info[param->cap_id];
+    vfd = pjmedia_format_get_video_format_detail(&param->fmt, PJ_TRUE);
+
+    /* Create and Initialize stream descriptor */
+    pool = pj_pool_create(cf->pf, vdi->info.name, 512, 512, NULL);
+    PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);
+
+    stream = PJ_POOL_ZALLOC_T(pool, vid4lin_stream);
+    pj_memcpy(&stream->param, param, sizeof(*param));
+    stream->pool = pool;
+    pj_memcpy(&stream->vid_cb, cb, sizeof(*cb));
+    strncpy(stream->name, vdi->info.name, sizeof(stream->name));
+    stream->name[sizeof(stream->name)-1] = '\0';
+    stream->user_data = user_data;
+    stream->fd = INVALID_FD;
+
+    stream->fd = v4l2_open(vdi->dev_name, O_RDWR, 0);
+    if (stream->fd < 0)
+	goto on_error;
+
+    status = vid4lin_stream_init_fmt(stream, param, fmt_map->v4l2_fmt_id);
+    if (status != PJ_SUCCESS)
+	goto on_error;
+
+    if (vdi->v4l2_cap.capabilities & V4L2_CAP_STREAMING)
+	status = vid4lin_stream_init_streaming(stream);
+
+    if (status!=PJ_SUCCESS && vdi->v4l2_cap.capabilities & V4L2_CAP_STREAMING)
+	status = vid4lin_stream_init_streaming_user(stream);
+
+    if (status!=PJ_SUCCESS && vdi->v4l2_cap.capabilities & V4L2_CAP_READWRITE)
+	status = vid4lin_stream_init_read_write(stream);
+
+    if (status != PJ_SUCCESS) {
+	PJ_LOG(1,(THIS_FILE, "Error: unable to initiate I/O on %s",
+		  stream->name));
+	goto on_error;
+    }
+
+    /* Done */
+    stream->base.op = &stream_op;
+    *p_vid_strm = &stream->base;
+
+    return PJ_SUCCESS;
+
+on_error:
+    if (status == PJ_SUCCESS)
+	status = PJ_RETURN_OS_ERROR(errno);
+
+    vid4lin_stream_destroy(&stream->base);
+    return status;
+}
+
+/* API: Get stream info. */
+static pj_status_t vid4lin_stream_get_param(pjmedia_vid_dev_stream *s,
+					    pjmedia_vid_dev_param *pi)
+{
+    vid4lin_stream *strm = (vid4lin_stream*)s;
+
+    PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
+
+    pj_memcpy(pi, &strm->param, sizeof(*pi));
+
+    return PJ_SUCCESS;
+}
+
+/* API: get capability */
+static pj_status_t vid4lin_stream_get_cap(pjmedia_vid_dev_stream *s,
+                                          pjmedia_vid_dev_cap cap,
+                                          void *pval)
+{
+    vid4lin_stream *strm = (vid4lin_stream*)s;
+
+    PJ_UNUSED_ARG(strm);
+
+    PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+    if (cap==PJMEDIA_VID_DEV_CAP_INPUT_SCALE)
+    {
+        return PJMEDIA_EVID_INVCAP;
+//	return PJ_SUCCESS;
+    } else {
+	return PJMEDIA_EVID_INVCAP;
+    }
+}
+
+/* API: set capability */
+static pj_status_t vid4lin_stream_set_cap(pjmedia_vid_dev_stream *s,
+                                          pjmedia_vid_dev_cap cap,
+                                          const void *pval)
+{
+    vid4lin_stream *strm = (vid4lin_stream*)s;
+
+
+    PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+    /*
+    if (cap==PJMEDIA_VID_DEV_CAP_INPUT_SCALE)
+    {
+	return PJ_SUCCESS;
+    }
+    */
+    PJ_UNUSED_ARG(strm);
+    PJ_UNUSED_ARG(cap);
+    PJ_UNUSED_ARG(pval);
+
+    return PJMEDIA_EVID_INVCAP;
+}
+
+/* get frame from mmap */
+static pj_status_t vid4lin_stream_get_frame_mmap(vid4lin_stream *stream,
+                                                 pjmedia_frame *frame)
+{
+    struct v4l2_buffer buf;
+    pj_time_val time;
+    pj_status_t status = PJ_SUCCESS;
+    unsigned tmp_idx;
+
+    pj_bzero(&buf, sizeof(buf));
+    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+    buf.memory = V4L2_MEMORY_MMAP;
+    status = xioctl(stream->fd, VIDIOC_DQBUF, &buf);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    if (frame->size < buf.bytesused) {
+	/* supplied buffer is too small */
+	pj_assert(!"frame buffer is too small for v4l2");
+	status = PJ_ETOOSMALL;
+	goto on_return;
+    }
+
+    time.sec = buf.timestamp.tv_sec;
+    time.msec = buf.timestamp.tv_usec / 1000;
+    PJ_TIME_VAL_SUB(time, stream->start_time);
+
+    frame->type = PJMEDIA_FRAME_TYPE_VIDEO;
+    frame->bit_info = 0;
+    frame->size = buf.bytesused;
+    frame->timestamp.u64 = PJ_UINT64(1) * PJ_TIME_VAL_MSEC(time) *
+			   stream->param.clock_rate / PJ_UINT64(1000);
+    pj_memcpy(frame->buf, stream->buffers[buf.index].start, buf.bytesused);
+
+on_return:
+    tmp_idx = buf.index;
+    pj_bzero(&buf, sizeof(buf));
+    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+    buf.memory = V4L2_MEMORY_MMAP;
+    buf.index = tmp_idx;
+    xioctl(stream->fd, VIDIOC_QBUF, &buf);
+
+    return status;
+}
+
+/* API: Get frame from stream */
+static pj_status_t vid4lin_stream_get_frame(pjmedia_vid_dev_stream *strm,
+                                            pjmedia_frame *frame)
+{
+    vid4lin_stream *stream = (vid4lin_stream*)strm;
+
+    if (stream->io_type == IO_TYPE_MMAP)
+	return vid4lin_stream_get_frame_mmap(stream, frame);
+    else {
+	pj_assert(!"Unsupported i/o type");
+	return PJ_EINVALIDOP;
+    }
+}
+
+/* API: Start stream. */
+static pj_status_t vid4lin_stream_start(pjmedia_vid_dev_stream *strm)
+{
+    vid4lin_stream *stream = (vid4lin_stream*)strm;
+    struct v4l2_buffer buf;
+    enum v4l2_buf_type type;
+    unsigned i;
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(stream->fd != -1, PJ_EINVALIDOP);
+
+    PJ_LOG(4, (THIS_FILE, "Starting v4l2 video stream %s", stream->name));
+
+    pj_gettimeofday(&stream->start_time);
+
+    for (i = 0; i < stream->buf_cnt; ++i) {
+	pj_bzero(&buf, sizeof(buf));
+	buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+	buf.memory = V4L2_MEMORY_MMAP;
+	buf.index = i;
+	status = xioctl(stream->fd, VIDIOC_QBUF, &buf);
+	if (status != PJ_SUCCESS)
+	    goto on_error;
+    }
+    type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+
+    status = xioctl(stream->fd, VIDIOC_STREAMON, &type);
+    if (status != PJ_SUCCESS)
+	goto on_error;
+
+    return PJ_SUCCESS;
+
+on_error:
+    if (i > 0) {
+	/* Dequeue already enqueued buffers. Can we do this while streaming
+	 * is not started?
+	 */
+	unsigned n = i;
+	for (i=0; i<n; ++i) {
+	    pj_bzero(&buf, sizeof(buf));
+	    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+	    buf.memory = V4L2_MEMORY_MMAP;
+	    xioctl(stream->fd, VIDIOC_DQBUF, &buf);
+	}
+    }
+    return status;
+}
+
+/* API: Stop stream. */
+static pj_status_t vid4lin_stream_stop(pjmedia_vid_dev_stream *strm)
+{
+    vid4lin_stream *stream = (vid4lin_stream*)strm;
+    enum v4l2_buf_type type;
+    pj_status_t status;
+
+    if (stream->fd < 0)
+	return PJ_SUCCESS;
+
+    PJ_LOG(4, (THIS_FILE, "Stopping v4l2 video stream %s", stream->name));
+
+    type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+    status = xioctl(stream->fd, VIDIOC_STREAMOFF, &type);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    return PJ_SUCCESS;
+}
+
+
+/* API: Destroy stream. */
+static pj_status_t vid4lin_stream_destroy(pjmedia_vid_dev_stream *strm)
+{
+    vid4lin_stream *stream = (vid4lin_stream*)strm;
+    unsigned i;
+
+    PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
+
+    vid4lin_stream_stop(strm);
+
+    PJ_LOG(4, (THIS_FILE, "Destroying v4l2 video stream %s", stream->name));
+
+    for (i=0; i<stream->buf_cnt; ++i) {
+	if (stream->buffers[i].start != MAP_FAILED) {
+	    v4l2_munmap(stream->buffers[i].start, stream->buffers[i].length);
+	    stream->buffers[i].start = MAP_FAILED;
+	}
+    }
+
+    if (stream->fd >= 0) {
+	v4l2_close(stream->fd);
+	stream->fd = -1;
+    }
+    pj_pool_release(stream->pool);
+
+    return PJ_SUCCESS;
+}
+
+#endif	/* PJMEDIA_VIDEO_DEV_HAS_V4L2 */
diff --git a/jni/pjproject-android/pjmedia/src/pjmedia-videodev/videodev.c b/jni/pjproject-android/pjmedia/src/pjmedia-videodev/videodev.c
new file mode 100644
index 0000000..080b437
--- /dev/null
+++ b/jni/pjproject-android/pjmedia/src/pjmedia-videodev/videodev.c
@@ -0,0 +1,877 @@
+/* $Id: videodev.c 4016 2012-04-04 05:05:50Z bennylp $ */
+/* 
+ * Copyright (C) 2008-2011 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/errno.h>
+#include <pj/log.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+
+
+#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
+
+
+#define THIS_FILE   "videodev.c"
+
+#define DEFINE_CAP(name, info)	{name, info}
+
+/* Capability names */
+static struct cap_info
+{
+    const char *name;
+    const char *info;
+} cap_infos[] = 
+{
+    DEFINE_CAP("format",        "Video format"),
+    DEFINE_CAP("scale",         "Input dimension"),
+    DEFINE_CAP("window",        "Window handle"),
+    DEFINE_CAP("resize",        "Renderer resize"),
+    DEFINE_CAP("position",      "Renderer position"),
+    DEFINE_CAP("hide",          "Renderer hide"),
+    DEFINE_CAP("preview",       "Input preview"),
+    DEFINE_CAP("orientation",   "Video orientation"),
+    DEFINE_CAP("switch",        "Switch device"),
+    DEFINE_CAP("wndflags",      "Window flags")
+};
+
+
+/*
+ * The device index seen by application and driver is different. 
+ *
+ * At application level, device index is index to global list of device.
+ * At driver level, device index is index to device list on that particular
+ * factory only.
+ */
+#define MAKE_DEV_ID(f_id, index)   (((f_id & 0xFFFF) << 16) | (index & 0xFFFF))
+#define GET_INDEX(dev_id)	   ((dev_id) & 0xFFFF)
+#define GET_FID(dev_id)		   ((dev_id) >> 16)
+#define DEFAULT_DEV_ID		    0
+
+
+/* extern functions to create factories */
+#if PJMEDIA_VIDEO_DEV_HAS_NULL_VIDEO
+pjmedia_vid_dev_factory* pjmedia_null_video_factory(pj_pool_factory *pf);
+#endif
+
+#if PJMEDIA_VIDEO_DEV_HAS_DSHOW
+pjmedia_vid_dev_factory* pjmedia_dshow_factory(pj_pool_factory *pf);
+#endif
+
+#if PJMEDIA_VIDEO_DEV_HAS_CBAR_SRC
+pjmedia_vid_dev_factory* pjmedia_cbar_factory(pj_pool_factory *pf);
+#endif
+
+#if PJMEDIA_VIDEO_DEV_HAS_SDL
+pjmedia_vid_dev_factory* pjmedia_sdl_factory(pj_pool_factory *pf);
+#endif
+
+#if PJMEDIA_VIDEO_DEV_HAS_FFMPEG
+pjmedia_vid_dev_factory* pjmedia_ffmpeg_factory(pj_pool_factory *pf);
+#endif
+
+#if PJMEDIA_VIDEO_DEV_HAS_V4L2
+pjmedia_vid_dev_factory* pjmedia_v4l2_factory(pj_pool_factory *pf);
+#endif
+
+#if PJMEDIA_VIDEO_DEV_HAS_QT
+pjmedia_vid_dev_factory* pjmedia_qt_factory(pj_pool_factory *pf);
+#endif
+
+#if PJMEDIA_VIDEO_DEV_HAS_IOS
+pjmedia_vid_dev_factory* pjmedia_ios_factory(pj_pool_factory *pf);
+#endif
+
+#define MAX_DRIVERS	16
+#define MAX_DEVS	64
+
+
+/* driver structure */
+struct driver
+{
+    /* Creation function */
+    pjmedia_vid_dev_factory_create_func_ptr create;
+    /* Factory instance */
+    pjmedia_vid_dev_factory *f;
+    char		     name[32];	    /* Driver name		    */
+    unsigned		     dev_cnt;	    /* Number of devices	    */
+    unsigned		     start_idx;	    /* Start index in global list   */
+    int			     cap_dev_idx;   /* Default capture device.	    */
+    int			     rend_dev_idx;  /* Default render device	    */
+};
+
+/* The video device subsystem */
+static struct vid_subsys
+{
+    unsigned	     init_count;	/* How many times init() is called  */
+    pj_pool_factory *pf;		/* The pool factory.		    */
+
+    unsigned	     drv_cnt;		/* Number of drivers.		    */
+    struct driver    drv[MAX_DRIVERS];	/* Array of drivers.		    */
+
+    unsigned	     dev_cnt;		/* Total number of devices.	    */
+    pj_uint32_t	     dev_list[MAX_DEVS];/* Array of device IDs.		    */
+
+} vid_subsys;
+
+/* API: get capability name/info */
+PJ_DEF(const char*) pjmedia_vid_dev_cap_name(pjmedia_vid_dev_cap cap,
+					     const char **p_desc)
+{
+    const char *desc;
+    unsigned i;
+
+    if (p_desc==NULL) p_desc = &desc;
+
+    for (i=0; i<PJ_ARRAY_SIZE(cap_infos); ++i) {
+	if ((1 << i)==cap)
+	    break;
+    }
+
+    if (i==PJ_ARRAY_SIZE(cap_infos)) {
+	*p_desc = "??";
+	return "??";
+    }
+
+    *p_desc = cap_infos[i].info;
+    return cap_infos[i].name;
+}
+
+static pj_status_t get_cap_pointer(const pjmedia_vid_dev_param *param,
+				   pjmedia_vid_dev_cap cap,
+				   void **ptr,
+				   unsigned *size)
+{
+#define FIELD_INFO(name)    *ptr = (void*)&param->name; \
+			    *size = sizeof(param->name)
+
+    switch (cap) {
+    case PJMEDIA_VID_DEV_CAP_FORMAT:
+	FIELD_INFO(fmt);
+	break;
+    case PJMEDIA_VID_DEV_CAP_INPUT_SCALE:
+	FIELD_INFO(disp_size);
+	break;
+    case PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW:
+	FIELD_INFO(window);
+	break;
+    case PJMEDIA_VID_DEV_CAP_OUTPUT_RESIZE:
+	FIELD_INFO(disp_size);
+	break;
+    case PJMEDIA_VID_DEV_CAP_OUTPUT_POSITION:
+	FIELD_INFO(window_pos);
+	break;
+    case PJMEDIA_VID_DEV_CAP_OUTPUT_HIDE:
+	FIELD_INFO(window_hide);
+	break;
+    case PJMEDIA_VID_DEV_CAP_INPUT_PREVIEW:
+	FIELD_INFO(native_preview);
+	break;
+    case PJMEDIA_VID_DEV_CAP_ORIENTATION:
+	FIELD_INFO(orient);
+	break;
+    /* The PJMEDIA_VID_DEV_CAP_SWITCH does not have an entry in the
+     * param (it doesn't make sense to open a stream and tell it
+     * to switch immediately).
+     */
+    case PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW_FLAGS:
+	FIELD_INFO(window_flags);
+	break;
+    default:
+	return PJMEDIA_EVID_INVCAP;
+    }
+
+#undef FIELD_INFO
+
+    return PJ_SUCCESS;
+}
+
+/* API: set cap value to param */
+PJ_DEF(pj_status_t)
+pjmedia_vid_dev_param_set_cap( pjmedia_vid_dev_param *param,
+			       pjmedia_vid_dev_cap cap,
+			       const void *pval)
+{
+    void *cap_ptr;
+    unsigned cap_size;
+    pj_status_t status;
+
+    status = get_cap_pointer(param, cap, &cap_ptr, &cap_size);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    pj_memcpy(cap_ptr, pval, cap_size);
+    param->flags |= cap;
+
+    return PJ_SUCCESS;
+}
+
+/* API: get cap value from param */
+PJ_DEF(pj_status_t)
+pjmedia_vid_dev_param_get_cap( const pjmedia_vid_dev_param *param,
+			       pjmedia_vid_dev_cap cap,
+			       void *pval)
+{
+    void *cap_ptr;
+    unsigned cap_size;
+    pj_status_t status;
+
+    status = get_cap_pointer(param, cap, &cap_ptr, &cap_size);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    if ((param->flags & cap) == 0) {
+	pj_bzero(cap_ptr, cap_size);
+	return PJMEDIA_EVID_INVCAP;
+    }
+
+    pj_memcpy(pval, cap_ptr, cap_size);
+    return PJ_SUCCESS;
+}
+
+/* Internal: init driver */
+static pj_status_t init_driver(unsigned drv_idx, pj_bool_t refresh)
+{
+    struct driver *drv = &vid_subsys.drv[drv_idx];
+    pjmedia_vid_dev_factory *f;
+    unsigned i, dev_cnt;
+    pj_status_t status;
+
+    if (!refresh) {
+        /* Create the factory */
+        f = (*drv->create)(vid_subsys.pf);
+        if (!f)
+            return PJ_EUNKNOWN;
+
+        /* Call factory->init() */
+        status = f->op->init(f);
+        if (status != PJ_SUCCESS) {
+            f->op->destroy(f);
+            return status;
+        }
+    } else {
+	f = drv->f;
+    }
+
+    /* Get number of devices */
+    dev_cnt = f->op->get_dev_count(f);
+    if (dev_cnt + vid_subsys.dev_cnt > MAX_DEVS) {
+	PJ_LOG(4,(THIS_FILE, "%d device(s) cannot be registered because"
+			      " there are too many devices",
+			      vid_subsys.dev_cnt + dev_cnt - MAX_DEVS));
+	dev_cnt = MAX_DEVS - vid_subsys.dev_cnt;
+    }
+
+    /* enabling this will cause pjsua-lib initialization to fail when there
+     * is no video device installed in the system, even when pjsua has been
+     * run with --null-video
+     *
+    if (dev_cnt == 0) {
+	f->op->destroy(f);
+	return PJMEDIA_EVID_NODEV;
+    }
+    */
+
+    /* Fill in default devices */
+    drv->rend_dev_idx = drv->cap_dev_idx = -1;
+    for (i=0; i<dev_cnt; ++i) {
+	pjmedia_vid_dev_info info;
+
+	status = f->op->get_dev_info(f, i, &info);
+	if (status != PJ_SUCCESS) {
+	    f->op->destroy(f);
+	    return status;
+	}
+
+	if (drv->name[0]=='\0') {
+	    /* Set driver name */
+	    pj_ansi_strncpy(drv->name, info.driver, sizeof(drv->name));
+	    drv->name[sizeof(drv->name)-1] = '\0';
+	}
+
+	if (drv->rend_dev_idx < 0 && (info.dir & PJMEDIA_DIR_RENDER)) {
+	    /* Set default render device */
+	    drv->rend_dev_idx = i;
+	}
+	if (drv->cap_dev_idx < 0 && (info.dir & PJMEDIA_DIR_CAPTURE)) {
+	    /* Set default capture device */
+	    drv->cap_dev_idx = i;
+	}
+
+        if (drv->rend_dev_idx >= 0 && drv->cap_dev_idx >= 0) {
+	    /* Done. */
+	    break;
+	}
+    }
+
+    /* Register the factory */
+    drv->f = f;
+    drv->f->sys.drv_idx = drv_idx;
+    drv->start_idx = vid_subsys.dev_cnt;
+    drv->dev_cnt = dev_cnt;
+
+    /* Register devices to global list */
+    for (i=0; i<dev_cnt; ++i) {
+	vid_subsys.dev_list[vid_subsys.dev_cnt++] = MAKE_DEV_ID(drv_idx, i);
+    }
+
+    return PJ_SUCCESS;
+}
+
+/* Internal: deinit driver */
+static void deinit_driver(unsigned drv_idx)
+{
+    struct driver *drv = &vid_subsys.drv[drv_idx];
+
+    if (drv->f) {
+	drv->f->op->destroy(drv->f);
+	drv->f = NULL;
+    }
+
+    drv->dev_cnt = 0;
+    drv->rend_dev_idx = drv->cap_dev_idx = -1;
+}
+
+/* API: Initialize the video device subsystem. */
+PJ_DEF(pj_status_t) pjmedia_vid_dev_subsys_init(pj_pool_factory *pf)
+{
+    unsigned i;
+    pj_status_t status = PJ_SUCCESS;
+
+    /* Allow init() to be called multiple times as long as there is matching
+     * number of shutdown().
+     */
+    if (vid_subsys.init_count++ != 0) {
+	return PJ_SUCCESS;
+    }
+
+    /* Register error subsystem */
+    pj_register_strerror(PJMEDIA_VIDEODEV_ERRNO_START, 
+			 PJ_ERRNO_SPACE_SIZE, 
+			 &pjmedia_videodev_strerror);
+
+    /* Init */
+    vid_subsys.pf = pf;
+    vid_subsys.drv_cnt = 0;
+    vid_subsys.dev_cnt = 0;
+
+    /* Register creation functions */
+#if PJMEDIA_VIDEO_DEV_HAS_V4L2
+    vid_subsys.drv[vid_subsys.drv_cnt++].create = &pjmedia_v4l2_factory;
+#endif
+#if PJMEDIA_VIDEO_DEV_HAS_QT
+    vid_subsys.drv[vid_subsys.drv_cnt++].create = &pjmedia_qt_factory;
+#endif
+#if PJMEDIA_VIDEO_DEV_HAS_IOS
+    vid_subsys.drv[vid_subsys.drv_cnt++].create = &pjmedia_ios_factory;
+#endif
+#if PJMEDIA_VIDEO_DEV_HAS_DSHOW
+    vid_subsys.drv[vid_subsys.drv_cnt++].create = &pjmedia_dshow_factory;
+#endif
+#if PJMEDIA_VIDEO_DEV_HAS_FFMPEG
+    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
+
+    /* Initialize each factory and build the device ID list */
+    for (i=0; i<vid_subsys.drv_cnt; ++i) {
+	status = init_driver(i, PJ_FALSE);
+	if (status != PJ_SUCCESS) {
+	    deinit_driver(i);
+	    continue;
+	}
+    }
+
+    return vid_subsys.dev_cnt ? PJ_SUCCESS : status;
+}
+
+/* API: register a video device factory to the video device subsystem. */
+PJ_DEF(pj_status_t)
+pjmedia_vid_register_factory(pjmedia_vid_dev_factory_create_func_ptr adf,
+                             pjmedia_vid_dev_factory *factory)
+{
+    pj_bool_t refresh = PJ_FALSE;
+    pj_status_t status;
+
+    if (vid_subsys.init_count == 0)
+	return PJMEDIA_EVID_INIT;
+
+    vid_subsys.drv[vid_subsys.drv_cnt].create = adf;
+    vid_subsys.drv[vid_subsys.drv_cnt].f = factory;
+
+    if (factory) {
+        /* Call factory->init() */
+        status = factory->op->init(factory);
+        if (status != PJ_SUCCESS) {
+            factory->op->destroy(factory);
+            return status;
+        }
+        refresh = PJ_TRUE;
+    }
+
+    status = init_driver(vid_subsys.drv_cnt, refresh);
+    if (status == PJ_SUCCESS) {
+	vid_subsys.drv_cnt++;
+    } else {
+	deinit_driver(vid_subsys.drv_cnt);
+    }
+
+    return status;
+}
+
+/* API: unregister a video device factory from the video device subsystem. */
+PJ_DEF(pj_status_t)
+pjmedia_vid_unregister_factory(pjmedia_vid_dev_factory_create_func_ptr adf,
+                               pjmedia_vid_dev_factory *factory)
+{
+    unsigned i, j;
+
+    if (vid_subsys.init_count == 0)
+	return PJMEDIA_EVID_INIT;
+
+    for (i=0; i<vid_subsys.drv_cnt; ++i) {
+	struct driver *drv = &vid_subsys.drv[i];
+
+	if ((factory && drv->f==factory) || (adf && drv->create == adf)) {
+	    for (j = drv->start_idx; j < drv->start_idx + drv->dev_cnt; j++)
+	    {
+		vid_subsys.dev_list[j] = (pj_uint32_t)PJMEDIA_VID_INVALID_DEV;
+	    }
+
+	    deinit_driver(i);
+	    pj_bzero(drv, sizeof(*drv));
+	    return PJ_SUCCESS;
+	}
+    }
+
+    return PJMEDIA_EVID_ERR;
+}
+
+/* API: get the pool factory registered to the video device subsystem. */
+PJ_DEF(pj_pool_factory*) pjmedia_vid_dev_subsys_get_pool_factory(void)
+{
+    return vid_subsys.pf;
+}
+
+/* API: Shutdown the video device subsystem. */
+PJ_DEF(pj_status_t) pjmedia_vid_dev_subsys_shutdown(void)
+{
+    unsigned i;
+
+    /* Allow shutdown() to be called multiple times as long as there is
+     * matching number of init().
+     */
+    if (vid_subsys.init_count == 0) {
+	return PJ_SUCCESS;
+    }
+    --vid_subsys.init_count;
+
+    if (vid_subsys.init_count == 0) {
+        for (i=0; i<vid_subsys.drv_cnt; ++i) {
+	    deinit_driver(i);
+        }
+
+        vid_subsys.pf = NULL;
+    }
+    return PJ_SUCCESS;
+}
+
+/* API: Refresh the list of video devices installed in the system. */
+PJ_DEF(pj_status_t) pjmedia_vid_dev_refresh(void)
+{
+    unsigned i;
+    
+    vid_subsys.dev_cnt = 0;
+    for (i=0; i<vid_subsys.drv_cnt; ++i) {
+	struct driver *drv = &vid_subsys.drv[i];
+	
+	if (drv->f && drv->f->op->refresh) {
+	    pj_status_t status = drv->f->op->refresh(drv->f);
+	    if (status != PJ_SUCCESS) {
+		PJ_PERROR(4, (THIS_FILE, status, "Unable to refresh device "
+						 "list for %s", drv->name));
+	    }
+	}
+	init_driver(i, PJ_TRUE);
+    }
+    return PJ_SUCCESS;
+}
+
+/* API: Get the number of video devices installed in the system. */
+PJ_DEF(unsigned) pjmedia_vid_dev_count(void)
+{
+    return vid_subsys.dev_cnt;
+}
+
+/* Internal: convert local index to global device index */
+static pj_status_t make_global_index(unsigned drv_idx, 
+				     pjmedia_vid_dev_index *id)
+{
+    if (*id < 0) {
+	return PJ_SUCCESS;
+    }
+
+    /* Check that factory still exists */
+    PJ_ASSERT_RETURN(vid_subsys.drv[drv_idx].f, PJ_EBUG);
+
+    /* Check that device index is valid */
+    PJ_ASSERT_RETURN(*id>=0 && *id<(int)vid_subsys.drv[drv_idx].dev_cnt, 
+		     PJ_EBUG);
+
+    *id += vid_subsys.drv[drv_idx].start_idx;
+    return PJ_SUCCESS;
+}
+
+/* Internal: lookup device id */
+static pj_status_t lookup_dev(pjmedia_vid_dev_index id,
+			      pjmedia_vid_dev_factory **p_f,
+			      unsigned *p_local_index)
+{
+    int f_id, index;
+
+    if (id < 0) {
+	unsigned i;
+
+	if (id <= PJMEDIA_VID_INVALID_DEV)
+	    return PJMEDIA_EVID_INVDEV;
+
+	for (i=0; i<vid_subsys.drv_cnt; ++i) {
+	    struct driver *drv = &vid_subsys.drv[i];
+	    if (id==PJMEDIA_VID_DEFAULT_CAPTURE_DEV && 
+		drv->cap_dev_idx >= 0) 
+	    {
+		id = drv->cap_dev_idx;
+		make_global_index(i, &id);
+		break;
+	    } else if (id==PJMEDIA_VID_DEFAULT_RENDER_DEV && 
+		drv->rend_dev_idx >= 0) 
+	    {
+		id = drv->rend_dev_idx;
+		make_global_index(i, &id);
+		break;
+	    }
+	}
+
+	if (id < 0) {
+	    return PJMEDIA_EVID_NODEFDEV;
+	}
+    }
+
+    f_id = GET_FID(vid_subsys.dev_list[id]);
+    index = GET_INDEX(vid_subsys.dev_list[id]);
+
+    if (f_id < 0 || f_id >= (int)vid_subsys.drv_cnt)
+	return PJMEDIA_EVID_INVDEV;
+
+    if (index < 0 || index >= (int)vid_subsys.drv[f_id].dev_cnt)
+	return PJMEDIA_EVID_INVDEV;
+
+    *p_f = vid_subsys.drv[f_id].f;
+    *p_local_index = (unsigned)index;
+
+    return PJ_SUCCESS;
+
+}
+
+/* API: lookup device id */
+PJ_DEF(pj_status_t)
+pjmedia_vid_dev_get_local_index(pjmedia_vid_dev_index id,
+                                pjmedia_vid_dev_factory **p_f,
+                                unsigned *p_local_index)
+{
+    return lookup_dev(id, p_f, p_local_index);
+}
+
+/* API: from factory and local index, get global index */
+PJ_DEF(pj_status_t)
+pjmedia_vid_dev_get_global_index(const pjmedia_vid_dev_factory *f,
+                                 unsigned local_idx,
+                                 pjmedia_vid_dev_index *pid)
+{
+    PJ_ASSERT_RETURN(f->sys.drv_idx >= 0 && f->sys.drv_idx < MAX_DRIVERS,
+                     PJ_EINVALIDOP);
+    *pid = local_idx;
+    return make_global_index(f->sys.drv_idx, pid);
+}
+
+/* API: Get device information. */
+PJ_DEF(pj_status_t) pjmedia_vid_dev_get_info(pjmedia_vid_dev_index id,
+					     pjmedia_vid_dev_info *info)
+{
+    pjmedia_vid_dev_factory *f;
+    unsigned index;
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(info, PJ_EINVAL);
+    PJ_ASSERT_RETURN(vid_subsys.pf, PJMEDIA_EVID_INIT);
+
+    if (id <= PJMEDIA_VID_INVALID_DEV)
+	return PJMEDIA_EVID_INVDEV;
+
+    status = lookup_dev(id, &f, &index);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    status = f->op->get_dev_info(f, index, info);
+
+    /* Make sure device ID is the real ID (not PJMEDIA_VID_DEFAULT_*_DEV) */
+    info->id = index;
+    make_global_index(f->sys.drv_idx, &info->id);
+
+    return status;
+}
+
+/* API: find device */
+PJ_DEF(pj_status_t) pjmedia_vid_dev_lookup( const char *drv_name,
+					    const char *dev_name,
+					    pjmedia_vid_dev_index *id)
+{
+    pjmedia_vid_dev_factory *f = NULL;
+    unsigned drv_idx, dev_idx;
+
+    PJ_ASSERT_RETURN(drv_name && dev_name && id, PJ_EINVAL);
+    PJ_ASSERT_RETURN(vid_subsys.pf, PJMEDIA_EVID_INIT);
+
+    for (drv_idx=0; drv_idx<vid_subsys.drv_cnt; ++drv_idx) {
+	if (!pj_ansi_stricmp(drv_name, vid_subsys.drv[drv_idx].name))
+	{
+	    f = vid_subsys.drv[drv_idx].f;
+	    break;
+	}
+    }
+
+    if (!f)
+	return PJ_ENOTFOUND;
+
+    for (dev_idx=0; dev_idx<vid_subsys.drv[drv_idx].dev_cnt; ++dev_idx)
+    {
+	pjmedia_vid_dev_info info;
+	pj_status_t status;
+
+	status = f->op->get_dev_info(f, dev_idx, &info);
+	if (status != PJ_SUCCESS)
+	    return status;
+
+	if (!pj_ansi_stricmp(dev_name, info.name))
+	    break;
+    }
+
+    if (dev_idx==vid_subsys.drv[drv_idx].dev_cnt)
+	return PJ_ENOTFOUND;
+
+    *id = dev_idx;
+    make_global_index(drv_idx, id);
+
+    return PJ_SUCCESS;
+}
+
+/* API: Initialize the video device parameters with default values for the
+ * specified device.
+ */
+PJ_DEF(pj_status_t) pjmedia_vid_dev_default_param(pj_pool_t *pool,
+                                                  pjmedia_vid_dev_index id,
+						  pjmedia_vid_dev_param *param)
+{
+    pjmedia_vid_dev_factory *f;
+    unsigned index;
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(param, PJ_EINVAL);
+    PJ_ASSERT_RETURN(vid_subsys.pf, PJMEDIA_EVID_INIT);
+
+    if (id <= PJMEDIA_VID_INVALID_DEV)
+	return PJMEDIA_EVID_INVDEV;
+
+    status = lookup_dev(id, &f, &index);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    status = f->op->default_param(pool, f, index, param);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    /* Normalize device IDs */
+    make_global_index(f->sys.drv_idx, &param->cap_id);
+    make_global_index(f->sys.drv_idx, &param->rend_id);
+
+    return PJ_SUCCESS;
+}
+
+/* API: Open video stream object using the specified parameters. */
+PJ_DEF(pj_status_t) pjmedia_vid_dev_stream_create(
+					pjmedia_vid_dev_param *prm,
+					const pjmedia_vid_dev_cb *cb,
+					void *user_data,
+					pjmedia_vid_dev_stream **p_vid_strm)
+{
+    pjmedia_vid_dev_factory *cap_f=NULL, *rend_f=NULL, *f=NULL;
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(prm && prm->dir && p_vid_strm, PJ_EINVAL);
+    PJ_ASSERT_RETURN(vid_subsys.pf, PJMEDIA_EVID_INIT);
+    PJ_ASSERT_RETURN(prm->dir==PJMEDIA_DIR_CAPTURE ||
+		     prm->dir==PJMEDIA_DIR_RENDER ||
+		     prm->dir==PJMEDIA_DIR_CAPTURE_RENDER,
+		     PJ_EINVAL);
+
+    /* Normalize cap_id */
+    if (prm->dir & PJMEDIA_DIR_CAPTURE) {
+	unsigned index;
+
+	if (prm->cap_id < 0)
+	    prm->cap_id = PJMEDIA_VID_DEFAULT_CAPTURE_DEV;
+
+	status = lookup_dev(prm->cap_id, &cap_f, &index);
+	if (status != PJ_SUCCESS)
+	    return status;
+
+	prm->cap_id = index;
+	f = cap_f;
+    }
+
+    /* Normalize rend_id */
+    if (prm->dir & PJMEDIA_DIR_RENDER) {
+	unsigned index;
+
+	if (prm->rend_id < 0)
+	    prm->rend_id = PJMEDIA_VID_DEFAULT_RENDER_DEV;
+
+	status = lookup_dev(prm->rend_id, &rend_f, &index);
+	if (status != PJ_SUCCESS)
+	    return status;
+
+	prm->rend_id = index;
+	f = rend_f;
+    }
+
+    PJ_ASSERT_RETURN(f != NULL, PJ_EBUG);
+
+    /* For now, cap_id and rend_id must belong to the same factory */
+    PJ_ASSERT_RETURN((prm->dir != PJMEDIA_DIR_CAPTURE_RENDER) ||
+		     (cap_f == rend_f),
+		     PJMEDIA_EVID_INVDEV);
+
+    /* Create the stream */
+    status = f->op->create_stream(f, prm, cb,
+				  user_data, p_vid_strm);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    /* Assign factory id to the stream */
+    (*p_vid_strm)->sys.drv_idx = f->sys.drv_idx;
+    return PJ_SUCCESS;
+}
+
+/* API: Get the running parameters for the specified video stream. */
+PJ_DEF(pj_status_t) pjmedia_vid_dev_stream_get_param(
+					    pjmedia_vid_dev_stream *strm,
+					    pjmedia_vid_dev_param *param)
+{
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(strm && param, PJ_EINVAL);
+    PJ_ASSERT_RETURN(vid_subsys.pf, PJMEDIA_EVID_INIT);
+
+    status = strm->op->get_param(strm, param);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    /* Normalize device id's */
+    make_global_index(strm->sys.drv_idx, &param->cap_id);
+    make_global_index(strm->sys.drv_idx, &param->rend_id);
+
+    return PJ_SUCCESS;
+}
+
+/* API: Get the value of a specific capability of the video stream. */
+PJ_DEF(pj_status_t) pjmedia_vid_dev_stream_get_cap(
+					    pjmedia_vid_dev_stream *strm,
+					    pjmedia_vid_dev_cap cap,
+					    void *value)
+{
+    return strm->op->get_cap(strm, cap, value);
+}
+
+/* API: Set the value of a specific capability of the video stream. */
+PJ_DEF(pj_status_t) pjmedia_vid_dev_stream_set_cap(
+					    pjmedia_vid_dev_stream *strm,
+					    pjmedia_vid_dev_cap cap,
+					    const void *value)
+{
+    return strm->op->set_cap(strm, cap, value);
+}
+
+/* API: Start the stream. */
+PJ_DEF(pj_status_t) pjmedia_vid_dev_stream_start(pjmedia_vid_dev_stream *strm)
+{
+    pj_status_t status;
+
+    if (pjmedia_vid_dev_stream_is_running(strm))
+	return PJ_SUCCESS;
+
+    status = strm->op->start(strm);
+    if (status == PJ_SUCCESS)
+	strm->sys.is_running = PJ_TRUE;
+    return status;
+}
+
+/* API: has it been started? */
+PJ_DEF(pj_bool_t)
+pjmedia_vid_dev_stream_is_running(pjmedia_vid_dev_stream *strm)
+{
+    return strm->sys.is_running;
+}
+
+PJ_DEF(pj_status_t) pjmedia_vid_dev_stream_get_frame(
+					    pjmedia_vid_dev_stream *strm,
+					    pjmedia_frame *frame)
+{
+    pj_assert(strm->op->get_frame);
+    return strm->op->get_frame(strm, frame);
+}
+
+PJ_DEF(pj_status_t) pjmedia_vid_dev_stream_put_frame(
+					    pjmedia_vid_dev_stream *strm,
+                                            const pjmedia_frame *frame)
+{
+    pj_assert(strm->op->put_frame);
+    return strm->op->put_frame(strm, frame);
+}
+
+/* API: Stop the stream. */
+PJ_DEF(pj_status_t) pjmedia_vid_dev_stream_stop(pjmedia_vid_dev_stream *strm)
+{
+    strm->sys.is_running = PJ_FALSE;
+    return strm->op->stop(strm);
+}
+
+/* API: Destroy the stream. */
+PJ_DEF(pj_status_t) pjmedia_vid_dev_stream_destroy(
+						pjmedia_vid_dev_stream *strm)
+{
+    strm->sys.is_running = PJ_FALSE;
+    return strm->op->destroy(strm);
+}
+
+
+#endif /* PJMEDIA_HAS_VIDEO */