summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBenny Prijono <bennylp@teluu.com>2012-04-04 05:05:50 +0000
committerBenny Prijono <bennylp@teluu.com>2012-04-04 05:05:50 +0000
commit3e8d57944439a50f12c3e3eef10a416ced22aa7f (patch)
tree9c4c632100e81d23b745e7c13f6608b931a9c641
parentb3a8dbd102f7c33b5a3a9beb0d606e5a2676f334 (diff)
Fixed #1478: AVI player virtual device. Initial spec:
- Currently only Works with raw video and audio AVI files - Added --play-avi and --auto-play-avi options in pjsua - No A/V synchronization yet git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@4016 74dad513-b988-da41-8d7b-12977e46ad98
-rw-r--r--pjmedia/build/Makefile2
-rw-r--r--pjmedia/include/pjmedia-videodev/avi_dev.h139
-rw-r--r--pjmedia/include/pjmedia-videodev/config.h11
-rw-r--r--pjmedia/include/pjmedia-videodev/videodev.h19
-rw-r--r--pjmedia/include/pjmedia-videodev/videodev_imp.h28
-rw-r--r--pjmedia/include/pjmedia_videodev.h1
-rw-r--r--pjmedia/src/pjmedia-videodev/avi_dev.c600
-rw-r--r--pjmedia/src/pjmedia-videodev/videodev.c44
-rw-r--r--pjsip-apps/src/pjsua/pjsua_app.c120
-rw-r--r--pjsip/include/pjsua-lib/pjsua.h3
10 files changed, 957 insertions, 10 deletions
diff --git a/pjmedia/build/Makefile b/pjmedia/build/Makefile
index 5e36fc5d..13bc28c9 100644
--- a/pjmedia/build/Makefile
+++ b/pjmedia/build/Makefile
@@ -89,7 +89,7 @@ export PJMEDIA_AUDIODEV_CFLAGS += $(_CFLAGS)
# Defines for building PJMEDIA-VIDEODEV library
#
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)
diff --git a/pjmedia/include/pjmedia-videodev/avi_dev.h b/pjmedia/include/pjmedia-videodev/avi_dev.h
new file mode 100644
index 00000000..71f1bc6e
--- /dev/null
+++ b/pjmedia/include/pjmedia-videodev/avi_dev.h
@@ -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__ */
diff --git a/pjmedia/include/pjmedia-videodev/config.h b/pjmedia/include/pjmedia-videodev/config.h
index 0a0eb54e..5d261fb3 100644
--- a/pjmedia/include/pjmedia-videodev/config.h
+++ b/pjmedia/include/pjmedia-videodev/config.h
@@ -122,6 +122,17 @@ PJ_BEGIN_DECL
# define PJMEDIA_VIDEO_DEV_HAS_V4L2 0
#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
+
+
/**
* @}
*/
diff --git a/pjmedia/include/pjmedia-videodev/videodev.h b/pjmedia/include/pjmedia-videodev/videodev.h
index fa6c2697..235d2a3e 100644
--- a/pjmedia/include/pjmedia-videodev/videodev.h
+++ b/pjmedia/include/pjmedia-videodev/videodev.h
@@ -559,16 +559,23 @@ PJ_DECL(pj_status_t) pjmedia_vid_dev_subsys_shutdown(void);
/**
* 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
* error code.
*/
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);
/**
@@ -577,13 +584,17 @@ pjmedia_vid_register_factory(pjmedia_vid_dev_factory_create_func_ptr vdf);
* Devices from this factory will be unlisted. If a device from this factory
* 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
* error code.
*/
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);
/**
diff --git a/pjmedia/include/pjmedia-videodev/videodev_imp.h b/pjmedia/include/pjmedia-videodev/videodev_imp.h
index 0a1d9830..e0ef5205 100644
--- a/pjmedia/include/pjmedia-videodev/videodev_imp.h
+++ b/pjmedia/include/pjmedia-videodev/videodev_imp.h
@@ -191,7 +191,35 @@ struct pjmedia_vid_dev_stream
};
+/**
+ * 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);
/**
* @}
diff --git a/pjmedia/include/pjmedia_videodev.h b/pjmedia/include/pjmedia_videodev.h
index b11817fa..fe7c8800 100644
--- a/pjmedia/include/pjmedia_videodev.h
+++ b/pjmedia/include/pjmedia_videodev.h
@@ -26,5 +26,6 @@
#include <pjmedia-videodev/videodev.h>
#include <pjmedia-videodev/videodev_imp.h>
+#include <pjmedia-videodev/avi_dev.h>
#endif /* __PJMEDIA_VIDEODEV_H__ */
diff --git a/pjmedia/src/pjmedia-videodev/avi_dev.c b/pjmedia/src/pjmedia-videodev/avi_dev.c
new file mode 100644
index 00000000..c34f5886
--- /dev/null
+++ b/pjmedia/src/pjmedia-videodev/avi_dev.c
@@ -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 */
diff --git a/pjmedia/src/pjmedia-videodev/videodev.c b/pjmedia/src/pjmedia-videodev/videodev.c
index 9b96520d..658d091a 100644
--- a/pjmedia/src/pjmedia-videodev/videodev.c
+++ b/pjmedia/src/pjmedia-videodev/videodev.c
@@ -407,15 +407,29 @@ PJ_DEF(pj_status_t) pjmedia_vid_dev_subsys_init(pj_pool_factory *pf)
/* 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;
if (vid_subsys.init_count == 0)
return PJMEDIA_EVID_INIT;
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++;
} else {
@@ -427,7 +441,8 @@ pjmedia_vid_register_factory(pjmedia_vid_dev_factory_create_func_ptr adf)
/* 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;
@@ -437,7 +452,7 @@ pjmedia_vid_unregister_factory(pjmedia_vid_dev_factory_create_func_ptr adf)
for (i=0; i<vid_subsys.drv_cnt; ++i) {
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++)
{
vid_subsys.dev_list[j] = (pj_uint32_t)PJMEDIA_VID_INVALID_DEV;
@@ -578,6 +593,27 @@ static pj_status_t lookup_dev(pjmedia_vid_dev_index id,
}
+/* 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)
diff --git a/pjsip-apps/src/pjsua/pjsua_app.c b/pjsip-apps/src/pjsua/pjsua_app.c
index ee2c64ed..0d6fa590 100644
--- a/pjsip-apps/src/pjsua/pjsua_app.c
+++ b/pjsip-apps/src/pjsua/pjsua_app.c
@@ -43,6 +43,7 @@
#define RING_CNT 3
#define RING_INTERVAL 3000
+#define MAX_AVI 4
/* Call specific data */
struct call_data
@@ -135,6 +136,13 @@ static struct app_config
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;
@@ -329,6 +337,8 @@ static void usage(void)
puts (" --video Enable video");
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
puts ("");
@@ -400,6 +410,9 @@ static void default_config(struct app_config *cfg)
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)
pjsua_acc_config_default(&cfg->acc_cfg[i]);
@@ -576,7 +589,7 @@ static pj_status_t parse_args(int argc, char *argv[],
OPT_NO_FORCE_LR,
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[] = {
{ "config-file",1, 0, OPT_CONFIG_FILE},
@@ -702,6 +715,8 @@ static pj_status_t parse_args(int argc, char *argv[],
{ "extra-audio",0, 0, OPT_EXTRA_AUDIO},
{ "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}
};
pj_status_t status;
@@ -1497,6 +1512,18 @@ static pj_status_t parse_args(int argc, char *argv[],
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;
+
default:
PJ_LOG(1,(THIS_FILE,
"Argument \"%s\" is not valid. Use --help to see help",
@@ -2083,6 +2110,14 @@ static int write_settings(const struct app_config *config,
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);
+ }
/* ptime */
if (config->media_cfg.ptime) {
@@ -2789,6 +2824,13 @@ static void on_call_audio_state(pjsua_call_info *ci, unsigned mi,
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);
+ }
+
/* Put call in conference with other calls, if desired */
if (app_config.auto_conf) {
pjsua_call_id call_ids[PJSUA_MAX_CALLS];
@@ -3975,6 +4017,12 @@ static void app_config_init_video(pjsua_acc_config *acc_cfg)
PJMEDIA_VID_DEV_WND_RESIZABLE;
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;
+ }
}
static void app_config_show_video(int acc_id, const pjsua_acc_config *acc_cfg)
@@ -5635,6 +5683,76 @@ pj_status_t app_init(int argc, char *argv[])
}
+ /* 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) {
pjsua_acc_id aid;
diff --git a/pjsip/include/pjsua-lib/pjsua.h b/pjsip/include/pjsua-lib/pjsua.h
index 18085201..98942e3a 100644
--- a/pjsip/include/pjsua-lib/pjsua.h
+++ b/pjsip/include/pjsua-lib/pjsua.h
@@ -35,6 +35,9 @@
/* Include all PJMEDIA-CODEC headers. */
#include <pjmedia-codec.h>
+/* Videodev too */
+#include <pjmedia_videodev.h>
+
/* Include all PJSIP-UA headers */
#include <pjsip_ua.h>