Index: /pjproject/trunk/pjmedia/build/Makefile
===================================================================
--- /pjproject/trunk/pjmedia/build/Makefile (revision 5938)
+++ /pjproject/trunk/pjmedia/build/Makefile (revision 5939)
@@ -74,5 +74,5 @@
transport_ice.o transport_loop.o transport_srtp.o transport_udp.o \
types.o vid_codec.o vid_codec_util.o \
- vid_port.o vid_stream.o vid_stream_info.o vid_tee.o \
+ vid_port.o vid_stream.o vid_stream_info.o vid_conf.o \
wav_player.o wav_playlist.o wav_writer.o wave.o \
wsola.o audiodev.o videodev.o
Index: /pjproject/trunk/pjmedia/build/pjmedia.vcproj
===================================================================
--- /pjproject/trunk/pjmedia/build/pjmedia.vcproj (revision 5938)
+++ /pjproject/trunk/pjmedia/build/pjmedia.vcproj (revision 5939)
@@ -7008,4 +7008,8 @@
+
+
@@ -7697,4 +7701,8 @@
+
+
Index: /pjproject/trunk/pjmedia/include/pjmedia.h
===================================================================
--- /pjproject/trunk/pjmedia/include/pjmedia.h (revision 5938)
+++ /pjproject/trunk/pjmedia/include/pjmedia.h (revision 5939)
@@ -69,8 +69,9 @@
#include
#include
+#include
+#include
#include
-#include
#include
-#include
+//#include
#include
#include
Index: /pjproject/trunk/pjmedia/include/pjmedia/converter.h
===================================================================
--- /pjproject/trunk/pjmedia/include/pjmedia/converter.h (revision 5938)
+++ /pjproject/trunk/pjmedia/include/pjmedia/converter.h (revision 5939)
@@ -124,4 +124,10 @@
/**
+ * Settings for pjmedia_converter_convert2().
+ */
+typedef void pjmedia_converter_convert_setting;
+
+
+/**
* Converter factory operation.
*/
@@ -158,5 +164,5 @@
{
/**
- * Convert the buffer in the source frame and save the result in the
+ * Convert the buffer of the source frame and save the result in the
* buffer of the destination frame, according to conversion format that
* was specified when the converter was created.
@@ -165,10 +171,10 @@
* of calling this function directly.
*
- * @param cv The converter instance.
- * @param src_frame The source frame.
- * @param dst_frame The destination frame.
- *
- * @return PJ_SUCCESS if conversion has been performed
- * successfully.
+ * @param cv The converter instance.
+ * @param src_frame The source frame.
+ * @param dst_frame The destination frame.
+ *
+ * @return PJ_SUCCESS if conversion has been performed
+ * successfully.
*/
pj_status_t (*convert)(pjmedia_converter *cv,
@@ -182,7 +188,37 @@
* of calling this function directly.
*
- * @param cv The converter.
+ * @param cv The converter.
*/
void (*destroy)(pjmedia_converter *cv);
+
+ /**
+ * Convert a region in the buffer of the source frame and put the result
+ * into a region in the buffer of the destination frame, according to
+ * conversion format that was specified when the converter was created.
+ *
+ * Note that application should use #pjmedia_converter_convert2() instead
+ * of calling this function directly.
+ *
+ * @param cv The converter instance.
+ * @param src_frame The source frame.
+ * @param src_frame_size The source frame size.
+ * @param src_reg_pos The source region position.
+ * @param dst_frame The destination frame.
+ * @param dst_frame_size The destination frame size.
+ * @param dst_reg_pos The destination region position.
+ * @param param This is unused for now and must be NULL.
+ *
+ * @return PJ_SUCCESS if conversion has been performed
+ * successfully.
+ */
+ pj_status_t (*convert2)(pjmedia_converter *cv,
+ pjmedia_frame *src_frame,
+ const pjmedia_rect_size *src_frame_size,
+ const pjmedia_coord *src_pos,
+ pjmedia_frame *dst_frame,
+ const pjmedia_rect_size *dst_frame_size,
+ const pjmedia_coord *dst_pos,
+ pjmedia_converter_convert_setting
+ *param);
};
@@ -303,4 +339,33 @@
pjmedia_frame *dst_frame);
+
+/**
+ * Convert a region in the buffer of the source frame and put the result
+ * into a region in the buffer of the destination frame, according to
+ * conversion format that was specified when the converter was created.
+ *
+ * @param cv The converter instance.
+ * @param src_frame The source frame.
+ * @param src_frame_size The source frame size.
+ * @param src_reg_pos The source region position.
+ * @param dst_frame The destination frame.
+ * @param dst_frame_size The destination frame size.
+ * @param dst_reg_pos The destination region position.
+ * @param param This is unused for now and must be NULL.
+ *
+ * @return PJ_SUCCESS if conversion has been performed
+ * successfully.
+ */
+PJ_DECL(pj_status_t) pjmedia_converter_convert2(
+ pjmedia_converter *cv,
+ pjmedia_frame *src_frame,
+ const pjmedia_rect_size *src_frame_size,
+ const pjmedia_coord *src_pos,
+ pjmedia_frame *dst_frame,
+ const pjmedia_rect_size *dst_frame_size,
+ const pjmedia_coord *dst_pos,
+ pjmedia_converter_convert_setting
+ *param);
+
/**
* Destroy the converter.
Index: /pjproject/trunk/pjmedia/include/pjmedia/signatures.h
===================================================================
--- /pjproject/trunk/pjmedia/include/pjmedia/signatures.h (revision 5938)
+++ /pjproject/trunk/pjmedia/include/pjmedia/signatures.h (revision 5939)
@@ -198,4 +198,5 @@
#define PJMEDIA_SIG_IS_CLASS_VID_OTHER(s) ((s)>>24=='V' && (s)>>16=='O')
+#define PJMEDIA_SIG_VID_CONF PJMEDIA_SIG_CLASS_VID_OTHER('C','F')
#define PJMEDIA_SIG_VID_PORT PJMEDIA_SIG_CLASS_VID_OTHER('P','O')
Index: /pjproject/trunk/pjmedia/include/pjmedia/vid_conf.h
===================================================================
--- /pjproject/trunk/pjmedia/include/pjmedia/vid_conf.h (revision 5939)
+++ /pjproject/trunk/pjmedia/include/pjmedia/vid_conf.h (revision 5939)
@@ -0,0 +1,294 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2019 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_CONF_H__
+#define __PJMEDIA_VID_CONF_H__
+
+/**
+ * @file vid_conf.h
+ * @brief Video conference bridge.
+ */
+#include
+
+/**
+ * @addtogroup PJMEDIA_VID_CONF Video conference bridge
+ * @ingroup PJMEDIA_PORT
+ * @brief Video conference bridge implementation
+ * destination
+ * @{
+ *
+ * This describes the video conference bridge implementation in PJMEDIA. The
+ * conference bridge provides powerful and efficient mechanism to route the
+ * video flow and combine multiple video data from multiple video sources.
+ */
+
+PJ_BEGIN_DECL
+
+
+/**
+ * Opaque type for video conference bridge.
+ */
+typedef struct pjmedia_vid_conf pjmedia_vid_conf;
+
+
+/**
+ * Enumeration of video conference layout mode.
+ */
+typedef enum pjmedia_vid_conf_layout
+{
+ /**
+ * In mixing video from multiple sources, each source will occupy about
+ * the same size in the mixing result frame at all time.
+ */
+ PJMEDIA_VID_CONF_LAYOUT_DEFAULT,
+
+ /**
+ * Warning: this is not implemented yet.
+ *
+ * In mixing video from multiple sources, one specified participant
+ * (or source port) will be the focus (i.e: occupy bigger portion
+ * than the others).
+ */
+ PJMEDIA_VID_CONF_LAYOUT_SELECTIVE_FOCUS,
+
+ /**
+ * Warning: this is not implemented yet.
+ *
+ * In mixing video from multiple sources, one participant will be the
+ * focus at a time (i.e: occupy bigger portion than the others), and
+ * after some interval the focus will be shifted to another participant,
+ * so each participant will have the same focus duration.
+ */
+ PJMEDIA_VID_CONF_LAYOUT_INTERVAL_FOCUS,
+
+ /**
+ * Warning: this is not implemented yet.
+ *
+ * In mixing video from multiple sources, each participant (or source
+ * port) will have specific layout configuration.
+ */
+ PJMEDIA_VID_CONF_LAYOUT_CUSTOM,
+
+} pjmedia_vid_conf_layout;
+
+
+/**
+ * Video conference bridge settings.
+ */
+typedef struct pjmedia_vid_conf_setting
+{
+ /**
+ * Maximum number of slots or media ports can be registered to the bridge.
+ *
+ * Default: 32
+ */
+ unsigned max_slot_cnt;
+
+ /**
+ * Frame rate the bridge will operate at. For video playback smoothness,
+ * ideally the bridge frame rate should be the common multiple of the
+ * frame rates of the ports. Otherwise, ports whose unaligned frame rates
+ * may experience jitter. For example, if the application will work with
+ * frame rates of 10, 15, and 30 fps, setting this to 30 should be okay.
+ * But if it also needs to handle 20 fps, better setting this to 60.
+ *
+ * Default: 60 (frames per second)
+ */
+ unsigned frame_rate;
+
+ /**
+ * Layout setting, see pjmedia_vid_conf_layout.
+ *
+ * Default: PJMEDIA_VID_CONF_LAYOUT_DEFAULT
+ */
+ unsigned layout;
+
+} pjmedia_vid_conf_setting;
+
+
+/**
+ * Video conference bridge port info.
+ */
+typedef struct pjmedia_vid_conf_port_info
+{
+ unsigned slot; /**< Slot index. */
+ pj_str_t name; /**< Port name. */
+ pjmedia_format format; /**< Format. */
+ unsigned listener_cnt; /**< Number of listeners. */
+ unsigned *listener_slots; /**< Array of listeners. */
+ unsigned transmitter_cnt; /**< Number of transmitter. */
+ unsigned *transmitter_slots; /**< Array of transmitter. */
+} pjmedia_vid_conf_port_info;
+
+
+/**
+ * Initialize video conference settings with default values.
+ *
+ * @param opt The settings to be initialized.
+ */
+PJ_DECL(void) pjmedia_vid_conf_setting_default(pjmedia_vid_conf_setting *opt);
+
+
+/**
+ * Create a video conference bridge.
+ *
+ * @param pool The memory pool.
+ * @param opt The video conference settings.
+ * @param p_vid_conf Pointer to receive the video conference bridge.
+ *
+ * @return PJ_SUCCESS on success, or the appropriate
+ * error code.
+ */
+PJ_DECL(pj_status_t) pjmedia_vid_conf_create(
+ pj_pool_t *pool,
+ const pjmedia_vid_conf_setting *opt,
+ pjmedia_vid_conf **p_vid_conf);
+
+
+/**
+ * Destroy video conference bridge.
+ *
+ * @param vid_conf The video conference bridge.
+ *
+ * @return PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjmedia_vid_conf_destroy(pjmedia_vid_conf *vid_conf);
+
+
+/**
+ * Add a media port to the video conference bridge.
+ *
+ * @param vid_conf The video conference bridge.
+ * @param pool The memory pool, the brige will create new pool
+ * based on this pool factory for this media port.
+ * @param port The media port to be added.
+ * @param name Name to be assigned to the slot. If not set, it will
+ * be set to the media port name.
+ * @param opt The option, for future use, currently this must
+ * be NULL.
+ * @param p_slot Pointer to receive the slot index of the port in
+ * the conference bridge.
+ *
+ * @return PJ_SUCCESS on success, or the appropriate error
+ * code.
+ */
+PJ_DECL(pj_status_t) pjmedia_vid_conf_add_port(pjmedia_vid_conf *vid_conf,
+ pj_pool_t *pool,
+ pjmedia_port *port,
+ const pj_str_t *name,
+ void *opt,
+ unsigned *p_slot);
+
+
+/**
+ * Remove a media port from the video conference bridge.
+ *
+ * @param vid_conf The video conference bridge.
+ * @param slot The media port's slot index to be removed.
+ *
+ * @return PJ_SUCCESS on success, or the appropriate error
+ * code.
+ */
+PJ_DECL(pj_status_t) pjmedia_vid_conf_remove_port(pjmedia_vid_conf *vid_conf,
+ unsigned slot);
+
+
+/**
+ * Get number of ports currently registered in the video conference bridge.
+ *
+ * @param vid_conf The video conference bridge.
+ *
+ * @return Number of ports currently registered to the video
+ * conference bridge.
+ */
+PJ_DECL(unsigned) pjmedia_vid_conf_get_port_count(pjmedia_vid_conf *vid_conf);
+
+
+/**
+ * Enumerate occupied slots in the video conference bridge.
+ *
+ * @param conf The video conference bridge.
+ * @param slots Array of slot to be filled in.
+ * @param count On input, specifies the maximum number of slot
+ * in the array. On return, it will be filled with
+ * the actual number of slot.
+ *
+ * @return PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjmedia_vid_conf_enum_ports(pjmedia_vid_conf *vid_conf,
+ unsigned slots[],
+ unsigned *count);
+
+
+/**
+ * Get port info.
+ *
+ * @param vid_conf The video conference bridge.
+ * @param slot Slot index.
+ * @param info Pointer to receive the info.
+ *
+ * @return PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjmedia_vid_conf_get_port_info(
+ pjmedia_vid_conf *vid_conf,
+ unsigned slot,
+ pjmedia_vid_conf_port_info *info);
+
+
+/**
+ * Enable unidirectional video flow from the specified source slot to
+ * the specified sink slot.
+ *
+ * @param conf The video conference bridge.
+ * @param src_slot Source slot.
+ * @param sink_slot Sink slot.
+ * @param opt The option, for future use, currently this must
+ * be NULL.
+ *
+ * @return PJ_SUCCES on success.
+ */
+PJ_DECL(pj_status_t) pjmedia_vid_conf_connect_port(
+ pjmedia_vid_conf *vid_conf,
+ unsigned src_slot,
+ unsigned sink_slot,
+ void *opt);
+
+
+/**
+ * Disconnect unidirectional video flow from the specified source to
+ * the specified sink slot.
+ *
+ * @param conf The video conference bridge.
+ * @param src_slot Source slot.
+ * @param sink_slot Sink slot.
+ *
+ * @return PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjmedia_vid_conf_disconnect_port(
+ pjmedia_vid_conf *vid_conf,
+ unsigned src_slot,
+ unsigned sink_slot);
+
+
+PJ_END_DECL
+
+/**
+ * @}
+ */
+
+#endif /* __PJMEDIA_VID_CONF_H__ */
Index: /pjproject/trunk/pjmedia/include/pjmedia/vid_port.h
===================================================================
--- /pjproject/trunk/pjmedia/include/pjmedia/vid_port.h (revision 5938)
+++ /pjproject/trunk/pjmedia/include/pjmedia/vid_port.h (revision 5939)
@@ -154,4 +154,19 @@
/**
+ * Subscribe media event notifications from the specified media port.
+ * Sample use case is that renderer video port needs to monitor stream port
+ * events so renderer can adjust its param whenever stream port detects
+ * format change.
+ *
+ * @param vid_port The video port.
+ * @param port The media port whose events to be monitored.
+ *
+ * @return PJ_SUCCESS on success or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjmedia_vid_port_subscribe_event(
+ pjmedia_vid_port *vid_port,
+ pjmedia_port *port);
+
+/**
* Connect the video port to a downstream (slave) media port. This operation
* is only valid for video ports created with active interface selected.
Index: /pjproject/trunk/pjmedia/src/pjmedia/converter.c
===================================================================
--- /pjproject/trunk/pjmedia/src/pjmedia/converter.c (revision 5938)
+++ /pjproject/trunk/pjmedia/src/pjmedia/converter.c (revision 5939)
@@ -192,3 +192,19 @@
}
-
+PJ_DEF(pj_status_t) pjmedia_converter_convert2(
+ pjmedia_converter *cv,
+ pjmedia_frame *src_frame,
+ const pjmedia_rect_size *src_frame_size,
+ const pjmedia_coord *src_pos,
+ pjmedia_frame *dst_frame,
+ const pjmedia_rect_size *dst_frame_size,
+ const pjmedia_coord *dst_pos,
+ void *param)
+{
+ if (!cv->op->convert2)
+ return PJ_ENOTSUP;
+
+ return (*cv->op->convert2)(cv, src_frame, src_frame_size, src_pos,
+ dst_frame, dst_frame_size, dst_pos, param);
+}
+
Index: /pjproject/trunk/pjmedia/src/pjmedia/converter_libswscale.c
===================================================================
--- /pjproject/trunk/pjmedia/src/pjmedia/converter_libswscale.c (revision 5938)
+++ /pjproject/trunk/pjmedia/src/pjmedia/converter_libswscale.c (revision 5939)
@@ -34,4 +34,14 @@
pjmedia_frame *src_frame,
pjmedia_frame *dst_frame);
+static pj_status_t libswscale_conv_convert2(
+ pjmedia_converter *converter,
+ pjmedia_frame *src_frame,
+ const pjmedia_rect_size *src_frame_size,
+ const pjmedia_coord *src_pos,
+ pjmedia_frame *dst_frame,
+ const pjmedia_rect_size *dst_frame_size,
+ const pjmedia_coord *dst_pos,
+ pjmedia_converter_convert_setting
+ *param);
static void libswscale_conv_destroy(pjmedia_converter *converter);
@@ -60,5 +70,6 @@
{
&libswscale_conv_convert,
- &libswscale_conv_destroy
+ &libswscale_conv_destroy,
+ &libswscale_conv_convert2
};
@@ -165,4 +176,73 @@
}
+static pj_status_t libswscale_conv_convert2(
+ pjmedia_converter *converter,
+ pjmedia_frame *src_frame,
+ const pjmedia_rect_size *src_frame_size,
+ const pjmedia_coord *src_pos,
+ pjmedia_frame *dst_frame,
+ const pjmedia_rect_size *dst_frame_size,
+ const pjmedia_coord *dst_pos,
+ pjmedia_converter_convert_setting
+ *param)
+{
+ struct ffmpeg_converter *fcv = (struct ffmpeg_converter*)converter;
+ struct fmt_info *src = &fcv->src,
+ *dst = &fcv->dst;
+ int h;
+ unsigned j;
+ pjmedia_rect_size orig_src_size;
+ pjmedia_rect_size orig_dst_size;
+
+ PJ_UNUSED_ARG(param);
+
+ /* Save original conversion sizes */
+ orig_src_size = src->apply_param.size;
+ orig_dst_size = dst->apply_param.size;
+
+ /* Set the first act buffer from src frame, and overwrite size. */
+ src->apply_param.buffer = src_frame->buf;
+ src->apply_param.size = *src_frame_size;
+ (*src->fmt_info->apply_fmt)(src->fmt_info, &src->apply_param);
+
+ /* Set the last act buffer from dst frame, and overwrite size. */
+ dst->apply_param.buffer = dst_frame->buf;
+ dst->apply_param.size = *dst_frame_size;
+ (*dst->fmt_info->apply_fmt)(dst->fmt_info, &dst->apply_param);
+
+ for (j = 0; j < src->fmt_info->plane_cnt; ++j) {
+ pjmedia_video_apply_fmt_param *ap = &src->apply_param;
+ int y = src_pos->y * ap->plane_bytes[j] / ap->strides[j] /
+ ap->size.h;
+ ap->planes[j] += y * ap->strides[j] +
+ src_pos->x * ap->strides[j] / ap->size.w;
+ }
+
+ for (j = 0; j < dst->fmt_info->plane_cnt; ++j) {
+ pjmedia_video_apply_fmt_param *ap = &dst->apply_param;
+ int y = dst_pos->y * ap->plane_bytes[j] / ap->strides[j] /
+ ap->size.h;
+ ap->planes[j] += y * ap->strides[j] +
+ dst_pos->x * ap->strides[j] / ap->size.w;
+ }
+
+ /* Return back the original conversion size */
+ src->apply_param.size = orig_src_size;
+ dst->apply_param.size = orig_dst_size;
+
+ h = sws_scale(fcv->sws_ctx,
+ (const uint8_t* const *)src->apply_param.planes,
+ src->apply_param.strides,
+ 0, src->apply_param.size.h,
+ dst->apply_param.planes, dst->apply_param.strides);
+
+ //sws_scale() return value can't be trusted? There are cases when
+ //sws_scale() returns zero but conversion seems to work okay.
+ //return h==(int)dst->apply_param.size.h ? PJ_SUCCESS : PJ_EUNKNOWN;
+ PJ_UNUSED_ARG(h);
+
+ return PJ_SUCCESS;
+}
+
static void libswscale_conv_destroy(pjmedia_converter *converter)
{
Index: /pjproject/trunk/pjmedia/src/pjmedia/converter_libyuv.c
===================================================================
--- /pjproject/trunk/pjmedia/src/pjmedia/converter_libyuv.c (revision 5938)
+++ /pjproject/trunk/pjmedia/src/pjmedia/converter_libyuv.c (revision 5939)
@@ -37,4 +37,14 @@
pjmedia_frame *dst_frame);
+static pj_status_t libyuv_conv_convert2(
+ pjmedia_converter *converter,
+ pjmedia_frame *src_frame,
+ const pjmedia_rect_size *src_frame_size,
+ const pjmedia_coord *src_pos,
+ pjmedia_frame *dst_frame,
+ const pjmedia_rect_size *dst_frame_size,
+ const pjmedia_coord *dst_pos,
+ void *param);
+
static void libyuv_conv_destroy(pjmedia_converter *converter);
@@ -48,5 +58,6 @@
{
&libyuv_conv_convert,
- &libyuv_conv_destroy
+ &libyuv_conv_destroy,
+ &libyuv_conv_convert2
};
@@ -347,5 +358,5 @@
/* Convert to I420 or BGRA if needed. */
- if ((src_id != PJMEDIA_FORMAT_I420) || (src_id != PJMEDIA_FORMAT_BGRA)) {
+ if ((src_id != PJMEDIA_FORMAT_I420) && (src_id != PJMEDIA_FORMAT_BGRA)) {
pj_uint32_t next_id = get_next_conv_fmt(src_id);
if (get_converter_map(src_id, next_id, src_size, dst_size, ++act_num,
@@ -359,6 +370,10 @@
/* Scale if needed */
- need_scale = ((src_size->w != dst_size->w) ||
- (src_size->h != dst_size->h));
+ //need_scale = ((src_size->w != dst_size->w) ||
+ //(src_size->h != dst_size->h));
+
+ // Always enable scale, as this can be used for rendering a region of
+ // a frame to another region of similar/another frame.
+ need_scale = PJ_TRUE;
if (need_scale) {
@@ -623,4 +638,166 @@
}
+static pj_status_t libyuv_conv_convert2(
+ pjmedia_converter *converter,
+ pjmedia_frame *src_frame,
+ const pjmedia_rect_size *src_frame_size,
+ const pjmedia_coord *src_pos,
+ pjmedia_frame *dst_frame,
+ const pjmedia_rect_size *dst_frame_size,
+ const pjmedia_coord *dst_pos,
+ pjmedia_converter_convert_setting
+ *param)
+{
+ struct libyuv_converter *lconv = (struct libyuv_converter*)converter;
+ int i = 0;
+ fmt_info *src_info = &lconv->act[0].src_fmt_info;
+ fmt_info *dst_info = &lconv->act[lconv->act_num-1].dst_fmt_info;
+ pjmedia_rect_size orig_src_size;
+ pjmedia_rect_size orig_dst_size;
+
+ PJ_UNUSED_ARG(param);
+
+ /* Save original conversion sizes */
+ orig_src_size = src_info->apply_param.size;
+ orig_dst_size = dst_info->apply_param.size;
+
+ /* Set the first act buffer from src frame, and overwrite size. */
+ src_info->apply_param.buffer = src_frame->buf;
+ src_info->apply_param.size = *src_frame_size;
+
+ /* Set the last act buffer from dst frame, and overwrite size. */
+ dst_info->apply_param.buffer = dst_frame->buf;
+ dst_info->apply_param.size = *dst_frame_size;
+
+ for (i=0;iact_num;++i) {
+ /* Use destination info as the source info for the next act. */
+ struct fmt_info *src_fmt_info = (i==0)? src_info :
+ &lconv->act[i-1].dst_fmt_info;
+
+ struct fmt_info *dst_fmt_info = &lconv->act[i].dst_fmt_info;
+
+ (*src_fmt_info->vid_fmt_info->apply_fmt)(src_fmt_info->vid_fmt_info,
+ &src_fmt_info->apply_param);
+
+ (*dst_fmt_info->vid_fmt_info->apply_fmt)(dst_fmt_info->vid_fmt_info,
+ &dst_fmt_info->apply_param);
+
+ /* For first and last acts, apply plane buffer offset and return back
+ * the original sizes.
+ */
+ if (i == 0) {
+ pjmedia_video_apply_fmt_param *ap = &src_fmt_info->apply_param;
+ unsigned j;
+ for (j = 0; j < src_fmt_info->vid_fmt_info->plane_cnt; ++j) {
+ int y = src_pos->y * ap->plane_bytes[j] / ap->strides[j] /
+ ap->size.h;
+ ap->planes[j] += y * ap->strides[j] + src_pos->x *
+ ap->strides[j] / ap->size.w;
+ }
+ ap->size = orig_src_size;
+ }
+ if (i == lconv->act_num-1) {
+ pjmedia_video_apply_fmt_param *ap = &dst_fmt_info->apply_param;
+ unsigned j;
+ for (j = 0; j < dst_fmt_info->vid_fmt_info->plane_cnt; ++j)
+ {
+ int y = dst_pos->y * ap->plane_bytes[j] / ap->strides[j] /
+ ap->size.h;
+ ap->planes[j] += y * ap->strides[j] + dst_pos->x *
+ ap->strides[j] / ap->size.w;
+ }
+ ap->size = orig_dst_size;
+ }
+
+ switch (lconv->act[i].act_type) {
+ case CONV_PACK_TO_PACK:
+ (*lconv->act[i].method.conv_pack_to_pack)(
+ (const uint8*)src_fmt_info->apply_param.planes[0],
+ src_fmt_info->apply_param.strides[0],
+ dst_fmt_info->apply_param.planes[0],
+ dst_fmt_info->apply_param.strides[0],
+ dst_fmt_info->apply_param.size.w,
+ dst_fmt_info->apply_param.size.h);
+ break;
+ case CONV_PACK_TO_PLANAR:
+ (*lconv->act[i].method.conv_pack_to_planar)(
+ (const uint8*)src_fmt_info->apply_param.planes[0],
+ src_fmt_info->apply_param.strides[0],
+ dst_fmt_info->apply_param.planes[0],
+ dst_fmt_info->apply_param.strides[0],
+ dst_fmt_info->apply_param.planes[1],
+ dst_fmt_info->apply_param.strides[1],
+ dst_fmt_info->apply_param.planes[2],
+ dst_fmt_info->apply_param.strides[2],
+ dst_fmt_info->apply_param.size.w,
+ dst_fmt_info->apply_param.size.h);
+ break;
+ case CONV_PLANAR_TO_PACK:
+ (*lconv->act[i].method.conv_planar_to_pack)(
+ (const uint8*)src_fmt_info->apply_param.planes[0],
+ src_fmt_info->apply_param.strides[0],
+ (const uint8*)src_fmt_info->apply_param.planes[1],
+ src_fmt_info->apply_param.strides[1],
+ (const uint8*)src_fmt_info->apply_param.planes[2],
+ src_fmt_info->apply_param.strides[2],
+ dst_fmt_info->apply_param.planes[0],
+ dst_fmt_info->apply_param.strides[0],
+ dst_fmt_info->apply_param.size.w,
+ dst_fmt_info->apply_param.size.h);
+ break;
+ case CONV_PLANAR_TO_PLANAR:
+ (*lconv->act[i].method.conv_planar_to_planar)(
+ (const uint8*)src_fmt_info->apply_param.planes[0],
+ src_fmt_info->apply_param.strides[0],
+ (const uint8*)src_fmt_info->apply_param.planes[1],
+ src_fmt_info->apply_param.strides[1],
+ (const uint8*)src_fmt_info->apply_param.planes[2],
+ src_fmt_info->apply_param.strides[2],
+ dst_fmt_info->apply_param.planes[0],
+ dst_fmt_info->apply_param.strides[0],
+ dst_fmt_info->apply_param.planes[1],
+ dst_fmt_info->apply_param.strides[1],
+ dst_fmt_info->apply_param.planes[2],
+ dst_fmt_info->apply_param.strides[2],
+ dst_fmt_info->apply_param.size.w,
+ dst_fmt_info->apply_param.size.h);
+ break;
+ case SCALE_PACK:
+ (*lconv->act[i].method.scale_pack)(
+ (const uint8*)src_fmt_info->apply_param.planes[0],
+ src_fmt_info->apply_param.strides[0],
+ src_fmt_info->apply_param.size.w,
+ src_fmt_info->apply_param.size.h,
+ (uint8*)dst_fmt_info->apply_param.planes[0],
+ dst_fmt_info->apply_param.strides[0],
+ dst_fmt_info->apply_param.size.w,
+ dst_fmt_info->apply_param.size.h,
+ LIBYUV_FILTER_MODE);
+ break;
+ case SCALE_PLANAR:
+ (*lconv->act[i].method.scale_planar)(
+ (const uint8*)src_fmt_info->apply_param.planes[0],
+ src_fmt_info->apply_param.strides[0],
+ (const uint8*)src_fmt_info->apply_param.planes[1],
+ src_fmt_info->apply_param.strides[1],
+ (const uint8*)src_fmt_info->apply_param.planes[2],
+ src_fmt_info->apply_param.strides[2],
+ src_fmt_info->apply_param.size.w,
+ src_fmt_info->apply_param.size.h,
+ (uint8*)dst_fmt_info->apply_param.planes[0],
+ dst_fmt_info->apply_param.strides[0],
+ (uint8*)dst_fmt_info->apply_param.planes[1],
+ dst_fmt_info->apply_param.strides[1],
+ (uint8*)dst_fmt_info->apply_param.planes[2],
+ dst_fmt_info->apply_param.strides[2],
+ dst_fmt_info->apply_param.size.w,
+ dst_fmt_info->apply_param.size.h,
+ LIBYUV_FILTER_MODE);
+ break;
+ };
+ }
+ return PJ_SUCCESS;
+}
+
static void libyuv_conv_destroy(pjmedia_converter *converter)
{
Index: /pjproject/trunk/pjmedia/src/pjmedia/vid_conf.c
===================================================================
--- /pjproject/trunk/pjmedia/src/pjmedia/vid_conf.c (revision 5939)
+++ /pjproject/trunk/pjmedia/src/pjmedia/vid_conf.c (revision 5939)
@@ -0,0 +1,1035 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2019 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
+#include
+#include
+#include
+#include
+#include
+#include
+
+#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
+
+
+#define CONF_NAME "vidconf"
+#define CONF_SIGN PJMEDIA_SIG_VID_CONF
+
+/* If set, conf will stop clock when there is no ports connection. However,
+ * this may cause stuck if port remove/disconnect is called from the clock
+ * callback. So better disable this for now.
+ */
+#define AUTO_STOP_CLOCK 0
+
+/* Clockrate for video timestamp unit */
+#define TS_CLOCK_RATE 90000
+
+#define THIS_FILE "vid_conf.c"
+#define TRACE_(x) PJ_LOG(5,x)
+
+
+/*
+ * Conference bridge.
+ */
+struct pjmedia_vid_conf
+{
+ pjmedia_vid_conf_setting opt; /**< Settings. */
+ unsigned port_cnt; /**< Current number of ports. */
+ unsigned connect_cnt; /**< Total number of connections */
+ pj_mutex_t *mutex; /**< Conference mutex. */
+ struct vconf_port **ports; /**< Array of ports. */
+ pjmedia_clock *clock; /**< Clock. */
+};
+
+
+/*
+ * Rendering state: converter, layout settings, etc.
+ */
+typedef struct render_state
+{
+ pjmedia_format_id src_fmt_id; /**< Source format ID. */
+ pjmedia_rect_size src_frame_size; /**< Source frame size. */
+ pjmedia_rect src_rect; /**< Source region to be rendered. */
+
+ pjmedia_format_id dst_fmt_id; /**< Destination format ID. */
+ pjmedia_rect_size dst_frame_size; /**< Destination frame size. */
+ pjmedia_rect dst_rect; /**< Destination region. */
+
+ pjmedia_converter *converter; /**< Converter. */
+
+} render_state;
+
+
+/*
+ * Conference bridge port.
+ */
+typedef struct vconf_port
+{
+ pj_pool_t *pool; /**< Pool. */
+ unsigned idx; /**< Port index. */
+ pj_str_t name; /**< Port name. */
+ pjmedia_port *port; /**< Video port. */
+ pj_uint32_t ts_interval; /**< Port put/get interval. */
+ pj_timestamp ts_next; /**< Time for next put/get_frame(). */
+ void *get_buf; /**< Buffer for get_frame(). */
+ pj_size_t get_buf_size; /**< Buffer size for get_frame(). */
+ void *put_buf; /**< Buffer for put_frame(). */
+ pj_size_t put_buf_size; /**< Buffer size for put_frame(). */
+
+ unsigned listener_cnt; /**< Number of listeners. */
+ unsigned *listener_slots;/**< Array of listeners (for info). */
+
+ unsigned transmitter_cnt;/**max_slot_cnt = 32;
+ opt->frame_rate = 60;
+}
+
+
+/*
+ * Create a video conference bridge.
+ */
+PJ_DEF(pj_status_t) pjmedia_vid_conf_create(
+ pj_pool_t *pool,
+ const pjmedia_vid_conf_setting *opt,
+ pjmedia_vid_conf **p_vid_conf)
+{
+ pjmedia_vid_conf *vid_conf;
+ pjmedia_clock_param clock_param;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(pool && p_vid_conf, PJ_EINVAL);
+
+ /* Allocate conf structure */
+ vid_conf = PJ_POOL_ZALLOC_T(pool, pjmedia_vid_conf);
+ PJ_ASSERT_RETURN(vid_conf, PJ_ENOMEM);
+
+ /* Init settings */
+ if (opt) {
+ vid_conf->opt = *opt;
+ } else {
+ pjmedia_vid_conf_setting_default(&vid_conf->opt);
+ }
+
+ /* Allocate ports */
+ vid_conf->ports = (vconf_port**)
+ pj_pool_zalloc(pool, vid_conf->opt.max_slot_cnt *
+ sizeof(vconf_port*));
+ PJ_ASSERT_RETURN(vid_conf->ports, PJ_ENOMEM);
+
+ /* Create mutex */
+ status = pj_mutex_create_recursive(pool, CONF_NAME, &vid_conf->mutex);
+ if (status != PJ_SUCCESS) {
+ pjmedia_vid_conf_destroy(vid_conf);
+ return status;
+ }
+
+ /* Create clock */
+ pj_bzero(&clock_param, sizeof(clock_param));
+ clock_param.clock_rate = TS_CLOCK_RATE;
+ clock_param.usec_interval = 1000000 / vid_conf->opt.frame_rate;
+ status = pjmedia_clock_create2(pool, &clock_param, 0, &on_clock_tick,
+ vid_conf, &vid_conf->clock);
+ if (status != PJ_SUCCESS) {
+ pjmedia_vid_conf_destroy(vid_conf);
+ return status;
+ }
+
+ /* Done */
+ *p_vid_conf = vid_conf;
+
+ PJ_LOG(5,(THIS_FILE, "Created video conference bridge with %d ports",
+ vid_conf->opt.max_slot_cnt));
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Destroy video conference bridge.
+ */
+PJ_DEF(pj_status_t) pjmedia_vid_conf_destroy(pjmedia_vid_conf *vid_conf)
+{
+ unsigned i;
+
+ PJ_ASSERT_RETURN(vid_conf, PJ_EINVAL);
+
+ /* Destroy clock */
+ if (vid_conf->clock) {
+ pjmedia_clock_destroy(vid_conf->clock);
+ vid_conf->clock = NULL;
+ }
+
+ /* Remove any registered ports (at least to cleanup their pool) */
+ for (i=0; i < vid_conf->opt.max_slot_cnt; ++i) {
+ pjmedia_vid_conf_remove_port(vid_conf, i);
+ }
+
+ /* Destroy mutex */
+ if (vid_conf->mutex) {
+ pj_mutex_destroy(vid_conf->mutex);
+ vid_conf->mutex = NULL;
+ }
+
+ PJ_LOG(5,(THIS_FILE, "Video conference bridge destroyed"));
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Add a media port to the video conference bridge.
+ */
+PJ_DEF(pj_status_t) pjmedia_vid_conf_add_port( pjmedia_vid_conf *vid_conf,
+ pj_pool_t *parent_pool,
+ pjmedia_port *port,
+ const pj_str_t *name,
+ void *opt,
+ unsigned *p_slot)
+{
+ pj_pool_t *pool;
+ vconf_port *cport;
+ unsigned index;
+
+ PJ_ASSERT_RETURN(vid_conf && parent_pool && port, PJ_EINVAL);
+ PJ_ASSERT_RETURN(port->info.fmt.type==PJMEDIA_TYPE_VIDEO &&
+ port->info.fmt.detail_type==PJMEDIA_FORMAT_DETAIL_VIDEO,
+ PJ_EINVAL);
+ PJ_UNUSED_ARG(opt);
+
+ /* If name is not specified, use the port's name */
+ if (!name)
+ name = &port->info.name;
+
+ pj_mutex_lock(vid_conf->mutex);
+
+ if (vid_conf->port_cnt >= vid_conf->opt.max_slot_cnt) {
+ pj_assert(!"Too many ports");
+ pj_mutex_unlock(vid_conf->mutex);
+ return PJ_ETOOMANY;
+ }
+
+ /* Find empty port in the conference bridge. */
+ for (index=0; index < vid_conf->opt.max_slot_cnt; ++index) {
+ if (vid_conf->ports[index] == NULL)
+ break;
+ }
+ pj_assert(index != vid_conf->opt.max_slot_cnt);
+
+ /* Create pool */
+ pool = pj_pool_create(parent_pool->factory, name->ptr, 500, 500, NULL);
+ PJ_ASSERT_RETURN(pool, PJ_ENOMEM);
+
+ /* Create port. */
+ cport = PJ_POOL_ZALLOC_T(pool, vconf_port);
+ PJ_ASSERT_RETURN(cport, PJ_ENOMEM);
+
+ /* Set pool, port, index, and name */
+ cport->pool = pool;
+ cport->port = port;
+ cport->idx = index;
+ pj_strdup_with_null(pool, &cport->name, name);
+
+ /* Init put/get_frame() intervals */
+ {
+ pjmedia_ratio *fps = &port->info.fmt.det.vid.fps;
+ pj_uint32_t vconf_interval = (pj_uint32_t)
+ (TS_CLOCK_RATE * 1.0 /
+ vid_conf->opt.frame_rate);
+ cport->ts_interval = (pj_uint32_t)(TS_CLOCK_RATE * 1.0 /
+ fps->num * fps->denum);
+
+ /* Normalize the interval */
+ if (cport->ts_interval < vconf_interval) {
+ cport->ts_interval = vconf_interval;
+ PJ_LOG(3,(THIS_FILE, "Warning: frame rate of port %s is higher "
+ "than video conference bridge (%d > %d)",
+ name->ptr, (int)(fps->num/fps->denum),
+ vid_conf->opt.frame_rate));
+ }
+ }
+
+ /* Allocate buffer for put/get_frame() */
+ {
+ const pjmedia_video_format_info *vfi;
+ pjmedia_video_apply_fmt_param vafp;
+ pj_status_t status;
+
+ vfi = pjmedia_get_video_format_info(NULL, port->info.fmt.id);
+ if (!vfi) {
+ PJ_LOG(4,(THIS_FILE, "pjmedia_vid_conf_add_port(): "
+ "unrecognized format %04X",
+ port->info.fmt.id));
+ 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) {
+ PJ_LOG(4,(THIS_FILE, "pjmedia_vid_conf_add_port(): "
+ "Failed to apply format %04X",
+ port->info.fmt.id));
+ return status;
+ }
+ if (port->put_frame) {
+ cport->put_buf_size = vafp.framebytes;
+ cport->put_buf = pj_pool_zalloc(cport->pool, cport->put_buf_size);
+ }
+ if (port->get_frame) {
+ cport->get_buf_size = vafp.framebytes;
+ cport->get_buf = pj_pool_zalloc(cport->pool, cport->get_buf_size);
+ }
+ }
+
+ /* Create listener array */
+ cport->listener_slots = (unsigned*)
+ pj_pool_zalloc(pool,
+ vid_conf->opt.max_slot_cnt *
+ sizeof(unsigned));
+ PJ_ASSERT_RETURN(cport->listener_slots, PJ_ENOMEM);
+
+ /* Create transmitter array */
+ cport->transmitter_slots = (unsigned*)
+ pj_pool_zalloc(pool,
+ vid_conf->opt.max_slot_cnt *
+ sizeof(unsigned));
+ PJ_ASSERT_RETURN(cport->transmitter_slots, PJ_ENOMEM);
+
+ /* Create pointer-to-render_state array */
+ cport->render_states = (render_state**)
+ pj_pool_zalloc(pool,
+ vid_conf->opt.max_slot_cnt *
+ sizeof(render_state*));
+ PJ_ASSERT_RETURN(cport->render_states, PJ_ENOMEM);
+
+ /* Create pointer-to-render-pool array */
+ cport->render_pool = (pj_pool_t**)
+ pj_pool_zalloc(pool,
+ vid_conf->opt.max_slot_cnt *
+ sizeof(pj_pool_t*));
+ PJ_ASSERT_RETURN(cport->render_pool, PJ_ENOMEM);
+
+ /* Register the conf port. */
+ vid_conf->ports[index] = cport;
+ vid_conf->port_cnt++;
+
+ PJ_LOG(4,(THIS_FILE,"Added port %d (%.*s)",
+ index, (int)cport->name.slen, cport->name.ptr));
+
+ pj_mutex_unlock(vid_conf->mutex);
+
+ /* Done. */
+ if (p_slot) {
+ *p_slot = index;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Remove a media port from the video conference bridge.
+ */
+PJ_DEF(pj_status_t) pjmedia_vid_conf_remove_port( pjmedia_vid_conf *vid_conf,
+ unsigned slot)
+{
+ vconf_port *cport;
+
+ PJ_ASSERT_RETURN(vid_conf && slotopt.max_slot_cnt, PJ_EINVAL);
+
+ pj_mutex_lock(vid_conf->mutex);
+
+ /* Port must be valid. */
+ cport = vid_conf->ports[slot];
+ if (cport == NULL) {
+ pj_mutex_unlock(vid_conf->mutex);
+ return PJ_EINVAL;
+ }
+
+ /* Disconnect slot -> listeners */
+ while (cport->listener_cnt) {
+ pjmedia_vid_conf_disconnect_port(vid_conf, slot,
+ cport->listener_slots[0]);
+ }
+
+ /* Disconnect transmitters -> slot */
+ while (cport->transmitter_cnt) {
+ pjmedia_vid_conf_disconnect_port(vid_conf,
+ cport->transmitter_slots[0], slot);
+ }
+
+ /* Remove the port. */
+ vid_conf->ports[slot] = NULL;
+ --vid_conf->port_cnt;
+
+ PJ_LOG(4,(THIS_FILE,"Removed port %d (%.*s)",
+ slot, (int)cport->name.slen, cport->name.ptr));
+
+ /* Release pool */
+ pj_pool_safe_release(&cport->pool);
+
+ if (AUTO_STOP_CLOCK && vid_conf->connect_cnt == 0) {
+ pj_status_t status;
+
+ /* Warning: will stuck if this is called from the clock thread */
+ status = pjmedia_clock_stop(vid_conf->clock);
+ if (status != PJ_SUCCESS) {
+ PJ_PERROR(4, (THIS_FILE, status, "Failed to stop clock"));
+ return status;
+ }
+ }
+
+ pj_mutex_unlock(vid_conf->mutex);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Get number of ports currently registered in the video conference bridge.
+ */
+PJ_DEF(unsigned) pjmedia_vid_conf_get_port_count(pjmedia_vid_conf *vid_conf)
+{
+ return vid_conf->port_cnt;
+}
+
+
+/*
+ * Enumerate occupied slots in the video conference bridge.
+ */
+PJ_DEF(pj_status_t) pjmedia_vid_conf_enum_ports( pjmedia_vid_conf *vid_conf,
+ unsigned slots[],
+ unsigned *count)
+{
+ unsigned i, tmp_count=0;
+
+ PJ_ASSERT_RETURN(vid_conf && slots && count, PJ_EINVAL);
+
+ /* Lock mutex */
+ pj_mutex_lock(vid_conf->mutex);
+
+ for (i=0; iopt.max_slot_cnt && tmp_count<*count; ++i) {
+ if (!vid_conf->ports[i])
+ continue;
+
+ slots[tmp_count++] = i;
+ }
+
+ /* Unlock mutex */
+ pj_mutex_unlock(vid_conf->mutex);
+
+ *count = tmp_count;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Get port info.
+ */
+PJ_DEF(pj_status_t) pjmedia_vid_conf_get_port_info(
+ pjmedia_vid_conf *vid_conf,
+ unsigned slot,
+ pjmedia_vid_conf_port_info *info)
+{
+ vconf_port *cp;
+
+ /* Check arguments */
+ PJ_ASSERT_RETURN(vid_conf && slotopt.max_slot_cnt, PJ_EINVAL);
+
+ /* Lock mutex */
+ pj_mutex_lock(vid_conf->mutex);
+
+ /* Port must be valid. */
+ cp = vid_conf->ports[slot];
+ if (cp == NULL) {
+ pj_mutex_unlock(vid_conf->mutex);
+ return PJ_EINVAL;
+ }
+
+ info->slot = slot;
+ info->name = cp->name;
+ pjmedia_format_copy(&info->format, &cp->port->info.fmt);
+ info->listener_cnt = cp->listener_cnt;
+ info->listener_slots = cp->listener_slots;
+ info->transmitter_cnt = cp->transmitter_cnt;
+ info->transmitter_slots = cp->transmitter_slots;
+
+ /* Unlock mutex */
+ pj_mutex_unlock(vid_conf->mutex);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Enable unidirectional video flow from the specified source slot to
+ * the specified sink slot.
+ */
+PJ_DEF(pj_status_t) pjmedia_vid_conf_connect_port(
+ pjmedia_vid_conf *vid_conf,
+ unsigned src_slot,
+ unsigned sink_slot,
+ void *opt)
+{
+ vconf_port *src_port, *dst_port;
+ unsigned i;
+
+ /* Check arguments */
+ PJ_ASSERT_RETURN(vid_conf &&
+ src_slotopt.max_slot_cnt &&
+ sink_slotopt.max_slot_cnt, PJ_EINVAL);
+ PJ_UNUSED_ARG(opt);
+
+ pj_mutex_lock(vid_conf->mutex);
+
+ /* Ports must be valid. */
+ src_port = vid_conf->ports[src_slot];
+ dst_port = vid_conf->ports[sink_slot];
+ if (!src_port || !src_port->port->get_frame ||
+ !dst_port || !dst_port->port->put_frame)
+ {
+ PJ_LOG(4,(THIS_FILE,"Failed connecting video ports, make sure that "
+ "source has get_frame() & sink has put_frame()"));
+ pj_mutex_unlock(vid_conf->mutex);
+ return PJ_EINVAL;
+ }
+
+ /* Check if connection has been made */
+ for (i=0; ilistener_cnt; ++i) {
+ if (src_port->listener_slots[i] == sink_slot)
+ break;
+ }
+
+ if (i == src_port->listener_cnt) {
+ src_port->listener_slots[src_port->listener_cnt] = sink_slot;
+ dst_port->transmitter_slots[dst_port->transmitter_cnt] = src_slot;
+ ++src_port->listener_cnt;
+ ++dst_port->transmitter_cnt;
+
+ update_render_state(vid_conf, dst_port);
+
+ ++vid_conf->connect_cnt;
+ if (vid_conf->connect_cnt == 1) {
+ pj_status_t status;
+ status = pjmedia_clock_start(vid_conf->clock);
+ if (status != PJ_SUCCESS) {
+ PJ_PERROR(4, (THIS_FILE, status, "Failed to start clock"));
+ return status;
+ }
+ }
+
+ PJ_LOG(4,(THIS_FILE,"Port %d (%.*s) transmitting to port %d (%.*s)",
+ src_slot,
+ (int)src_port->name.slen,
+ src_port->name.ptr,
+ sink_slot,
+ (int)dst_port->name.slen,
+ dst_port->name.ptr));
+ }
+
+ pj_mutex_unlock(vid_conf->mutex);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Disconnect unidirectional video flow from the specified source to
+ * the specified sink slot.
+ */
+PJ_DEF(pj_status_t) pjmedia_vid_conf_disconnect_port(
+ pjmedia_vid_conf *vid_conf,
+ unsigned src_slot,
+ unsigned sink_slot)
+{
+ vconf_port *src_port, *dst_port;
+ unsigned i, j;
+
+ /* Check arguments */
+ PJ_ASSERT_RETURN(vid_conf &&
+ src_slotopt.max_slot_cnt &&
+ sink_slotopt.max_slot_cnt, PJ_EINVAL);
+
+ pj_mutex_lock(vid_conf->mutex);
+
+ /* Ports must be valid. */
+ src_port = vid_conf->ports[src_slot];
+ dst_port = vid_conf->ports[sink_slot];
+ if (!src_port || !dst_port) {
+ pj_mutex_unlock(vid_conf->mutex);
+ return PJ_EINVAL;
+ }
+
+ /* Check if connection has been made */
+ for (i=0; ilistener_cnt; ++i) {
+ if (src_port->listener_slots[i] == sink_slot)
+ break;
+ }
+ for (j=0; jtransmitter_cnt; ++j) {
+ if (dst_port->transmitter_slots[j] == src_slot)
+ break;
+ }
+
+ if (i != src_port->listener_cnt) {
+ pj_assert(j != dst_port->transmitter_cnt);
+ pj_assert(src_port->listener_cnt > 0 &&
+ src_port->listener_cnt < vid_conf->opt.max_slot_cnt);
+ pj_assert(dst_port->transmitter_cnt > 0 &&
+ dst_port->transmitter_cnt < vid_conf->opt.max_slot_cnt);
+ pj_array_erase(src_port->listener_slots, sizeof(unsigned),
+ src_port->listener_cnt, i);
+ pj_array_erase(dst_port->transmitter_slots, sizeof(unsigned),
+ dst_port->transmitter_cnt, j);
+ --src_port->listener_cnt;
+ --dst_port->transmitter_cnt;
+
+ cleanup_render_state(src_port, j);
+ update_render_state(vid_conf, dst_port);
+
+ --vid_conf->connect_cnt;
+
+ if (AUTO_STOP_CLOCK && vid_conf->connect_cnt == 0) {
+ pj_status_t status;
+ /* Warning: will stuck if this is called from the clock thread */
+ status = pjmedia_clock_stop(vid_conf->clock);
+ if (status != PJ_SUCCESS) {
+ PJ_PERROR(4, (THIS_FILE, status, "Failed to stop clock"));
+ return status;
+ }
+ }
+
+ PJ_LOG(4,(THIS_FILE,
+ "Port %d (%.*s) stop transmitting to port %d (%.*s)",
+ src_slot,
+ (int)src_port->name.slen,
+ src_port->name.ptr,
+ sink_slot,
+ (int)dst_port->name.slen,
+ dst_port->name.ptr));
+ }
+
+ pj_mutex_unlock(vid_conf->mutex);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Internal functions.
+ */
+
+static void on_clock_tick(const pj_timestamp *now, void *user_data)
+{
+ pjmedia_vid_conf *vid_conf = (pjmedia_vid_conf*)user_data;
+ unsigned ci, i;
+ pj_int32_t ts_diff;
+ pjmedia_frame frame;
+ pj_status_t status;
+
+ pj_mutex_lock(vid_conf->mutex);
+
+ /* Iterate all (sink) ports */
+ for (i=0, ci=0; iopt.max_slot_cnt &&
+ ciport_cnt; ++i)
+ {
+ unsigned j;
+ pj_bool_t got_frame = PJ_FALSE;
+ pj_bool_t ts_incremented = PJ_FALSE;
+ vconf_port *sink = vid_conf->ports[i];
+
+ /* Skip empty port */
+ if (!sink)
+ continue;
+
+ /* Increment occupied port counter */
+ ++ci;
+
+ /* Skip non-sink port */
+ if (!sink->port->put_frame)
+ continue;
+
+ if (sink->ts_next.u64 == 0)
+ sink->ts_next = *now;
+
+ /* Skip if too early for put_frame(), note:
+ * early = (now < ts_next)
+ * But careful for timestamp wrapped around.
+ */
+ ts_diff = pj_timestamp_diff32(&sink->ts_next, now);
+ if (ts_diff < 0 || ts_diff > TS_CLOCK_RATE)
+ continue;
+
+ /* Clean up sink put buffer (should we draw black instead?) */
+ //pj_bzero(sink->put_buf, sink->put_buf_size);
+
+ /* Iterate transmitters of this sink port */
+ for (j=0; j < sink->transmitter_cnt; ++j) {
+ vconf_port *src = vid_conf->ports[sink->transmitter_slots[j]];
+ pj_int32_t src_ts_diff;
+
+ if (src->ts_next.u64 == 0)
+ src->ts_next = *now;
+
+ /* Is it time for src->get_frame()? yes, if (now >= ts_next) */
+ src_ts_diff = pj_timestamp_diff32(&src->ts_next, now);
+ if (src_ts_diff >= 0) {
+
+ /* Call src->get_frame().
+ * Possible optimization: if this src only has one listener,
+ * perhaps we can skip this src buffer and directly render it
+ * to sink buffer (but still need buffer if conversion any).
+ */
+ pj_bzero(&frame, sizeof(frame));
+ frame.type = PJMEDIA_FRAME_TYPE_VIDEO;
+ frame.timestamp = *now;
+ frame.buf = src->get_buf;
+ frame.size = src->get_buf_size;
+ status = pjmedia_port_get_frame(src->port, &frame);
+
+ /* Update next src put/get */
+ pj_add_timestamp32(&src->ts_next, src->ts_interval);
+ ts_incremented = src==sink;
+ }
+
+ /* Render src get buffer to sink put buffer (based on sink layout
+ * settings, if any)
+ */
+ status = render_src_frame(src, sink, j);
+ got_frame = PJ_TRUE;
+ }
+
+ /* Call sink->put_frame()
+ * Note that if transmitter_cnt==0, we should still call put_frame()
+ * with zero frame size, as sink may need to send keep-alive packets
+ * and get timestamp update.
+ */
+ pj_bzero(&frame, sizeof(frame));
+ frame.type = PJMEDIA_FRAME_TYPE_VIDEO;
+ frame.timestamp = *now;
+ if (got_frame) {
+ frame.buf = sink->put_buf;
+ frame.size = sink->put_buf_size;
+ }
+ status = pjmedia_port_put_frame(sink->port, &frame);
+
+ /* Update next put/get, careful that it may have been updated
+ * if this port transmits to itself!
+ */
+ if (!ts_incremented) {
+ pj_add_timestamp32(&sink->ts_next, sink->ts_interval);
+ }
+ }
+
+ pj_mutex_unlock(vid_conf->mutex);
+}
+
+static pj_bool_t is_landscape(const pjmedia_rect_size *size) {
+ return (size->w >= size->h);
+}
+
+/* Adjust a frame size to match ratio specified in the ref_size.
+ * Either dimension of the frame may be cropped (but will never be
+ * expanded).
+ */
+static void match_ratio_crop(pjmedia_rect_size *size,
+ const pjmedia_rect_size *ref_size)
+{
+ pjmedia_rect_size res;
+
+ /* Try match width first */
+ res.w = size->w;
+ res.h = ref_size->h * size->w / ref_size->w;
+
+ /* If original height turns out to be shorther, match height */
+ if (size->h < res.h) {
+ res.w = ref_size->w * size->h / ref_size->h;
+ res.h = size->h;
+ }
+
+ *size = res;
+ return;
+}
+
+/* Cleanup rendering states, called when a transmitter is disconnected
+ * from a listener, or before reinit-ing rendering state of a listener
+ * when new connection has just been made.
+ */
+static void cleanup_render_state(vconf_port *cp,
+ unsigned transmitter_idx)
+{
+ render_state *rs = cp->render_states[transmitter_idx];
+ if (rs && rs->converter)
+ {
+ pjmedia_converter_destroy(rs->converter);
+ rs->converter = NULL;
+ }
+ cp->render_states[transmitter_idx] = NULL;
+
+ if (cp->render_pool[transmitter_idx]) {
+ pj_pool_safe_release(&cp->render_pool[transmitter_idx]);
+ }
+}
+
+/* This function will do:
+ * - Recalculate layout setting, i.e: get video pos and size
+ * for each transmitter
+ * - Check if converter is needed and setup it.
+ * - Those above things will be stored in render_state and
+ * will be used by render_src_frame()
+ */
+static void update_render_state(pjmedia_vid_conf *vid_conf, vconf_port *cp)
+{
+ pjmedia_format_id fmt_id, tr_fmt_id[4];
+ pjmedia_rect_size size, tr_size[4];
+ unsigned i;
+ pj_status_t status;
+ char buf[5];
+
+ /* Nothing to render, just return */
+ if (cp->transmitter_cnt == 0)
+ return;
+
+ TRACE_((THIS_FILE, "Updating render state for port id %d (%d sources)..",
+ cp->idx, cp->transmitter_cnt));
+
+ fmt_id = cp->port->info.fmt.id;
+ size = cp->port->info.fmt.det.vid.size;
+ for (i = 0; i < cp->transmitter_cnt; ++i) {
+ vconf_port *tr = vid_conf->ports[cp->transmitter_slots[i]];
+
+ /* Cleanup render states & pool */
+ cleanup_render_state(cp, i);
+
+ /* Gather format ID, size of each transmitter */
+ tr_fmt_id[i] = tr->port->info.fmt.id;
+ tr_size[i] = tr->port->info.fmt.det.vid.size;
+ }
+
+ /* If only one transmitter and it has matched format & size, just use
+ * plain memcpy(). Usually preview window or call stream window will
+ * have matched format & size with its source.
+ */
+ if (cp->transmitter_cnt == 1 && fmt_id == tr_fmt_id[0] &&
+ pj_memcmp(&size, &tr_size[0], sizeof(size))==0)
+ {
+ TRACE_((THIS_FILE, "This port only has single source with "
+ "matched format & size, no conversion needed"));
+ return;
+ }
+
+ for (i = 0; i < cp->transmitter_cnt && i < 4; ++i) {
+ pj_pool_t *pool;
+ render_state *rs;
+ pjmedia_conversion_param cparam;
+
+ /* Create pool & render state */
+ pool = pj_pool_create(cp->pool->factory, "vconf_rdr", 128, 128, NULL);
+ cp->render_pool[i] = pool;
+ rs = cp->render_states[i] = PJ_POOL_ZALLOC_T(pool, render_state);
+
+ /* Setup format & frame */
+ rs->src_fmt_id = tr_fmt_id[i];
+ rs->dst_fmt_id = fmt_id;
+ rs->src_frame_size = tr_size[i];
+ rs->dst_frame_size = size;
+
+ /* For now, draw the whole source frame, will adjust ratio later */
+ rs->src_rect.coord.x = rs->src_rect.coord.y = 0;
+ rs->src_rect.size = tr_size[i];
+
+ /* Setup layout */
+ if (cp->transmitter_cnt == 1) {
+ rs->dst_rect.coord.x = rs->dst_rect.coord.y = 0;
+ rs->dst_rect.size = size;
+ } else if (cp->transmitter_cnt == 2) {
+ if (is_landscape(&size)) {
+ /*
+ * |
+ * Source 0 | Source 1
+ * |
+ */
+ rs->dst_rect.coord.x = i * (size.w/2);
+ rs->dst_rect.coord.y = 0;
+ rs->dst_rect.size.w = size.w / 2;
+ rs->dst_rect.size.h = size.h;
+ } else {
+ /*
+ * Source 0
+ * --------
+ * Source 1
+ */
+ rs->dst_rect.coord.x = 0;
+ rs->dst_rect.coord.y = i * (size.h/2);
+ rs->dst_rect.size.w = size.w;
+ rs->dst_rect.size.h = size.h / 2;
+ }
+ } else if (cp->transmitter_cnt == 3) {
+ if (is_landscape(&size)) {
+ /*
+ * | Source 1
+ * Source 0 |---------
+ * | Source 2
+ */
+ rs->dst_rect.coord.x = (i==0)? 0 : size.w/2;
+ rs->dst_rect.coord.y = (i!=2)? 0 : size.h/2;
+ rs->dst_rect.size.w = size.w / 2;
+ rs->dst_rect.size.h = (i==0)? size.h : size.h/2;
+ } else {
+ /*
+ * Source 0
+ * --------
+ * Source 1
+ * --------
+ * Source 2
+ */
+ rs->dst_rect.coord.x = 0;
+ rs->dst_rect.coord.y = i * size.h/3;
+ rs->dst_rect.size.w = size.w;
+ rs->dst_rect.size.h = size.h/3;
+ }
+ } else if (cp->transmitter_cnt == 4) {
+ if (is_landscape(&size)) {
+ /*
+ * Source 0 | Source 1
+ * ---------|---------
+ * Source 2 | Source 3
+ */
+ rs->dst_rect.coord.x = (i%2==0)? 0 : size.w/2;
+ rs->dst_rect.coord.y = (i/2==0)? 0 : size.h/2;
+ rs->dst_rect.size.w = size.w/2;
+ rs->dst_rect.size.h = size.h/2;
+ } else {
+ /*
+ * Source 0
+ * --------
+ * Source 1
+ * --------
+ * Source 2
+ * --------
+ * Source 3
+ */
+ rs->dst_rect.coord.x = 0;
+ rs->dst_rect.coord.y = i * size.h/4;
+ rs->dst_rect.size.w = size.w;
+ rs->dst_rect.size.h = size.h/4;
+ }
+ }
+
+ /* Adjust source size to match aspect ratio of rendering space. */
+ match_ratio_crop(&rs->src_rect.size, &rs->dst_rect.size);
+
+ /* Now adjust source position after source size adjustment. */
+ if (rs->src_rect.size.w < tr_size[i].w)
+ rs->src_rect.coord.x = (tr_size[i].w - rs->src_rect.size.w)/2;
+ if (rs->src_rect.size.h < tr_size[i].h)
+ rs->src_rect.coord.y = (tr_size[i].h - rs->src_rect.size.h)/2;
+
+ TRACE_((THIS_FILE, "src%d=%s/%dx%d->%dx%d@%d,%d dst=%dx%d@%d,%d",
+ i, pjmedia_fourcc_name(tr_fmt_id[i], buf),
+ tr_size[i].w, tr_size[i].h,
+ rs->src_rect.size.w, rs->src_rect.size.h,
+ rs->src_rect.coord.x, rs->src_rect.coord.y,
+ rs->dst_rect.size.w, rs->dst_rect.size.h,
+ rs->dst_rect.coord.x, rs->dst_rect.coord.y));
+
+ /* Create converter */
+ pjmedia_format_init_video(&cparam.src, rs->src_fmt_id,
+ rs->src_rect.size.w,
+ rs->src_rect.size.h,
+ 0, 1);
+ pjmedia_format_init_video(&cparam.dst, rs->dst_fmt_id,
+ rs->dst_rect.size.w,
+ rs->dst_rect.size.h,
+ 0, 1);
+ status = pjmedia_converter_create(NULL, pool, &cparam,
+ &rs->converter);
+ if (status != PJ_SUCCESS) {
+ PJ_PERROR(4,(THIS_FILE, status,
+ "Port %d failed creating converter "
+ "for source %d", cp->idx, i));
+ }
+ }
+}
+
+/* Render frame from source to sink buffer based on rendering settings. */
+static pj_status_t render_src_frame(vconf_port *src, vconf_port *sink,
+ unsigned transmitter_idx)
+{
+ pj_status_t status;
+ render_state *rs = sink->render_states[transmitter_idx];
+
+ if (sink->transmitter_cnt == 1 && (!rs || !rs->converter)) {
+ /* The only transmitter and no conversion needed */
+ pj_assert(src->get_buf_size <= sink->put_buf_size);
+ pj_memcpy(sink->put_buf, src->get_buf, src->get_buf_size);
+ } else if (rs && rs->converter) {
+ pjmedia_frame src_frame, dst_frame;
+
+ pj_bzero(&src_frame, sizeof(src_frame));
+ src_frame.buf = src->get_buf;
+ src_frame.size = src->get_buf_size;
+
+ pj_bzero(&dst_frame, sizeof(dst_frame));
+ dst_frame.buf = sink->put_buf;
+ dst_frame.size = sink->put_buf_size;
+
+ status = pjmedia_converter_convert2(rs->converter,
+ &src_frame,
+ &rs->src_frame_size,
+ &rs->src_rect.coord,
+ &dst_frame,
+ &rs->dst_frame_size,
+ &rs->dst_rect.coord,
+ NULL);
+ if (status != PJ_SUCCESS) {
+ PJ_PERROR(4,(THIS_FILE, status,
+ "Port id %d: converter failed in "
+ "rendering frame from port id %d",
+ sink->idx, transmitter_idx));
+ return status;
+ }
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+#endif /* PJMEDIA_HAS_VIDEO */
Index: /pjproject/trunk/pjmedia/src/pjmedia/vid_port.c
===================================================================
--- /pjproject/trunk/pjmedia/src/pjmedia/vid_port.c (revision 5938)
+++ /pjproject/trunk/pjmedia/src/pjmedia/vid_port.c (revision 5939)
@@ -647,6 +647,8 @@
vp->pasv_port = pp = PJ_POOL_ZALLOC_T(pool, vid_pasv_port);
pp->vp = vp;
- pp->base.get_frame = &vid_pasv_port_get_frame;
- pp->base.put_frame = &vid_pasv_port_put_frame;
+ if (prm->vidparam.dir & PJMEDIA_DIR_CAPTURE)
+ pp->base.get_frame = &vid_pasv_port_get_frame;
+ if (prm->vidparam.dir & PJMEDIA_DIR_RENDER)
+ pp->base.put_frame = &vid_pasv_port_put_frame;
pjmedia_port_info_init2(&pp->base.info, &vp->dev_name,
PJMEDIA_SIG_VID_PORT,
@@ -730,4 +732,14 @@
}
+
+PJ_DEF(pj_status_t) pjmedia_vid_port_subscribe_event(
+ pjmedia_vid_port *vp,
+ pjmedia_port *port)
+{
+ PJ_ASSERT_RETURN(vp && port, PJ_EINVAL);
+
+ /* Subscribe to port's events */
+ return pjmedia_event_subscribe(NULL, &client_port_event_cb, vp, port);
+}
PJ_DEF(pj_status_t) pjmedia_vid_port_connect(pjmedia_vid_port *vp,
@@ -1001,5 +1013,5 @@
}
- if (vp->stream_role == ROLE_PASSIVE) {
+ if (vp->role == ROLE_ACTIVE && vp->stream_role == ROLE_PASSIVE) {
pjmedia_clock_param clock_param;
@@ -1017,4 +1029,10 @@
/* pjmedia_vid_port_start(vp); */
pjmedia_vid_dev_stream_start(vp->strm);
+
+ /* Update passive port info from the video stream */
+ if (vp->role == ROLE_PASSIVE) {
+ pjmedia_format_copy(&vp->pasv_port->base.info.fmt,
+ &event->data.fmt_changed.new_fmt);
+ }
}
Index: /pjproject/trunk/pjmedia/src/pjmedia/vid_stream.c
===================================================================
--- /pjproject/trunk/pjmedia/src/pjmedia/vid_stream.c (revision 5938)
+++ /pjproject/trunk/pjmedia/src/pjmedia/vid_stream.c (revision 5939)
@@ -929,4 +929,12 @@
rtp_ts_len = stream->frame_ts_len;
+ /* Empty video frame? Just update RTP timestamp for now */
+ if (frame->type==PJMEDIA_FRAME_TYPE_VIDEO && frame->size==0) {
+ pjmedia_rtp_encode_rtp(&channel->rtp, channel->pt, 1, 0,
+ rtp_ts_len, (const void**)&rtphdr,
+ &rtphdrlen);
+ return PJ_SUCCESS;
+ }
+
/* Init frame_out buffer. */
pj_bzero(&frame_out, sizeof(frame_out));
Index: /pjproject/trunk/pjsip-apps/src/pjsua/pjsua_app_cli.c
===================================================================
--- /pjproject/trunk/pjsip-apps/src/pjsua/pjsua_app_cli.c (revision 5938)
+++ /pjproject/trunk/pjsip-apps/src/pjsua/pjsua_app_cli.c (revision 5939)
@@ -103,4 +103,5 @@
#define CMD_VIDEO_CODEC ((CMD_VIDEO*10)+6)
#define CMD_VIDEO_WIN ((CMD_VIDEO*10)+7)
+#define CMD_VIDEO_CONF ((CMD_VIDEO*10)+8)
/* video level 3 command */
@@ -130,4 +131,7 @@
#define CMD_VIDEO_WIN_MOVE ((CMD_VIDEO_WIN*10)+5)
#define CMD_VIDEO_WIN_RESIZE ((CMD_VIDEO_WIN*10)+6)
+#define CMD_VIDEO_CONF_LIST ((CMD_VIDEO_CONF*10)+1)
+#define CMD_VIDEO_CONF_CONNECT ((CMD_VIDEO_CONF*10)+2)
+#define CMD_VIDEO_CONF_DISCONNECT ((CMD_VIDEO_CONF*10)+3)
/* dynamic choice argument list */
@@ -2466,4 +2470,79 @@
}
+static pj_status_t cmd_vid_conf_list()
+{
+ pjsua_conf_port_id id[100];
+ unsigned count = PJ_ARRAY_SIZE(id);
+ unsigned i;
+ pj_status_t status;
+
+ status = pjsua_vid_conf_enum_ports(id, &count);
+ if (status != PJ_SUCCESS) {
+ PJ_PERROR(1,(THIS_FILE, status,
+ "Failed enumerating video conf bridge ports"));
+ return status;
+ }
+
+ PJ_LOG(3,(THIS_FILE," Video conference has %d ports:\n", count));
+ PJ_LOG(3,(THIS_FILE," id name format rx tx \n"));
+ PJ_LOG(3,(THIS_FILE," ------------------------------------------------------------------\n"));
+ for (i=0; iw, size->h, (float)(fps->num*1.0/fps->denum));
+ PJ_LOG(3,(THIS_FILE,"%3d %.*s%.*s %s%.*s %s%.*s %s\n",
+ id[i],
+ (int)info.name.slen, info.name.ptr,
+ 22-(int)info.name.slen, " ",
+ s,
+ 20-pj_ansi_strlen(s), " ",
+ tr_list,
+ 12-pj_ansi_strlen(tr_list), " ",
+ li_list));
+ }
+ return PJ_SUCCESS;
+}
+
+static pj_status_t cmd_vid_conf_connect(pj_cli_cmd_val *cval, pj_bool_t connect)
+{
+ int P, Q;
+
+ P = (int)pj_strtol(&cval->argv[1]);
+ Q = (int)pj_strtol(&cval->argv[2]);
+ if (connect)
+ return pjsua_vid_conf_connect(P, Q, NULL);
+ else
+ return pjsua_vid_conf_disconnect(P, Q);
+}
+
+
/* Video handler */
static pj_status_t cmd_video_handler(pj_cli_cmd_val *cval)
@@ -2545,4 +2624,11 @@
case CMD_VIDEO_WIN_RESIZE:
status = cmd_resize_vid_win(cval);
+ break;
+ case CMD_VIDEO_CONF_LIST:
+ status = cmd_vid_conf_list();
+ break;
+ case CMD_VIDEO_CONF_CONNECT:
+ case CMD_VIDEO_CONF_DISCONNECT:
+ status = cmd_vid_conf_connect(cval, (cmd_id==CMD_VIDEO_CONF_CONNECT));
break;
}
@@ -3051,4 +3137,15 @@
" "
" "
+ " "
+ " "
+ " "
+ " "
+ " "
+ " "
+ " "
+ " "
+ " "
+ " "
+ " "
"";
Index: /pjproject/trunk/pjsip-apps/src/pjsua/pjsua_app_legacy.c
===================================================================
--- /pjproject/trunk/pjsip-apps/src/pjsua/pjsua_app_legacy.c (revision 5938)
+++ /pjproject/trunk/pjsip-apps/src/pjsua/pjsua_app_legacy.c (revision 5939)
@@ -297,4 +297,7 @@
puts("| vid win move ID X Y Move window ID to position X,Y |");
puts("| vid win resize ID w h Resize window ID to the specified width, height |");
+ puts("| vid conf list List all video ports in video conference bridge |");
+ puts("| vid conf cc P Q Connect port P to Q in the video conf bridge |");
+ puts("| vid conf cd P Q Disconnect port P to Q in the video conf bridge |");
puts("+=============================================================================+");
printf("| Video will be %s in the next offer/answer %s |\n",
@@ -592,4 +595,78 @@
} else
goto on_error;
+ } else if (strcmp(argv[1], "conf")==0) {
+ pj_status_t status;
+
+ if (argc==3 && strcmp(argv[2], "list")==0) {
+ pjsua_conf_port_id id[100];
+ unsigned count = PJ_ARRAY_SIZE(id);
+
+ status = pjsua_vid_conf_enum_ports(id, &count);
+ if (status != PJ_SUCCESS) {
+ PJ_PERROR(1,(THIS_FILE, status,
+ "Failed enumerating video conf bridge ports"));
+ } else {
+ unsigned i;
+ printf(" Video conference has %d ports:\n", count);
+ printf(" id name format rx tx \n");
+ printf(" ------------------------------------------------------------------\n");
+ for (i=0; iw, size->h,
+ (float)(fps->num*1.0/fps->denum));
+ printf("%3d %.*s%.*s %s%.*s %s%.*s %s\n",
+ id[i],
+ (int)info.name.slen, info.name.ptr,
+ 22-(int)info.name.slen, " ",
+ s,
+ 20-pj_ansi_strlen(s), " ",
+ tr_list,
+ 12-pj_ansi_strlen(tr_list), " ",
+ li_list);
+ }
+ }
+ } else if (argc==5 && strcmp(argv[2], "cc")==0) {
+ int P, Q;
+ P = atoi(argv[3]);
+ Q = atoi(argv[4]);
+ pjsua_vid_conf_connect(P, Q, NULL);
+ } else if (argc==5 && strcmp(argv[2], "cd")==0) {
+ int P, Q;
+ P = atoi(argv[3]);
+ Q = atoi(argv[4]);
+ pjsua_vid_conf_disconnect(P, Q);
+ } else {
+ goto on_error;
+ }
} else
goto on_error;
Index: /pjproject/trunk/pjsip/include/pjsua-lib/pjsua.h
===================================================================
--- /pjproject/trunk/pjsip/include/pjsua-lib/pjsua.h (revision 5938)
+++ /pjproject/trunk/pjsip/include/pjsua-lib/pjsua.h (revision 5939)
@@ -4680,6 +4680,19 @@
pjsua_vid_win_id win_in;
- /** The video capture device for outgoing transmission,
- * if any, or PJMEDIA_VID_INVALID_DEV
+ /**
+ * The video conference port number for the call in decoding
+ * direction.
+ */
+ pjsua_conf_port_id dec_slot;
+
+ /**
+ * The video conference port number for the call in encoding
+ * direction.
+ */
+ pjsua_conf_port_id enc_slot;
+
+ /**
+ * The video capture device for outgoing transmission,
+ * if any, or PJMEDIA_VID_INVALID_DEV
*/
pjmedia_vid_dev_index cap_dev;
@@ -5168,4 +5181,34 @@
*/
PJ_DECL(pjsua_conf_port_id) pjsua_call_get_conf_port(pjsua_call_id call_id);
+
+
+/**
+ * Get the video window associated with the call. Note that this function
+ * will only evaluate the first video stream in the call, to query any other
+ * video stream, use pjsua_call_get_info().
+ *
+ * @param call_id Call identification.
+ *
+ * @return Video window, or PJSUA_INVALID_ID when the
+ * media has not been established or is not active.
+ */
+PJ_DECL(pjsua_vid_win_id) pjsua_call_get_vid_win(pjsua_call_id call_id);
+
+
+/**
+ * Get the video conference port identification associated with the call.
+ * Note that this function will only evaluate the first video stream in
+ * the call, to query any other video stream, use pjsua_call_get_info().
+ *
+ * @param call_id Call identification.
+ * @param dir Port direction to be queried. Valid values are
+ * PJMEDIA_DIR_ENCODING and PJMEDIA_DIR_DECODING only.
+ *
+ * @return Conference port ID, or PJSUA_INVALID_ID when the
+ * media has not been established or is not active.
+ */
+PJ_DECL(pjsua_conf_port_id) pjsua_call_get_vid_conf_port(
+ pjsua_call_id call_id,
+ pjmedia_dir dir);
/**
@@ -6704,5 +6747,5 @@
/**
- * This structure descibes information about a particular media port that
+ * This structure describes information about a particular media port that
* has been registered into the conference bridge. Application can query
* this info by calling #pjsua_conf_get_port_info().
@@ -6741,5 +6784,5 @@
/** Array of listeners (in other words, ports where this port is
- * transmitting to.
+ * transmitting to).
*/
pjsua_conf_port_id listeners[PJSUA_MAX_CONF_PORTS];
@@ -7808,4 +7851,16 @@
/**
+ * Get video conference slot ID of the specified capture device, if any.
+ *
+ * @param id The capture device ID.
+ *
+ * @return The video conference slot ID of the specified capture
+ * device ID, or PJSUA_INVALID_ID if preview has not been
+ * started for the device.
+ */
+PJ_DECL(pjsua_conf_port_id) pjsua_vid_preview_get_vid_conf_port(
+ pjmedia_vid_dev_index id);
+
+/**
* Stop video preview.
*
@@ -7843,4 +7898,9 @@
*/
pjmedia_vid_dev_index rdr_dev;
+
+ /**
+ * Renderer port ID in the video conference bridge.
+ */
+ pjsua_conf_port_id slot_id;
/**
@@ -8018,4 +8078,145 @@
+/*
+ * Video conference API
+ */
+
+/**
+ * This structure describes information about a particular video media port
+ * that has been registered into the video conference bridge. Application
+ * can query this info by calling #pjsua_vid_conf_get_port_info().
+ */
+typedef struct pjsua_vid_conf_port_info
+{
+ /** Conference port number. */
+ pjsua_conf_port_id slot_id;
+
+ /** Port name. */
+ pj_str_t name;
+
+ /** Format. */
+ pjmedia_format format;
+
+ /** Number of listeners in the array. */
+ unsigned listener_cnt;
+
+ /** Array of listeners (in other words, ports where this port is
+ * transmitting to).
+ */
+ pjsua_conf_port_id listeners[PJSUA_MAX_CONF_PORTS];
+
+ /** Number of transmitters in the array. */
+ unsigned transmitter_cnt;
+
+ /** Array of transmitters (in other words, ports where this port is
+ * receiving from).
+ */
+ pjsua_conf_port_id transmitters[PJSUA_MAX_CONF_PORTS];
+
+} pjsua_vid_conf_port_info;
+
+
+/**
+ * Get current number of active ports in the bridge.
+ *
+ * @return The number.
+ */
+PJ_DECL(unsigned) pjsua_vid_conf_get_active_ports(void);
+
+
+/**
+ * Enumerate all video conference ports.
+ *
+ * @param id Array of conference port ID to be initialized.
+ * @param count On input, specifies max elements in the array.
+ * On return, it contains actual number of elements
+ * that have been initialized.
+ *
+ * @return PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_vid_conf_enum_ports(pjsua_conf_port_id id[],
+ unsigned *count);
+
+
+/**
+ * Get information about the specified video conference port
+ *
+ * @param port_id Port identification.
+ * @param info Pointer to store the port info.
+ *
+ * @return PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_vid_conf_get_port_info(
+ pjsua_conf_port_id port_id,
+ pjsua_vid_conf_port_info *info);
+
+
+/**
+ * Add arbitrary video media port to PJSUA's video conference bridge.
+ * Application can use this function to add the media port that it creates.
+ * For media ports that are created by PJSUA-LIB (such as calls, AVI player),
+ * PJSUA-LIB will automatically add the port to the bridge.
+ *
+ * @param pool Pool to use.
+ * @param port Media port to be added to the bridge.
+ * @param param Currently this is not used and must be set to NULL.
+ * @param p_id Optional pointer to receive the conference
+ * slot id.
+ *
+ * @return PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_vid_conf_add_port(pj_pool_t *pool,
+ pjmedia_port *port,
+ const void *param,
+ pjsua_conf_port_id *p_id);
+
+
+/**
+ * Remove arbitrary slot from the video conference bridge. Application should
+ * only call this function if it registered the port manually with previous
+ * call to #pjsua_vid_conf_add_port().
+ *
+ * @param port_id The slot id of the port to be removed.
+ *
+ * @return PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_vid_conf_remove_port(pjsua_conf_port_id port_id);
+
+
+/**
+ * Establish unidirectional video flow from souce to sink. One source
+ * may transmit to multiple destinations/sink. And if multiple
+ * sources are transmitting to the same sink, the video will be mixed
+ * together (currently, each source will be resized down so all sources will
+ * occupy the same portion in the sink video frame). Source and sink may
+ * refer to the same ID, effectively looping the media.
+ *
+ * If bidirectional media flow is desired, application needs to call
+ * this function twice, with the second one having the arguments
+ * reversed.
+ *
+ * @param source Port ID of the source media/transmitter.
+ * @param sink Port ID of the destination media/received.
+ * @param param Currently this is not used and must be set to NULL.
+ *
+ * @return PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_vid_conf_connect(pjsua_conf_port_id source,
+ pjsua_conf_port_id sink,
+ const void *param);
+
+
+/**
+ * Disconnect video flow from the source to destination port.
+ *
+ * @param source Port ID of the source media/transmitter.
+ * @param sink Port ID of the destination media/received.
+ *
+ * @return PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_vid_conf_disconnect(pjsua_conf_port_id source,
+ pjsua_conf_port_id sink);
+
+
/* end of VIDEO API */
Index: /pjproject/trunk/pjsip/include/pjsua-lib/pjsua_internal.h
===================================================================
--- /pjproject/trunk/pjsip/include/pjsua-lib/pjsua_internal.h (revision 5938)
+++ /pjproject/trunk/pjsip/include/pjsua-lib/pjsua_internal.h (revision 5939)
@@ -60,4 +60,6 @@
struct {
pjmedia_vid_stream *stream; /**< The video stream. */
+ pjsua_conf_port_id strm_enc_slot; /**< Stream encode slot */
+ pjsua_conf_port_id strm_dec_slot; /**< Stream decode slot */
pjsua_vid_win_id cap_win_id;/**< The video capture window */
pjsua_vid_win_id rdr_win_id;/**< The video render window */
@@ -404,5 +406,6 @@
pjmedia_vid_port *vp_cap; /**< Capture vidport. */
pjmedia_vid_port *vp_rend; /**< Renderer vidport */
- pjmedia_port *tee; /**< Video tee */
+ pjsua_conf_port_id cap_slot; /**< Capturer conf slot */
+ pjsua_conf_port_id rend_slot; /**< Renderer conf slot */
pjmedia_vid_dev_index preview_cap_id;/**< Capture dev id */
pj_bool_t preview_running;/**< Preview is started*/
@@ -513,4 +516,5 @@
/* For keeping video device settings */
#if PJSUA_HAS_VIDEO
+ pjmedia_vid_conf *vid_conf;
pj_uint32_t vid_caps[PJMEDIA_VID_DEV_MAX_DEVS];
pjmedia_vid_dev_param vid_param[PJMEDIA_VID_DEV_MAX_DEVS];
Index: /pjproject/trunk/pjsip/src/pjsua-lib/pjsua_call.c
===================================================================
--- /pjproject/trunk/pjsip/src/pjsua-lib/pjsua_call.c (revision 5938)
+++ /pjproject/trunk/pjsip/src/pjsua-lib/pjsua_call.c (revision 5939)
@@ -153,4 +153,6 @@
call_med->strm.v.cap_win_id = PJSUA_INVALID_ID;
call_med->strm.v.rdr_win_id = PJSUA_INVALID_ID;
+ call_med->strm.v.strm_dec_slot = PJSUA_INVALID_ID;
+ call_med->strm.v.strm_enc_slot = PJSUA_INVALID_ID;
call_med->call = call;
call_med->idx = i;
@@ -2224,4 +2226,9 @@
info->media[info->media_cnt].stream.vid.win_in =
call_med->strm.v.rdr_win_id;
+
+ info->media[info->media_cnt].stream.vid.dec_slot =
+ call_med->strm.v.strm_dec_slot;
+ info->media[info->media_cnt].stream.vid.enc_slot =
+ call_med->strm.v.strm_enc_slot;
if (call_med->strm.v.cap_win_id != PJSUA_INVALID_ID) {
Index: /pjproject/trunk/pjsip/src/pjsua-lib/pjsua_media.c
===================================================================
--- /pjproject/trunk/pjsip/src/pjsua-lib/pjsua_media.c (revision 5938)
+++ /pjproject/trunk/pjsip/src/pjsua-lib/pjsua_media.c (revision 5939)
@@ -1477,4 +1477,17 @@
call_med->last_req_keyframe = now;
}
+ }
+ }
+ break;
+
+ case PJMEDIA_EVENT_FMT_CHANGED:
+ if (call_med->strm.v.rdr_win_id != PJSUA_INVALID_ID) {
+ pjsua_vid_win *w = &pjsua_var.win[call_med->strm.v.rdr_win_id];
+ if (event->src == w->vp_rend) {
+ /* Renderer just changed format, reconnect stream */
+ pjsua_vid_conf_disconnect(call_med->strm.v.strm_dec_slot,
+ w->rend_slot);
+ pjsua_vid_conf_connect(call_med->strm.v.strm_dec_slot,
+ w->rend_slot, NULL);
}
}
Index: /pjproject/trunk/pjsip/src/pjsua-lib/pjsua_vid.c
===================================================================
--- /pjproject/trunk/pjsip/src/pjsua-lib/pjsua_vid.c (revision 5938)
+++ /pjproject/trunk/pjsip/src/pjsua-lib/pjsua_vid.c (revision 5939)
@@ -27,5 +27,4 @@
#define ENABLE_EVENT 1
-#define VID_TEE_MAX_PORT (PJSUA_MAX_CALLS + 1)
#define PJSUA_SHOW_WINDOW 1
@@ -66,4 +65,12 @@
PJ_PERROR(1,(THIS_FILE, status,
"Error creating PJMEDIA video codec manager"));
+ goto on_error;
+ }
+
+ status = pjmedia_vid_conf_create(pjsua_var.pool, NULL,
+ &pjsua_var.vid_conf);
+ if (status != PJ_SUCCESS) {
+ PJ_PERROR(1,(THIS_FILE, status,
+ "Error creating PJMEDIA video conference bridge"));
goto on_error;
}
@@ -139,4 +146,9 @@
pjsua_var.win[i].pool = NULL;
}
+ }
+
+ if (pjsua_var.vid_conf) {
+ pjmedia_vid_conf_destroy(pjsua_var.vid_conf);
+ pjsua_var.vid_conf = NULL;
}
@@ -515,4 +527,22 @@
}
+/*
+ * Get video conference slot ID of the specified capture device.
+ */
+PJ_DEF(pjsua_conf_port_id) pjsua_vid_preview_get_vid_conf_port(
+ pjmedia_vid_dev_index id)
+{
+ pjsua_vid_win_id wid;
+ pjsua_vid_win *w;
+
+ wid = vid_preview_get_win(id, PJ_TRUE);
+ if (wid == PJSUA_INVALID_ID)
+ return PJSUA_INVALID_ID;
+
+ w = &pjsua_var.win[wid];
+ return w->cap_slot;
+}
+
+
PJ_DEF(void) pjsua_vid_win_reset(pjsua_vid_win_id wid)
{
@@ -528,7 +558,7 @@
/* Allocate and initialize pjsua video window:
- * - If the type is preview, video capture, tee, and render
- * will be instantiated.
- * - If the type is stream, only renderer will be created.
+ * - If the type is preview: capture port and render port
+ * will be instantiated, and connected via conf.
+ * - If the type is stream: only render port will be created.
*/
static pj_status_t create_vid_win(pjsua_vid_win_type type,
@@ -673,5 +703,5 @@
/* Create capture video port */
- vp_param.active = PJ_TRUE;
+ vp_param.active = PJ_FALSE;
vp_param.vidparam.dir = PJMEDIA_DIR_CAPTURE;
@@ -703,12 +733,9 @@
fmt = &fmt_;
- /* Create video tee */
- status = pjmedia_vid_tee_create(w->pool, fmt, VID_TEE_MAX_PORT,
- &w->tee);
- if (status != PJ_SUCCESS)
- goto on_error;
-
- /* Connect capturer to the video tee */
- status = pjmedia_vid_port_connect(w->vp_cap, w->tee, PJ_FALSE);
+ /* Register capturer to the video conf */
+ status = pjsua_vid_conf_add_port(
+ w->pool,
+ pjmedia_vid_port_get_passive_port(w->vp_cap),
+ NULL, &w->cap_slot);
if (status != PJ_SUCCESS)
goto on_error;
@@ -739,5 +766,5 @@
goto on_error;
- vp_param.active = (w->type == PJSUA_WND_TYPE_STREAM);
+ vp_param.active = PJ_FALSE;
vp_param.vidparam.dir = PJMEDIA_DIR_RENDER;
vp_param.vidparam.fmt = *fmt;
@@ -756,10 +783,15 @@
goto on_error;
- /* For preview window, connect capturer & renderer (via tee) */
+ /* Register renderer to the video conf */
+ status = pjsua_vid_conf_add_port(
+ w->pool,
+ pjmedia_vid_port_get_passive_port(w->vp_rend),
+ NULL, &w->rend_slot);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* For preview window, connect capturer & renderer (via conf) */
if (w->type == PJSUA_WND_TYPE_PREVIEW) {
- pjmedia_port *rend_port;
-
- rend_port = pjmedia_vid_port_get_passive_port(w->vp_rend);
- status = pjmedia_vid_tee_add_dst_port2(w->tee, 0, rend_port);
+ status = pjsua_vid_conf_connect(w->cap_slot, w->rend_slot, NULL);
if (status != PJ_SUCCESS)
goto on_error;
@@ -799,11 +831,12 @@
if (w->vp_cap) {
+ pjsua_vid_conf_remove_port(w->cap_slot);
pjmedia_event_unsubscribe(NULL, &call_media_on_event, NULL,
w->vp_cap);
pjmedia_vid_port_stop(w->vp_cap);
- pjmedia_vid_port_disconnect(w->vp_cap);
pjmedia_vid_port_destroy(w->vp_cap);
}
if (w->vp_rend) {
+ pjsua_vid_conf_remove_port(w->rend_slot);
pjmedia_event_unsubscribe(NULL, &call_media_on_event, NULL,
w->vp_rend);
@@ -811,7 +844,4 @@
pjmedia_vid_port_destroy(w->vp_rend);
}
- if (w->tee) {
- pjmedia_port_destroy(w->tee);
- }
pjsua_vid_win_reset(wid);
@@ -850,4 +880,6 @@
call_med->strm.v.rdr_dev = acc->cfg.vid_rend_dev;
call_med->strm.v.cap_dev = acc->cfg.vid_cap_dev;
+ call_med->strm.v.strm_dec_slot = PJSUA_INVALID_ID;
+ call_med->strm.v.strm_enc_slot = PJSUA_INVALID_ID;
if (call_med->strm.v.rdr_dev == PJMEDIA_VID_DEFAULT_RENDER_DEV) {
pjmedia_vid_dev_info info;
@@ -979,4 +1011,5 @@
pj_log_push_indent();
+ /* Retrieve stream decoding port */
status = pjmedia_vid_stream_get_port(call_med->strm.v.stream,
PJMEDIA_DIR_DECODING,
@@ -1010,7 +1043,20 @@
#endif
- /* Connect renderer to stream */
- status = pjmedia_vid_port_connect(w->vp_rend, media_port,
- PJ_FALSE);
+ /* Register renderer to stream events */
+ pjmedia_vid_port_subscribe_event(w->vp_rend, media_port);
+
+ /* Register stream decoding to conf, using tmp_pool should be fine
+ * as bridge will create its own pool (using tmp_pool factory).
+ */
+ status = pjsua_vid_conf_add_port(tmp_pool, media_port, NULL,
+ &call_med->strm.v.strm_dec_slot);
+ if (status != PJ_SUCCESS) {
+ pj_log_pop_indent();
+ goto on_error;
+ }
+
+ /* Connect stream to renderer (via conf) */
+ status = pjsua_vid_conf_connect(call_med->strm.v.strm_dec_slot,
+ w->rend_slot, NULL);
if (status != PJ_SUCCESS) {
pj_log_pop_indent();
@@ -1042,4 +1088,5 @@
pj_log_push_indent();
+ /* Retrieve stream encoding port */
status = pjmedia_vid_stream_get_port(call_med->strm.v.stream,
PJMEDIA_DIR_ENCODING,
@@ -1079,6 +1126,18 @@
#endif
- /* Connect stream to capturer (via video window tee) */
- status = pjmedia_vid_tee_add_dst_port2(w->tee, 0, media_port);
+ /* Register stream encoding to conf, using tmp_pool should be fine
+ * as bridge will create its own pool (using tmp_pool factory).
+ */
+ status = pjsua_vid_conf_add_port(tmp_pool, media_port, NULL,
+ &call_med->strm.v.strm_enc_slot);
+ if (status != PJ_SUCCESS) {
+ pj_log_pop_indent();
+ goto on_error;
+ }
+
+ /* Connect capturer to stream encoding (via conf) */
+ status = pjsua_vid_conf_connect(w->cap_slot,
+ call_med->strm.v.strm_enc_slot,
+ NULL);
if (status != PJ_SUCCESS) {
pj_log_pop_indent();
@@ -1133,30 +1192,18 @@
pj_log_push_indent();
+ /* Unregister video stream ports (encode+decode) from conference */
+ pjsua_vid_conf_remove_port(call_med->strm.v.strm_enc_slot);
+ pjsua_vid_conf_remove_port(call_med->strm.v.strm_dec_slot);
+
pjmedia_vid_stream_send_rtcp_bye(strm);
if (call_med->strm.v.cap_win_id != PJSUA_INVALID_ID) {
- pjmedia_port *media_port;
pjsua_vid_win *w = &pjsua_var.win[call_med->strm.v.cap_win_id];
- pj_status_t status;
-
- /* Stop the capture before detaching stream and unsubscribing event */
- pjmedia_vid_port_stop(w->vp_cap);
-
- /* Disconnect video stream from capture device */
- status = pjmedia_vid_stream_get_port(call_med->strm.v.stream,
- PJMEDIA_DIR_ENCODING,
- &media_port);
- if (status == PJ_SUCCESS) {
- pjmedia_vid_tee_remove_dst_port(w->tee, media_port);
- }
-
- /* Unsubscribe event */
+
+ /* Unsubscribe event */
pjmedia_event_unsubscribe(NULL, &call_media_on_event, call_med,
w->vp_cap);
- /* Re-start capture again, if it is used by other stream */
- if (w->ref_cnt > 1)
- pjmedia_vid_port_start(w->vp_cap);
-
+ /* Decrement ref count of preview video window */
dec_vid_win(call_med->strm.v.cap_win_id);
call_med->strm.v.cap_win_id = PJSUA_INVALID_ID;
@@ -1166,9 +1213,10 @@
pjsua_vid_win *w = &pjsua_var.win[call_med->strm.v.rdr_win_id];
- /* Stop the render before unsubscribing event */
+ /* Unsubscribe event, but stop the render first */
pjmedia_vid_port_stop(w->vp_rend);
pjmedia_event_unsubscribe(NULL, &call_media_on_event, call_med,
w->vp_rend);
+ /* Decrement ref count of stream video window */
dec_vid_win(call_med->strm.v.rdr_win_id);
call_med->strm.v.rdr_win_id = PJSUA_INVALID_ID;
@@ -1432,4 +1480,5 @@
wi->rdr_dev = vparam.rend_id;
+ wi->slot_id = w->rend_slot;
wi->hwnd = vparam.window;
wi->show = !vparam.window_hide;
@@ -2043,8 +2092,11 @@
w->preview_cap_id = cap_dev;
call_med->strm.v.cap_dev = cap_dev;
+ /* Yay, change capturer done! */
return PJ_SUCCESS;
}
- /* No it doesn't support fast switching. Do slow switching then.. */
+ /* Oh no, it doesn't support fast switching. Do normal change then,
+ * i.e: remove the old and create a new capture.
+ */
status = pjmedia_vid_stream_get_port(call_med->strm.v.stream,
PJMEDIA_DIR_ENCODING, &media_port);
@@ -2055,20 +2107,10 @@
w->vp_cap);
- /* temporarily disconnect while we operate on the tee. */
- pjmedia_vid_port_disconnect(w->vp_cap);
-
- /* = Detach stream port from the old capture device's tee = */
- status = pjmedia_vid_tee_remove_dst_port(w->tee, media_port);
- if (status != PJ_SUCCESS) {
- /* Something wrong, assume that media_port has been removed
- * and continue.
- */
- PJ_PERROR(4,(THIS_FILE, status,
- "Warning: call %d: unable to remove video from tee",
- call->index));
- }
-
- /* Reconnect again immediately. We're done with w->tee */
- pjmedia_vid_port_connect(w->vp_cap, w->tee, PJ_FALSE);
+ /* Disconnect the old capture device to stream encoding port */
+ status = pjsua_vid_conf_disconnect(w->cap_slot,
+ call_med->strm.v.strm_enc_slot);
+ if (status != PJ_SUCCESS)
+ return status;
+
/* = Attach stream port to the new capture device = */
@@ -2098,9 +2140,4 @@
inc_vid_win(new_wid);
new_w = &pjsua_var.win[new_wid];
-
- /* Connect stream to capturer (via video window tee) */
- status = pjmedia_vid_tee_add_dst_port2(new_w->tee, 0, media_port);
- if (status != PJ_SUCCESS)
- goto on_error;
if (new_w->vp_rend) {
@@ -2123,4 +2160,11 @@
}
+ /* Connect capturer to stream encoding port (via conf) */
+ status = pjsua_vid_conf_connect(new_w->cap_slot,
+ call_med->strm.v.strm_enc_slot,
+ NULL);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
/* Finally */
call_med->strm.v.cap_dev = cap_dev;
@@ -2139,6 +2183,5 @@
pjmedia_event_unsubscribe(NULL, &call_media_on_event, call_med,
new_w->vp_cap);
- /* Disconnect media port from the new capturer */
- pjmedia_vid_tee_remove_dst_port(new_w->tee, media_port);
+
/* Release the new capturer */
dec_vid_win(new_wid);
@@ -2146,7 +2189,6 @@
/* Revert back to the old capturer */
- pjmedia_vid_port_disconnect(w->vp_cap);
- status = pjmedia_vid_tee_add_dst_port2(w->tee, 0, media_port);
- pjmedia_vid_port_connect(w->vp_cap, w->tee, PJ_FALSE);
+ status = pjsua_vid_conf_connect(w->cap_slot,
+ call_med->strm.v.strm_enc_slot, NULL);
if (status != PJ_SUCCESS)
return status;
@@ -2372,4 +2414,201 @@
}
+
+/*****************************************************************************
+ * Video conference
+ */
+
+/*
+ * Get current number of active ports in the bridge.
+ */
+PJ_DEF(unsigned) pjsua_vid_conf_get_active_ports(void)
+{
+ return pjmedia_vid_conf_get_port_count(pjsua_var.vid_conf);
+}
+
+
+/*
+ * Enumerate all video conference ports.
+ */
+PJ_DEF(pj_status_t) pjsua_vid_conf_enum_ports( pjsua_conf_port_id id[],
+ unsigned *count)
+{
+ return pjmedia_vid_conf_enum_ports(pjsua_var.vid_conf,
+ (unsigned*)id, count);
+}
+
+
+/*
+ * Get information about the specified video conference port
+ */
+PJ_DEF(pj_status_t) pjsua_vid_conf_get_port_info(
+ pjsua_conf_port_id port_id,
+ pjsua_vid_conf_port_info *info)
+{
+ pjmedia_vid_conf_port_info cinfo;
+ unsigned i;
+ pj_status_t status;
+
+ status = pjmedia_vid_conf_get_port_info(pjsua_var.vid_conf,
+ (unsigned)port_id, &cinfo);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ pj_bzero(info, sizeof(*info));
+ info->slot_id = port_id;
+ info->name = cinfo.name;
+ pjmedia_format_copy(&info->format, &cinfo.format);
+
+ /* Build array of listeners */
+ info->listener_cnt = cinfo.listener_cnt;
+ for (i=0; ilisteners[i] = cinfo.listener_slots[i];
+ }
+
+ /* Build array of transmitters */
+ info->transmitter_cnt = cinfo.transmitter_cnt;
+ for (i=0; itransmitters[i] = cinfo.transmitter_slots[i];
+ }
+
+ return PJ_SUCCESS;
+
+}
+
+
+/*
+ * Add arbitrary video media port to PJSUA's video conference bridge.
+ */
+PJ_DEF(pj_status_t) pjsua_vid_conf_add_port( pj_pool_t *pool,
+ pjmedia_port *port,
+ const void *param,
+ pjsua_conf_port_id *p_id)
+{
+ pj_status_t status;
+
+ PJ_UNUSED_ARG(param);
+
+ status = pjmedia_vid_conf_add_port(pjsua_var.vid_conf, pool,
+ port, NULL, NULL, (unsigned*)p_id);
+ if (status != PJ_SUCCESS) {
+ if (p_id)
+ *p_id = PJSUA_INVALID_ID;
+ }
+
+ return status;
+}
+
+
+/*
+ * Remove arbitrary slot from the video conference bridge.
+ */
+PJ_DEF(pj_status_t) pjsua_vid_conf_remove_port(pjsua_conf_port_id id)
+{
+ return pjmedia_vid_conf_remove_port(pjsua_var.vid_conf, (unsigned)id);
+}
+
+
+/*
+ * Establish unidirectional video flow from souce to sink.
+ */
+PJ_DEF(pj_status_t) pjsua_vid_conf_connect( pjsua_conf_port_id source,
+ pjsua_conf_port_id sink,
+ const void *param)
+{
+ PJ_UNUSED_ARG(param);
+ return pjmedia_vid_conf_connect_port(pjsua_var.vid_conf, source, sink,
+ NULL);
+}
+
+
+/*
+ * Disconnect video flow from the source to destination port.
+ */
+PJ_DEF(pj_status_t) pjsua_vid_conf_disconnect(pjsua_conf_port_id source,
+ pjsua_conf_port_id sink)
+{
+ return pjmedia_vid_conf_disconnect_port(pjsua_var.vid_conf, source, sink);
+}
+
+/*
+ * Get the video window associated with the call.
+ */
+PJ_DEF(pjsua_vid_win_id) pjsua_call_get_vid_win(pjsua_call_id call_id)
+{
+ pjsua_call *call;
+ pjsua_vid_win_id wid = PJSUA_INVALID_ID;
+ unsigned i;
+
+ PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
+ PJ_EINVAL);
+
+ /* Use PJSUA_LOCK() instead of acquire_call():
+ * https://trac.pjsip.org/repos/ticket/1371
+ */
+ PJSUA_LOCK();
+
+ if (!pjsua_call_is_active(call_id))
+ goto on_return;
+
+ call = &pjsua_var.calls[call_id];
+ for (i = 0; i < call->med_cnt; ++i) {
+ if (call->media[i].type == PJMEDIA_TYPE_VIDEO &&
+ (call->media[i].dir & PJMEDIA_DIR_DECODING))
+ {
+ wid = call->media[i].strm.v.rdr_win_id;
+ break;
+ }
+ }
+
+on_return:
+ PJSUA_UNLOCK();
+
+ return wid;
+}
+
+
+/*
+ * Get the video conference port identification associated with the call.
+ */
+PJ_DEF(pjsua_conf_port_id) pjsua_call_get_vid_conf_port(
+ pjsua_call_id call_id,
+ pjmedia_dir dir)
+{
+ pjsua_call *call;
+ pjsua_conf_port_id port_id = PJSUA_INVALID_ID;
+ unsigned i;
+
+ PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
+ PJ_EINVAL);
+ PJ_ASSERT_RETURN(dir==PJMEDIA_DIR_ENCODING || dir==PJMEDIA_DIR_DECODING,
+ PJ_EINVAL);
+
+ /* Use PJSUA_LOCK() instead of acquire_call():
+ * https://trac.pjsip.org/repos/ticket/1371
+ */
+ PJSUA_LOCK();
+
+ if (!pjsua_call_is_active(call_id))
+ goto on_return;
+
+ call = &pjsua_var.calls[call_id];
+ for (i = 0; i < call->med_cnt; ++i) {
+ if (call->media[i].type == PJMEDIA_TYPE_VIDEO &&
+ (call->media[i].dir & dir))
+ {
+ port_id = (dir==PJMEDIA_DIR_ENCODING)?
+ call->media[i].strm.v.strm_enc_slot :
+ call->media[i].strm.v.strm_dec_slot;
+ break;
+ }
+ }
+
+on_return:
+ PJSUA_UNLOCK();
+
+ return port_id;
+}
+
+
#endif /* PJSUA_HAS_VIDEO */