Index: /pjproject/branches/projects/2.0-dev/pjmedia/include/pjmedia/vid_tee.h
===================================================================
--- /pjproject/branches/projects/2.0-dev/pjmedia/include/pjmedia/vid_tee.h	(revision 3614)
+++ /pjproject/branches/projects/2.0-dev/pjmedia/include/pjmedia/vid_tee.h	(revision 3614)
@@ -0,0 +1,118 @@
+/* $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 
+ */
+#ifndef __PJMEDIA_VID_TEE_H__
+#define __PJMEDIA_VID_TEE_H__
+
+/**
+ * @file vid_tee.h
+ * @brief Video tee (source duplicator).
+ */
+#include <pjmedia/port.h>
+
+/**
+ * @addtogroup PJMEDIA_VID_TEE Video source duplicator
+ * @ingroup PJMEDIA_PORT
+ * @brief Duplicate video data from a media port into multiple media port 
+ *  destinations
+ * @{
+ *
+ * This section describes media port to duplicate video data in the stream.
+ *
+ * A video tee branches video stream flow from one source port to multiple
+ * destination ports by simply duplicating the video data supplied by the
+ * source port and delivering the copy to all registered destinations.
+ *
+ * The video tee is a unidirectional port, i.e: data flows from source port
+ * to destination ports only. Also, the video source port MUST actively call
+ * pjmedia_port_put_frame() to the video tee and the video destination ports
+ * MUST NEVER call pjmedia_port_get_frame() to the video tee.
+ *
+ * The video tee is not thread-safe, so it is application responsibility
+ * to synchronize video tee operations, e.g: make sure the source port is
+ * paused during adding or removing a destination channel.
+ */
+
+PJ_BEGIN_DECL
+
+
+/**
+ * Enumeration of video tee flags.
+ */
+typedef enum pjmedia_vid_tee_flag
+{
+    /**
+     * Tell the video tee that the destination port will do in-place
+     * processing, so the delivered data may be modified by this port.
+     */
+    PJMEDIA_VID_TEE_DST_DO_IN_PLACE_PROC    = 4,
+
+} pjmedia_vid_tee_flag;
+
+
+/**
+ * Create a video tee port with the specified source media port.
+ *
+ * @param pool		    The pool.
+ * @param src		    The source media port.
+ * @param max_ch_cnt	    The maximum channel count.
+ * @param p_vid_tee	    Pointer to receive the video tee port.
+ *
+ * @return		    PJ_SUCCESS on success, or the appropriate
+ *			    error code.
+ */
+PJ_DECL(pj_status_t) pjmedia_vid_tee_create(pj_pool_t *pool,
+					    pjmedia_format *fmt,
+					    unsigned max_ch_cnt,
+					    pjmedia_port **p_vid_tee);
+
+/**
+ * Add a destination media port to the video tee.
+ *
+ * @param vid_tee	    The video tee.
+ * @param ch_option	    Channel option, see @pjmedia_vid_tee_flag.
+ * @param port		    The destination media port.
+ *
+ * @return		    PJ_SUCCESS on success, or the appropriate error
+ *			    code.
+ */
+PJ_DECL(pj_status_t) pjmedia_vid_tee_add_channel(pjmedia_port *vid_tee,
+						 unsigned option,
+						 pjmedia_port *port);
+
+
+/**
+ * Remove a destination media port from the video tee.
+ *
+ * @param vid_tee	    The video tee.
+ * @param port		    The destination media port to be removed.
+ *
+ * @return		    PJ_SUCCESS on success, or the appropriate error
+ *			    code.
+ */
+PJ_DECL(pj_status_t) pjmedia_vid_tee_remove_channel(pjmedia_port *vid_tee,
+						    pjmedia_port *port);
+
+
+PJ_END_DECL
+
+/**
+ * @}
+ */
+
+#endif /* __PJMEDIA_VID_TEE_H__ */
Index: /pjproject/branches/projects/2.0-dev/pjmedia/src/pjmedia/vid_tee.c
===================================================================
--- /pjproject/branches/projects/2.0-dev/pjmedia/src/pjmedia/vid_tee.c	(revision 3614)
+++ /pjproject/branches/projects/2.0-dev/pjmedia/src/pjmedia/vid_tee.c	(revision 3614)
@@ -0,0 +1,212 @@
+/* $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/errno.h>
+#include <pj/array.h>
+#include <pj/pool.h>
+
+#define TEE_PORT_NAME	"vid_tee"
+#define TEE_PORT_SIGN	PJMEDIA_PORT_SIGNATURE('V', 'T', 'E', 'E')
+
+
+typedef struct vid_tee_channel
+{
+    pjmedia_port	*dst;
+    unsigned		 option;
+} vid_tee_channel;
+
+
+typedef struct vid_tee_port
+{
+    pjmedia_port	 base;
+    void		*buf;
+    pj_size_t		 buf_size;
+    unsigned		 channel_maxcnt;
+    unsigned		 channel_cnt;
+    vid_tee_channel	*channels;
+} 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,
+					    pjmedia_format *fmt,
+					    unsigned max_ch_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);
+
+    /* Allocate video tee structure */
+    tee = PJ_POOL_ZALLOC_T(pool, vid_tee_port);
+
+    /* Initialize video tee structure */
+    tee->channel_maxcnt = max_ch_cnt;
+    tee->channels = (vid_tee_channel*)pj_pool_calloc(pool, max_ch_cnt,
+						     sizeof(vid_tee_channel));
+
+    /* 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;
+    tee->buf = pj_pool_zalloc(pool, tee->buf_size);
+
+    /* 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;
+}
+
+
+/*
+ * Add a destination media port to the video tee.
+ */
+PJ_DEF(pj_status_t) pjmedia_vid_tee_add_channel( 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->channel_cnt >= tee->channel_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;
+    }
+
+    tee->channels[tee->channel_cnt].dst = port;
+    tee->channels[tee->channel_cnt].option = option;
+    ++tee->channel_cnt;
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Remove a destination media port from the video tee.
+ */
+PJ_DECL(pj_status_t) pjmedia_vid_tee_remove_channel(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->channel_cnt; ++i) {
+	if (tee->channels[i].dst == port) {
+	    pj_array_erase(tee->channels, sizeof(tee->channels[0]),
+			   tee->channel_cnt, i);
+	    --tee->channel_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;
+
+    for (i = 0; i < tee->channel_cnt; ++i) {
+	pjmedia_frame frame_ = *frame;
+
+	/* For channels that do in-place processing, we need to duplicate
+	 * the data source first.
+	 */
+	if (tee->channels[i].option & PJMEDIA_VID_TEE_DST_DO_IN_PLACE_PROC) {
+	    PJ_ASSERT_RETURN(tee->buf_size <= frame->size, PJ_ETOOBIG);
+	    frame_.buf = tee->buf;
+	    frame_.size = frame->size;
+	    pj_memcpy(frame_.buf, frame->buf, frame->size);
+	}
+
+	/* Deliver the data */
+	pjmedia_port_put_frame(tee->channels[i].dst, &frame_);
+    }
+
+    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_bzero(tee, sizeof(*tee));
+
+    return PJ_SUCCESS;
+}
