Index: /pjproject/trunk/pjmedia/build/Makefile
===================================================================
--- /pjproject/trunk/pjmedia/build/Makefile	(revision 4015)
+++ /pjproject/trunk/pjmedia/build/Makefile	(revision 4016)
@@ -90,5 +90,5 @@
 #
 export PJMEDIA_VIDEODEV_SRCDIR = ../src/pjmedia-videodev
-export PJMEDIA_VIDEODEV_OBJS +=  errno.o videodev.o ffmpeg_dev.o \
+export PJMEDIA_VIDEODEV_OBJS +=  errno.o videodev.o avi_dev.o ffmpeg_dev.o \
 				colorbar_dev.o v4l2_dev.o
 export PJMEDIA_VIDEODEV_CFLAGS += $(_CFLAGS)
Index: /pjproject/trunk/pjmedia/include/pjmedia-videodev/avi_dev.h
===================================================================
--- /pjproject/trunk/pjmedia/include/pjmedia-videodev/avi_dev.h	(revision 4016)
+++ /pjproject/trunk/pjmedia/include/pjmedia-videodev/avi_dev.h	(revision 4016)
@@ -0,0 +1,139 @@
+/* $Id$ */
+/*
+ * 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
+ */
+#ifndef PJMEDIA_VIDEODEV_AVI_DEV_H__
+#define PJMEDIA_VIDEODEV_AVI_DEV_H__
+
+/**
+ * @file avi_dev.h
+ * @brief AVI player virtual device
+ */
+#include <pjmedia-videodev/videodev.h>
+#include <pjmedia/avi_stream.h>
+
+PJ_BEGIN_DECL
+
+/**
+ * @defgroup avi_dev AVI Player Virtual Device
+ * @ingroup video_device_api
+ * @brief AVI player virtual device
+ * @{
+ * This describes a virtual capture device which takes its input from an AVI
+ * file.
+ */
+
+/**
+ * Settings for the AVI player virtual device. This param corresponds to
+ * PJMEDIA_VID_DEV_CAP_AVI_PLAY capability of the video device/stream.
+ */
+typedef struct pjmedia_avi_dev_param
+{
+    /**
+     * Specifies the full path of the AVI file to be played.
+     */
+    pj_str_t	path;
+
+    /**
+     * If this setting is specified when setting the device, this specifies
+     * the title to be assigned as the device name. If this setting not
+     * specified, the filename part of the path will be used.
+     */
+    pj_str_t	title;
+
+    /**
+     * The underlying AVI streams created by the device. If the value is NULL,
+     * that means the device has not been configured yet. Application can use
+     * this field to retrieve the audio stream of the AVI. This setting is
+     * "get"-only and will be ignored in "set capability" operation.
+     */
+    pjmedia_avi_streams *avi_streams;
+
+} pjmedia_avi_dev_param;
+
+
+/**
+ * Reset pjmedia_avi_dev_param with the default settings. This mostly will
+ * reset all values to NULL or zero.
+ *
+ * @param p	The parameter to be initialized.
+ */
+PJ_DECL(void) pjmedia_avi_dev_param_default(pjmedia_avi_dev_param *p);
+
+
+/**
+ * Create a AVI device factory, and register it to the video device
+ * subsystem. At least one factory needs to be created before an AVI
+ * device can be allocated and used, and normally only one factory is
+ * needed per application.
+ *
+ * @param pf		Pool factory to be used.
+ * @param max_dev	Number of devices to be reserved.
+ * @param p_ret		Pointer to return the factory instance, to be
+ * 			used when allocating a virtual device.
+ *
+ * @return		PJ_SUCCESS on success or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjmedia_avi_dev_create_factory(
+				    pj_pool_factory *pf,
+				    unsigned max_dev,
+				    pjmedia_vid_dev_factory **p_ret);
+
+/**
+ * Allocate one device ID to be used to play the specified AVI file in
+ * the parameter.
+ *
+ * @param param		The parameter, with at least the AVI file path
+ * 			set.
+ * @param p_id		Optional pointer to receive device ID to play
+ * 			the file.
+ *
+ * @return		PJ_SUCCESS or the appropriate error code.
+ *
+ */
+PJ_DECL(pj_status_t) pjmedia_avi_dev_alloc(pjmedia_vid_dev_factory *f,
+                                           pjmedia_avi_dev_param *param,
+                                           pjmedia_vid_dev_index *p_id);
+
+/**
+ * Retrieve the parameters set for the virtual device.
+ *
+ * @param id		Device ID.
+ * @param prm		Structure to receive the settings.
+ *
+ * @return		PJ_SUCCESS or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjmedia_avi_dev_get_param(pjmedia_vid_dev_index id,
+                                               pjmedia_avi_dev_param *param);
+
+/**
+ * Free the resources associated with the virtual device.
+ *
+ * @param id		The device ID.
+ *
+ * @return		PJ_SUCCESS or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjmedia_avi_dev_free(pjmedia_vid_dev_index id);
+
+/**
+ * @}
+ */
+
+PJ_END_DECL
+
+
+#endif    /* PJMEDIA_VIDEODEV_AVI_DEV_H__ */
Index: /pjproject/trunk/pjmedia/include/pjmedia-videodev/config.h
===================================================================
--- /pjproject/trunk/pjmedia/include/pjmedia-videodev/config.h	(revision 4015)
+++ /pjproject/trunk/pjmedia/include/pjmedia-videodev/config.h	(revision 4016)
@@ -123,4 +123,15 @@
 #endif
 
+
+/**
+ * Enable support for AVI player virtual capture device.
+ *
+ * Default: 1
+ */
+#ifndef PJMEDIA_VIDEO_DEV_HAS_AVI
+#   define PJMEDIA_VIDEO_DEV_HAS_AVI		1
+#endif
+
+
 /**
  * @}
Index: /pjproject/trunk/pjmedia/include/pjmedia-videodev/videodev.h
===================================================================
--- /pjproject/trunk/pjmedia/include/pjmedia-videodev/videodev.h	(revision 4015)
+++ /pjproject/trunk/pjmedia/include/pjmedia-videodev/videodev.h	(revision 4016)
@@ -560,8 +560,14 @@
 /**
  * Register a supported video device factory to the video device subsystem.
+ * Application can either register a function to create the factory, or
+ * an instance of an already created factory.
+ *
  * This function can only be called after calling
  * #pjmedia_vid_dev_subsys_init().
  *
- * @param vdf       The video device factory.
+ * @param vdf       The factory creation function. Either vdf or factory
+ * 		    argument must be specified.
+ * @param factory   Factory instance. Either vdf or factory
+ * 		    argument must be specified.
  *
  * @return          PJ_SUCCESS on successful operation or the appropriate
@@ -569,5 +575,6 @@
  */
 PJ_DECL(pj_status_t)
-pjmedia_vid_register_factory(pjmedia_vid_dev_factory_create_func_ptr vdf);
+pjmedia_vid_register_factory(pjmedia_vid_dev_factory_create_func_ptr vdf,
+                             pjmedia_vid_dev_factory *factory);
 
 
@@ -578,5 +585,8 @@
  * is currently in use, then the behavior is undefined.
  *
- * @param vdf       The video device factory.
+ * @param vdf       The video device factory. Either vdf or factory argument
+ * 		    must be specified.
+ * @param factory   The factory instance. Either vdf or factory argument
+ * 		    must be specified.
  *
  * @return          PJ_SUCCESS on successful operation or the appropriate
@@ -584,5 +594,6 @@
  */
 PJ_DECL(pj_status_t)
-pjmedia_vid_unregister_factory(pjmedia_vid_dev_factory_create_func_ptr vdf);
+pjmedia_vid_unregister_factory(pjmedia_vid_dev_factory_create_func_ptr vdf,
+                               pjmedia_vid_dev_factory *factory);
 
 
Index: /pjproject/trunk/pjmedia/include/pjmedia-videodev/videodev_imp.h
===================================================================
--- /pjproject/trunk/pjmedia/include/pjmedia-videodev/videodev_imp.h	(revision 4015)
+++ /pjproject/trunk/pjmedia/include/pjmedia-videodev/videodev_imp.h	(revision 4016)
@@ -192,5 +192,33 @@
 
 
-
+/**
+ * Internal API: return the factory instance and device index that's local
+ * to the factory for a given device ID.
+ *
+ * @param id		Device id.
+ * @param p_f		Out: factory instance
+ * @param p_local_index Out: device index within the factory
+ *
+ * @return		PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t)
+pjmedia_vid_dev_get_local_index(pjmedia_vid_dev_index id,
+                                pjmedia_vid_dev_factory **p_f,
+                                unsigned *p_local_index);
+
+/**
+ * Internal API: return the global device index given a factory instance and
+ * a local device index.
+ *
+ * @param f		Factory.
+ * @param local_idx	Local index.
+ * @param pid		Returned global index.
+ *
+ * @return		PJ_SUCCESS on success.
+ */
+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);
 
 /**
Index: /pjproject/trunk/pjmedia/include/pjmedia_videodev.h
===================================================================
--- /pjproject/trunk/pjmedia/include/pjmedia_videodev.h	(revision 4015)
+++ /pjproject/trunk/pjmedia/include/pjmedia_videodev.h	(revision 4016)
@@ -27,4 +27,5 @@
 #include <pjmedia-videodev/videodev.h>
 #include <pjmedia-videodev/videodev_imp.h>
+#include <pjmedia-videodev/avi_dev.h>
 
 #endif	/* __PJMEDIA_VIDEODEV_H__ */
Index: /pjproject/trunk/pjmedia/src/pjmedia-videodev/avi_dev.c
===================================================================
--- /pjproject/trunk/pjmedia/src/pjmedia-videodev/avi_dev.c	(revision 4016)
+++ /pjproject/trunk/pjmedia/src/pjmedia-videodev/avi_dev.c	(revision 4016)
@@ -0,0 +1,600 @@
+/* $Id$ */
+/*
+ * 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>
+
+#if defined(PJMEDIA_VIDEO_DEV_HAS_AVI) && PJMEDIA_VIDEO_DEV_HAS_AVI != 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;
+};
+
+/* 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->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 = (struct avi_factory*)f;
+    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;
+    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;
+    }
+
+    /* 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;
+    adi->info.fmt[0] = adi->vid->info.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->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_status_t status;
+
+    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;
+
+    /* Override format (hack?) */
+    pj_memcpy(&param->fmt, &adi->vid->info.fmt, sizeof(pjmedia_format));
+
+    /* Done */
+    strm->base.op = &stream_op;
+    adi->strm = strm;
+    *p_vid_strm = &strm->base;
+
+    return PJ_SUCCESS;
+
+on_error:
+    if (pool)
+	pj_pool_release(pool);
+    return status;
+}
+
+/* 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_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_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;
+    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 */
Index: /pjproject/trunk/pjmedia/src/pjmedia-videodev/videodev.c
===================================================================
--- /pjproject/trunk/pjmedia/src/pjmedia-videodev/videodev.c	(revision 4015)
+++ /pjproject/trunk/pjmedia/src/pjmedia-videodev/videodev.c	(revision 4016)
@@ -408,6 +408,8 @@
 /* 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_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;
 
@@ -416,5 +418,17 @@
 
     vid_subsys.drv[vid_subsys.drv_cnt].create = adf;
-    status = init_driver(vid_subsys.drv_cnt, PJ_FALSE);
+    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++;
@@ -428,5 +442,6 @@
 /* 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_unregister_factory(pjmedia_vid_dev_factory_create_func_ptr adf,
+                               pjmedia_vid_dev_factory *factory)
 {
     unsigned i, j;
@@ -438,5 +453,5 @@
 	struct driver *drv = &vid_subsys.drv[i];
 
-	if (drv->create == adf) {
+	if ((factory && drv->f==factory) || (adf && drv->create == adf)) {
 	    for (j = drv->start_idx; j < drv->start_idx + drv->dev_cnt; j++)
 	    {
@@ -579,4 +594,25 @@
 }
 
+/* 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,
Index: /pjproject/trunk/pjsip-apps/src/pjsua/pjsua_app.c
===================================================================
--- /pjproject/trunk/pjsip-apps/src/pjsua/pjsua_app.c	(revision 4015)
+++ /pjproject/trunk/pjsip-apps/src/pjsua/pjsua_app.c	(revision 4016)
@@ -44,4 +44,5 @@
 #define RING_INTERVAL	    3000
 
+#define MAX_AVI             4
 
 /* Call specific data */
@@ -136,4 +137,11 @@
     struct app_vid	    vid;
     unsigned		    aud_cnt;
+
+    /* AVI to play */
+    unsigned                avi_cnt;
+    pj_str_t	            avi[MAX_AVI];
+    pj_bool_t               avi_auto_play;
+    pjmedia_vid_dev_index   avi_dev_id;
+    pjsua_conf_port_id      avi_slot;
 } app_config;
 
@@ -330,4 +338,6 @@
     puts  ("  --vcapture-dev=id   Video capture device ID (default=-1)");
     puts  ("  --vrender-dev=id    Video render device ID (default=-1)");
+    puts  ("  --play-avi=FILE     Load this AVI as virtual capture device");
+    puts  ("  --auto-play-avi     Automatically play the AVI media to call");
 #endif
 
@@ -400,4 +410,7 @@
     cfg->ringback_slot = PJSUA_INVALID_ID;
     cfg->ring_slot = PJSUA_INVALID_ID;
+
+    cfg->avi_dev_id = PJMEDIA_VID_INVALID_DEV;
+    cfg->avi_slot = PJSUA_INVALID_ID;
 
     for (i=0; i<PJ_ARRAY_SIZE(cfg->acc_cfg); ++i)
@@ -577,5 +590,5 @@
 	   OPT_TIMER, OPT_TIMER_SE, OPT_TIMER_MIN_SE,
 	   OPT_VIDEO, OPT_EXTRA_AUDIO,
-	   OPT_VCAPTURE_DEV, OPT_VRENDER_DEV,
+	   OPT_VCAPTURE_DEV, OPT_VRENDER_DEV, OPT_PLAY_AVI, OPT_AUTO_PLAY_AVI
     };
     struct pj_getopt_option long_options[] = {
@@ -703,4 +716,6 @@
 	{ "vcapture-dev", 1, 0, OPT_VCAPTURE_DEV},
 	{ "vrender-dev",  1, 0, OPT_VRENDER_DEV},
+	{ "play-avi",	1, 0, OPT_PLAY_AVI},
+	{ "auto-play-avi", 0, 0, OPT_AUTO_PLAY_AVI},
 	{ NULL, 0, 0, 0}
     };
@@ -1496,4 +1511,16 @@
 	    cfg->vid.vrender_dev = atoi(pj_optarg);
 	    cur_acc->vid_rend_dev = cfg->vid.vrender_dev;
+	    break;
+
+	case OPT_PLAY_AVI:
+	    if (app_config.avi_cnt >= MAX_AVI) {
+		PJ_LOG(1,(THIS_FILE, "Too many AVIs"));
+		return -1;
+	    }
+	    app_config.avi[app_config.avi_cnt++] = pj_str(pj_optarg);
+	    break;
+
+	case OPT_AUTO_PLAY_AVI:
+	    app_config.avi_auto_play = PJ_TRUE;
 	    break;
 
@@ -2082,4 +2109,12 @@
     if (config->vid.vrender_dev != PJMEDIA_VID_DEFAULT_RENDER_DEV) {
 	pj_ansi_sprintf(line, "--vrender-dev %d\n", config->vid.vrender_dev);
+	pj_strcat2(&cfg, line);
+    }
+    for (i=0; i<config->avi_cnt; ++i) {
+	pj_ansi_sprintf(line, "--play-avi %s\n", config->avi[i].ptr);
+	pj_strcat2(&cfg, line);
+    }
+    if (config->avi_auto_play) {
+	pj_ansi_sprintf(line, "--auto-play-avi\n");
 	pj_strcat2(&cfg, line);
     }
@@ -2788,4 +2823,11 @@
 	    pjsua_conf_connect(app_config.wav_port, call_conf_slot);
 	    connect_sound = PJ_FALSE;
+	}
+
+	/* Stream AVI, if desired */
+	if (app_config.avi_auto_play &&
+	    app_config.avi_slot != PJSUA_INVALID_ID)
+	{
+	    pjsua_conf_connect(app_config.avi_slot, call_conf_slot);
 	}
 
@@ -3976,4 +4018,10 @@
     acc_cfg->vid_cap_dev = app_config.vid.vcapture_dev;
     acc_cfg->vid_rend_dev = app_config.vid.vrender_dev;
+
+    if (app_config.avi_auto_play &&
+	app_config.avi_dev_id != PJMEDIA_VID_INVALID_DEV)
+    {
+	acc_cfg->vid_cap_dev = app_config.avi_dev_id;
+    }
 }
 
@@ -5636,4 +5684,74 @@
     }
 
+    /* Create AVI player virtual devices */
+    if (app_config.avi_cnt) {
+#if PJMEDIA_VIDEO_DEV_HAS_AVI
+	pjmedia_vid_dev_factory *avi_factory;
+
+	status = pjmedia_avi_dev_create_factory(pjsua_get_pool_factory(),
+	                                        app_config.avi_cnt,
+	                                        &avi_factory);
+	if (status != PJ_SUCCESS) {
+	    PJ_PERROR(1,(THIS_FILE, status, "Error creating AVI factory"));
+	    goto on_error;
+	}
+
+	for (i=0; i<app_config.avi_cnt; ++i) {
+	    pjmedia_avi_dev_param avdp;
+	    pjmedia_vid_dev_index avid;
+	    unsigned strm_idx, strm_cnt;
+
+	    pjmedia_avi_dev_param_default(&avdp);
+	    avdp.path = app_config.avi[i];
+
+	    status =  pjmedia_avi_dev_alloc(avi_factory, &avdp, &avid);
+	    if (status != PJ_SUCCESS) {
+		PJ_PERROR(1,(THIS_FILE, status,
+			     "Error creating AVI player for %.*s",
+			     (int)avdp.path.slen, avdp.path.ptr));
+		goto on_error;
+	    }
+
+	    PJ_LOG(4,(THIS_FILE, "AVI player %.*s created, dev_id=%d",
+		      (int)avdp.title.slen, avdp.title.ptr, avid));
+	    app_config.avi_dev_id = avid;
+
+	    strm_cnt = pjmedia_avi_streams_get_num_streams(avdp.avi_streams);
+	    for (strm_idx=0; strm_idx<strm_cnt; ++strm_idx) {
+		pjmedia_port *aud;
+		pjmedia_format *fmt;
+		pjsua_conf_port_id slot;
+		char fmt_name[5];
+
+		aud = pjmedia_avi_streams_get_stream(avdp.avi_streams,
+		                                     strm_idx);
+		fmt = &aud->info.fmt;
+
+		pjmedia_fourcc_name(fmt->id, fmt_name);
+
+		if (fmt->id == PJMEDIA_FORMAT_PCM) {
+		    status = pjsua_conf_add_port(app_config.pool, aud,
+		                                 &slot);
+		    if (status == PJ_SUCCESS) {
+			PJ_LOG(4,(THIS_FILE,
+				  "AVI %.*s: audio added to slot %d",
+				  (int)avdp.title.slen, avdp.title.ptr,
+				  slot));
+			app_config.avi_slot = slot;
+		    }
+		} else {
+		    PJ_LOG(4,(THIS_FILE,
+			      "AVI %.*s: audio ignored, format=%s",
+			      (int)avdp.title.slen, avdp.title.ptr,
+			      fmt_name));
+		}
+	    }
+	}
+#else
+	PJ_LOG(2,(THIS_FILE,
+		  "Warning: --play-avi is ignored because AVI is disabled"));
+#endif	/* PJMEDIA_VIDEO_DEV_HAS_AVI */
+    }
+
     /* Add UDP transport unless it's disabled. */
     if (!app_config.no_udp) {
Index: /pjproject/trunk/pjsip/include/pjsua-lib/pjsua.h
===================================================================
--- /pjproject/trunk/pjsip/include/pjsua-lib/pjsua.h	(revision 4015)
+++ /pjproject/trunk/pjsip/include/pjsua-lib/pjsua.h	(revision 4016)
@@ -35,4 +35,7 @@
 /* Include all PJMEDIA-CODEC headers. */
 #include <pjmedia-codec.h>
+
+/* Videodev too */
+#include <pjmedia_videodev.h>
 
 /* Include all PJSIP-UA headers */
