diff options
Diffstat (limited to 'pjmedia/src/pjmedia-videodev')
-rw-r--r-- | pjmedia/src/pjmedia-videodev/avi_dev.c | 678 | ||||
-rw-r--r-- | pjmedia/src/pjmedia-videodev/colorbar_dev.c | 631 | ||||
-rw-r--r-- | pjmedia/src/pjmedia-videodev/dshow_dev.c | 1062 | ||||
-rw-r--r-- | pjmedia/src/pjmedia-videodev/dshowclasses.cpp | 242 | ||||
-rw-r--r-- | pjmedia/src/pjmedia-videodev/errno.c | 119 | ||||
-rw-r--r-- | pjmedia/src/pjmedia-videodev/ffmpeg_dev.c | 516 | ||||
-rw-r--r-- | pjmedia/src/pjmedia-videodev/ios_dev.m | 703 | ||||
-rw-r--r-- | pjmedia/src/pjmedia-videodev/qt_dev.m | 697 | ||||
-rw-r--r-- | pjmedia/src/pjmedia-videodev/sdl_dev.c | 1432 | ||||
-rw-r--r-- | pjmedia/src/pjmedia-videodev/sdl_dev_m.m | 20 | ||||
-rw-r--r-- | pjmedia/src/pjmedia-videodev/v4l2_dev.c | 819 | ||||
-rw-r--r-- | pjmedia/src/pjmedia-videodev/videodev.c | 877 |
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(¶m->fmt, &di->info.fmt[0], sizeof(param->fmt)); + + return PJ_SUCCESS; +} + +/* reset dev info */ +static void reset_dev_info(struct avi_dev_info *adi) +{ + /* Close avi streams */ + if (adi->avi) { + unsigned i, cnt; + + cnt = pjmedia_avi_streams_get_num_streams(adi->avi); + for (i=0; i<cnt; ++i) { + pjmedia_avi_stream *as; + + as = pjmedia_avi_streams_get_stream(adi->avi, i); + if (as) { + pjmedia_port *port; + port = pjmedia_avi_stream_get_port(as); + pjmedia_port_destroy(port); + } + } + adi->avi = NULL; + } + + if (adi->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(¶m->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(¶m->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(¶m->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, + ¶m->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(¶m->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(¶m->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, + ¶m->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(¶m->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(¶m->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(¶m->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, + ¶m->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(¶m->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, + ¶m->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(¶m->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(¶m->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(¶m->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(¶m->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*)¶m->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, ¶m->cap_id); + make_global_index(f->sys.drv_idx, ¶m->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, ¶m->cap_id); + make_global_index(strm->sys.drv_idx, ¶m->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 */ |