diff options
author | Benny Prijono <bennylp@teluu.com> | 2012-04-04 05:05:50 +0000 |
---|---|---|
committer | Benny Prijono <bennylp@teluu.com> | 2012-04-04 05:05:50 +0000 |
commit | 3e8d57944439a50f12c3e3eef10a416ced22aa7f (patch) | |
tree | 9c4c632100e81d23b745e7c13f6608b931a9c641 /pjmedia/src | |
parent | b3a8dbd102f7c33b5a3a9beb0d606e5a2676f334 (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
Diffstat (limited to 'pjmedia/src')
-rw-r--r-- | pjmedia/src/pjmedia-videodev/avi_dev.c | 600 | ||||
-rw-r--r-- | pjmedia/src/pjmedia-videodev/videodev.c | 44 |
2 files changed, 640 insertions, 4 deletions
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) |