Initial commit re #1263 (PJSUA-LIB Video API):
- API designed and reviewed (pjsua.h)
- Implemented these APIs and added to pjsua sample application:
- video device enums API
- video capture preview API
- refactoring in PJSUA-LIB:
- video stuffs go to pjsua_vid.c
- call dump goes to pjsua_dump.c
We're still missing:
- video call API implementation
- media info and statistic API implementation
git-svn-id: https://svn.pjsip.org/repos/pjproject/branches/projects/2.0-dev@3609 74dad513-b988-da41-8d7b-12977e46ad98
diff --git a/pjsip/build/Makefile b/pjsip/build/Makefile
index b92f89d..247ee7f 100644
--- a/pjsip/build/Makefile
+++ b/pjsip/build/Makefile
@@ -75,7 +75,8 @@
export PJSUA_LIB_SRCDIR = ../src/pjsua-lib
export PJSUA_LIB_OBJS += $(OS_OBJS) $(M_OBJS) $(CC_OBJS) $(HOST_OBJS) \
pjsua_acc.o pjsua_call.o pjsua_core.o \
- pjsua_im.o pjsua_media.o pjsua_pres.o
+ pjsua_im.o pjsua_media.o pjsua_pres.o \
+ pjsua_dump.o pjsua_vid.o
export PJSUA_LIB_CFLAGS += $(_CFLAGS) $(PJ_VIDEO_CFLAGS)
diff --git a/pjsip/include/pjsua-lib/pjsua.h b/pjsip/include/pjsua-lib/pjsua.h
index 012f5b1..9153afd 100644
--- a/pjsip/include/pjsua-lib/pjsua.h
+++ b/pjsip/include/pjsua-lib/pjsua.h
@@ -324,6 +324,12 @@
# define PJSUA_ACQUIRE_CALL_TIMEOUT 2000
#endif
+/**
+ * Is video enabled.
+ */
+#ifndef PJSUA_HAS_VIDEO
+# define PJSUA_HAS_VIDEO PJMEDIA_HAS_VIDEO
+#endif
/**
* This enumeration represents pjsua state.
@@ -2438,13 +2444,60 @@
/**
* Maximum number of simultaneous active video streams to be allowed
* for calls on this account. Setting this to zero will disable video
- * in calls on this account.
+ * in calls on this account, regardless of other video settings.
*
- * Default: 0
+ * Default: 1
*/
unsigned max_video_cnt;
/**
+ * Specify whether incoming video should be shown to screen by default.
+ * This applies to incoming call (INVITE), incoming re-INVITE, and
+ * incoming UPDATE requests.
+ *
+ * Regardless of this setting, application can detect incoming video
+ * by implementing \a on_call_media_state() callback and enumerating
+ * the media stream(s) with #pjsua_call_get_info(). Once incoming
+ * video is recognised, application may retrieve the window associated
+ * with the incoming video and show or hide it with
+ * #pjsua_vid_win_set_show().
+ *
+ * Default: PJ_FALSE
+ */
+ pj_bool_t vid_in_auto_show;
+
+ /**
+ * Specify whether outgoing video should be activated by default when
+ * making outgoing calls and/or when incoming video is detected. This
+ * applies to incoming and outgoing calls, incoming re-INVITE, and
+ * incoming UPDATE. If the setting is non-zero, outgoing video
+ * transmission will be started as soon as response to these requests
+ * is sent (or received).
+ *
+ * Regardless of the value of this setting, application can start and
+ * stop outgoing video transmission with #pjsua_call_set_vid_out().
+ *
+ * Default: PJ_FALSE
+ */
+ pj_bool_t vid_out_auto_transmit;
+
+ /**
+ * Specify the default capture device to be used by this account. If
+ * \a vid_out_auto_transmit is enabled, this device will be used for
+ * capturing video.
+ *
+ * Default: PJMEDIA_VID_DEFAULT_CAPTURE_DEV
+ */
+ pjmedia_vid_dev_index vid_cap_dev;
+
+ /**
+ * Specify the default rendering device to be used by this account.
+ *
+ * Default: PJMEDIA_VID_DEFAULT_RENDER_DEV
+ */
+ pjmedia_vid_dev_index vid_rend_dev;
+
+ /**
* Media transport config.
*/
pjsua_transport_config rtp_cfg;
@@ -2975,6 +3028,17 @@
# define PJSUA_MAX_CALLS 32
#endif
+/**
+ * Maximum active video windows
+ */
+#ifndef PJSUA_MAX_VID_WINS
+# define PJSUA_MAX_VID_WINS 16
+#endif
+
+/**
+ * Video window ID.
+ */
+typedef int pjsua_vid_win_id;
/**
@@ -3083,15 +3147,24 @@
union {
/** Audio stream */
struct {
- pjsua_conf_port_id conf_slot; /**< The conference port
- number for the call. */
- } audio;
+ /** The conference port number for the call. */
+ pjsua_conf_port_id conf_slot;
+ } aud;
/** Video stream */
struct {
- pjmedia_vid_port *capturer; /**< Video capturer. */
- pjmedia_vid_port *renderer; /**< Video renderer. */
- } video;
+ /**
+ * The window id for incoming video, if any, or
+ * PJSUA_INVALID_ID.
+ */
+ pjsua_vid_win_id win_in;
+
+ /** The video capture device for outgoing transmission,
+ * if any, or PJMEDIA_VID_INVALID_DEV
+ */
+ pjmedia_vid_dev_index cap_dev;
+
+ } vid;
} stream;
} media[PJMEDIA_MAX_SDP_MEDIA];
@@ -3117,6 +3190,39 @@
} pjsua_call_info;
+/**
+ * Media stream info.
+ */
+typedef struct pjsua_stream_info
+{
+ /** Media type of this stream. */
+ pjmedia_type type;
+
+ /** Stream info (union). */
+ union {
+ /** Audio stream info */
+ pjmedia_stream_info aud;
+
+ /** Video stream info */
+ pjmedia_vid_stream_info vid;
+ } info;
+
+} pjsua_stream_info;
+
+
+/**
+ * Media stream statistic.
+ */
+typedef struct pjsua_stream_stat
+{
+ /** RTCP statistic. */
+ pjmedia_rtcp_stat rtcp;
+
+ /** Jitter buffer statistic. */
+ pjmedia_jb_state jbuf;
+
+} pjsua_stream_stat;
+
/**
* Get maximum number of calls configured in pjsua.
@@ -3190,38 +3296,12 @@
PJ_DECL(pj_bool_t) pjsua_call_has_media(pjsua_call_id call_id);
-#if DISABLED_FOR_TICKET_1185
-/**
- * Retrieve the media session associated with this call. Note that the media
- * session may not be available depending on the current call's media status
- * (the pjsua_call_media_status information in pjsua_call_info). Application
- * may use the media session to retrieve more detailed information about the
- * call's media.
- *
- * @param call_id Call identification.
- *
- * @return Call media session.
- */
-PJ_DECL(pjmedia_session*) pjsua_call_get_media_session(pjsua_call_id call_id);
-
-/**
- * Retrieve the media transport instance that is used for this call.
- * Application may use the media transport to query more detailed information
- * about the media transport.
- *
- * @param cid Call identification (the call_id).
- *
- * @return Call media transport.
- */
-PJ_DECL(pjmedia_transport*) pjsua_call_get_media_transport(pjsua_call_id cid);
-#endif /* DISABLED_FOR_TICKET_1185 */
-
/**
* Get the conference port identification associated with the call.
*
* @param call_id Call identification.
*
- * @return Conference port ID, or PJSUA_INVALID_ID when the
+ * @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_conf_port(pjsua_call_id call_id);
@@ -3572,6 +3652,97 @@
const char *indent);
/**
+ * Get the media stream index of the default video stream in the call.
+ * Typically this will just retrieve the stream index of the first
+ * activated video stream in the call.
+ *
+ * @param call_id Call identification.
+ *
+ * @return The media stream index or -1 if no video stream
+ * is present in the call.
+ */
+PJ_DECL(int) pjsua_call_get_vid_stream_idx(pjsua_call_id call_id);
+
+/**
+ * Start, stop, and/or manipulate video transmission for the specified
+ * call. This would trigger a re-INVITE or UPDATE to be sent for the
+ * call. This function may add, remove, or modify existing video media
+ * stream, depending on the media index specified (the \a med_idx argument).
+ *
+ * To add a new or edit existing video stream (for transmission), specify
+ * a valid video capture device ID or PJMEDIA_VID_DEFAULT_CAPTURE_DEV in
+ * the \a cap_dev argument. If \a med_idx is set to default stream (-1),
+ * then the function will modify existing video stream if one exists, or
+ * add a new one if it doesn't. If \a med_idx is set to a specific stream
+ * index, the function will modify that video stream. Otherwise if \a med_idx
+ * is set to value larger than the current media count, a new video stream
+ * will be added to the call.
+ *
+ * To remove an existing video stream, specify PJMEDIA_VID_INVALID_DEV in
+ * \a cap_dev argument. If \a med_idx is set to default stream (-1), this
+ * will remove the default/first video stream in the call, otherwise
+ * application can put a specific value to request removal of that particular
+ * video stream.
+ *
+ * @param call_id Call identification.
+ * @param med_idx The media stream index. Currently the value MUST
+ * be -1 to denote the default video stream in the
+ * call.
+ * @param cap_dev To add or modify existing video media stream,
+ * specify PJMEDIA_VID_DEFAULT_CAPTURE_DEV to use
+ * the default capture device as configured in the
+ * account, or specify a specific capture device ID.
+ * To disable an existing video stream, specify
+ * PJMEDIA_VID_INVALID_DEV for this parameter.
+ *
+ * @return PJ_SUCCESS on success or the appropriate error.
+ */
+PJ_DECL(pj_status_t) pjsua_call_set_vid_out(pjsua_call_id call_id,
+ int med_idx,
+ pjmedia_vid_dev_index cap_dev);
+
+/**
+ * Get media stream info for the specified media index.
+ *
+ * @param call_id The call identification.
+ * @param med_idx Media stream index.
+ * @param psi To be filled with the stream info.
+ *
+ * @return PJ_SUCCESS on success or the appropriate error.
+ */
+PJ_DECL(pj_status_t) pjsua_call_get_stream_info(pjsua_call_id call_id,
+ unsigned med_idx,
+ pjsua_stream_info *psi);
+
+/**
+ * Get media stream statistic for the specified media index.
+ *
+ * @param call_id The call identification.
+ * @param med_idx Media stream index.
+ * @param psi To be filled with the stream statistic.
+ *
+ * @return PJ_SUCCESS on success or the appropriate error.
+ */
+PJ_DECL(pj_status_t) pjsua_call_get_stream_stat(pjsua_call_id call_id,
+ unsigned med_idx,
+ pjsua_stream_stat *stat);
+
+/**
+ * Get media transport info for the specified media index.
+ *
+ * @param call_id The call identification.
+ * @param med_idx Media stream index.
+ * @param t To be filled with the transport info.
+ *
+ * @return PJ_SUCCESS on success or the appropriate error.
+ */
+PJ_DECL(pj_status_t) pjsua_call_get_transport_info(pjsua_call_id call_id,
+ unsigned med_idx,
+ pjmedia_transport_info *t);
+
+
+
+/**
* @}
*/
@@ -4447,8 +4618,6 @@
} pjsua_media_transport;
-
-
/**
* Get maxinum number of conference ports.
*
@@ -4939,98 +5108,6 @@
/*****************************************************************************
- * Video devices.
- */
-
-/**
- * Enum all video devices installed in the system.
- *
- * @param info Array of info 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_enum_devs(pjmedia_vid_dev_info info[],
- unsigned *count);
-
-
-/**
- * Get currently active video devices. If video devices has not been created
- * (for example when pjsua_start() is not called), it is possible that
- * the function returns PJ_SUCCESS with -1 as device IDs.
- *
- * @param capture_dev On return it will be filled with device ID of the
- * capture device.
- * @param render_dev On return it will be filled with device ID of the
- * device ID of the render device.
- *
- * @return PJ_SUCCESS on success, or the appropriate error code.
- */
-PJ_DECL(pj_status_t) pjsua_vid_get_dev(int *capture_dev, int *render_dev);
-
-
-/**
- * Select video device for the next video sessions.
- *
- * @param capture_dev Device ID of the capture device.
- * @param render_dev Device ID of the render device.
- *
- * @return PJ_SUCCESS on success, or the appropriate error code.
- */
-PJ_DECL(pj_status_t) pjsua_vid_set_dev(int capture_dev, int render_dev);
-
-
-/**
- * Configure video device setting to the video device being used. If video
- * device is currently active, the function will forward the setting to the
- * video device instance to be applied immediately, if it supports it.
- *
- * The setting will be saved for future opening of the video device, if the
- * "keep" argument is set to non-zero. If the video device is currently
- * inactive, and the "keep" argument is false, this function will return
- * error.
- *
- * Note that in case the setting is kept for future use, it will be applied
- * to any devices, even when application has changed the video device to be
- * used.
- *
- * See also #pjmedia_vid_dev_stream_set_cap() for more information about
- * setting an video device capability.
- *
- * @param cap The video device setting to change.
- * @param pval Pointer to value. Please see #pjmedia_vid_dev_cap
- * documentation about the type of value to be
- * supplied for each setting.
- * @param keep Specify whether the setting is to be kept for future
- * use.
- *
- * @return PJ_SUCCESS on success or the appropriate error code.
- */
-PJ_DECL(pj_status_t) pjsua_vid_set_setting(pjmedia_vid_dev_cap cap,
- const void *pval,
- pj_bool_t keep);
-
-/**
- * Retrieve a video device setting. If video device is currently active,
- * the function will forward the request to the video device. If video device
- * is currently inactive, and if application had previously set the setting
- * and mark the setting as kept, then that setting will be returned.
- * Otherwise, this function will return error.
- *
- * @param cap The video device setting to retrieve.
- * @param pval Pointer to receive the value.
- * Please see #pjmedia_vid_dev_cap documentation about
- * the type of value to be supplied for each setting.
- *
- * @return PJ_SUCCESS on success or the appropriate error code.
- */
-PJ_DECL(pj_status_t) pjsua_vid_get_setting(pjmedia_vid_dev_cap cap,
- void *pval);
-
-
-/*****************************************************************************
* Codecs.
*/
@@ -5087,8 +5164,225 @@
PJ_DECL(pj_status_t) pjsua_codec_set_param( const pj_str_t *codec_id,
const pjmedia_codec_param *param);
+
+#if DISABLED_FOR_TICKET_1185
+/**
+ * Create UDP media transports for all the calls. This function creates
+ * one UDP media transport for each call.
+ *
+ * @param cfg Media transport configuration. The "port" field in the
+ * configuration is used as the start port to bind the
+ * sockets.
+ *
+ * @return PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t)
+pjsua_media_transports_create(const pjsua_transport_config *cfg);
+
+
+/**
+ * Register custom media transports to be used by calls. There must
+ * enough media transports for all calls.
+ *
+ * @param tp The media transport array.
+ * @param count Number of elements in the array. This number MUST
+ * match the number of maximum calls configured when
+ * pjsua is created.
+ * @param auto_delete Flag to indicate whether the transports should be
+ * destroyed when pjsua is shutdown.
+ *
+ * @return PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t)
+pjsua_media_transports_attach( pjsua_media_transport tp[],
+ unsigned count,
+ pj_bool_t auto_delete);
+#endif
+
+
+/* end of MEDIA API */
+/**
+ * @}
+ */
+
+
/*****************************************************************************
- * Video codecs.
+ * VIDEO API
+ */
+
+
+/**
+ * @defgroup PJSUA_LIB_VIDEO PJSUA-API Video
+ * @ingroup PJSUA_LIB
+ * @brief Video support
+ * @{
+ */
+
+/*
+ * Video devices API
+ */
+
+/**
+ * Get the number of video devices installed in the system.
+ *
+ * @return The number of devices.
+ */
+PJ_DECL(unsigned) pjsua_vid_dev_count(void);
+
+/**
+ * Retrieve the video device info for the specified device index.
+ *
+ * @param id The device index.
+ * @param vdi Device info to be initialized.
+ *
+ * @return PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_vid_dev_get_info(pjmedia_vid_dev_index id,
+ pjmedia_vid_dev_info *vdi);
+
+/**
+ * Enum all video devices installed in the system.
+ *
+ * @param info Array of info 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_enum_devs(pjmedia_vid_dev_info info[],
+ unsigned *count);
+
+
+/*
+ * Video preview API
+ */
+
+/**
+ * Parameters for starting video preview with pjsua_vid_preview_start().
+ * Application should initialize this structure with
+ * pjsua_vid_preview_param_default().
+ */
+typedef struct pjsua_vid_preview_param
+{
+ /**
+ * Device ID for the video renderer to be used for rendering the
+ * capture stream for preview.
+ */
+ pjmedia_vid_dev_index rend_id;
+} pjsua_vid_preview_param;
+
+
+/**
+ * Start video preview window for the specified capture device.
+ *
+ * @param id The capture device ID where its preview will be
+ * started.
+ * @param prm Optional video preview parameters. Specify NULL
+ * to use default values.
+ *
+ * @return PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_vid_preview_start(pjmedia_vid_dev_index id,
+ pjsua_vid_preview_param *prm);
+
+/**
+ * Get the preview window handle associated with the capture device, if any.
+ *
+ * @param id The capture device ID.
+ *
+ * @return The window ID of the preview window for the
+ * specified capture device ID, or NULL if preview
+ * does not exist.
+ */
+PJ_DECL(pjsua_vid_win_id) pjsua_vid_preview_get_win(pjmedia_vid_dev_index id);
+
+/**
+ * Stop video preview.
+ *
+ * @param id The capture device ID.
+ *
+ * @return PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_vid_preview_stop(pjmedia_vid_dev_index id);
+
+
+/*
+ * Video window manipulation API.
+ */
+
+/**
+ * This structure describes video window info.
+ */
+typedef struct pjsua_vid_win_info
+{
+ /**
+ * Window show status. The window is hidden if false.
+ */
+ pj_bool_t show;
+
+ /**
+ * Window position.
+ */
+ pjmedia_coord pos;
+
+ /**
+ * Window size.
+ */
+ pjmedia_rect_size size;
+
+} pjsua_vid_win_info;
+
+
+/**
+ * Get window info.
+ *
+ * @param wid The video window ID.
+ * @param wi The video window info to be initialized.
+ *
+ * @return PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_vid_win_get_info(pjsua_vid_win_id wid,
+ pjsua_vid_win_info *wi);
+
+/**
+ * Show or hide window.
+ *
+ * @param wid The video window ID.
+ * @param show Set to PJ_TRUE to show the window, PJ_FALSE to
+ * hide the window.
+ *
+ * @return PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_vid_win_set_show(pjsua_vid_win_id wid,
+ pj_bool_t show);
+
+/**
+ * Set video window position.
+ *
+ * @param wid The video window ID.
+ * @param pos The window position.
+ *
+ * @return PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_vid_win_set_pos(pjsua_vid_win_id wid,
+ const pjmedia_coord *pos);
+
+/**
+ * Resize window.
+ *
+ * @param wid The video window ID.
+ * @param size The new window size.
+ *
+ * @return PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_vid_win_set_size(pjsua_vid_win_id wid,
+ const pjmedia_rect_size *size);
+
+
+
+/*
+ * Video codecs API
*/
/**
@@ -5147,47 +5441,13 @@
const pjmedia_vid_codec_param *param);
-#if DISABLED_FOR_TICKET_1185
-/**
- * Create UDP media transports for all the calls. This function creates
- * one UDP media transport for each call.
- *
- * @param cfg Media transport configuration. The "port" field in the
- * configuration is used as the start port to bind the
- * sockets.
- *
- * @return PJ_SUCCESS on success, or the appropriate error code.
- */
-PJ_DECL(pj_status_t)
-pjsua_media_transports_create(const pjsua_transport_config *cfg);
-
-/**
- * Register custom media transports to be used by calls. There must
- * enough media transports for all calls.
- *
- * @param tp The media transport array.
- * @param count Number of elements in the array. This number MUST
- * match the number of maximum calls configured when
- * pjsua is created.
- * @param auto_delete Flag to indicate whether the transports should be
- * destroyed when pjsua is shutdown.
- *
- * @return PJ_SUCCESS on success, or the appropriate error code.
- */
-PJ_DECL(pj_status_t)
-pjsua_media_transports_attach( pjsua_media_transport tp[],
- unsigned count,
- pj_bool_t auto_delete);
-#endif
-
-
+/* end of VIDEO API */
/**
* @}
*/
-
/**
* @}
*/
diff --git a/pjsip/include/pjsua-lib/pjsua_internal.h b/pjsip/include/pjsua-lib/pjsua_internal.h
index b6e6e59..5f92043 100644
--- a/pjsip/include/pjsua-lib/pjsua_internal.h
+++ b/pjsip/include/pjsua-lib/pjsua_internal.h
@@ -303,6 +303,22 @@
pj_stun_sock *stun_sock; /**< Testing STUN sock */
} pjsua_stun_resolve;
+typedef enum pjsua_vid_win_type
+{
+ PJSUA_WND_TYPE_NONE,
+ PJSUA_WND_TYPE_PREVIEW,
+ PJSUA_WND_TYPE_STREAM
+} pjsua_vid_win_type;
+
+typedef struct pjsua_vid_win
+{
+ pjsua_vid_win_type type; /**< Type. */
+ pj_pool_t *pool; /**< Own pool. */
+ pjsua_call_id call_id; /**< Owner call or -1 */
+ pjmedia_vid_port *vp_cap; /**< Capture vidport. */
+ pjmedia_vid_port *vp_rend; /**< Renderer vidport */
+ pjmedia_vid_dev_index preview_cap_id;/* Capture dev id */
+} pjsua_vid_win;
/**
* Global pjsua application data.
@@ -397,6 +413,11 @@
/* File recorders: */
unsigned rec_cnt; /**< Number of file recorders. */
pjsua_file_data recorder[PJSUA_MAX_RECORDERS];/**< Array of recs.*/
+
+ /* Video windows */
+#if PJSUA_HAS_VIDEO
+ pjsua_vid_win win[PJSUA_MAX_VID_WINS]; /**< Array of windows */
+#endif
};
@@ -631,6 +652,27 @@
int call_id,
char *buf, pj_size_t size);
+/*
+ * Video
+ */
+pj_status_t pjsua_vid_subsys_init(void);
+pj_status_t pjsua_vid_subsys_start(void);
+pj_status_t pjsua_vid_subsys_destroy(void);
+
+PJ_INLINE(void) pjsua_vid_win_reset(pjsua_vid_win_id wid)
+{
+#if PJSUA_HAS_VIDEO
+ pjsua_vid_win *w = &pjsua_var.win[wid];
+ pj_pool_t *pool = w->pool;
+
+ pj_bzero(w, sizeof(*w));
+ if (pool) pj_pool_reset(pool);
+ w->call_id = PJSUA_INVALID_ID;
+ w->pool = pool;
+ w->preview_cap_id = PJMEDIA_VID_INVALID_DEV;
+#endif
+}
+
PJ_END_DECL
diff --git a/pjsip/src/pjsua-lib/pjsua_call.c b/pjsip/src/pjsua-lib/pjsua_call.c
index a7705ff..441bc77 100644
--- a/pjsip/src/pjsua-lib/pjsua_call.c
+++ b/pjsip/src/pjsua-lib/pjsua_call.c
@@ -1116,29 +1116,6 @@
}
-#if DISABLED_FOR_TICKET_1185
-/*
- * Retrieve the media session associated with this call.
- */
-PJ_DEF(pjmedia_session*) pjsua_call_get_media_session(pjsua_call_id call_id)
-{
- PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
- NULL);
- return pjsua_var.calls[call_id].session;
-}
-
-
-/*
- * Retrieve the media transport instance that is used for this call.
- */
-PJ_DEF(pjmedia_transport*) pjsua_call_get_media_transport(pjsua_call_id cid)
-{
- PJ_ASSERT_RETURN(cid>=0 && cid<(int)pjsua_var.ua_cfg.max_calls,
- NULL);
- return pjsua_var.calls[cid].tp;
-}
-#endif /* Removed in 2.0 */
-
/* Acquire lock to the specified call_id */
pj_status_t acquire_call(const char *title,
pjsua_call_id call_id,
@@ -1336,13 +1313,13 @@
info->media[info->media_cnt].type = call_med->type;
if (call_med->type == PJMEDIA_TYPE_AUDIO) {
- info->media[info->media_cnt].stream.audio.conf_slot =
+ info->media[info->media_cnt].stream.aud.conf_slot =
call_med->strm.a.conf_slot;
} else if (call_med->type == PJMEDIA_TYPE_VIDEO) {
- info->media[info->media_cnt].stream.video.capturer =
- call_med->strm.v.capturer;
- info->media[info->media_cnt].stream.video.renderer =
- call_med->strm.v.renderer;
+ PJ_TODO(vid_fill_in_call_info);
+ info->media[info->media_cnt].stream.vid.win_in = PJSUA_INVALID_ID;
+ info->media[info->media_cnt].stream.vid.cap_dev =
+ PJMEDIA_VID_INVALID_DEV;
} else {
continue;
}
@@ -2183,929 +2160,6 @@
}
-const char *good_number(char *buf, pj_int32_t val)
-{
- if (val < 1000) {
- pj_ansi_sprintf(buf, "%d", val);
- } else if (val < 1000000) {
- pj_ansi_sprintf(buf, "%d.%dK",
- val / 1000,
- (val % 1000) / 100);
- } else {
- pj_ansi_sprintf(buf, "%d.%02dM",
- val / 1000000,
- (val % 1000000) / 10000);
- }
-
- return buf;
-}
-
-static unsigned dump_media_stat(const char *indent,
- char *buf, unsigned maxlen,
- const pjmedia_rtcp_stat *stat,
- const char *rx_info, const char *tx_info)
-{
- char last_update[64];
- char packets[32], bytes[32], ipbytes[32], avg_bps[32], avg_ipbps[32];
- pj_time_val media_duration, now;
- char *p = buf, *end = buf+maxlen;
- int len;
-
- if (stat->rx.update_cnt == 0)
- strcpy(last_update, "never");
- else {
- pj_gettimeofday(&now);
- PJ_TIME_VAL_SUB(now, stat->rx.update);
- sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
- now.sec / 3600,
- (now.sec % 3600) / 60,
- now.sec % 60,
- now.msec);
- }
-
- pj_gettimeofday(&media_duration);
- PJ_TIME_VAL_SUB(media_duration, stat->start);
- if (PJ_TIME_VAL_MSEC(media_duration) == 0)
- media_duration.msec = 1;
-
- len = pj_ansi_snprintf(p, end-p,
- "%s RX %s last update:%s\n"
- "%s total %spkt %sB (%sB +IP hdr) @avg=%sbps/%sbps\n"
- "%s pkt loss=%d (%3.1f%%), discrd=%d (%3.1f%%), dup=%d (%2.1f%%), reord=%d (%3.1f%%)\n"
- "%s (msec) min avg max last dev\n"
- "%s loss period: %7.3f %7.3f %7.3f %7.3f %7.3f\n"
- "%s jitter : %7.3f %7.3f %7.3f %7.3f %7.3f\n"
-#if defined(PJMEDIA_RTCP_STAT_HAS_RAW_JITTER) && PJMEDIA_RTCP_STAT_HAS_RAW_JITTER!=0
- "%s raw jitter : %7.3f %7.3f %7.3f %7.3f %7.3f\n"
-#endif
-#if defined(PJMEDIA_RTCP_STAT_HAS_IPDV) && PJMEDIA_RTCP_STAT_HAS_IPDV!=0
- "%s IPDV : %7.3f %7.3f %7.3f %7.3f %7.3f\n"
-#endif
- "%s",
- indent,
- rx_info? rx_info : "",
- last_update,
-
- indent,
- good_number(packets, stat->rx.pkt),
- good_number(bytes, stat->rx.bytes),
- good_number(ipbytes, stat->rx.bytes + stat->rx.pkt * 40),
- good_number(avg_bps, (pj_int32_t)((pj_int64_t)stat->rx.bytes * 8 * 1000 / PJ_TIME_VAL_MSEC(media_duration))),
- good_number(avg_ipbps, (pj_int32_t)(((pj_int64_t)stat->rx.bytes + stat->rx.pkt * 40) * 8 * 1000 / PJ_TIME_VAL_MSEC(media_duration))),
- indent,
- stat->rx.loss,
- (stat->rx.loss? stat->rx.loss * 100.0 / (stat->rx.pkt + stat->rx.loss) : 0),
- stat->rx.discard,
- (stat->rx.discard? stat->rx.discard * 100.0 / (stat->rx.pkt + stat->rx.loss) : 0),
- stat->rx.dup,
- (stat->rx.dup? stat->rx.dup * 100.0 / (stat->rx.pkt + stat->rx.loss) : 0),
- stat->rx.reorder,
- (stat->rx.reorder? stat->rx.reorder * 100.0 / (stat->rx.pkt + stat->rx.loss) : 0),
- indent, indent,
- stat->rx.loss_period.min / 1000.0,
- stat->rx.loss_period.mean / 1000.0,
- stat->rx.loss_period.max / 1000.0,
- stat->rx.loss_period.last / 1000.0,
- pj_math_stat_get_stddev(&stat->rx.loss_period) / 1000.0,
- indent,
- stat->rx.jitter.min / 1000.0,
- stat->rx.jitter.mean / 1000.0,
- stat->rx.jitter.max / 1000.0,
- stat->rx.jitter.last / 1000.0,
- pj_math_stat_get_stddev(&stat->rx.jitter) / 1000.0,
-#if defined(PJMEDIA_RTCP_STAT_HAS_RAW_JITTER) && PJMEDIA_RTCP_STAT_HAS_RAW_JITTER!=0
- indent,
- stat->rx_raw_jitter.min / 1000.0,
- stat->rx_raw_jitter.mean / 1000.0,
- stat->rx_raw_jitter.max / 1000.0,
- stat->rx_raw_jitter.last / 1000.0,
- pj_math_stat_get_stddev(&stat->rx_raw_jitter) / 1000.0,
-#endif
-#if defined(PJMEDIA_RTCP_STAT_HAS_IPDV) && PJMEDIA_RTCP_STAT_HAS_IPDV!=0
- indent,
- stat->rx_ipdv.min / 1000.0,
- stat->rx_ipdv.mean / 1000.0,
- stat->rx_ipdv.max / 1000.0,
- stat->rx_ipdv.last / 1000.0,
- pj_math_stat_get_stddev(&stat->rx_ipdv) / 1000.0,
-#endif
- ""
- );
-
- if (len < 1 || len > end-p) {
- *p = '\0';
- return (p-buf);
- }
- p += len;
-
- if (stat->tx.update_cnt == 0)
- strcpy(last_update, "never");
- else {
- pj_gettimeofday(&now);
- PJ_TIME_VAL_SUB(now, stat->tx.update);
- sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
- now.sec / 3600,
- (now.sec % 3600) / 60,
- now.sec % 60,
- now.msec);
- }
-
- len = pj_ansi_snprintf(p, end-p,
- "%s TX %s last update:%s\n"
- "%s total %spkt %sB (%sB +IP hdr) @avg %sbps/%sbps\n"
- "%s pkt loss=%d (%3.1f%%), dup=%d (%3.1f%%), reorder=%d (%3.1f%%)\n"
- "%s (msec) min avg max last dev \n"
- "%s loss period: %7.3f %7.3f %7.3f %7.3f %7.3f\n"
- "%s jitter : %7.3f %7.3f %7.3f %7.3f %7.3f\n",
- indent,
- tx_info,
- last_update,
-
- indent,
- good_number(packets, stat->tx.pkt),
- good_number(bytes, stat->tx.bytes),
- good_number(ipbytes, stat->tx.bytes + stat->tx.pkt * 40),
- good_number(avg_bps, (pj_int32_t)((pj_int64_t)stat->tx.bytes * 8 * 1000 / PJ_TIME_VAL_MSEC(media_duration))),
- good_number(avg_ipbps, (pj_int32_t)(((pj_int64_t)stat->tx.bytes + stat->tx.pkt * 40) * 8 * 1000 / PJ_TIME_VAL_MSEC(media_duration))),
-
- indent,
- stat->tx.loss,
- (stat->tx.loss? stat->tx.loss * 100.0 / (stat->tx.pkt + stat->tx.loss) : 0),
- stat->tx.dup,
- (stat->tx.dup? stat->tx.dup * 100.0 / (stat->tx.pkt + stat->tx.loss) : 0),
- stat->tx.reorder,
- (stat->tx.reorder? stat->tx.reorder * 100.0 / (stat->tx.pkt + stat->tx.loss) : 0),
-
- indent, indent,
- stat->tx.loss_period.min / 1000.0,
- stat->tx.loss_period.mean / 1000.0,
- stat->tx.loss_period.max / 1000.0,
- stat->tx.loss_period.last / 1000.0,
- pj_math_stat_get_stddev(&stat->tx.loss_period) / 1000.0,
- indent,
- stat->tx.jitter.min / 1000.0,
- stat->tx.jitter.mean / 1000.0,
- stat->tx.jitter.max / 1000.0,
- stat->tx.jitter.last / 1000.0,
- pj_math_stat_get_stddev(&stat->tx.jitter) / 1000.0
- );
-
- if (len < 1 || len > end-p) {
- *p = '\0';
- return (p-buf);
- }
- p += len;
-
- len = pj_ansi_snprintf(p, end-p,
- "%s RTT msec : %7.3f %7.3f %7.3f %7.3f %7.3f\n",
- indent,
- stat->rtt.min / 1000.0,
- stat->rtt.mean / 1000.0,
- stat->rtt.max / 1000.0,
- stat->rtt.last / 1000.0,
- pj_math_stat_get_stddev(&stat->rtt) / 1000.0
- );
- if (len < 1 || len > end-p) {
- *p = '\0';
- return (p-buf);
- }
- p += len;
-
- return (p-buf);
-}
-
-
-/* Dump media session */
-static void dump_media_session(const char *indent,
- char *buf, unsigned maxlen,
- pjsua_call *call)
-{
- unsigned i;
- char *p = buf, *end = buf+maxlen;
- int len;
-
- for (i=0; i<call->med_cnt; ++i) {
- pjsua_call_media *call_med = &call->media[i];
- pjmedia_rtcp_stat stat;
- pj_bool_t has_stat;
- pjmedia_transport_info tp_info;
- char rem_addr_buf[80];
- char codec_info[32] = {'0'};
- char rx_info[80] = {'\0'};
- char tx_info[80] = {'\0'};
- const char *rem_addr;
- const char *dir_str;
- const char *media_type_str;
-
- switch (call_med->type) {
- case PJMEDIA_TYPE_AUDIO:
- media_type_str = "audio";
- break;
- case PJMEDIA_TYPE_VIDEO:
- media_type_str = "video";
- break;
- case PJMEDIA_TYPE_APPLICATION:
- media_type_str = "application";
- break;
- default:
- media_type_str = "unknown";
- break;
- }
-
- /* Check if the stream is deactivated */
- if (call_med->tp == NULL ||
- (!call_med->strm.a.stream && !call_med->strm.v.stream))
- {
- len = pj_ansi_snprintf(p, end-p,
- "%s #%d %s deactivated\n",
- indent, i, media_type_str);
- if (len < 1 || len > end-p) {
- *p = '\0';
- return;
- }
-
- p += len;
- continue;
- }
-
- pjmedia_transport_info_init(&tp_info);
- pjmedia_transport_get_info(call_med->tp, &tp_info);
-
- // rem_addr will contain actual address of RTP originator, instead of
- // remote RTP address specified by stream which is fetched from the SDP.
- // Please note that we are assuming only one stream per call.
- //rem_addr = pj_sockaddr_print(&info.stream_info[i].rem_addr,
- // rem_addr_buf, sizeof(rem_addr_buf), 3);
- if (pj_sockaddr_has_addr(&tp_info.src_rtp_name)) {
- rem_addr = pj_sockaddr_print(&tp_info.src_rtp_name, rem_addr_buf,
- sizeof(rem_addr_buf), 3);
- } else {
- pj_ansi_snprintf(rem_addr_buf, sizeof(rem_addr_buf), "-");
- rem_addr = rem_addr_buf;
- }
-
- if (call_med->dir == PJMEDIA_DIR_NONE) {
- /* To handle when the stream that is currently being paused
- * (http://trac.pjsip.org/repos/ticket/1079)
- */
- dir_str = "inactive";
- } else if (call_med->dir == PJMEDIA_DIR_ENCODING)
- dir_str = "sendonly";
- else if (call_med->dir == PJMEDIA_DIR_DECODING)
- dir_str = "recvonly";
- else if (call_med->dir == PJMEDIA_DIR_ENCODING_DECODING)
- dir_str = "sendrecv";
- else
- dir_str = "inactive";
-
- if (call_med->type == PJMEDIA_TYPE_AUDIO) {
- pjmedia_stream *stream = call_med->strm.a.stream;
- pjmedia_stream_info info;
-
- pjmedia_stream_get_stat(stream, &stat);
- has_stat = PJ_TRUE;
-
- pjmedia_stream_get_info(stream, &info);
- pj_ansi_snprintf(codec_info, sizeof(codec_info), " %.*s @%dkHz",
- (int)info.fmt.encoding_name.slen,
- info.fmt.encoding_name.ptr,
- info.fmt.clock_rate / 1000);
- pj_ansi_snprintf(rx_info, sizeof(rx_info), "pt=%d,",
- info.fmt.pt);
- pj_ansi_snprintf(tx_info, sizeof(tx_info), "pt=%d, ptime=%d,",
- info.tx_pt,
- info.param->setting.frm_per_pkt*
- info.param->info.frm_ptime);
- } else if (call_med->type == PJMEDIA_TYPE_VIDEO) {
- pjmedia_vid_stream *stream = call_med->strm.v.stream;
- pjmedia_vid_stream_info info;
-
- pjmedia_vid_stream_get_stat(stream, &stat);
- has_stat = PJ_TRUE;
-
- pjmedia_vid_stream_get_info(stream, &info);
- pj_ansi_snprintf(codec_info, sizeof(codec_info), " %.*s",
- (int)info.codec_info.encoding_name.slen,
- info.codec_info.encoding_name.ptr);
- if (call_med->dir & PJMEDIA_DIR_DECODING) {
- pjmedia_video_format_detail *vfd;
- vfd = pjmedia_format_get_video_format_detail(
- &info.codec_param->dec_fmt, PJ_TRUE);
- pj_ansi_snprintf(rx_info, sizeof(rx_info),
- "pt=%d, size=%dx%d, fps=%.2f,",
- info.rx_pt,
- vfd->size.w, vfd->size.h,
- vfd->fps.num*1.0/vfd->fps.denum);
- }
- if (call_med->dir & PJMEDIA_DIR_ENCODING) {
- pjmedia_video_format_detail *vfd;
- vfd = pjmedia_format_get_video_format_detail(
- &info.codec_param->enc_fmt, PJ_TRUE);
- pj_ansi_snprintf(tx_info, sizeof(tx_info),
- "pt=%d, size=%dx%d, fps=%.2f,",
- info.tx_pt,
- vfd->size.w, vfd->size.h,
- vfd->fps.num*1.0/vfd->fps.denum);
- }
- } else {
- has_stat = PJ_FALSE;
- }
-
- len = pj_ansi_snprintf(p, end-p,
- "%s #%d %s%s, %s, peer=%s\n",
- indent,
- call_med->idx,
- media_type_str,
- codec_info,
- dir_str,
- rem_addr);
- if (len < 1 || len > end-p) {
- *p = '\0';
- return;
- }
- p += len;
-
- /* Get and ICE SRTP status */
- if (call_med->tp) {
- pjmedia_transport_info tp_info;
-
- pjmedia_transport_info_init(&tp_info);
- pjmedia_transport_get_info(call_med->tp, &tp_info);
- if (tp_info.specific_info_cnt > 0) {
- unsigned j;
- for (j = 0; j < tp_info.specific_info_cnt; ++j) {
- if (tp_info.spc_info[j].type == PJMEDIA_TRANSPORT_TYPE_SRTP)
- {
- pjmedia_srtp_info *srtp_info =
- (pjmedia_srtp_info*) tp_info.spc_info[j].buffer;
-
- len = pj_ansi_snprintf(p, end-p,
- " %s SRTP status: %s Crypto-suite: %s",
- indent,
- (srtp_info->active?"Active":"Not active"),
- srtp_info->tx_policy.name.ptr);
- if (len > 0 && len < end-p) {
- p += len;
- *p++ = '\n';
- *p = '\0';
- }
- } else if (tp_info.spc_info[j].type==PJMEDIA_TRANSPORT_TYPE_ICE) {
- const pjmedia_ice_transport_info *ii;
-
- ii = (const pjmedia_ice_transport_info*)
- tp_info.spc_info[j].buffer;
-
- len = pj_ansi_snprintf(p, end-p,
- " %s ICE role: %s, state: %s, comp_cnt: %u",
- indent,
- pj_ice_sess_role_name(ii->role),
- pj_ice_strans_state_name(ii->sess_state),
- ii->comp_cnt);
- if (len > 0 && len < end-p) {
- p += len;
- *p++ = '\n';
- *p = '\0';
- }
- }
- }
- }
- }
-
-
- if (has_stat) {
- len = dump_media_stat(indent, p, end-p, &stat,
- rx_info, tx_info);
- p += len;
- }
-
-#if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0)
-# define SAMPLES_TO_USEC(usec, samples, clock_rate) \
- do { \
- if (samples <= 4294) \
- usec = samples * 1000000 / clock_rate; \
- else { \
- usec = samples * 1000 / clock_rate; \
- usec *= 1000; \
- } \
- } while(0)
-
-# define PRINT_VOIP_MTC_VAL(s, v) \
- if (v == 127) \
- sprintf(s, "(na)"); \
- else \
- sprintf(s, "%d", v)
-
-# define VALIDATE_PRINT_BUF() \
- if (len < 1 || len > end-p) { *p = '\0'; return; } \
- p += len; *p++ = '\n'; *p = '\0'
-
-
- if (call_med->type == PJMEDIA_TYPE_AUDIO) {
- pjmedia_stream_info info;
- char last_update[64];
- char loss[16], dup[16];
- char jitter[80];
- char toh[80];
- char plc[16], jba[16], jbr[16];
- char signal_lvl[16], noise_lvl[16], rerl[16];
- char r_factor[16], ext_r_factor[16], mos_lq[16], mos_cq[16];
- pjmedia_rtcp_xr_stat xr_stat;
- unsigned clock_rate;
- pj_time_val now;
-
- if (pjmedia_stream_get_stat_xr(call_med->strm.a.stream,
- &xr_stat) != PJ_SUCCESS)
- {
- continue;
- }
-
- if (pjmedia_stream_get_info(call_med->strm.a.stream, &info)
- != PJ_SUCCESS)
- {
- continue;
- }
-
- clock_rate = info.fmt.clock_rate;
- pj_gettimeofday(&now);
-
- len = pj_ansi_snprintf(p, end-p, "\n%s Extended reports:", indent);
- VALIDATE_PRINT_BUF();
-
- /* Statistics Summary */
- len = pj_ansi_snprintf(p, end-p, "%s Statistics Summary", indent);
- VALIDATE_PRINT_BUF();
-
- if (xr_stat.rx.stat_sum.l)
- sprintf(loss, "%d", xr_stat.rx.stat_sum.lost);
- else
- sprintf(loss, "(na)");
-
- if (xr_stat.rx.stat_sum.d)
- sprintf(dup, "%d", xr_stat.rx.stat_sum.dup);
- else
- sprintf(dup, "(na)");
-
- if (xr_stat.rx.stat_sum.j) {
- unsigned jmin, jmax, jmean, jdev;
-
- SAMPLES_TO_USEC(jmin, xr_stat.rx.stat_sum.jitter.min,
- clock_rate);
- SAMPLES_TO_USEC(jmax, xr_stat.rx.stat_sum.jitter.max,
- clock_rate);
- SAMPLES_TO_USEC(jmean, xr_stat.rx.stat_sum.jitter.mean,
- clock_rate);
- SAMPLES_TO_USEC(jdev,
- pj_math_stat_get_stddev(&xr_stat.rx.stat_sum.jitter),
- clock_rate);
- sprintf(jitter, "%7.3f %7.3f %7.3f %7.3f",
- jmin/1000.0, jmean/1000.0, jmax/1000.0, jdev/1000.0);
- } else
- sprintf(jitter, "(report not available)");
-
- if (xr_stat.rx.stat_sum.t) {
- sprintf(toh, "%11d %11d %11d %11d",
- xr_stat.rx.stat_sum.toh.min,
- xr_stat.rx.stat_sum.toh.mean,
- xr_stat.rx.stat_sum.toh.max,
- pj_math_stat_get_stddev(&xr_stat.rx.stat_sum.toh));
- } else
- sprintf(toh, "(report not available)");
-
- if (xr_stat.rx.stat_sum.update.sec == 0)
- strcpy(last_update, "never");
- else {
- pj_gettimeofday(&now);
- PJ_TIME_VAL_SUB(now, xr_stat.rx.stat_sum.update);
- sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
- now.sec / 3600,
- (now.sec % 3600) / 60,
- now.sec % 60,
- now.msec);
- }
-
- len = pj_ansi_snprintf(p, end-p,
- "%s RX last update: %s\n"
- "%s begin seq=%d, end seq=%d\n"
- "%s pkt loss=%s, dup=%s\n"
- "%s (msec) min avg max dev\n"
- "%s jitter : %s\n"
- "%s toh : %s",
- indent, last_update,
- indent,
- xr_stat.rx.stat_sum.begin_seq, xr_stat.rx.stat_sum.end_seq,
- indent, loss, dup,
- indent,
- indent, jitter,
- indent, toh
- );
- VALIDATE_PRINT_BUF();
-
- if (xr_stat.tx.stat_sum.l)
- sprintf(loss, "%d", xr_stat.tx.stat_sum.lost);
- else
- sprintf(loss, "(na)");
-
- if (xr_stat.tx.stat_sum.d)
- sprintf(dup, "%d", xr_stat.tx.stat_sum.dup);
- else
- sprintf(dup, "(na)");
-
- if (xr_stat.tx.stat_sum.j) {
- unsigned jmin, jmax, jmean, jdev;
-
- SAMPLES_TO_USEC(jmin, xr_stat.tx.stat_sum.jitter.min,
- clock_rate);
- SAMPLES_TO_USEC(jmax, xr_stat.tx.stat_sum.jitter.max,
- clock_rate);
- SAMPLES_TO_USEC(jmean, xr_stat.tx.stat_sum.jitter.mean,
- clock_rate);
- SAMPLES_TO_USEC(jdev,
- pj_math_stat_get_stddev(&xr_stat.tx.stat_sum.jitter),
- clock_rate);
- sprintf(jitter, "%7.3f %7.3f %7.3f %7.3f",
- jmin/1000.0, jmean/1000.0, jmax/1000.0, jdev/1000.0);
- } else
- sprintf(jitter, "(report not available)");
-
- if (xr_stat.tx.stat_sum.t) {
- sprintf(toh, "%11d %11d %11d %11d",
- xr_stat.tx.stat_sum.toh.min,
- xr_stat.tx.stat_sum.toh.mean,
- xr_stat.tx.stat_sum.toh.max,
- pj_math_stat_get_stddev(&xr_stat.rx.stat_sum.toh));
- } else
- sprintf(toh, "(report not available)");
-
- if (xr_stat.tx.stat_sum.update.sec == 0)
- strcpy(last_update, "never");
- else {
- pj_gettimeofday(&now);
- PJ_TIME_VAL_SUB(now, xr_stat.tx.stat_sum.update);
- sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
- now.sec / 3600,
- (now.sec % 3600) / 60,
- now.sec % 60,
- now.msec);
- }
-
- len = pj_ansi_snprintf(p, end-p,
- "%s TX last update: %s\n"
- "%s begin seq=%d, end seq=%d\n"
- "%s pkt loss=%s, dup=%s\n"
- "%s (msec) min avg max dev\n"
- "%s jitter : %s\n"
- "%s toh : %s",
- indent, last_update,
- indent,
- xr_stat.tx.stat_sum.begin_seq, xr_stat.tx.stat_sum.end_seq,
- indent, loss, dup,
- indent,
- indent, jitter,
- indent, toh
- );
- VALIDATE_PRINT_BUF();
-
-
- /* VoIP Metrics */
- len = pj_ansi_snprintf(p, end-p, "%s VoIP Metrics", indent);
- VALIDATE_PRINT_BUF();
-
- PRINT_VOIP_MTC_VAL(signal_lvl, xr_stat.rx.voip_mtc.signal_lvl);
- PRINT_VOIP_MTC_VAL(noise_lvl, xr_stat.rx.voip_mtc.noise_lvl);
- PRINT_VOIP_MTC_VAL(rerl, xr_stat.rx.voip_mtc.rerl);
- PRINT_VOIP_MTC_VAL(r_factor, xr_stat.rx.voip_mtc.r_factor);
- PRINT_VOIP_MTC_VAL(ext_r_factor, xr_stat.rx.voip_mtc.ext_r_factor);
- PRINT_VOIP_MTC_VAL(mos_lq, xr_stat.rx.voip_mtc.mos_lq);
- PRINT_VOIP_MTC_VAL(mos_cq, xr_stat.rx.voip_mtc.mos_cq);
-
- switch ((xr_stat.rx.voip_mtc.rx_config>>6) & 3) {
- case PJMEDIA_RTCP_XR_PLC_DIS:
- sprintf(plc, "DISABLED");
- break;
- case PJMEDIA_RTCP_XR_PLC_ENH:
- sprintf(plc, "ENHANCED");
- break;
- case PJMEDIA_RTCP_XR_PLC_STD:
- sprintf(plc, "STANDARD");
- break;
- case PJMEDIA_RTCP_XR_PLC_UNK:
- default:
- sprintf(plc, "UNKNOWN");
- break;
- }
-
- switch ((xr_stat.rx.voip_mtc.rx_config>>4) & 3) {
- case PJMEDIA_RTCP_XR_JB_FIXED:
- sprintf(jba, "FIXED");
- break;
- case PJMEDIA_RTCP_XR_JB_ADAPTIVE:
- sprintf(jba, "ADAPTIVE");
- break;
- default:
- sprintf(jba, "UNKNOWN");
- break;
- }
-
- sprintf(jbr, "%d", xr_stat.rx.voip_mtc.rx_config & 0x0F);
-
- if (xr_stat.rx.voip_mtc.update.sec == 0)
- strcpy(last_update, "never");
- else {
- pj_gettimeofday(&now);
- PJ_TIME_VAL_SUB(now, xr_stat.rx.voip_mtc.update);
- sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
- now.sec / 3600,
- (now.sec % 3600) / 60,
- now.sec % 60,
- now.msec);
- }
-
- len = pj_ansi_snprintf(p, end-p,
- "%s RX last update: %s\n"
- "%s packets : loss rate=%d (%.2f%%), discard rate=%d (%.2f%%)\n"
- "%s burst : density=%d (%.2f%%), duration=%d%s\n"
- "%s gap : density=%d (%.2f%%), duration=%d%s\n"
- "%s delay : round trip=%d%s, end system=%d%s\n"
- "%s level : signal=%s%s, noise=%s%s, RERL=%s%s\n"
- "%s quality : R factor=%s, ext R factor=%s\n"
- "%s MOS LQ=%s, MOS CQ=%s\n"
- "%s config : PLC=%s, JB=%s, JB rate=%s, Gmin=%d\n"
- "%s JB delay : cur=%d%s, max=%d%s, abs max=%d%s",
- indent,
- last_update,
- /* packets */
- indent,
- xr_stat.rx.voip_mtc.loss_rate, xr_stat.rx.voip_mtc.loss_rate*100.0/256,
- xr_stat.rx.voip_mtc.discard_rate, xr_stat.rx.voip_mtc.discard_rate*100.0/256,
- /* burst */
- indent,
- xr_stat.rx.voip_mtc.burst_den, xr_stat.rx.voip_mtc.burst_den*100.0/256,
- xr_stat.rx.voip_mtc.burst_dur, "ms",
- /* gap */
- indent,
- xr_stat.rx.voip_mtc.gap_den, xr_stat.rx.voip_mtc.gap_den*100.0/256,
- xr_stat.rx.voip_mtc.gap_dur, "ms",
- /* delay */
- indent,
- xr_stat.rx.voip_mtc.rnd_trip_delay, "ms",
- xr_stat.rx.voip_mtc.end_sys_delay, "ms",
- /* level */
- indent,
- signal_lvl, "dB",
- noise_lvl, "dB",
- rerl, "",
- /* quality */
- indent,
- r_factor, ext_r_factor,
- indent,
- mos_lq, mos_cq,
- /* config */
- indent,
- plc, jba, jbr, xr_stat.rx.voip_mtc.gmin,
- /* JB delay */
- indent,
- xr_stat.rx.voip_mtc.jb_nom, "ms",
- xr_stat.rx.voip_mtc.jb_max, "ms",
- xr_stat.rx.voip_mtc.jb_abs_max, "ms"
- );
- VALIDATE_PRINT_BUF();
-
- PRINT_VOIP_MTC_VAL(signal_lvl, xr_stat.tx.voip_mtc.signal_lvl);
- PRINT_VOIP_MTC_VAL(noise_lvl, xr_stat.tx.voip_mtc.noise_lvl);
- PRINT_VOIP_MTC_VAL(rerl, xr_stat.tx.voip_mtc.rerl);
- PRINT_VOIP_MTC_VAL(r_factor, xr_stat.tx.voip_mtc.r_factor);
- PRINT_VOIP_MTC_VAL(ext_r_factor, xr_stat.tx.voip_mtc.ext_r_factor);
- PRINT_VOIP_MTC_VAL(mos_lq, xr_stat.tx.voip_mtc.mos_lq);
- PRINT_VOIP_MTC_VAL(mos_cq, xr_stat.tx.voip_mtc.mos_cq);
-
- switch ((xr_stat.tx.voip_mtc.rx_config>>6) & 3) {
- case PJMEDIA_RTCP_XR_PLC_DIS:
- sprintf(plc, "DISABLED");
- break;
- case PJMEDIA_RTCP_XR_PLC_ENH:
- sprintf(plc, "ENHANCED");
- break;
- case PJMEDIA_RTCP_XR_PLC_STD:
- sprintf(plc, "STANDARD");
- break;
- case PJMEDIA_RTCP_XR_PLC_UNK:
- default:
- sprintf(plc, "unknown");
- break;
- }
-
- switch ((xr_stat.tx.voip_mtc.rx_config>>4) & 3) {
- case PJMEDIA_RTCP_XR_JB_FIXED:
- sprintf(jba, "FIXED");
- break;
- case PJMEDIA_RTCP_XR_JB_ADAPTIVE:
- sprintf(jba, "ADAPTIVE");
- break;
- default:
- sprintf(jba, "unknown");
- break;
- }
-
- sprintf(jbr, "%d", xr_stat.tx.voip_mtc.rx_config & 0x0F);
-
- if (xr_stat.tx.voip_mtc.update.sec == 0)
- strcpy(last_update, "never");
- else {
- pj_gettimeofday(&now);
- PJ_TIME_VAL_SUB(now, xr_stat.tx.voip_mtc.update);
- sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
- now.sec / 3600,
- (now.sec % 3600) / 60,
- now.sec % 60,
- now.msec);
- }
-
- len = pj_ansi_snprintf(p, end-p,
- "%s TX last update: %s\n"
- "%s packets : loss rate=%d (%.2f%%), discard rate=%d (%.2f%%)\n"
- "%s burst : density=%d (%.2f%%), duration=%d%s\n"
- "%s gap : density=%d (%.2f%%), duration=%d%s\n"
- "%s delay : round trip=%d%s, end system=%d%s\n"
- "%s level : signal=%s%s, noise=%s%s, RERL=%s%s\n"
- "%s quality : R factor=%s, ext R factor=%s\n"
- "%s MOS LQ=%s, MOS CQ=%s\n"
- "%s config : PLC=%s, JB=%s, JB rate=%s, Gmin=%d\n"
- "%s JB delay : cur=%d%s, max=%d%s, abs max=%d%s",
- indent,
- last_update,
- /* pakcets */
- indent,
- xr_stat.tx.voip_mtc.loss_rate, xr_stat.tx.voip_mtc.loss_rate*100.0/256,
- xr_stat.tx.voip_mtc.discard_rate, xr_stat.tx.voip_mtc.discard_rate*100.0/256,
- /* burst */
- indent,
- xr_stat.tx.voip_mtc.burst_den, xr_stat.tx.voip_mtc.burst_den*100.0/256,
- xr_stat.tx.voip_mtc.burst_dur, "ms",
- /* gap */
- indent,
- xr_stat.tx.voip_mtc.gap_den, xr_stat.tx.voip_mtc.gap_den*100.0/256,
- xr_stat.tx.voip_mtc.gap_dur, "ms",
- /* delay */
- indent,
- xr_stat.tx.voip_mtc.rnd_trip_delay, "ms",
- xr_stat.tx.voip_mtc.end_sys_delay, "ms",
- /* level */
- indent,
- signal_lvl, "dB",
- noise_lvl, "dB",
- rerl, "",
- /* quality */
- indent,
- r_factor, ext_r_factor,
- indent,
- mos_lq, mos_cq,
- /* config */
- indent,
- plc, jba, jbr, xr_stat.tx.voip_mtc.gmin,
- /* JB delay */
- indent,
- xr_stat.tx.voip_mtc.jb_nom, "ms",
- xr_stat.tx.voip_mtc.jb_max, "ms",
- xr_stat.tx.voip_mtc.jb_abs_max, "ms"
- );
- VALIDATE_PRINT_BUF();
-
-
- /* RTT delay (by receiver side) */
- len = pj_ansi_snprintf(p, end-p,
- "%s RTT (from recv) min avg max last dev",
- indent);
- VALIDATE_PRINT_BUF();
- len = pj_ansi_snprintf(p, end-p,
- "%s RTT msec : %7.3f %7.3f %7.3f %7.3f %7.3f",
- indent,
- xr_stat.rtt.min / 1000.0,
- xr_stat.rtt.mean / 1000.0,
- xr_stat.rtt.max / 1000.0,
- xr_stat.rtt.last / 1000.0,
- pj_math_stat_get_stddev(&xr_stat.rtt) / 1000.0
- );
- VALIDATE_PRINT_BUF();
- } /* if audio */;
-#endif
-
- }
-}
-
-
-/* Print call info */
-void print_call(const char *title,
- int call_id,
- char *buf, pj_size_t size)
-{
- int len;
- pjsip_inv_session *inv = pjsua_var.calls[call_id].inv;
- pjsip_dialog *dlg = inv->dlg;
- char userinfo[128];
-
- /* Dump invite sesion info. */
-
- len = pjsip_hdr_print_on(dlg->remote.info, userinfo, sizeof(userinfo));
- if (len < 0)
- pj_ansi_strcpy(userinfo, "<--uri too long-->");
- else
- userinfo[len] = '\0';
-
- len = pj_ansi_snprintf(buf, size, "%s[%s] %s",
- title,
- pjsip_inv_state_name(inv->state),
- userinfo);
- if (len < 1 || len >= (int)size) {
- pj_ansi_strcpy(buf, "<--uri too long-->");
- len = 18;
- } else
- buf[len] = '\0';
-}
-
-
-/*
- * Dump call and media statistics to string.
- */
-PJ_DEF(pj_status_t) pjsua_call_dump( pjsua_call_id call_id,
- pj_bool_t with_media,
- char *buffer,
- unsigned maxlen,
- const char *indent)
-{
- pjsua_call *call;
- pjsip_dialog *dlg;
- pj_time_val duration, res_delay, con_delay;
- char tmp[128];
- char *p, *end;
- pj_status_t status;
- int len;
-
- PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
- PJ_EINVAL);
-
- status = acquire_call("pjsua_call_dump()", call_id, &call, &dlg);
- if (status != PJ_SUCCESS)
- return status;
-
- *buffer = '\0';
- p = buffer;
- end = buffer + maxlen;
- len = 0;
-
- print_call(indent, call_id, tmp, sizeof(tmp));
-
- len = pj_ansi_strlen(tmp);
- pj_ansi_strcpy(buffer, tmp);
-
- p += len;
- *p++ = '\r';
- *p++ = '\n';
-
- /* Calculate call duration */
- if (call->conn_time.sec != 0) {
- pj_gettimeofday(&duration);
- PJ_TIME_VAL_SUB(duration, call->conn_time);
- con_delay = call->conn_time;
- PJ_TIME_VAL_SUB(con_delay, call->start_time);
- } else {
- duration.sec = duration.msec = 0;
- con_delay.sec = con_delay.msec = 0;
- }
-
- /* Calculate first response delay */
- if (call->res_time.sec != 0) {
- res_delay = call->res_time;
- PJ_TIME_VAL_SUB(res_delay, call->start_time);
- } else {
- res_delay.sec = res_delay.msec = 0;
- }
-
- /* Print duration */
- len = pj_ansi_snprintf(p, end-p,
- "%s Call time: %02dh:%02dm:%02ds, "
- "1st res in %d ms, conn in %dms",
- indent,
- (int)(duration.sec / 3600),
- (int)((duration.sec % 3600)/60),
- (int)(duration.sec % 60),
- (int)PJ_TIME_VAL_MSEC(res_delay),
- (int)PJ_TIME_VAL_MSEC(con_delay));
-
- if (len > 0 && len < end-p) {
- p += len;
- *p++ = '\n';
- *p = '\0';
- }
-
- /* Dump session statistics */
- if (with_media && pjsua_call_has_media(call_id))
- dump_media_session(indent, p, end-p, call);
-
- pjsip_dlg_dec_lock(dlg);
-
- return PJ_SUCCESS;
-}
-
/* Proto */
static pj_status_t perform_lock_codec(pjsua_call *call);
diff --git a/pjsip/src/pjsua-lib/pjsua_core.c b/pjsip/src/pjsua-lib/pjsua_core.c
index 436c5f5..7cd809c 100644
--- a/pjsip/src/pjsua-lib/pjsua_core.c
+++ b/pjsip/src/pjsua-lib/pjsua_core.c
@@ -67,6 +67,10 @@
pj_list_init(&pjsua_var.outbound_proxy);
pjsua_config_default(&pjsua_var.ua_cfg);
+
+ for (i=0; i<PJSUA_MAX_VID_WINS; ++i) {
+ pjsua_vid_win_reset(i);
+ }
}
@@ -175,6 +179,8 @@
cfg->ka_interval = 15;
cfg->ka_data = pj_str("\r\n");
cfg->max_audio_cnt = 1;
+ cfg->vid_cap_dev = PJMEDIA_VID_DEFAULT_CAPTURE_DEV;
+ cfg->vid_rend_dev = PJMEDIA_VID_DEFAULT_RENDER_DEV;
pjsua_transport_config_default(&cfg->rtp_cfg);
cfg->use_srtp = pjsua_var.ua_cfg.use_srtp;
cfg->srtp_secure_signaling = pjsua_var.ua_cfg.srtp_secure_signaling;
diff --git a/pjsip/src/pjsua-lib/pjsua_dump.c b/pjsip/src/pjsua-lib/pjsua_dump.c
new file mode 100644
index 0000000..f94e036
--- /dev/null
+++ b/pjsip/src/pjsua-lib/pjsua_dump.c
@@ -0,0 +1,944 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2011-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 <pjsua-lib/pjsua.h>
+#include <pjsua-lib/pjsua_internal.h>
+
+const char *good_number(char *buf, pj_int32_t val)
+{
+ if (val < 1000) {
+ pj_ansi_sprintf(buf, "%d", val);
+ } else if (val < 1000000) {
+ pj_ansi_sprintf(buf, "%d.%dK",
+ val / 1000,
+ (val % 1000) / 100);
+ } else {
+ pj_ansi_sprintf(buf, "%d.%02dM",
+ val / 1000000,
+ (val % 1000000) / 10000);
+ }
+
+ return buf;
+}
+
+static unsigned dump_media_stat(const char *indent,
+ char *buf, unsigned maxlen,
+ const pjmedia_rtcp_stat *stat,
+ const char *rx_info, const char *tx_info)
+{
+ char last_update[64];
+ char packets[32], bytes[32], ipbytes[32], avg_bps[32], avg_ipbps[32];
+ pj_time_val media_duration, now;
+ char *p = buf, *end = buf+maxlen;
+ int len;
+
+ if (stat->rx.update_cnt == 0)
+ strcpy(last_update, "never");
+ else {
+ pj_gettimeofday(&now);
+ PJ_TIME_VAL_SUB(now, stat->rx.update);
+ sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
+ now.sec / 3600,
+ (now.sec % 3600) / 60,
+ now.sec % 60,
+ now.msec);
+ }
+
+ pj_gettimeofday(&media_duration);
+ PJ_TIME_VAL_SUB(media_duration, stat->start);
+ if (PJ_TIME_VAL_MSEC(media_duration) == 0)
+ media_duration.msec = 1;
+
+ len = pj_ansi_snprintf(p, end-p,
+ "%s RX %s last update:%s\n"
+ "%s total %spkt %sB (%sB +IP hdr) @avg=%sbps/%sbps\n"
+ "%s pkt loss=%d (%3.1f%%), discrd=%d (%3.1f%%), dup=%d (%2.1f%%), reord=%d (%3.1f%%)\n"
+ "%s (msec) min avg max last dev\n"
+ "%s loss period: %7.3f %7.3f %7.3f %7.3f %7.3f\n"
+ "%s jitter : %7.3f %7.3f %7.3f %7.3f %7.3f\n"
+#if defined(PJMEDIA_RTCP_STAT_HAS_RAW_JITTER) && PJMEDIA_RTCP_STAT_HAS_RAW_JITTER!=0
+ "%s raw jitter : %7.3f %7.3f %7.3f %7.3f %7.3f\n"
+#endif
+#if defined(PJMEDIA_RTCP_STAT_HAS_IPDV) && PJMEDIA_RTCP_STAT_HAS_IPDV!=0
+ "%s IPDV : %7.3f %7.3f %7.3f %7.3f %7.3f\n"
+#endif
+ "%s",
+ indent,
+ rx_info? rx_info : "",
+ last_update,
+
+ indent,
+ good_number(packets, stat->rx.pkt),
+ good_number(bytes, stat->rx.bytes),
+ good_number(ipbytes, stat->rx.bytes + stat->rx.pkt * 40),
+ good_number(avg_bps, (pj_int32_t)((pj_int64_t)stat->rx.bytes * 8 * 1000 / PJ_TIME_VAL_MSEC(media_duration))),
+ good_number(avg_ipbps, (pj_int32_t)(((pj_int64_t)stat->rx.bytes + stat->rx.pkt * 40) * 8 * 1000 / PJ_TIME_VAL_MSEC(media_duration))),
+ indent,
+ stat->rx.loss,
+ (stat->rx.loss? stat->rx.loss * 100.0 / (stat->rx.pkt + stat->rx.loss) : 0),
+ stat->rx.discard,
+ (stat->rx.discard? stat->rx.discard * 100.0 / (stat->rx.pkt + stat->rx.loss) : 0),
+ stat->rx.dup,
+ (stat->rx.dup? stat->rx.dup * 100.0 / (stat->rx.pkt + stat->rx.loss) : 0),
+ stat->rx.reorder,
+ (stat->rx.reorder? stat->rx.reorder * 100.0 / (stat->rx.pkt + stat->rx.loss) : 0),
+ indent, indent,
+ stat->rx.loss_period.min / 1000.0,
+ stat->rx.loss_period.mean / 1000.0,
+ stat->rx.loss_period.max / 1000.0,
+ stat->rx.loss_period.last / 1000.0,
+ pj_math_stat_get_stddev(&stat->rx.loss_period) / 1000.0,
+ indent,
+ stat->rx.jitter.min / 1000.0,
+ stat->rx.jitter.mean / 1000.0,
+ stat->rx.jitter.max / 1000.0,
+ stat->rx.jitter.last / 1000.0,
+ pj_math_stat_get_stddev(&stat->rx.jitter) / 1000.0,
+#if defined(PJMEDIA_RTCP_STAT_HAS_RAW_JITTER) && PJMEDIA_RTCP_STAT_HAS_RAW_JITTER!=0
+ indent,
+ stat->rx_raw_jitter.min / 1000.0,
+ stat->rx_raw_jitter.mean / 1000.0,
+ stat->rx_raw_jitter.max / 1000.0,
+ stat->rx_raw_jitter.last / 1000.0,
+ pj_math_stat_get_stddev(&stat->rx_raw_jitter) / 1000.0,
+#endif
+#if defined(PJMEDIA_RTCP_STAT_HAS_IPDV) && PJMEDIA_RTCP_STAT_HAS_IPDV!=0
+ indent,
+ stat->rx_ipdv.min / 1000.0,
+ stat->rx_ipdv.mean / 1000.0,
+ stat->rx_ipdv.max / 1000.0,
+ stat->rx_ipdv.last / 1000.0,
+ pj_math_stat_get_stddev(&stat->rx_ipdv) / 1000.0,
+#endif
+ ""
+ );
+
+ if (len < 1 || len > end-p) {
+ *p = '\0';
+ return (p-buf);
+ }
+ p += len;
+
+ if (stat->tx.update_cnt == 0)
+ strcpy(last_update, "never");
+ else {
+ pj_gettimeofday(&now);
+ PJ_TIME_VAL_SUB(now, stat->tx.update);
+ sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
+ now.sec / 3600,
+ (now.sec % 3600) / 60,
+ now.sec % 60,
+ now.msec);
+ }
+
+ len = pj_ansi_snprintf(p, end-p,
+ "%s TX %s last update:%s\n"
+ "%s total %spkt %sB (%sB +IP hdr) @avg %sbps/%sbps\n"
+ "%s pkt loss=%d (%3.1f%%), dup=%d (%3.1f%%), reorder=%d (%3.1f%%)\n"
+ "%s (msec) min avg max last dev \n"
+ "%s loss period: %7.3f %7.3f %7.3f %7.3f %7.3f\n"
+ "%s jitter : %7.3f %7.3f %7.3f %7.3f %7.3f\n",
+ indent,
+ tx_info,
+ last_update,
+
+ indent,
+ good_number(packets, stat->tx.pkt),
+ good_number(bytes, stat->tx.bytes),
+ good_number(ipbytes, stat->tx.bytes + stat->tx.pkt * 40),
+ good_number(avg_bps, (pj_int32_t)((pj_int64_t)stat->tx.bytes * 8 * 1000 / PJ_TIME_VAL_MSEC(media_duration))),
+ good_number(avg_ipbps, (pj_int32_t)(((pj_int64_t)stat->tx.bytes + stat->tx.pkt * 40) * 8 * 1000 / PJ_TIME_VAL_MSEC(media_duration))),
+
+ indent,
+ stat->tx.loss,
+ (stat->tx.loss? stat->tx.loss * 100.0 / (stat->tx.pkt + stat->tx.loss) : 0),
+ stat->tx.dup,
+ (stat->tx.dup? stat->tx.dup * 100.0 / (stat->tx.pkt + stat->tx.loss) : 0),
+ stat->tx.reorder,
+ (stat->tx.reorder? stat->tx.reorder * 100.0 / (stat->tx.pkt + stat->tx.loss) : 0),
+
+ indent, indent,
+ stat->tx.loss_period.min / 1000.0,
+ stat->tx.loss_period.mean / 1000.0,
+ stat->tx.loss_period.max / 1000.0,
+ stat->tx.loss_period.last / 1000.0,
+ pj_math_stat_get_stddev(&stat->tx.loss_period) / 1000.0,
+ indent,
+ stat->tx.jitter.min / 1000.0,
+ stat->tx.jitter.mean / 1000.0,
+ stat->tx.jitter.max / 1000.0,
+ stat->tx.jitter.last / 1000.0,
+ pj_math_stat_get_stddev(&stat->tx.jitter) / 1000.0
+ );
+
+ if (len < 1 || len > end-p) {
+ *p = '\0';
+ return (p-buf);
+ }
+ p += len;
+
+ len = pj_ansi_snprintf(p, end-p,
+ "%s RTT msec : %7.3f %7.3f %7.3f %7.3f %7.3f\n",
+ indent,
+ stat->rtt.min / 1000.0,
+ stat->rtt.mean / 1000.0,
+ stat->rtt.max / 1000.0,
+ stat->rtt.last / 1000.0,
+ pj_math_stat_get_stddev(&stat->rtt) / 1000.0
+ );
+ if (len < 1 || len > end-p) {
+ *p = '\0';
+ return (p-buf);
+ }
+ p += len;
+
+ return (p-buf);
+}
+
+
+/* Dump media session */
+static void dump_media_session(const char *indent,
+ char *buf, unsigned maxlen,
+ pjsua_call *call)
+{
+ unsigned i;
+ char *p = buf, *end = buf+maxlen;
+ int len;
+
+ for (i=0; i<call->med_cnt; ++i) {
+ pjsua_call_media *call_med = &call->media[i];
+ pjmedia_rtcp_stat stat;
+ pj_bool_t has_stat;
+ pjmedia_transport_info tp_info;
+ char rem_addr_buf[80];
+ char codec_info[32] = {'0'};
+ char rx_info[80] = {'\0'};
+ char tx_info[80] = {'\0'};
+ const char *rem_addr;
+ const char *dir_str;
+ const char *media_type_str;
+
+ switch (call_med->type) {
+ case PJMEDIA_TYPE_AUDIO:
+ media_type_str = "audio";
+ break;
+ case PJMEDIA_TYPE_VIDEO:
+ media_type_str = "video";
+ break;
+ case PJMEDIA_TYPE_APPLICATION:
+ media_type_str = "application";
+ break;
+ default:
+ media_type_str = "unknown";
+ break;
+ }
+
+ /* Check if the stream is deactivated */
+ if (call_med->tp == NULL ||
+ (!call_med->strm.a.stream && !call_med->strm.v.stream))
+ {
+ len = pj_ansi_snprintf(p, end-p,
+ "%s #%d %s deactivated\n",
+ indent, i, media_type_str);
+ if (len < 1 || len > end-p) {
+ *p = '\0';
+ return;
+ }
+
+ p += len;
+ continue;
+ }
+
+ pjmedia_transport_info_init(&tp_info);
+ pjmedia_transport_get_info(call_med->tp, &tp_info);
+
+ // rem_addr will contain actual address of RTP originator, instead of
+ // remote RTP address specified by stream which is fetched from the SDP.
+ // Please note that we are assuming only one stream per call.
+ //rem_addr = pj_sockaddr_print(&info.stream_info[i].rem_addr,
+ // rem_addr_buf, sizeof(rem_addr_buf), 3);
+ if (pj_sockaddr_has_addr(&tp_info.src_rtp_name)) {
+ rem_addr = pj_sockaddr_print(&tp_info.src_rtp_name, rem_addr_buf,
+ sizeof(rem_addr_buf), 3);
+ } else {
+ pj_ansi_snprintf(rem_addr_buf, sizeof(rem_addr_buf), "-");
+ rem_addr = rem_addr_buf;
+ }
+
+ if (call_med->dir == PJMEDIA_DIR_NONE) {
+ /* To handle when the stream that is currently being paused
+ * (http://trac.pjsip.org/repos/ticket/1079)
+ */
+ dir_str = "inactive";
+ } else if (call_med->dir == PJMEDIA_DIR_ENCODING)
+ dir_str = "sendonly";
+ else if (call_med->dir == PJMEDIA_DIR_DECODING)
+ dir_str = "recvonly";
+ else if (call_med->dir == PJMEDIA_DIR_ENCODING_DECODING)
+ dir_str = "sendrecv";
+ else
+ dir_str = "inactive";
+
+ if (call_med->type == PJMEDIA_TYPE_AUDIO) {
+ pjmedia_stream *stream = call_med->strm.a.stream;
+ pjmedia_stream_info info;
+
+ pjmedia_stream_get_stat(stream, &stat);
+ has_stat = PJ_TRUE;
+
+ pjmedia_stream_get_info(stream, &info);
+ pj_ansi_snprintf(codec_info, sizeof(codec_info), " %.*s @%dkHz",
+ (int)info.fmt.encoding_name.slen,
+ info.fmt.encoding_name.ptr,
+ info.fmt.clock_rate / 1000);
+ pj_ansi_snprintf(rx_info, sizeof(rx_info), "pt=%d,",
+ info.fmt.pt);
+ pj_ansi_snprintf(tx_info, sizeof(tx_info), "pt=%d, ptime=%d,",
+ info.tx_pt,
+ info.param->setting.frm_per_pkt*
+ info.param->info.frm_ptime);
+ } else if (call_med->type == PJMEDIA_TYPE_VIDEO) {
+ pjmedia_vid_stream *stream = call_med->strm.v.stream;
+ pjmedia_vid_stream_info info;
+
+ pjmedia_vid_stream_get_stat(stream, &stat);
+ has_stat = PJ_TRUE;
+
+ pjmedia_vid_stream_get_info(stream, &info);
+ pj_ansi_snprintf(codec_info, sizeof(codec_info), " %.*s",
+ (int)info.codec_info.encoding_name.slen,
+ info.codec_info.encoding_name.ptr);
+ if (call_med->dir & PJMEDIA_DIR_DECODING) {
+ pjmedia_video_format_detail *vfd;
+ vfd = pjmedia_format_get_video_format_detail(
+ &info.codec_param->dec_fmt, PJ_TRUE);
+ pj_ansi_snprintf(rx_info, sizeof(rx_info),
+ "pt=%d, size=%dx%d, fps=%.2f,",
+ info.rx_pt,
+ vfd->size.w, vfd->size.h,
+ vfd->fps.num*1.0/vfd->fps.denum);
+ }
+ if (call_med->dir & PJMEDIA_DIR_ENCODING) {
+ pjmedia_video_format_detail *vfd;
+ vfd = pjmedia_format_get_video_format_detail(
+ &info.codec_param->enc_fmt, PJ_TRUE);
+ pj_ansi_snprintf(tx_info, sizeof(tx_info),
+ "pt=%d, size=%dx%d, fps=%.2f,",
+ info.tx_pt,
+ vfd->size.w, vfd->size.h,
+ vfd->fps.num*1.0/vfd->fps.denum);
+ }
+ } else {
+ has_stat = PJ_FALSE;
+ }
+
+ len = pj_ansi_snprintf(p, end-p,
+ "%s #%d %s%s, %s, peer=%s\n",
+ indent,
+ call_med->idx,
+ media_type_str,
+ codec_info,
+ dir_str,
+ rem_addr);
+ if (len < 1 || len > end-p) {
+ *p = '\0';
+ return;
+ }
+ p += len;
+
+ /* Get and ICE SRTP status */
+ if (call_med->tp) {
+ pjmedia_transport_info tp_info;
+
+ pjmedia_transport_info_init(&tp_info);
+ pjmedia_transport_get_info(call_med->tp, &tp_info);
+ if (tp_info.specific_info_cnt > 0) {
+ unsigned j;
+ for (j = 0; j < tp_info.specific_info_cnt; ++j) {
+ if (tp_info.spc_info[j].type == PJMEDIA_TRANSPORT_TYPE_SRTP)
+ {
+ pjmedia_srtp_info *srtp_info =
+ (pjmedia_srtp_info*) tp_info.spc_info[j].buffer;
+
+ len = pj_ansi_snprintf(p, end-p,
+ " %s SRTP status: %s Crypto-suite: %s",
+ indent,
+ (srtp_info->active?"Active":"Not active"),
+ srtp_info->tx_policy.name.ptr);
+ if (len > 0 && len < end-p) {
+ p += len;
+ *p++ = '\n';
+ *p = '\0';
+ }
+ } else if (tp_info.spc_info[j].type==PJMEDIA_TRANSPORT_TYPE_ICE) {
+ const pjmedia_ice_transport_info *ii;
+
+ ii = (const pjmedia_ice_transport_info*)
+ tp_info.spc_info[j].buffer;
+
+ len = pj_ansi_snprintf(p, end-p,
+ " %s ICE role: %s, state: %s, comp_cnt: %u",
+ indent,
+ pj_ice_sess_role_name(ii->role),
+ pj_ice_strans_state_name(ii->sess_state),
+ ii->comp_cnt);
+ if (len > 0 && len < end-p) {
+ p += len;
+ *p++ = '\n';
+ *p = '\0';
+ }
+ }
+ }
+ }
+ }
+
+
+ if (has_stat) {
+ len = dump_media_stat(indent, p, end-p, &stat,
+ rx_info, tx_info);
+ p += len;
+ }
+
+#if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0)
+# define SAMPLES_TO_USEC(usec, samples, clock_rate) \
+ do { \
+ if (samples <= 4294) \
+ usec = samples * 1000000 / clock_rate; \
+ else { \
+ usec = samples * 1000 / clock_rate; \
+ usec *= 1000; \
+ } \
+ } while(0)
+
+# define PRINT_VOIP_MTC_VAL(s, v) \
+ if (v == 127) \
+ sprintf(s, "(na)"); \
+ else \
+ sprintf(s, "%d", v)
+
+# define VALIDATE_PRINT_BUF() \
+ if (len < 1 || len > end-p) { *p = '\0'; return; } \
+ p += len; *p++ = '\n'; *p = '\0'
+
+
+ if (call_med->type == PJMEDIA_TYPE_AUDIO) {
+ pjmedia_stream_info info;
+ char last_update[64];
+ char loss[16], dup[16];
+ char jitter[80];
+ char toh[80];
+ char plc[16], jba[16], jbr[16];
+ char signal_lvl[16], noise_lvl[16], rerl[16];
+ char r_factor[16], ext_r_factor[16], mos_lq[16], mos_cq[16];
+ pjmedia_rtcp_xr_stat xr_stat;
+ unsigned clock_rate;
+ pj_time_val now;
+
+ if (pjmedia_stream_get_stat_xr(call_med->strm.a.stream,
+ &xr_stat) != PJ_SUCCESS)
+ {
+ continue;
+ }
+
+ if (pjmedia_stream_get_info(call_med->strm.a.stream, &info)
+ != PJ_SUCCESS)
+ {
+ continue;
+ }
+
+ clock_rate = info.fmt.clock_rate;
+ pj_gettimeofday(&now);
+
+ len = pj_ansi_snprintf(p, end-p, "\n%s Extended reports:", indent);
+ VALIDATE_PRINT_BUF();
+
+ /* Statistics Summary */
+ len = pj_ansi_snprintf(p, end-p, "%s Statistics Summary", indent);
+ VALIDATE_PRINT_BUF();
+
+ if (xr_stat.rx.stat_sum.l)
+ sprintf(loss, "%d", xr_stat.rx.stat_sum.lost);
+ else
+ sprintf(loss, "(na)");
+
+ if (xr_stat.rx.stat_sum.d)
+ sprintf(dup, "%d", xr_stat.rx.stat_sum.dup);
+ else
+ sprintf(dup, "(na)");
+
+ if (xr_stat.rx.stat_sum.j) {
+ unsigned jmin, jmax, jmean, jdev;
+
+ SAMPLES_TO_USEC(jmin, xr_stat.rx.stat_sum.jitter.min,
+ clock_rate);
+ SAMPLES_TO_USEC(jmax, xr_stat.rx.stat_sum.jitter.max,
+ clock_rate);
+ SAMPLES_TO_USEC(jmean, xr_stat.rx.stat_sum.jitter.mean,
+ clock_rate);
+ SAMPLES_TO_USEC(jdev,
+ pj_math_stat_get_stddev(&xr_stat.rx.stat_sum.jitter),
+ clock_rate);
+ sprintf(jitter, "%7.3f %7.3f %7.3f %7.3f",
+ jmin/1000.0, jmean/1000.0, jmax/1000.0, jdev/1000.0);
+ } else
+ sprintf(jitter, "(report not available)");
+
+ if (xr_stat.rx.stat_sum.t) {
+ sprintf(toh, "%11d %11d %11d %11d",
+ xr_stat.rx.stat_sum.toh.min,
+ xr_stat.rx.stat_sum.toh.mean,
+ xr_stat.rx.stat_sum.toh.max,
+ pj_math_stat_get_stddev(&xr_stat.rx.stat_sum.toh));
+ } else
+ sprintf(toh, "(report not available)");
+
+ if (xr_stat.rx.stat_sum.update.sec == 0)
+ strcpy(last_update, "never");
+ else {
+ pj_gettimeofday(&now);
+ PJ_TIME_VAL_SUB(now, xr_stat.rx.stat_sum.update);
+ sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
+ now.sec / 3600,
+ (now.sec % 3600) / 60,
+ now.sec % 60,
+ now.msec);
+ }
+
+ len = pj_ansi_snprintf(p, end-p,
+ "%s RX last update: %s\n"
+ "%s begin seq=%d, end seq=%d\n"
+ "%s pkt loss=%s, dup=%s\n"
+ "%s (msec) min avg max dev\n"
+ "%s jitter : %s\n"
+ "%s toh : %s",
+ indent, last_update,
+ indent,
+ xr_stat.rx.stat_sum.begin_seq, xr_stat.rx.stat_sum.end_seq,
+ indent, loss, dup,
+ indent,
+ indent, jitter,
+ indent, toh
+ );
+ VALIDATE_PRINT_BUF();
+
+ if (xr_stat.tx.stat_sum.l)
+ sprintf(loss, "%d", xr_stat.tx.stat_sum.lost);
+ else
+ sprintf(loss, "(na)");
+
+ if (xr_stat.tx.stat_sum.d)
+ sprintf(dup, "%d", xr_stat.tx.stat_sum.dup);
+ else
+ sprintf(dup, "(na)");
+
+ if (xr_stat.tx.stat_sum.j) {
+ unsigned jmin, jmax, jmean, jdev;
+
+ SAMPLES_TO_USEC(jmin, xr_stat.tx.stat_sum.jitter.min,
+ clock_rate);
+ SAMPLES_TO_USEC(jmax, xr_stat.tx.stat_sum.jitter.max,
+ clock_rate);
+ SAMPLES_TO_USEC(jmean, xr_stat.tx.stat_sum.jitter.mean,
+ clock_rate);
+ SAMPLES_TO_USEC(jdev,
+ pj_math_stat_get_stddev(&xr_stat.tx.stat_sum.jitter),
+ clock_rate);
+ sprintf(jitter, "%7.3f %7.3f %7.3f %7.3f",
+ jmin/1000.0, jmean/1000.0, jmax/1000.0, jdev/1000.0);
+ } else
+ sprintf(jitter, "(report not available)");
+
+ if (xr_stat.tx.stat_sum.t) {
+ sprintf(toh, "%11d %11d %11d %11d",
+ xr_stat.tx.stat_sum.toh.min,
+ xr_stat.tx.stat_sum.toh.mean,
+ xr_stat.tx.stat_sum.toh.max,
+ pj_math_stat_get_stddev(&xr_stat.rx.stat_sum.toh));
+ } else
+ sprintf(toh, "(report not available)");
+
+ if (xr_stat.tx.stat_sum.update.sec == 0)
+ strcpy(last_update, "never");
+ else {
+ pj_gettimeofday(&now);
+ PJ_TIME_VAL_SUB(now, xr_stat.tx.stat_sum.update);
+ sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
+ now.sec / 3600,
+ (now.sec % 3600) / 60,
+ now.sec % 60,
+ now.msec);
+ }
+
+ len = pj_ansi_snprintf(p, end-p,
+ "%s TX last update: %s\n"
+ "%s begin seq=%d, end seq=%d\n"
+ "%s pkt loss=%s, dup=%s\n"
+ "%s (msec) min avg max dev\n"
+ "%s jitter : %s\n"
+ "%s toh : %s",
+ indent, last_update,
+ indent,
+ xr_stat.tx.stat_sum.begin_seq, xr_stat.tx.stat_sum.end_seq,
+ indent, loss, dup,
+ indent,
+ indent, jitter,
+ indent, toh
+ );
+ VALIDATE_PRINT_BUF();
+
+
+ /* VoIP Metrics */
+ len = pj_ansi_snprintf(p, end-p, "%s VoIP Metrics", indent);
+ VALIDATE_PRINT_BUF();
+
+ PRINT_VOIP_MTC_VAL(signal_lvl, xr_stat.rx.voip_mtc.signal_lvl);
+ PRINT_VOIP_MTC_VAL(noise_lvl, xr_stat.rx.voip_mtc.noise_lvl);
+ PRINT_VOIP_MTC_VAL(rerl, xr_stat.rx.voip_mtc.rerl);
+ PRINT_VOIP_MTC_VAL(r_factor, xr_stat.rx.voip_mtc.r_factor);
+ PRINT_VOIP_MTC_VAL(ext_r_factor, xr_stat.rx.voip_mtc.ext_r_factor);
+ PRINT_VOIP_MTC_VAL(mos_lq, xr_stat.rx.voip_mtc.mos_lq);
+ PRINT_VOIP_MTC_VAL(mos_cq, xr_stat.rx.voip_mtc.mos_cq);
+
+ switch ((xr_stat.rx.voip_mtc.rx_config>>6) & 3) {
+ case PJMEDIA_RTCP_XR_PLC_DIS:
+ sprintf(plc, "DISABLED");
+ break;
+ case PJMEDIA_RTCP_XR_PLC_ENH:
+ sprintf(plc, "ENHANCED");
+ break;
+ case PJMEDIA_RTCP_XR_PLC_STD:
+ sprintf(plc, "STANDARD");
+ break;
+ case PJMEDIA_RTCP_XR_PLC_UNK:
+ default:
+ sprintf(plc, "UNKNOWN");
+ break;
+ }
+
+ switch ((xr_stat.rx.voip_mtc.rx_config>>4) & 3) {
+ case PJMEDIA_RTCP_XR_JB_FIXED:
+ sprintf(jba, "FIXED");
+ break;
+ case PJMEDIA_RTCP_XR_JB_ADAPTIVE:
+ sprintf(jba, "ADAPTIVE");
+ break;
+ default:
+ sprintf(jba, "UNKNOWN");
+ break;
+ }
+
+ sprintf(jbr, "%d", xr_stat.rx.voip_mtc.rx_config & 0x0F);
+
+ if (xr_stat.rx.voip_mtc.update.sec == 0)
+ strcpy(last_update, "never");
+ else {
+ pj_gettimeofday(&now);
+ PJ_TIME_VAL_SUB(now, xr_stat.rx.voip_mtc.update);
+ sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
+ now.sec / 3600,
+ (now.sec % 3600) / 60,
+ now.sec % 60,
+ now.msec);
+ }
+
+ len = pj_ansi_snprintf(p, end-p,
+ "%s RX last update: %s\n"
+ "%s packets : loss rate=%d (%.2f%%), discard rate=%d (%.2f%%)\n"
+ "%s burst : density=%d (%.2f%%), duration=%d%s\n"
+ "%s gap : density=%d (%.2f%%), duration=%d%s\n"
+ "%s delay : round trip=%d%s, end system=%d%s\n"
+ "%s level : signal=%s%s, noise=%s%s, RERL=%s%s\n"
+ "%s quality : R factor=%s, ext R factor=%s\n"
+ "%s MOS LQ=%s, MOS CQ=%s\n"
+ "%s config : PLC=%s, JB=%s, JB rate=%s, Gmin=%d\n"
+ "%s JB delay : cur=%d%s, max=%d%s, abs max=%d%s",
+ indent,
+ last_update,
+ /* packets */
+ indent,
+ xr_stat.rx.voip_mtc.loss_rate, xr_stat.rx.voip_mtc.loss_rate*100.0/256,
+ xr_stat.rx.voip_mtc.discard_rate, xr_stat.rx.voip_mtc.discard_rate*100.0/256,
+ /* burst */
+ indent,
+ xr_stat.rx.voip_mtc.burst_den, xr_stat.rx.voip_mtc.burst_den*100.0/256,
+ xr_stat.rx.voip_mtc.burst_dur, "ms",
+ /* gap */
+ indent,
+ xr_stat.rx.voip_mtc.gap_den, xr_stat.rx.voip_mtc.gap_den*100.0/256,
+ xr_stat.rx.voip_mtc.gap_dur, "ms",
+ /* delay */
+ indent,
+ xr_stat.rx.voip_mtc.rnd_trip_delay, "ms",
+ xr_stat.rx.voip_mtc.end_sys_delay, "ms",
+ /* level */
+ indent,
+ signal_lvl, "dB",
+ noise_lvl, "dB",
+ rerl, "",
+ /* quality */
+ indent,
+ r_factor, ext_r_factor,
+ indent,
+ mos_lq, mos_cq,
+ /* config */
+ indent,
+ plc, jba, jbr, xr_stat.rx.voip_mtc.gmin,
+ /* JB delay */
+ indent,
+ xr_stat.rx.voip_mtc.jb_nom, "ms",
+ xr_stat.rx.voip_mtc.jb_max, "ms",
+ xr_stat.rx.voip_mtc.jb_abs_max, "ms"
+ );
+ VALIDATE_PRINT_BUF();
+
+ PRINT_VOIP_MTC_VAL(signal_lvl, xr_stat.tx.voip_mtc.signal_lvl);
+ PRINT_VOIP_MTC_VAL(noise_lvl, xr_stat.tx.voip_mtc.noise_lvl);
+ PRINT_VOIP_MTC_VAL(rerl, xr_stat.tx.voip_mtc.rerl);
+ PRINT_VOIP_MTC_VAL(r_factor, xr_stat.tx.voip_mtc.r_factor);
+ PRINT_VOIP_MTC_VAL(ext_r_factor, xr_stat.tx.voip_mtc.ext_r_factor);
+ PRINT_VOIP_MTC_VAL(mos_lq, xr_stat.tx.voip_mtc.mos_lq);
+ PRINT_VOIP_MTC_VAL(mos_cq, xr_stat.tx.voip_mtc.mos_cq);
+
+ switch ((xr_stat.tx.voip_mtc.rx_config>>6) & 3) {
+ case PJMEDIA_RTCP_XR_PLC_DIS:
+ sprintf(plc, "DISABLED");
+ break;
+ case PJMEDIA_RTCP_XR_PLC_ENH:
+ sprintf(plc, "ENHANCED");
+ break;
+ case PJMEDIA_RTCP_XR_PLC_STD:
+ sprintf(plc, "STANDARD");
+ break;
+ case PJMEDIA_RTCP_XR_PLC_UNK:
+ default:
+ sprintf(plc, "unknown");
+ break;
+ }
+
+ switch ((xr_stat.tx.voip_mtc.rx_config>>4) & 3) {
+ case PJMEDIA_RTCP_XR_JB_FIXED:
+ sprintf(jba, "FIXED");
+ break;
+ case PJMEDIA_RTCP_XR_JB_ADAPTIVE:
+ sprintf(jba, "ADAPTIVE");
+ break;
+ default:
+ sprintf(jba, "unknown");
+ break;
+ }
+
+ sprintf(jbr, "%d", xr_stat.tx.voip_mtc.rx_config & 0x0F);
+
+ if (xr_stat.tx.voip_mtc.update.sec == 0)
+ strcpy(last_update, "never");
+ else {
+ pj_gettimeofday(&now);
+ PJ_TIME_VAL_SUB(now, xr_stat.tx.voip_mtc.update);
+ sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
+ now.sec / 3600,
+ (now.sec % 3600) / 60,
+ now.sec % 60,
+ now.msec);
+ }
+
+ len = pj_ansi_snprintf(p, end-p,
+ "%s TX last update: %s\n"
+ "%s packets : loss rate=%d (%.2f%%), discard rate=%d (%.2f%%)\n"
+ "%s burst : density=%d (%.2f%%), duration=%d%s\n"
+ "%s gap : density=%d (%.2f%%), duration=%d%s\n"
+ "%s delay : round trip=%d%s, end system=%d%s\n"
+ "%s level : signal=%s%s, noise=%s%s, RERL=%s%s\n"
+ "%s quality : R factor=%s, ext R factor=%s\n"
+ "%s MOS LQ=%s, MOS CQ=%s\n"
+ "%s config : PLC=%s, JB=%s, JB rate=%s, Gmin=%d\n"
+ "%s JB delay : cur=%d%s, max=%d%s, abs max=%d%s",
+ indent,
+ last_update,
+ /* pakcets */
+ indent,
+ xr_stat.tx.voip_mtc.loss_rate, xr_stat.tx.voip_mtc.loss_rate*100.0/256,
+ xr_stat.tx.voip_mtc.discard_rate, xr_stat.tx.voip_mtc.discard_rate*100.0/256,
+ /* burst */
+ indent,
+ xr_stat.tx.voip_mtc.burst_den, xr_stat.tx.voip_mtc.burst_den*100.0/256,
+ xr_stat.tx.voip_mtc.burst_dur, "ms",
+ /* gap */
+ indent,
+ xr_stat.tx.voip_mtc.gap_den, xr_stat.tx.voip_mtc.gap_den*100.0/256,
+ xr_stat.tx.voip_mtc.gap_dur, "ms",
+ /* delay */
+ indent,
+ xr_stat.tx.voip_mtc.rnd_trip_delay, "ms",
+ xr_stat.tx.voip_mtc.end_sys_delay, "ms",
+ /* level */
+ indent,
+ signal_lvl, "dB",
+ noise_lvl, "dB",
+ rerl, "",
+ /* quality */
+ indent,
+ r_factor, ext_r_factor,
+ indent,
+ mos_lq, mos_cq,
+ /* config */
+ indent,
+ plc, jba, jbr, xr_stat.tx.voip_mtc.gmin,
+ /* JB delay */
+ indent,
+ xr_stat.tx.voip_mtc.jb_nom, "ms",
+ xr_stat.tx.voip_mtc.jb_max, "ms",
+ xr_stat.tx.voip_mtc.jb_abs_max, "ms"
+ );
+ VALIDATE_PRINT_BUF();
+
+
+ /* RTT delay (by receiver side) */
+ len = pj_ansi_snprintf(p, end-p,
+ "%s RTT (from recv) min avg max last dev",
+ indent);
+ VALIDATE_PRINT_BUF();
+ len = pj_ansi_snprintf(p, end-p,
+ "%s RTT msec : %7.3f %7.3f %7.3f %7.3f %7.3f",
+ indent,
+ xr_stat.rtt.min / 1000.0,
+ xr_stat.rtt.mean / 1000.0,
+ xr_stat.rtt.max / 1000.0,
+ xr_stat.rtt.last / 1000.0,
+ pj_math_stat_get_stddev(&xr_stat.rtt) / 1000.0
+ );
+ VALIDATE_PRINT_BUF();
+ } /* if audio */;
+#endif
+
+ }
+}
+
+
+/* Print call info */
+void print_call(const char *title,
+ int call_id,
+ char *buf, pj_size_t size)
+{
+ int len;
+ pjsip_inv_session *inv = pjsua_var.calls[call_id].inv;
+ pjsip_dialog *dlg = inv->dlg;
+ char userinfo[128];
+
+ /* Dump invite sesion info. */
+
+ len = pjsip_hdr_print_on(dlg->remote.info, userinfo, sizeof(userinfo));
+ if (len < 0)
+ pj_ansi_strcpy(userinfo, "<--uri too long-->");
+ else
+ userinfo[len] = '\0';
+
+ len = pj_ansi_snprintf(buf, size, "%s[%s] %s",
+ title,
+ pjsip_inv_state_name(inv->state),
+ userinfo);
+ if (len < 1 || len >= (int)size) {
+ pj_ansi_strcpy(buf, "<--uri too long-->");
+ len = 18;
+ } else
+ buf[len] = '\0';
+}
+
+
+/*
+ * Dump call and media statistics to string.
+ */
+PJ_DEF(pj_status_t) pjsua_call_dump( pjsua_call_id call_id,
+ pj_bool_t with_media,
+ char *buffer,
+ unsigned maxlen,
+ const char *indent)
+{
+ pjsua_call *call;
+ pjsip_dialog *dlg;
+ pj_time_val duration, res_delay, con_delay;
+ char tmp[128];
+ char *p, *end;
+ pj_status_t status;
+ int len;
+
+ PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
+ PJ_EINVAL);
+
+ status = acquire_call("pjsua_call_dump()", call_id, &call, &dlg);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ *buffer = '\0';
+ p = buffer;
+ end = buffer + maxlen;
+ len = 0;
+
+ print_call(indent, call_id, tmp, sizeof(tmp));
+
+ len = pj_ansi_strlen(tmp);
+ pj_ansi_strcpy(buffer, tmp);
+
+ p += len;
+ *p++ = '\r';
+ *p++ = '\n';
+
+ /* Calculate call duration */
+ if (call->conn_time.sec != 0) {
+ pj_gettimeofday(&duration);
+ PJ_TIME_VAL_SUB(duration, call->conn_time);
+ con_delay = call->conn_time;
+ PJ_TIME_VAL_SUB(con_delay, call->start_time);
+ } else {
+ duration.sec = duration.msec = 0;
+ con_delay.sec = con_delay.msec = 0;
+ }
+
+ /* Calculate first response delay */
+ if (call->res_time.sec != 0) {
+ res_delay = call->res_time;
+ PJ_TIME_VAL_SUB(res_delay, call->start_time);
+ } else {
+ res_delay.sec = res_delay.msec = 0;
+ }
+
+ /* Print duration */
+ len = pj_ansi_snprintf(p, end-p,
+ "%s Call time: %02dh:%02dm:%02ds, "
+ "1st res in %d ms, conn in %dms",
+ indent,
+ (int)(duration.sec / 3600),
+ (int)((duration.sec % 3600)/60),
+ (int)(duration.sec % 60),
+ (int)PJ_TIME_VAL_MSEC(res_delay),
+ (int)PJ_TIME_VAL_MSEC(con_delay));
+
+ if (len > 0 && len < end-p) {
+ p += len;
+ *p++ = '\n';
+ *p = '\0';
+ }
+
+ /* Dump session statistics */
+ if (with_media && pjsua_call_has_media(call_id))
+ dump_media_session(indent, p, end-p, call);
+
+ pjsip_dlg_dec_lock(dlg);
+
+ return PJ_SUCCESS;
+}
+
diff --git a/pjsip/src/pjsua-lib/pjsua_media.c b/pjsip/src/pjsua-lib/pjsua_media.c
index 15a86e9..6289ac9 100644
--- a/pjsip/src/pjsua-lib/pjsua_media.c
+++ b/pjsip/src/pjsua-lib/pjsua_media.c
@@ -275,45 +275,6 @@
#endif /* PJMEDIA_HAS_L16_CODEC */
-#if PJMEDIA_HAS_VIDEO
- status = pjmedia_video_format_mgr_create(pjsua_var.pool, 64, 0, NULL);
- if (status != PJ_SUCCESS) {
- pjsua_perror(THIS_FILE, "Error creating PJMEDIA video format manager",
- status);
- return status;
- }
-
- status = pjmedia_converter_mgr_create(pjsua_var.pool, NULL);
- if (status != PJ_SUCCESS) {
- pjsua_perror(THIS_FILE, "Error creating PJMEDIA converter manager",
- status);
- return status;
- }
-
- status = pjmedia_vid_codec_mgr_create(pjsua_var.pool, NULL);
- if (status != PJ_SUCCESS) {
- pjsua_perror(THIS_FILE, "Error creating PJMEDIA video codec manager",
- status);
- return status;
- }
-
- status = pjmedia_vid_dev_subsys_init(&pjsua_var.cp.factory);
- if (status != PJ_SUCCESS) {
- pjsua_perror(THIS_FILE, "Error creating PJMEDIA video subsystem",
- status);
- return status;
- }
-#endif
-
-#if PJMEDIA_HAS_VIDEO && PJMEDIA_HAS_FFMPEG_CODEC
- /* Init ffmpeg video codecs */
- status = pjmedia_codec_ffmpeg_init(NULL, &pjsua_var.cp.factory);
- if (status != PJ_SUCCESS) {
- pjsua_perror(THIS_FILE, "Error initializing ffmpeg library",
- status);
- return status;
- }
-#endif
/* Save additional conference bridge parameters for future
* reference.
@@ -336,7 +297,6 @@
opt |= PJMEDIA_CONF_USE_LINEAR;
}
-
/* Init conference bridge. */
status = pjmedia_conf_create(pjsua_var.pool,
pjsua_var.media_cfg.max_media_ports,
@@ -374,6 +334,13 @@
}
#endif
+ /* Video */
+#if PJMEDIA_HAS_VIDEO
+ status = pjsua_vid_subsys_init();
+ if (status != PJ_SUCCESS)
+ return status;
+#endif
+
return PJ_SUCCESS;
}
@@ -472,6 +439,13 @@
pj_timer_entry_init(&pjsua_var.snd_idle_timer, PJ_FALSE, NULL,
&close_snd_timer_cb);
+ /* Video */
+#if PJMEDIA_HAS_VIDEO
+ status = pjsua_vid_subsys_start();
+ if (status != PJ_SUCCESS)
+ return status;
+#endif
+
/* Perform NAT detection */
status = pjsua_detect_nat_type();
if (status != PJ_SUCCESS) {
@@ -538,16 +512,9 @@
/* Destroy media endpoint. */
if (pjsua_var.med_endpt) {
- /* Videodev */
# if PJMEDIA_HAS_VIDEO
- pjmedia_vid_dev_subsys_shutdown();
+ pjsua_vid_subsys_destroy();
# endif
-
- /* ffmpeg */
-# if PJMEDIA_HAS_VIDEO && PJMEDIA_HAS_FFMPEG_CODEC
- pjmedia_codec_ffmpeg_deinit();
-# endif
-
/* Shutdown all codecs: */
# if PJMEDIA_HAS_SPEEX_CODEC
pjmedia_codec_speex_deinit();
@@ -3937,220 +3904,3 @@
}
-#if PJMEDIA_HAS_VIDEO
-
-/*****************************************************************************
- * Video codecs.
- */
-
-/*
- * Enum all supported video codecs in the system.
- */
-PJ_DEF(pj_status_t) pjsua_vid_enum_codecs( pjsua_codec_info id[],
- unsigned *p_count )
-{
- pjmedia_vid_codec_info info[32];
- unsigned i, j, count, prio[32];
- pj_status_t status;
-
- count = PJ_ARRAY_SIZE(info);
- status = pjmedia_vid_codec_mgr_enum_codecs(NULL, &count, info, prio);
- if (status != PJ_SUCCESS) {
- *p_count = 0;
- return status;
- }
-
- for (i=0, j=0; i<count && j<*p_count; ++i) {
- if (info[i].has_rtp_pack) {
- pj_bzero(&id[j], sizeof(pjsua_codec_info));
-
- pjmedia_vid_codec_info_to_id(&info[i], id[j].buf_, sizeof(id[j].buf_));
- id[j].codec_id = pj_str(id[j].buf_);
- id[j].priority = (pj_uint8_t) prio[i];
-
- if (id[j].codec_id.slen < sizeof(id[j].buf_)) {
- id[j].desc.ptr = id[j].codec_id.ptr + id[j].codec_id.slen + 1;
- pj_strncpy(&id[j].desc, &info[i].encoding_desc,
- sizeof(id[j].buf_) - id[j].codec_id.slen - 1);
- }
-
- ++j;
- }
- }
-
- *p_count = j;
-
- return PJ_SUCCESS;
-}
-
-
-/*
- * Change video codec priority.
- */
-PJ_DEF(pj_status_t) pjsua_vid_codec_set_priority( const pj_str_t *codec_id,
- pj_uint8_t priority )
-{
- const pj_str_t all = { NULL, 0 };
-
- if (codec_id->slen==1 && *codec_id->ptr=='*')
- codec_id = &all;
-
- return pjmedia_vid_codec_mgr_set_codec_priority(NULL, codec_id,
- priority);
-}
-
-
-/*
- * Get video codec parameters.
- */
-PJ_DEF(pj_status_t) pjsua_vid_codec_get_param(
- const pj_str_t *codec_id,
- pjmedia_vid_codec_param *param)
-{
- const pj_str_t all = { NULL, 0 };
- const pjmedia_vid_codec_info *info;
- unsigned count = 1;
- pj_status_t status;
-
- if (codec_id->slen==1 && *codec_id->ptr=='*')
- codec_id = &all;
-
- status = pjmedia_vid_codec_mgr_find_codecs_by_id(NULL, codec_id,
- &count, &info, NULL);
- if (status != PJ_SUCCESS)
- return status;
-
- if (count != 1)
- return (count > 1? PJ_ETOOMANY : PJ_ENOTFOUND);
-
- status = pjmedia_vid_codec_mgr_get_default_param(NULL, info, param);
- return status;
-}
-
-
-/*
- * Set video codec parameters.
- */
-PJ_DEF(pj_status_t) pjsua_vid_codec_set_param(
- const pj_str_t *codec_id,
- const pjmedia_vid_codec_param *param)
-{
- const pjmedia_vid_codec_info *info[2];
- unsigned count = 2;
- pj_status_t status;
-
- status = pjmedia_vid_codec_mgr_find_codecs_by_id(NULL, codec_id,
- &count, info, NULL);
- if (status != PJ_SUCCESS)
- return status;
-
- /* Codec ID should be specific */
- if (count > 1) {
- pj_assert(!"Codec ID is not specific");
- return PJ_ETOOMANY;
- }
-
- status = pjmedia_vid_codec_mgr_set_default_param(NULL, pjsua_var.pool,
- info[0], param);
- return status;
-}
-
-
-/*****************************************************************************
- * Video devices.
- */
-
-/*
- * Enum all video devices installed in the system.
- */
-PJ_DEF(pj_status_t) pjsua_vid_enum_devs(pjmedia_vid_dev_info info[],
- unsigned *count)
-{
- unsigned i, dev_count;
-
- dev_count = pjmedia_vid_dev_count();
-
- if (dev_count > *count) dev_count = *count;
-
- for (i=0; i<dev_count; ++i) {
- pj_status_t status;
-
- status = pjmedia_vid_dev_get_info(i, &info[i]);
- if (status != PJ_SUCCESS)
- return status;
- }
-
- *count = dev_count;
-
- return PJ_SUCCESS;
-}
-
-
-/*
- * Get currently active video devices.
- */
-PJ_DEF(pj_status_t) pjsua_vid_get_dev(int *capture_dev, int *render_dev)
-{
- if (capture_dev)
- *capture_dev = pjsua_var.vcap_dev;
- if (render_dev)
- *render_dev = pjsua_var.vrdr_dev;
-
- return PJ_SUCCESS;
-}
-
-
-/*
- * Select video device for the next video sessions.
- */
-PJ_DEF(pj_status_t) pjsua_vid_set_dev(int capture_dev, int render_dev)
-{
- pjmedia_vid_dev_info info;
- pj_status_t status;
-
- if (capture_dev < 0)
- capture_dev = PJMEDIA_VID_DEFAULT_CAPTURE_DEV;
- if (render_dev < 0)
- render_dev = PJMEDIA_VID_DEFAULT_RENDER_DEV;
-
- status = pjmedia_vid_dev_get_info(capture_dev, &info);
- if (status != PJ_SUCCESS)
- return status;
-
- status = pjmedia_vid_dev_get_info(render_dev, &info);
- if (status != PJ_SUCCESS)
- return status;
-
- pjsua_var.vcap_dev = capture_dev;
- pjsua_var.vrdr_dev = render_dev;
-
- return PJ_SUCCESS;
-}
-
-
-/*
- * Configure video device setting to the video device being used.
- */
-PJ_DEF(pj_status_t) pjsua_vid_set_setting(pjmedia_vid_dev_cap cap,
- const void *pval,
- pj_bool_t keep)
-{
- PJ_UNUSED_ARG(cap);
- PJ_UNUSED_ARG(pval);
- PJ_UNUSED_ARG(keep);
- return PJ_ENOTSUP;
-}
-
-
-/*
- * Retrieve a video device setting.
- */
-PJ_DEF(pj_status_t) pjsua_vid_get_setting(pjmedia_vid_dev_cap cap,
- void *pval)
-{
- PJ_UNUSED_ARG(cap);
- PJ_UNUSED_ARG(pval);
- return PJ_ENOTSUP;
-}
-
-#endif /* PJMEDIA_HAS_VIDEO */
diff --git a/pjsip/src/pjsua-lib/pjsua_vid.c b/pjsip/src/pjsua-lib/pjsua_vid.c
new file mode 100644
index 0000000..aa96d8d
--- /dev/null
+++ b/pjsip/src/pjsua-lib/pjsua_vid.c
@@ -0,0 +1,551 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2011-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 <pjsua-lib/pjsua.h>
+#include <pjsua-lib/pjsua_internal.h>
+
+#define THIS_FILE "pjsua_vid.c"
+
+#if PJSUA_HAS_VIDEO
+
+/*****************************************************************************
+ * pjsua video subsystem.
+ */
+pj_status_t pjsua_vid_subsys_init(void)
+{
+ unsigned i;
+ pj_status_t status;
+
+ status = pjmedia_video_format_mgr_create(pjsua_var.pool, 64, 0, NULL);
+ if (status != PJ_SUCCESS) {
+ PJ_PERROR(1,(THIS_FILE, status,
+ "Error creating PJMEDIA video format manager"));
+ return status;
+ }
+
+ status = pjmedia_converter_mgr_create(pjsua_var.pool, NULL);
+ if (status != PJ_SUCCESS) {
+ PJ_PERROR(1,(THIS_FILE, status,
+ "Error creating PJMEDIA converter manager"));
+ return status;
+ }
+
+ status = pjmedia_vid_codec_mgr_create(pjsua_var.pool, NULL);
+ if (status != PJ_SUCCESS) {
+ PJ_PERROR(1,(THIS_FILE, status,
+ "Error creating PJMEDIA video codec manager"));
+ return status;
+ }
+
+ status = pjmedia_vid_dev_subsys_init(&pjsua_var.cp.factory);
+ if (status != PJ_SUCCESS) {
+ PJ_PERROR(1,(THIS_FILE, status,
+ "Error creating PJMEDIA video subsystem"));
+ return status;
+ }
+
+#if PJMEDIA_HAS_VIDEO && PJMEDIA_HAS_FFMPEG_CODEC
+ status = pjmedia_codec_ffmpeg_init(NULL, &pjsua_var.cp.factory);
+ if (status != PJ_SUCCESS) {
+ PJ_PERROR(1,(THIS_FILE, status,
+ "Error initializing ffmpeg library"));
+ return status;
+ }
+#endif
+
+ for (i=0; i<PJSUA_MAX_VID_WINS; ++i) {
+ if (pjsua_var.win[i].pool == NULL) {
+ pjsua_var.win[i].pool = pjsua_pool_create("win%p", 512, 512);
+ if (pjsua_var.win[i].pool == NULL)
+ return PJ_ENOMEM;
+ }
+ }
+
+ return PJ_SUCCESS;
+}
+
+pj_status_t pjsua_vid_subsys_start(void)
+{
+ return PJ_SUCCESS;
+}
+
+pj_status_t pjsua_vid_subsys_destroy(void)
+{
+ unsigned i;
+
+ for (i=0; i<PJSUA_MAX_VID_WINS; ++i) {
+ if (pjsua_var.win[i].pool) {
+ pj_pool_release(pjsua_var.win[i].pool);
+ pjsua_var.win[i].pool = NULL;
+ }
+ }
+
+ pjmedia_vid_dev_subsys_shutdown();
+
+#if PJMEDIA_HAS_FFMPEG_CODEC
+ pjmedia_codec_ffmpeg_deinit();
+#endif
+
+ return PJ_SUCCESS;
+}
+
+
+/*****************************************************************************
+ * Devices.
+ */
+
+/*
+ * Get the number of video devices installed in the system.
+ */
+PJ_DEF(unsigned) pjsua_vid_dev_count(void)
+{
+ return pjmedia_vid_dev_count();
+}
+
+/*
+ * Retrieve the video device info for the specified device index.
+ */
+PJ_DEF(pj_status_t) pjsua_vid_dev_get_info(pjmedia_vid_dev_index id,
+ pjmedia_vid_dev_info *vdi)
+{
+ return pjmedia_vid_dev_get_info(id, vdi);
+}
+
+/*
+ * Enum all video devices installed in the system.
+ */
+PJ_DEF(pj_status_t) pjsua_vid_enum_devs(pjmedia_vid_dev_info info[],
+ unsigned *count)
+{
+ unsigned i, dev_count;
+
+ dev_count = pjmedia_vid_dev_count();
+
+ if (dev_count > *count) dev_count = *count;
+
+ for (i=0; i<dev_count; ++i) {
+ pj_status_t status;
+
+ status = pjmedia_vid_dev_get_info(i, &info[i]);
+ if (status != PJ_SUCCESS)
+ return status;
+ }
+
+ *count = dev_count;
+
+ return PJ_SUCCESS;
+}
+
+
+/*****************************************************************************
+ * Codecs.
+ */
+
+/*
+ * Enum all supported video codecs in the system.
+ */
+PJ_DEF(pj_status_t) pjsua_vid_enum_codecs( pjsua_codec_info id[],
+ unsigned *p_count )
+{
+ pjmedia_vid_codec_info info[32];
+ unsigned i, j, count, prio[32];
+ pj_status_t status;
+
+ count = PJ_ARRAY_SIZE(info);
+ status = pjmedia_vid_codec_mgr_enum_codecs(NULL, &count, info, prio);
+ if (status != PJ_SUCCESS) {
+ *p_count = 0;
+ return status;
+ }
+
+ for (i=0, j=0; i<count && j<*p_count; ++i) {
+ if (info[i].has_rtp_pack) {
+ pj_bzero(&id[j], sizeof(pjsua_codec_info));
+
+ pjmedia_vid_codec_info_to_id(&info[i], id[j].buf_, sizeof(id[j].buf_));
+ id[j].codec_id = pj_str(id[j].buf_);
+ id[j].priority = (pj_uint8_t) prio[i];
+
+ if (id[j].codec_id.slen < sizeof(id[j].buf_)) {
+ id[j].desc.ptr = id[j].codec_id.ptr + id[j].codec_id.slen + 1;
+ pj_strncpy(&id[j].desc, &info[i].encoding_desc,
+ sizeof(id[j].buf_) - id[j].codec_id.slen - 1);
+ }
+
+ ++j;
+ }
+ }
+
+ *p_count = j;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Change video codec priority.
+ */
+PJ_DEF(pj_status_t) pjsua_vid_codec_set_priority( const pj_str_t *codec_id,
+ pj_uint8_t priority )
+{
+ const pj_str_t all = { NULL, 0 };
+
+ if (codec_id->slen==1 && *codec_id->ptr=='*')
+ codec_id = &all;
+
+ return pjmedia_vid_codec_mgr_set_codec_priority(NULL, codec_id,
+ priority);
+}
+
+
+/*
+ * Get video codec parameters.
+ */
+PJ_DEF(pj_status_t) pjsua_vid_codec_get_param(
+ const pj_str_t *codec_id,
+ pjmedia_vid_codec_param *param)
+{
+ const pj_str_t all = { NULL, 0 };
+ const pjmedia_vid_codec_info *info;
+ unsigned count = 1;
+ pj_status_t status;
+
+ if (codec_id->slen==1 && *codec_id->ptr=='*')
+ codec_id = &all;
+
+ status = pjmedia_vid_codec_mgr_find_codecs_by_id(NULL, codec_id,
+ &count, &info, NULL);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ if (count != 1)
+ return (count > 1? PJ_ETOOMANY : PJ_ENOTFOUND);
+
+ status = pjmedia_vid_codec_mgr_get_default_param(NULL, info, param);
+ return status;
+}
+
+
+/*
+ * Set video codec parameters.
+ */
+PJ_DEF(pj_status_t) pjsua_vid_codec_set_param(
+ const pj_str_t *codec_id,
+ const pjmedia_vid_codec_param *param)
+{
+ const pjmedia_vid_codec_info *info[2];
+ unsigned count = 2;
+ pj_status_t status;
+
+ status = pjmedia_vid_codec_mgr_find_codecs_by_id(NULL, codec_id,
+ &count, info, NULL);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Codec ID should be specific */
+ if (count > 1) {
+ pj_assert(!"Codec ID is not specific");
+ return PJ_ETOOMANY;
+ }
+
+ status = pjmedia_vid_codec_mgr_set_default_param(NULL, pjsua_var.pool,
+ info[0], param);
+ return status;
+}
+
+
+/*****************************************************************************
+ * Preview
+ */
+
+/*
+ * Get the preview window handle associated with the capture device, if any.
+ */
+PJ_DEF(pjsua_vid_win_id) pjsua_vid_preview_get_win(pjmedia_vid_dev_index id)
+{
+ pjsua_vid_win_id wid = PJSUA_INVALID_ID;
+ unsigned i;
+
+ PJSUA_LOCK();
+ for (i=0; i<PJSUA_MAX_VID_WINS; ++i) {
+ pjsua_vid_win *w = &pjsua_var.win[i];
+ if (w->type == PJSUA_WND_TYPE_PREVIEW && w->preview_cap_id == id) {
+ wid = i;
+ break;
+ }
+ }
+ PJSUA_UNLOCK();
+
+ return wid;
+}
+
+static pjsua_vid_win_id alloc_vid_win(pjsua_vid_win_type type)
+{
+ pjsua_vid_win_id wid = PJSUA_INVALID_ID;
+ unsigned i;
+
+ for (i=0; i<PJSUA_MAX_VID_WINS; ++i) {
+ pjsua_vid_win *w = &pjsua_var.win[i];
+ if (w->type == PJSUA_WND_TYPE_NONE) {
+ wid = i;
+ w->type = type;
+ break;
+ }
+ }
+
+ return wid;
+}
+
+static void free_vid_win(pjsua_vid_win_id wid)
+{
+ pjsua_vid_win *w = &pjsua_var.win[wid];
+ if (w->vp_cap) {
+ pjmedia_vid_port_destroy(w->vp_cap);
+ }
+ if (w->vp_rend) {
+ pjmedia_vid_port_destroy(w->vp_rend);
+ }
+ pjsua_vid_win_reset(wid);
+}
+
+/*
+ * Start video preview window for the specified capture device.
+ */
+PJ_DEF(pj_status_t) pjsua_vid_preview_start(pjmedia_vid_dev_index id,
+ pjsua_vid_preview_param *prm)
+{
+ pjsua_vid_win_id wid = PJSUA_INVALID_ID;
+ pjsua_vid_win *w = NULL;
+ pjmedia_vid_port_param cap_param, rend_param;
+ pjmedia_port *rend_port;
+ const pjmedia_video_format_detail *vfd;
+ pj_status_t status;
+
+ PJSUA_LOCK();
+
+ wid = pjsua_vid_preview_get_win(id);
+ if (wid != PJSUA_INVALID_ID) {
+ /* Preview already started for this device */
+ PJSUA_UNLOCK();
+ return PJ_SUCCESS;
+ }
+
+ wid = alloc_vid_win(PJSUA_WND_TYPE_PREVIEW);
+ if (wid != PJSUA_INVALID_ID) {
+ pjsua_var.win[wid].preview_cap_id = id;
+ }
+ if (wid == PJSUA_INVALID_ID) {
+ PJSUA_UNLOCK();
+ return PJ_ETOOMANY;
+ }
+ w = &pjsua_var.win[wid];
+
+ /* Create capture video port */
+ pjmedia_vid_port_param_default(&cap_param);
+ cap_param.active = PJ_TRUE;
+ cap_param.vidparam.dir = PJMEDIA_DIR_CAPTURE;
+ status = pjmedia_vid_dev_default_param(w->pool, id,
+ &cap_param.vidparam);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ status = pjmedia_vid_port_create(w->pool, &cap_param, &w->vp_cap);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ vfd = pjmedia_format_get_video_format_detail(&cap_param.vidparam.fmt,
+ PJ_TRUE);
+ if (vfd == NULL) {
+ status = PJ_ENOTFOUND;
+ goto on_error;
+ }
+
+ /* Create renderer video port */
+ pjmedia_vid_port_param_default(&rend_param);
+ status = pjmedia_vid_dev_default_param(w->pool,
+ PJMEDIA_VID_DEFAULT_RENDER_DEV,
+ &rend_param.vidparam);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ rend_param.active = PJ_FALSE;
+ rend_param.vidparam.dir = PJMEDIA_DIR_RENDER;
+ rend_param.vidparam.fmt = cap_param.vidparam.fmt;
+ rend_param.vidparam.disp_size = vfd->size;
+
+ status = pjmedia_vid_port_create(w->pool, &rend_param, &w->vp_rend);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Connect capture dev to renderer */
+ rend_port = pjmedia_vid_port_get_passive_port(w->vp_rend);
+ status = pjmedia_vid_port_connect(w->vp_cap, rend_port, PJ_FALSE);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Start devices */
+ status = pjmedia_vid_port_start(w->vp_rend);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ status = pjmedia_vid_port_start(w->vp_cap);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Done */
+ PJSUA_UNLOCK();
+ return PJ_SUCCESS;
+
+on_error:
+ if (wid != PJSUA_INVALID_ID) {
+ free_vid_win(wid);
+ }
+
+ PJSUA_UNLOCK();
+ return status;
+}
+
+/*
+ * Stop video preview.
+ */
+PJ_DEF(pj_status_t) pjsua_vid_preview_stop(pjmedia_vid_dev_index id)
+{
+ pjsua_vid_win_id wid = PJSUA_INVALID_ID;
+ pjsua_vid_win *w;
+ pj_status_t status;
+
+ PJSUA_LOCK();
+ wid = pjsua_vid_preview_get_win(id);
+ if (wid == PJSUA_INVALID_ID) {
+ PJSUA_UNLOCK();
+ return PJ_ENOTFOUND;
+ }
+
+ w = &pjsua_var.win[wid];
+
+ status = pjmedia_vid_port_stop(w->vp_cap);
+ status = pjmedia_vid_port_stop(w->vp_rend);
+
+ free_vid_win(wid);
+
+ PJSUA_UNLOCK();
+
+ return PJ_SUCCESS;
+}
+
+
+/*****************************************************************************
+ * Window
+ */
+
+/*
+ * Get window info.
+ */
+PJ_DEF(pj_status_t) pjsua_vid_win_get_info( pjsua_vid_win_id wid,
+ pjsua_vid_win_info *wi)
+{
+ pjsua_vid_win *w;
+
+ PJ_ASSERT_RETURN(wid >= 0 && wid < PJSUA_MAX_VID_WINS && wi, PJ_EINVAL);
+
+ PJSUA_LOCK();
+ w = &pjsua_var.win[wid];
+ if (w->vp_rend == NULL) {
+ PJSUA_UNLOCK();
+ return PJ_EINVAL;
+ }
+
+ PJ_TODO(vid_implement_pjsua_vid_win_get_info);
+ PJSUA_UNLOCK();
+
+ return PJ_ENOTSUP;
+}
+
+/*
+ * Show or hide window.
+ */
+PJ_DEF(pj_status_t) pjsua_vid_win_set_show( pjsua_vid_win_id wid,
+ pj_bool_t show)
+{
+ pjsua_vid_win *w;
+
+ PJ_ASSERT_RETURN(wid >= 0 && wid < PJSUA_MAX_VID_WINS, PJ_EINVAL);
+
+ PJSUA_LOCK();
+ w = &pjsua_var.win[wid];
+ if (w->vp_rend == NULL) {
+ PJSUA_UNLOCK();
+ return PJ_EINVAL;
+ }
+
+ PJ_TODO(vid_implement_pjsua_vid_win_set_show);
+ PJSUA_UNLOCK();
+
+ return PJ_ENOTSUP;
+}
+
+/*
+ * Set video window position.
+ */
+PJ_DEF(pj_status_t) pjsua_vid_win_set_pos( pjsua_vid_win_id wid,
+ const pjmedia_coord *pos)
+{
+ pjsua_vid_win *w;
+
+ PJ_ASSERT_RETURN(wid >= 0 && wid < PJSUA_MAX_VID_WINS && pos, PJ_EINVAL);
+
+ PJSUA_LOCK();
+ w = &pjsua_var.win[wid];
+ if (w->vp_rend == NULL) {
+ PJSUA_UNLOCK();
+ return PJ_EINVAL;
+ }
+
+ PJ_TODO(vid_implement_pjsua_vid_win_set_pos);
+ PJSUA_UNLOCK();
+
+ return PJ_ENOTSUP;
+}
+
+/*
+ * Resize window.
+ */
+PJ_DEF(pj_status_t) pjsua_vid_win_set_size( pjsua_vid_win_id wid,
+ const pjmedia_rect_size *size)
+{
+ pjsua_vid_win *w;
+
+ PJ_ASSERT_RETURN(wid >= 0 && wid < PJSUA_MAX_VID_WINS && size, PJ_EINVAL);
+
+ PJSUA_LOCK();
+ w = &pjsua_var.win[wid];
+ if (w->vp_rend == NULL) {
+ PJSUA_UNLOCK();
+ return PJ_EINVAL;
+ }
+
+ PJ_TODO(vid_implement_pjsua_vid_win_set_size);
+ PJSUA_UNLOCK();
+
+ return PJ_ENOTSUP;
+}
+
+
+
+#endif /* PJSUA_HAS_VIDEO */
+