summaryrefslogtreecommitdiff
path: root/pjmedia/src/pjmedia/vid_tee.c
diff options
context:
space:
mode:
Diffstat (limited to 'pjmedia/src/pjmedia/vid_tee.c')
-rw-r--r--pjmedia/src/pjmedia/vid_tee.c384
1 files changed, 384 insertions, 0 deletions
diff --git a/pjmedia/src/pjmedia/vid_tee.c b/pjmedia/src/pjmedia/vid_tee.c
new file mode 100644
index 00000000..dd12ec3e
--- /dev/null
+++ b/pjmedia/src/pjmedia/vid_tee.c
@@ -0,0 +1,384 @@
+/* $Id$ */
+/*
+ * Copyright (C) 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/vid_tee.h>
+#include <pjmedia/converter.h>
+#include <pjmedia/errno.h>
+#include <pj/array.h>
+#include <pj/log.h>
+#include <pj/pool.h>
+
+#define TEE_PORT_NAME "vid_tee"
+#define TEE_PORT_SIGN PJMEDIA_SIG_PORT_VID_TEE
+#define MAX_DST_PORT_COUNT 20
+
+
+typedef struct vid_tee_dst_port
+{
+ pjmedia_port *dst;
+ unsigned option;
+} vid_tee_dst_port;
+
+
+typedef struct vid_tee_port
+{
+ pjmedia_port base;
+ pj_pool_t *pool;
+ pj_pool_factory *pf;
+ pj_pool_t *buf_pool;
+ void *buf[2];
+ unsigned buf_cnt;
+ pj_size_t buf_size;
+ unsigned dst_port_maxcnt;
+ unsigned dst_port_cnt;
+ vid_tee_dst_port *dst_ports;
+
+ struct vid_tee_conv_t {
+ pjmedia_converter *conv;
+ pj_size_t conv_buf_size;
+ } *tee_conv;
+} vid_tee_port;
+
+
+static pj_status_t tee_put_frame(pjmedia_port *port, pjmedia_frame *frame);
+static pj_status_t tee_get_frame(pjmedia_port *port, pjmedia_frame *frame);
+static pj_status_t tee_destroy(pjmedia_port *port);
+
+/*
+ * Create a video tee port with the specified source media port.
+ */
+PJ_DEF(pj_status_t) pjmedia_vid_tee_create( pj_pool_t *pool,
+ const pjmedia_format *fmt,
+ unsigned max_dst_cnt,
+ pjmedia_port **p_vid_tee)
+{
+ vid_tee_port *tee;
+ pj_str_t name_st;
+ const pjmedia_video_format_info *vfi;
+ pjmedia_video_apply_fmt_param vafp;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(pool && fmt && p_vid_tee, PJ_EINVAL);
+ PJ_ASSERT_RETURN(fmt->type == PJMEDIA_TYPE_VIDEO, PJ_EINVAL);
+ PJ_ASSERT_RETURN(max_dst_cnt <= MAX_DST_PORT_COUNT, PJ_ETOOMANY);
+
+ /* Allocate video tee structure */
+ tee = PJ_POOL_ZALLOC_T(pool, vid_tee_port);
+ tee->pf = pool->factory;
+ tee->pool = pj_pool_create(tee->pf, "video tee", 500, 500, NULL);
+
+ /* Initialize video tee structure */
+ tee->dst_port_maxcnt = max_dst_cnt;
+ tee->dst_ports = (vid_tee_dst_port*)
+ pj_pool_calloc(pool, max_dst_cnt,
+ sizeof(vid_tee_dst_port));
+ tee->tee_conv = (struct vid_tee_conv_t *)
+ pj_pool_calloc(pool, max_dst_cnt,
+ sizeof(struct vid_tee_conv_t));
+
+ /* Initialize video tee buffer, its size is one frame */
+ vfi = pjmedia_get_video_format_info(NULL, fmt->id);
+ if (vfi == NULL)
+ return PJMEDIA_EBADFMT;
+
+ pj_bzero(&vafp, sizeof(vafp));
+ vafp.size = fmt->det.vid.size;
+ status = vfi->apply_fmt(vfi, &vafp);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ tee->buf_size = vafp.framebytes;
+
+ /* Initialize video tee port */
+ status = pjmedia_port_info_init2(&tee->base.info,
+ pj_strset2(&name_st, (char*)TEE_PORT_NAME),
+ TEE_PORT_SIGN,
+ PJMEDIA_DIR_ENCODING,
+ fmt);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ tee->base.get_frame = &tee_get_frame;
+ tee->base.put_frame = &tee_put_frame;
+ tee->base.on_destroy = &tee_destroy;
+
+ /* Done */
+ *p_vid_tee = &tee->base;
+
+ return PJ_SUCCESS;
+}
+
+static void realloc_buf(vid_tee_port *vid_tee,
+ unsigned buf_cnt, pj_size_t buf_size)
+{
+ unsigned i;
+
+ if (buf_cnt > vid_tee->buf_cnt)
+ vid_tee->buf_cnt = buf_cnt;
+
+ if (buf_size > vid_tee->buf_size) {
+ /* We need a larger buffer here. */
+ vid_tee->buf_size = buf_size;
+ if (vid_tee->buf_pool) {
+ pj_pool_release(vid_tee->buf_pool);
+ vid_tee->buf_pool = NULL;
+ }
+ vid_tee->buf[0] = vid_tee->buf[1] = NULL;
+ }
+
+ if (!vid_tee->buf_pool) {
+ vid_tee->buf_pool = pj_pool_create(vid_tee->pf, "video tee buffer",
+ 1000, 1000, NULL);
+ }
+
+ for (i = 0; i < vid_tee->buf_cnt; i++) {
+ if (!vid_tee->buf[i])
+ vid_tee->buf[i] = pj_pool_alloc(vid_tee->buf_pool,
+ vid_tee->buf_size);
+ }
+}
+
+/*
+ * Add a destination media port to the video tee.
+ */
+PJ_DEF(pj_status_t) pjmedia_vid_tee_add_dst_port(pjmedia_port *vid_tee,
+ unsigned option,
+ pjmedia_port *port)
+{
+ vid_tee_port *tee = (vid_tee_port*)vid_tee;
+ pjmedia_video_format_detail *vfd;
+
+ PJ_ASSERT_RETURN(vid_tee && vid_tee->info.signature==TEE_PORT_SIGN,
+ PJ_EINVAL);
+
+ if (tee->dst_port_cnt >= tee->dst_port_maxcnt)
+ return PJ_ETOOMANY;
+
+ if (vid_tee->info.fmt.id != port->info.fmt.id)
+ return PJMEDIA_EBADFMT;
+
+ vfd = pjmedia_format_get_video_format_detail(&port->info.fmt, PJ_TRUE);
+ if (vfd->size.w != vid_tee->info.fmt.det.vid.size.w ||
+ vfd->size.h != vid_tee->info.fmt.det.vid.size.h)
+ {
+ return PJMEDIA_EBADFMT;
+ }
+
+ realloc_buf(tee, (option & PJMEDIA_VID_TEE_DST_DO_IN_PLACE_PROC)?
+ 1: 0, tee->buf_size);
+
+ pj_bzero(&tee->tee_conv[tee->dst_port_cnt], sizeof(tee->tee_conv[0]));
+ tee->dst_ports[tee->dst_port_cnt].dst = port;
+ tee->dst_ports[tee->dst_port_cnt].option = option;
+ ++tee->dst_port_cnt;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Add a destination media port to the video tee. Create a converter if
+ * necessary.
+ */
+PJ_DEF(pj_status_t) pjmedia_vid_tee_add_dst_port2(pjmedia_port *vid_tee,
+ unsigned option,
+ pjmedia_port *port)
+{
+ vid_tee_port *tee = (vid_tee_port*)vid_tee;
+ pjmedia_video_format_detail *vfd;
+
+ PJ_ASSERT_RETURN(vid_tee && vid_tee->info.signature==TEE_PORT_SIGN,
+ PJ_EINVAL);
+
+ if (tee->dst_port_cnt >= tee->dst_port_maxcnt)
+ return PJ_ETOOMANY;
+
+ pj_bzero(&tee->tee_conv[tee->dst_port_cnt], sizeof(tee->tee_conv[0]));
+
+ /* Check if we need to create a converter. */
+ vfd = pjmedia_format_get_video_format_detail(&port->info.fmt, PJ_TRUE);
+ if (vid_tee->info.fmt.id != port->info.fmt.id ||
+ vfd->size.w != vid_tee->info.fmt.det.vid.size.w ||
+ vfd->size.h != vid_tee->info.fmt.det.vid.size.h)
+ {
+ const pjmedia_video_format_info *vfi;
+ pjmedia_video_apply_fmt_param vafp;
+ pjmedia_conversion_param conv_param;
+ pj_status_t status;
+
+ vfi = pjmedia_get_video_format_info(NULL, port->info.fmt.id);
+ if (vfi == NULL)
+ return PJMEDIA_EBADFMT;
+
+ pj_bzero(&vafp, sizeof(vafp));
+ vafp.size = port->info.fmt.det.vid.size;
+ status = vfi->apply_fmt(vfi, &vafp);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ realloc_buf(tee, (option & PJMEDIA_VID_TEE_DST_DO_IN_PLACE_PROC)?
+ 2: 1, vafp.framebytes);
+
+ pjmedia_format_copy(&conv_param.src, &vid_tee->info.fmt);
+ pjmedia_format_copy(&conv_param.dst, &port->info.fmt);
+
+ status = pjmedia_converter_create(
+ NULL, tee->pool, &conv_param,
+ &tee->tee_conv[tee->dst_port_cnt].conv);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ tee->tee_conv[tee->dst_port_cnt].conv_buf_size = vafp.framebytes;
+ } else {
+ realloc_buf(tee, (option & PJMEDIA_VID_TEE_DST_DO_IN_PLACE_PROC)?
+ 1: 0, tee->buf_size);
+ }
+
+ tee->dst_ports[tee->dst_port_cnt].dst = port;
+ tee->dst_ports[tee->dst_port_cnt].option = option;
+ ++tee->dst_port_cnt;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Remove a destination media port from the video tee.
+ */
+PJ_DECL(pj_status_t) pjmedia_vid_tee_remove_dst_port(pjmedia_port *vid_tee,
+ pjmedia_port *port)
+{
+ vid_tee_port *tee = (vid_tee_port*)vid_tee;
+ unsigned i;
+
+ PJ_ASSERT_RETURN(vid_tee && vid_tee->info.signature==TEE_PORT_SIGN,
+ PJ_EINVAL);
+
+ for (i = 0; i < tee->dst_port_cnt; ++i) {
+ if (tee->dst_ports[i].dst == port) {
+ if (tee->tee_conv[i].conv)
+ pjmedia_converter_destroy(tee->tee_conv[i].conv);
+
+ pj_array_erase(tee->dst_ports, sizeof(tee->dst_ports[0]),
+ tee->dst_port_cnt, i);
+ pj_array_erase(tee->tee_conv, sizeof(tee->tee_conv[0]),
+ tee->dst_port_cnt, i);
+ --tee->dst_port_cnt;
+ return PJ_SUCCESS;
+ }
+ }
+
+ return PJ_ENOTFOUND;
+}
+
+
+static pj_status_t tee_put_frame(pjmedia_port *port, pjmedia_frame *frame)
+{
+ vid_tee_port *tee = (vid_tee_port*)port;
+ unsigned i, j;
+ pj_bool_t done[MAX_DST_PORT_COUNT];
+
+ pj_bzero(done, sizeof(done));
+
+ for (i = 0; i < tee->dst_port_cnt; ++i) {
+ pjmedia_frame frame_ = *frame;
+
+ if (done[i])
+ continue;
+
+ if (tee->tee_conv[i].conv) {
+ pj_status_t status;
+
+ frame_.buf = tee->buf[0];
+ frame_.size = tee->tee_conv[i].conv_buf_size;
+ status = pjmedia_converter_convert(tee->tee_conv[i].conv,
+ frame, &frame_);
+ if (status != PJ_SUCCESS) {
+ PJ_LOG(3, ("", "Failed to convert frame for destination"
+ "port %d (%.*s)", i,
+ tee->dst_ports[i].dst->info.name.slen,
+ tee->dst_ports[i].dst->info.name.ptr));
+ continue;
+ }
+ }
+
+ /* Find other destination ports which has the same format so
+ * we don't need to do the same conversion twice.
+ */
+ for (j = i; j < tee->dst_port_cnt; ++j) {
+ pjmedia_frame framep;
+
+ if (done[j] ||
+ (tee->dst_ports[j].dst->info.fmt.id !=
+ tee->dst_ports[i].dst->info.fmt.id) ||
+ (tee->dst_ports[j].dst->info.fmt.det.vid.size.w !=
+ tee->dst_ports[i].dst->info.fmt.det.vid.size.w) ||
+ (tee->dst_ports[j].dst->info.fmt.det.vid.size.h !=
+ tee->dst_ports[i].dst->info.fmt.det.vid.size.h))
+ {
+ continue;
+ }
+
+ framep = frame_;
+ /* For dst_ports that do in-place processing, we need to duplicate
+ * the data source first.
+ */
+ if (tee->dst_ports[j].option & PJMEDIA_VID_TEE_DST_DO_IN_PLACE_PROC)
+ {
+ PJ_ASSERT_RETURN(tee->buf_size <= frame_.size, PJ_ETOOBIG);
+ framep.buf = tee->buf[tee->buf_cnt-1];
+ framep.size = frame_.size;
+ pj_memcpy(framep.buf, frame_.buf, frame_.size);
+ }
+
+ /* Deliver the data */
+ pjmedia_port_put_frame(tee->dst_ports[j].dst, &framep);
+ done[j] = PJ_TRUE;
+
+ if (!tee->tee_conv[i].conv)
+ break;
+ }
+ }
+
+ return PJ_SUCCESS;
+}
+
+static pj_status_t tee_get_frame(pjmedia_port *port, pjmedia_frame *frame)
+{
+ PJ_UNUSED_ARG(port);
+ PJ_UNUSED_ARG(frame);
+
+ pj_assert(!"Bug! Tee port get_frame() shouldn't be called.");
+
+ return PJ_EBUG;
+}
+
+static pj_status_t tee_destroy(pjmedia_port *port)
+{
+ vid_tee_port *tee = (vid_tee_port*)port;
+
+ PJ_ASSERT_RETURN(port && port->info.signature==TEE_PORT_SIGN, PJ_EINVAL);
+
+ pj_pool_release(tee->pool);
+ if (tee->buf_pool)
+ pj_pool_release(tee->buf_pool);
+
+ pj_bzero(tee, sizeof(*tee));
+
+ return PJ_SUCCESS;
+}