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