diff options
-rw-r--r-- | pjmedia/build/Makefile | 2 | ||||
-rw-r--r-- | pjmedia/include/pjmedia-videodev/avi_dev.h | 139 | ||||
-rw-r--r-- | pjmedia/include/pjmedia-videodev/config.h | 11 | ||||
-rw-r--r-- | pjmedia/include/pjmedia-videodev/videodev.h | 19 | ||||
-rw-r--r-- | pjmedia/include/pjmedia-videodev/videodev_imp.h | 28 | ||||
-rw-r--r-- | pjmedia/include/pjmedia_videodev.h | 1 | ||||
-rw-r--r-- | pjmedia/src/pjmedia-videodev/avi_dev.c | 600 | ||||
-rw-r--r-- | pjmedia/src/pjmedia-videodev/videodev.c | 44 | ||||
-rw-r--r-- | pjsip-apps/src/pjsua/pjsua_app.c | 120 | ||||
-rw-r--r-- | pjsip/include/pjsua-lib/pjsua.h | 3 |
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(¶m->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(¶m->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> |