diff options
Diffstat (limited to 'pjsip/src/pjsua-lib/pjsua_vid.c')
-rw-r--r-- | pjsip/src/pjsua-lib/pjsua_vid.c | 1648 |
1 files changed, 1648 insertions, 0 deletions
diff --git a/pjsip/src/pjsua-lib/pjsua_vid.c b/pjsip/src/pjsua-lib/pjsua_vid.c new file mode 100644 index 00000000..afb564e5 --- /dev/null +++ b/pjsip/src/pjsua-lib/pjsua_vid.c @@ -0,0 +1,1648 @@ +/* $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 + +static void free_vid_win(pjsua_vid_win_id wid); + +/***************************************************************************** + * 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) { + free_vid_win(i); + 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(); + + /* Get real capture ID, if set to PJMEDIA_VID_DEFAULT_CAPTURE_DEV */ + if (id == PJMEDIA_VID_DEFAULT_CAPTURE_DEV) { + pjmedia_vid_dev_info info; + pjmedia_vid_dev_get_info(id, &info); + id = info.id; + } + + 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; +} + + +/* Allocate and initialize pjsua video window: + * - If the type is preview, video capture, tee, and render + * will be instantiated. + * - If the type is stream, only renderer will be created. + */ +static pj_status_t create_vid_win(pjsua_vid_win_type type, + const pjmedia_format *fmt, + pjmedia_vid_dev_index rend_id, + pjmedia_vid_dev_index cap_id, + pj_bool_t show, + pjsua_vid_win_id *id) +{ + pjsua_vid_win_id wid = PJSUA_INVALID_ID; + pjsua_vid_win *w = NULL; + pjmedia_vid_port_param vp_param; + pjmedia_format fmt_; + pj_status_t status; + unsigned i; + + /* If type is preview, check if it exists already */ + if (type == PJSUA_WND_TYPE_PREVIEW) { + wid = pjsua_vid_preview_get_win(cap_id); + if (wid != PJSUA_INVALID_ID) { + /* Yes, it exists */ + + /* Show window if requested */ + if (show) { + pjmedia_vid_dev_stream *rdr; + pj_bool_t hide = PJ_FALSE; + + rdr = pjmedia_vid_port_get_stream(pjsua_var.win[wid].vp_rend); + pj_assert(rdr); + status = pjmedia_vid_dev_stream_set_cap( + rdr, + PJMEDIA_VID_DEV_CAP_OUTPUT_HIDE, + &hide); + } + + /* Done */ + *id = wid; + return PJ_SUCCESS; + } + } + + /* Allocate window */ + for (i=0; i<PJSUA_MAX_VID_WINS; ++i) { + w = &pjsua_var.win[i]; + if (w->type == PJSUA_WND_TYPE_NONE) { + wid = i; + w->type = type; + break; + } + } + if (i == PJSUA_MAX_VID_WINS) + return PJ_ETOOMANY; + + /* Initialize window */ + pjmedia_vid_port_param_default(&vp_param); + + if (w->type == PJSUA_WND_TYPE_PREVIEW) { + status = pjmedia_vid_dev_default_param(w->pool, cap_id, + &vp_param.vidparam); + if (status != PJ_SUCCESS) + goto on_error; + + /* Normalize capture ID, in case it was set to + * PJMEDIA_VID_DEFAULT_CAPTURE_DEV + */ + cap_id = vp_param.vidparam.cap_id; + + /* Assign preview capture device ID */ + w->preview_cap_id = cap_id; + + /* Create capture video port */ + vp_param.active = PJ_TRUE; + vp_param.vidparam.dir = PJMEDIA_DIR_CAPTURE; + if (fmt) + vp_param.vidparam.fmt = *fmt; + + status = pjmedia_vid_port_create(w->pool, &vp_param, &w->vp_cap); + if (status != PJ_SUCCESS) + goto on_error; + + /* Update format info */ + fmt_ = vp_param.vidparam.fmt; + fmt = &fmt_; + + /* Create video tee */ + status = pjmedia_vid_tee_create(w->pool, fmt, 2, &w->tee); + if (status != PJ_SUCCESS) + goto on_error; + } + + /* Create renderer video port */ + status = pjmedia_vid_dev_default_param(w->pool, rend_id, + &vp_param.vidparam); + if (status != PJ_SUCCESS) + goto on_error; + + vp_param.active = (w->type == PJSUA_WND_TYPE_STREAM); + vp_param.vidparam.dir = PJMEDIA_DIR_RENDER; + vp_param.vidparam.fmt = *fmt; + vp_param.vidparam.disp_size = fmt->det.vid.size; + vp_param.vidparam.flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_HIDE; + vp_param.vidparam.window_hide = !show; + + status = pjmedia_vid_port_create(w->pool, &vp_param, &w->vp_rend); + if (status != PJ_SUCCESS) + goto on_error; + + /* For preview window, connect capturer & renderer (via tee) */ + if (w->type == PJSUA_WND_TYPE_PREVIEW) { + pjmedia_port *rend_port; + + status = pjmedia_vid_port_connect(w->vp_cap, w->tee, PJ_FALSE); + if (status != PJ_SUCCESS) + goto on_error; + + rend_port = pjmedia_vid_port_get_passive_port(w->vp_rend); + status = pjmedia_vid_tee_add_dst_port2(w->tee, 0, rend_port); + if (status != PJ_SUCCESS) + goto on_error; + } + + /* Done */ + *id = wid; + + return PJ_SUCCESS; + +on_error: + free_vid_win(wid); + return status; +} + + +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_stop(w->vp_cap); + pjmedia_vid_port_disconnect(w->vp_cap); + pjmedia_vid_port_destroy(w->vp_cap); + } + if (w->vp_rend) { + pjmedia_vid_port_stop(w->vp_rend); + pjmedia_vid_port_destroy(w->vp_rend); + } + if (w->tee) { + pjmedia_port_destroy(w->tee); + } + pjsua_vid_win_reset(wid); +} + + +static void inc_vid_win(pjsua_vid_win_id wid) +{ + pjsua_vid_win *w; + + pj_assert(wid >= 0 && wid < PJSUA_MAX_VID_WINS); + + w = &pjsua_var.win[wid]; + pj_assert(w->type != PJSUA_WND_TYPE_NONE); + ++w->ref_cnt; +} + +static void dec_vid_win(pjsua_vid_win_id wid) +{ + pjsua_vid_win *w; + + pj_assert(wid >= 0 && wid < PJSUA_MAX_VID_WINS); + + w = &pjsua_var.win[wid]; + pj_assert(w->type != PJSUA_WND_TYPE_NONE); + if (--w->ref_cnt == 0) + free_vid_win(wid); +} + + +/* Internal function: update video channel after SDP negotiation */ +pj_status_t video_channel_update(pjsua_call_media *call_med, + pj_pool_t *tmp_pool, + const pjmedia_sdp_session *local_sdp, + const pjmedia_sdp_session *remote_sdp) +{ + pjsua_call *call = call_med->call; + pjsua_acc *acc = &pjsua_var.acc[call->acc_id]; + pjmedia_vid_stream_info the_si, *si = &the_si; + pjmedia_port *media_port; + unsigned strm_idx = call_med->idx; + pj_status_t status; + + status = pjmedia_vid_stream_info_from_sdp(si, tmp_pool, pjsua_var.med_endpt, + local_sdp, remote_sdp, strm_idx); + if (status != PJ_SUCCESS) + return status; + + /* Check if no media is active */ + if (si->dir == PJMEDIA_DIR_NONE) { + /* Call media state */ + call_med->state = PJSUA_CALL_MEDIA_NONE; + + /* Call media direction */ + call_med->dir = PJMEDIA_DIR_NONE; + + } else { + pjmedia_transport_info tp_info; + + /* Start/restart media transport */ + status = pjmedia_transport_media_start(call_med->tp, + tmp_pool, local_sdp, + remote_sdp, strm_idx); + if (status != PJ_SUCCESS) + return status; + + call_med->tp_st = PJSUA_MED_TP_RUNNING; + + /* Get remote SRTP usage policy */ + pjmedia_transport_info_init(&tp_info); + pjmedia_transport_get_info(call_med->tp, &tp_info); + if (tp_info.specific_info_cnt > 0) { + unsigned i; + for (i = 0; i < tp_info.specific_info_cnt; ++i) { + if (tp_info.spc_info[i].type == PJMEDIA_TRANSPORT_TYPE_SRTP) + { + pjmedia_srtp_info *srtp_info = + (pjmedia_srtp_info*) tp_info.spc_info[i].buffer; + + call_med->rem_srtp_use = srtp_info->peer_use; + break; + } + } + } + + /* Optionally, application may modify other stream settings here + * (such as jitter buffer parameters, codec ptime, etc.) + */ + si->jb_init = pjsua_var.media_cfg.jb_init; + si->jb_min_pre = pjsua_var.media_cfg.jb_min_pre; + si->jb_max_pre = pjsua_var.media_cfg.jb_max_pre; + si->jb_max = pjsua_var.media_cfg.jb_max; + + /* Set SSRC */ + si->ssrc = call_med->ssrc; + + /* Set RTP timestamp & sequence, normally these value are intialized + * automatically when stream session created, but for some cases (e.g: + * call reinvite, call update) timestamp and sequence need to be kept + * contigue. + */ + si->rtp_ts = call_med->rtp_tx_ts; + si->rtp_seq = call_med->rtp_tx_seq; + si->rtp_seq_ts_set = call_med->rtp_tx_seq_ts_set; + +#if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA!=0 + /* Enable/disable stream keep-alive and NAT hole punch. */ + si->use_ka = pjsua_var.acc[call->acc_id].cfg.use_stream_ka; +#endif + + /* Try to get shared format ID between the capture device and + * the encoder to avoid format conversion in the capture device. + */ + if (si->dir & PJMEDIA_DIR_ENCODING) { + pjmedia_vid_dev_info dev_info; + pjmedia_vid_codec_info *codec_info = &si->codec_info; + unsigned i, j; + + status = pjmedia_vid_dev_get_info(call_med->strm.v.cap_dev, + &dev_info); + if (status != PJ_SUCCESS) + return status; + + /* Find matched format ID */ + for (i = 0; i < codec_info->dec_fmt_id_cnt; ++i) { + for (j = 0; j < dev_info.fmt_cnt; ++j) { + if (codec_info->dec_fmt_id[i] == + (pjmedia_format_id)dev_info.fmt[j].id) + { + /* Apply the matched format ID to the codec */ + si->codec_param->dec_fmt.id = + codec_info->dec_fmt_id[i]; + + /* Force outer loop to break */ + i = codec_info->dec_fmt_id_cnt; + break; + } + } + } + } + + /* Create session based on session info. */ + status = pjmedia_vid_stream_create(pjsua_var.med_endpt, NULL, si, + call_med->tp, NULL, + &call_med->strm.v.stream); + if (status != PJ_SUCCESS) + return status; + + /* Start stream */ + status = pjmedia_vid_stream_start(call_med->strm.v.stream); + if (status != PJ_SUCCESS) + return status; + + /* Setup decoding direction */ + if (si->dir & PJMEDIA_DIR_DECODING) + { + pjsua_vid_win_id wid; + pjsua_vid_win *w; + + status = pjmedia_vid_stream_get_port(call_med->strm.v.stream, + PJMEDIA_DIR_DECODING, + &media_port); + if (status != PJ_SUCCESS) + return status; + + /* Create stream video window */ + status = create_vid_win(PJSUA_WND_TYPE_STREAM, + &media_port->info.fmt, + call_med->strm.v.rdr_dev, + //acc->cfg.vid_rend_dev, + PJSUA_INVALID_ID, + acc->cfg.vid_in_auto_show, + &wid); + if (status != PJ_SUCCESS) + return status; + + w = &pjsua_var.win[wid]; + + /* Register to video events */ + pjmedia_event_subscribe( + pjmedia_vid_port_get_event_publisher(w->vp_rend), + &call_med->esub_rend); + + /* Connect renderer to stream */ + status = pjmedia_vid_port_connect(w->vp_rend, media_port, + PJ_FALSE); + if (status != PJ_SUCCESS) + return status; + + /* Start renderer */ + status = pjmedia_vid_port_start(w->vp_rend); + if (status != PJ_SUCCESS) + return status; + + /* Done */ + inc_vid_win(wid); + call_med->strm.v.rdr_win_id = wid; + } + + /* Setup encoding direction */ + if (si->dir & PJMEDIA_DIR_ENCODING && !call->local_hold) + { + pjsua_vid_win *w; + pjsua_vid_win_id wid; + + status = pjmedia_vid_stream_get_port(call_med->strm.v.stream, + PJMEDIA_DIR_ENCODING, + &media_port); + if (status != PJ_SUCCESS) + return status; + + /* Create preview video window */ + status = create_vid_win(PJSUA_WND_TYPE_PREVIEW, + &media_port->info.fmt, + call_med->strm.v.rdr_dev, + call_med->strm.v.cap_dev, + //acc->cfg.vid_rend_dev, + //acc->cfg.vid_cap_dev, + PJ_FALSE, + &wid); + if (status != PJ_SUCCESS) + return status; + + w = &pjsua_var.win[wid]; + + pjmedia_event_subscribe( + pjmedia_vid_port_get_event_publisher(w->vp_cap), + &call_med->esub_cap); + + /* Connect stream to capturer (via video window tee) */ + status = pjmedia_vid_tee_add_dst_port2(w->tee, 0, media_port); + if (status != PJ_SUCCESS) + return status; + + /* Start renderer */ + status = pjmedia_vid_port_start(w->vp_rend); + if (status != PJ_SUCCESS) + return status; + + /* Start capturer */ + status = pjmedia_vid_port_start(w->vp_cap); + if (status != PJ_SUCCESS) + return status; + + /* Done */ + inc_vid_win(wid); + call_med->strm.v.cap_win_id = wid; + } + + /* Call media direction */ + call_med->dir = si->dir; + + /* Call media state */ + if (call->local_hold) + call_med->state = PJSUA_CALL_MEDIA_LOCAL_HOLD; + else if (call_med->dir == PJMEDIA_DIR_DECODING) + call_med->state = PJSUA_CALL_MEDIA_REMOTE_HOLD; + else + call_med->state = PJSUA_CALL_MEDIA_ACTIVE; + } + + /* Print info. */ + { + char info[80]; + int info_len = 0; + int len; + const char *dir; + + switch (si->dir) { + case PJMEDIA_DIR_NONE: + dir = "inactive"; + break; + case PJMEDIA_DIR_ENCODING: + dir = "sendonly"; + break; + case PJMEDIA_DIR_DECODING: + dir = "recvonly"; + break; + case PJMEDIA_DIR_ENCODING_DECODING: + dir = "sendrecv"; + break; + default: + dir = "unknown"; + break; + } + len = pj_ansi_sprintf( info+info_len, + ", stream #%d: %.*s (%s)", strm_idx, + (int)si->codec_info.encoding_name.slen, + si->codec_info.encoding_name.ptr, + dir); + if (len > 0) + info_len += len; + PJ_LOG(4,(THIS_FILE,"Media updates%s", info)); + } + + if (!acc->cfg.vid_out_auto_transmit && call_med->strm.v.stream) { + status = pjmedia_vid_stream_pause(call_med->strm.v.stream, + PJMEDIA_DIR_ENCODING); + if (status != PJ_SUCCESS) + return status; + } + + return PJ_SUCCESS; +} + + +/* Internal function to stop video stream */ +void stop_video_stream(pjsua_call_media *call_med) +{ + pjmedia_vid_stream *strm = call_med->strm.v.stream; + pjmedia_rtcp_stat stat; + + pj_assert(call_med->type == PJMEDIA_TYPE_VIDEO); + + if (!strm) + return; + + /* Unsubscribe events */ + pjmedia_event_unsubscribe(&call_med->esub_rend); + pjmedia_event_unsubscribe(&call_med->esub_cap); + + if (call_med->dir & PJMEDIA_DIR_ENCODING && + call_med->strm.v.cap_win_id != PJSUA_INVALID_ID) + { + pjmedia_port *media_port; + pjsua_vid_win *w = + &pjsua_var.win[call_med->strm.v.cap_win_id]; + + pjmedia_vid_stream_get_port(call_med->strm.v.stream, + PJMEDIA_DIR_ENCODING, + &media_port); + pj_assert(media_port); + + pjmedia_vid_port_stop(w->vp_cap); + pjmedia_vid_tee_remove_dst_port(w->tee, media_port); + pjmedia_vid_port_start(w->vp_cap); + + dec_vid_win(call_med->strm.v.cap_win_id); + } + + if (call_med->dir & PJMEDIA_DIR_DECODING && + call_med->strm.v.rdr_win_id != PJSUA_INVALID_ID) + { + dec_vid_win(call_med->strm.v.rdr_win_id); + } + + if ((call_med->dir & PJMEDIA_DIR_ENCODING) && + (pjmedia_vid_stream_get_stat(strm, &stat) == PJ_SUCCESS)) + { + /* Save RTP timestamp & sequence, so when media session is + * restarted, those values will be restored as the initial + * RTP timestamp & sequence of the new media session. So in + * the same call session, RTP timestamp and sequence are + * guaranteed to be contigue. + */ + call_med->rtp_tx_seq_ts_set = 1 | (1 << 1); + call_med->rtp_tx_seq = stat.rtp_tx_last_seq; + call_med->rtp_tx_ts = stat.rtp_tx_last_ts; + } + + pjmedia_vid_stream_destroy(strm); + call_med->strm.v.stream = NULL; +} + + +/* + * 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_vid_win *w; + pjmedia_vid_dev_index rend_id; + pj_status_t status; + + PJSUA_LOCK(); + + if (prm) { + rend_id = prm->rend_id; + } else { + rend_id = PJMEDIA_VID_DEFAULT_RENDER_DEV; + } + + status = create_vid_win(PJSUA_WND_TYPE_PREVIEW, NULL, rend_id, id, + PJ_TRUE, &wid); + if (status != PJ_SUCCESS) { + PJSUA_UNLOCK(); + return status; + } + + w = &pjsua_var.win[wid]; + + /* Start capturer */ + status = pjmedia_vid_port_start(w->vp_rend); + if (status != PJ_SUCCESS) { + PJSUA_UNLOCK(); + return status; + } + + /* Start renderer */ + status = pjmedia_vid_port_start(w->vp_cap); + if (status != PJ_SUCCESS) { + PJSUA_UNLOCK(); + return status; + } + + inc_vid_win(wid); + + PJSUA_UNLOCK(); + return PJ_SUCCESS; +} + +/* + * 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_LOCK(); + wid = pjsua_vid_preview_get_win(id); + if (wid == PJSUA_INVALID_ID) { + PJSUA_UNLOCK(); + return PJ_ENOTFOUND; + } + + dec_vid_win(wid); + + PJSUA_UNLOCK(); + + return PJ_SUCCESS; +} + + +/***************************************************************************** + * Window + */ + + +/* + * Enumerates all video windows. + */ +PJ_DEF(pj_status_t) pjsua_vid_enum_wins( pjsua_vid_win_id wids[], + unsigned *count) +{ + unsigned i, cnt; + + cnt = 0; + + for (i=0; i<PJSUA_MAX_VID_WINS && cnt <*count; ++i) { + pjsua_vid_win *w = &pjsua_var.win[i]; + if (w->type != PJSUA_WND_TYPE_NONE) + wids[cnt++] = i; + } + + *count = cnt; + + return PJ_SUCCESS; +} + + +/* + * 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; + pjmedia_vid_dev_stream *s; + pjmedia_vid_dev_param vparam; + pj_status_t status; + + 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; + } + + s = pjmedia_vid_port_get_stream(w->vp_rend); + if (s == NULL) { + PJSUA_UNLOCK(); + return PJ_EINVAL; + } + + status = pjmedia_vid_dev_stream_get_param(s, &vparam); + if (status != PJ_SUCCESS) { + PJSUA_UNLOCK(); + return status; + } + + wi->show = !vparam.window_hide; + wi->pos = vparam.window_pos; + wi->size = vparam.disp_size; + + PJSUA_UNLOCK(); + + return PJ_SUCCESS; +} + +/* + * 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; + pjmedia_vid_dev_stream *s; + pj_bool_t hide; + pj_status_t status; + + 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; + } + + s = pjmedia_vid_port_get_stream(w->vp_rend); + if (s == NULL) { + PJSUA_UNLOCK(); + return PJ_EINVAL; + } + + hide = !show; + status = pjmedia_vid_dev_stream_set_cap(s, + PJMEDIA_VID_DEV_CAP_OUTPUT_HIDE, &hide); + + PJSUA_UNLOCK(); + + return status; +} + +/* + * 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; + pjmedia_vid_dev_stream *s; + pj_status_t status; + + 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; + } + + s = pjmedia_vid_port_get_stream(w->vp_rend); + if (s == NULL) { + PJSUA_UNLOCK(); + return PJ_EINVAL; + } + + status = pjmedia_vid_dev_stream_set_cap(s, + PJMEDIA_VID_DEV_CAP_OUTPUT_POSITION, pos); + + PJSUA_UNLOCK(); + + return status; +} + +/* + * 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; + pjmedia_vid_dev_stream *s; + pj_status_t status; + + 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; + } + + s = pjmedia_vid_port_get_stream(w->vp_rend); + if (s == NULL) { + PJSUA_UNLOCK(); + return PJ_EINVAL; + } + + status = pjmedia_vid_dev_stream_set_cap(s, + PJMEDIA_VID_DEV_CAP_OUTPUT_RESIZE, size); + + PJSUA_UNLOCK(); + + return status; +} + + +static void call_get_vid_strm_info(pjsua_call *call, + int *first_active, + int *first_inactive, + unsigned *active_cnt, + unsigned *cnt) +{ + unsigned i, var_cnt = 0; + + if (first_active && ++var_cnt) + *first_active = -1; + if (first_inactive && ++var_cnt) + *first_inactive = -1; + if (active_cnt && ++var_cnt) + *active_cnt = 0; + if (cnt && ++var_cnt) + *cnt = 0; + + for (i = 0; i < call->med_cnt && var_cnt; ++i) { + if (call->media[i].type == PJMEDIA_TYPE_VIDEO) { + if (call->media[i].dir != PJMEDIA_DIR_NONE) + { + if (first_active && *first_active == -1) { + *first_active = i; + --var_cnt; + } + if (active_cnt) + ++(*active_cnt); + } else if (first_inactive && *first_inactive == -1) { + *first_inactive = i; + --var_cnt; + } + if (cnt) + ++(*cnt); + } + } +} + + +/* Send SDP reoffer. */ +static pj_status_t call_reoffer_sdp(pjsua_call_id call_id, + const pjmedia_sdp_session *sdp) +{ + pjsua_call *call; + pjsip_tx_data *tdata; + pjsip_dialog *dlg; + pj_status_t status; + + status = acquire_call("call_reoffer_sdp()", call_id, &call, &dlg); + if (status != PJ_SUCCESS) + return status; + + if (call->inv->state != PJSIP_INV_STATE_CONFIRMED) { + PJ_LOG(3,(THIS_FILE, "Can not re-INVITE call that is not confirmed")); + pjsip_dlg_dec_lock(dlg); + return PJSIP_ESESSIONSTATE; + } + + /* Create re-INVITE with new offer */ + status = pjsip_inv_reinvite( call->inv, NULL, sdp, &tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create re-INVITE", status); + pjsip_dlg_dec_lock(dlg); + return status; + } + + /* Send the request */ + status = pjsip_inv_send_msg( call->inv, tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to send re-INVITE", status); + pjsip_dlg_dec_lock(dlg); + return status; + } + + pjsip_dlg_dec_lock(dlg); + + return PJ_SUCCESS; +} + +/* Add a new video stream into a call */ +static pj_status_t call_add_video(pjsua_call *call, + pjmedia_vid_dev_index cap_dev, + pjmedia_dir dir) +{ + pj_pool_t *pool = call->inv->pool_prov; + pjsua_acc_config *acc_cfg = &pjsua_var.acc[call->acc_id].cfg; + pjsua_call_media *call_med; + pjmedia_sdp_session *sdp; + pjmedia_sdp_media *sdp_m; + pjmedia_transport_info tpinfo; + unsigned active_cnt; + pj_status_t status; + + /* Verify media slot availability */ + if (call->med_cnt == PJSUA_MAX_CALL_MEDIA) + return PJ_ETOOMANY; + + call_get_vid_strm_info(call, NULL, NULL, &active_cnt, NULL); + if (active_cnt == acc_cfg->max_video_cnt) + return PJ_ETOOMANY; + + /* Get active local SDP */ + status = pjmedia_sdp_neg_get_active_local(call->inv->neg, &sdp); + if (status != PJ_SUCCESS) + return status; + + /* Initialize call media */ + call_med = &call->media[call->med_cnt++]; + + status = pjsua_call_media_init(call_med, PJMEDIA_TYPE_VIDEO, + &acc_cfg->rtp_cfg, call->secure_level, + NULL); + if (status != PJ_SUCCESS) + goto on_error; + + /* Override default capture device setting */ + call_med->strm.v.cap_dev = cap_dev; + + /* Init transport media */ + status = pjmedia_transport_media_create(call_med->tp, pool, 0, + NULL, call_med->idx); + if (status != PJ_SUCCESS) + goto on_error; + + call_med->tp_st = PJSUA_MED_TP_INIT; + + /* Get transport address info */ + pjmedia_transport_info_init(&tpinfo); + pjmedia_transport_get_info(call_med->tp, &tpinfo); + + /* Create SDP media line */ + status = pjmedia_endpt_create_video_sdp(pjsua_var.med_endpt, pool, + &tpinfo.sock_info, 0, &sdp_m); + if (status != PJ_SUCCESS) + goto on_error; + + sdp->media[sdp->media_count++] = sdp_m; + + /* Update media direction, if it is not 'sendrecv' */ + if (dir != PJMEDIA_DIR_ENCODING_DECODING) { + pjmedia_sdp_attr *a; + + /* Remove sendrecv direction attribute, if any */ + pjmedia_sdp_media_remove_all_attr(sdp_m, "sendrecv"); + + if (dir == PJMEDIA_DIR_ENCODING) + a = pjmedia_sdp_attr_create(pool, "sendonly", NULL); + else if (dir == PJMEDIA_DIR_DECODING) + a = pjmedia_sdp_attr_create(pool, "recvonly", NULL); + else + a = pjmedia_sdp_attr_create(pool, "inactive", NULL); + + pjmedia_sdp_media_add_attr(sdp_m, a); + } + + /* Update SDP media line by media transport */ + status = pjmedia_transport_encode_sdp(call_med->tp, pool, + sdp, NULL, call_med->idx); + if (status != PJ_SUCCESS) + goto on_error; + + status = call_reoffer_sdp(call->index, sdp); + if (status != PJ_SUCCESS) + goto on_error; + + return PJ_SUCCESS; + +on_error: + if (call_med->tp) { + pjmedia_transport_close(call_med->tp); + call_med->tp = call_med->tp_orig = NULL; + } + + return status; +} + + +/* Modify a video stream from a call, i.e: update direction, + * remove/disable. + */ +static pj_status_t call_modify_video(pjsua_call *call, + int med_idx, + pjmedia_dir dir, + pj_bool_t remove) +{ + pjsua_call_media *call_med; + pjmedia_sdp_session *sdp; + pj_status_t status; + + /* Verify and normalize media index */ + if (med_idx == -1) { + int first_active; + + call_get_vid_strm_info(call, &first_active, NULL, NULL, NULL); + if (first_active == -1) + return PJ_ENOTFOUND; + + med_idx = first_active; + } + + call_med = &call->media[med_idx]; + + /* Verify if the stream media type is video */ + if (call_med->type != PJMEDIA_TYPE_VIDEO) + return PJ_EINVAL; + + /* Verify if the stream dir is not changed */ + if ((!remove && call_med->dir == dir) || + ( remove && (call_med->tp_st == PJSUA_MED_TP_DISABLED || + call_med->tp == NULL))) + { + return PJ_SUCCESS; + } + + /* Get active local SDP */ + status = pjmedia_sdp_neg_get_active_local(call->inv->neg, &sdp); + if (status != PJ_SUCCESS) + return status; + + pj_assert(med_idx < (int)sdp->media_count); + + if (!remove) { + pjsua_acc_config *acc_cfg = &pjsua_var.acc[call->acc_id].cfg; + pj_pool_t *pool = call->inv->pool_prov; + pjmedia_sdp_media *sdp_m; + + status = pjsua_call_media_init(call_med, PJMEDIA_TYPE_VIDEO, + &acc_cfg->rtp_cfg, call->secure_level, + NULL); + if (status != PJ_SUCCESS) + goto on_error; + + /* Init transport media */ + if (call_med->tp && call_med->tp_st == PJSUA_MED_TP_IDLE) { + status = pjmedia_transport_media_create(call_med->tp, pool, 0, + NULL, call_med->idx); + if (status != PJ_SUCCESS) + goto on_error; + } + + sdp_m = sdp->media[med_idx]; + + /* Create new SDP media line if the stream is disabled */ + if (sdp->media[med_idx]->desc.port == 0) { + pjmedia_transport_info tpinfo; + + /* Get transport address info */ + pjmedia_transport_info_init(&tpinfo); + pjmedia_transport_get_info(call_med->tp, &tpinfo); + + status = pjmedia_endpt_create_video_sdp(pjsua_var.med_endpt, pool, + &tpinfo.sock_info, 0, &sdp_m); + if (status != PJ_SUCCESS) + goto on_error; + } + + { + pjmedia_sdp_attr *a; + + /* Remove any direction attributes */ + pjmedia_sdp_media_remove_all_attr(sdp_m, "sendrecv"); + pjmedia_sdp_media_remove_all_attr(sdp_m, "sendonly"); + pjmedia_sdp_media_remove_all_attr(sdp_m, "recvonly"); + pjmedia_sdp_media_remove_all_attr(sdp_m, "inactive"); + + /* Update media direction */ + if (dir == PJMEDIA_DIR_ENCODING_DECODING) + a = pjmedia_sdp_attr_create(pool, "sendrecv", NULL); + else if (dir == PJMEDIA_DIR_ENCODING) + a = pjmedia_sdp_attr_create(pool, "sendonly", NULL); + else if (dir == PJMEDIA_DIR_DECODING) + a = pjmedia_sdp_attr_create(pool, "recvonly", NULL); + else + a = pjmedia_sdp_attr_create(pool, "inactive", NULL); + + pjmedia_sdp_media_add_attr(sdp_m, a); + } + + sdp->media[med_idx] = sdp_m; + + /* Update SDP media line by media transport */ + status = pjmedia_transport_encode_sdp(call_med->tp, pool, + sdp, NULL, call_med->idx); + if (status != PJ_SUCCESS) + goto on_error; + +on_error: + if (status != PJ_SUCCESS) { + return status; + } + + } else { + + pj_pool_t *pool = call->inv->pool_prov; + + /* Mark media transport to disabled */ + // Don't close this here, as SDP negotiation has not been + // done and stream may be still active. + call_med->tp_st = PJSUA_MED_TP_DISABLED; + + /* Deactivate the stream */ + pjmedia_sdp_media_deactivate(pool, sdp->media[med_idx]); + + } + + status = call_reoffer_sdp(call->index, sdp); + if (status != PJ_SUCCESS) + return status; + + return PJ_SUCCESS; +} + + +/* Change capture device of a video stream in a call */ +static pj_status_t call_change_cap_dev(pjsua_call *call, + int med_idx, + pjmedia_vid_dev_index cap_dev) +{ + pjsua_call_media *call_med; + pjmedia_vid_dev_info info; + pjsua_vid_win *w, *new_w = NULL; + pjsua_vid_win_id wid, new_wid; + pjmedia_port *media_port; + pj_status_t status; + + /* Verify and normalize media index */ + if (med_idx == -1) { + int first_active; + + call_get_vid_strm_info(call, &first_active, NULL, NULL, NULL); + if (first_active == -1) + return PJ_ENOTFOUND; + + med_idx = first_active; + } + + call_med = &call->media[med_idx]; + + /* Verify if the stream media type is video */ + if (call_med->type != PJMEDIA_TYPE_VIDEO) + return PJ_EINVAL; + + /* Verify the capture device */ + status = pjmedia_vid_dev_get_info(cap_dev, &info); + if (status != PJ_SUCCESS || info.dir != PJMEDIA_DIR_CAPTURE) + return PJ_EINVAL; + + /* The specified capture device is being used already */ + if (call_med->strm.v.cap_dev == cap_dev) + return PJ_SUCCESS; + + /* == Apply the new capture device == */ + + wid = call_med->strm.v.cap_win_id; + w = &pjsua_var.win[wid]; + pj_assert(w->type == PJSUA_WND_TYPE_PREVIEW && w->vp_cap); + + status = pjmedia_vid_stream_get_port(call_med->strm.v.stream, + PJMEDIA_DIR_ENCODING, &media_port); + if (status != PJ_SUCCESS) + return status; + + pjmedia_event_unsubscribe(&call_med->esub_cap); + + /* = Detach stream port from the old capture device = */ + status = pjmedia_vid_port_disconnect(w->vp_cap); + if (status != PJ_SUCCESS) + return status; + + status = pjmedia_vid_tee_remove_dst_port(w->tee, media_port); + if (status != PJ_SUCCESS) { + /* Connect back the old capturer */ + pjmedia_vid_port_connect(w->vp_cap, media_port, PJ_FALSE); + return status; + } + + /* = Attach stream port to the new capture device = */ + + /* Create preview video window */ + status = create_vid_win(PJSUA_WND_TYPE_PREVIEW, + &media_port->info.fmt, + call_med->strm.v.rdr_dev, + cap_dev, + PJ_FALSE, + &new_wid); + if (status != PJ_SUCCESS) + goto on_error; + + inc_vid_win(new_wid); + new_w = &pjsua_var.win[new_wid]; + + /* Connect stream to capturer (via video window tee) */ + status = pjmedia_vid_tee_add_dst_port2(new_w->tee, 0, media_port); + if (status != PJ_SUCCESS) + goto on_error; + + /* Connect capturer to tee */ + status = pjmedia_vid_port_connect(new_w->vp_cap, new_w->tee, PJ_FALSE); + if (status != PJ_SUCCESS) + return status; + + pjmedia_event_subscribe( + pjmedia_vid_port_get_event_publisher(w->vp_rend), + &call_med->esub_cap); + + /* Start renderer */ + status = pjmedia_vid_port_start(new_w->vp_rend); + if (status != PJ_SUCCESS) + goto on_error; + + /* Start capturer */ + status = pjmedia_vid_port_start(new_w->vp_cap); + if (status != PJ_SUCCESS) + goto on_error; + + /* Finally */ + call_med->strm.v.cap_dev = cap_dev; + call_med->strm.v.cap_win_id = new_wid; + dec_vid_win(wid); + + return PJ_SUCCESS; + +on_error: + if (new_w) { + /* Disconnect media port from the new capturer */ + pjmedia_vid_tee_remove_dst_port(new_w->tee, media_port); + /* Release the new capturer */ + dec_vid_win(new_wid); + } + + /* Revert back to the old capturer */ + status = pjmedia_vid_tee_add_dst_port2(w->tee, 0, media_port); + if (status != PJ_SUCCESS) + return status; + + status = pjmedia_vid_port_connect(w->vp_cap, w->tee, PJ_FALSE); + if (status != PJ_SUCCESS) + return status; + + return status; +} + + +/* Start/stop transmitting video stream in a call */ +static pj_status_t call_set_tx_video(pjsua_call *call, + int med_idx, + pj_bool_t enable) +{ + pjsua_call_media *call_med; + pj_status_t status; + + /* Verify and normalize media index */ + if (med_idx == -1) { + int first_active; + + call_get_vid_strm_info(call, &first_active, NULL, NULL, NULL); + if (first_active == -1) + return PJ_ENOTFOUND; + + med_idx = first_active; + } + + call_med = &call->media[med_idx]; + + /* Verify if the stream is transmitting video */ + if (call_med->type != PJMEDIA_TYPE_VIDEO || + (enable && (call_med->dir & PJMEDIA_DIR_ENCODING) == 0)) + { + return PJ_EINVAL; + } + + if (enable) { + /* Start stream in encoding direction */ + status = pjmedia_vid_stream_resume(call_med->strm.v.stream, + PJMEDIA_DIR_ENCODING); + } else { + /* Pause stream in encoding direction */ + status = pjmedia_vid_stream_pause( call_med->strm.v.stream, + PJMEDIA_DIR_ENCODING); + } + + return status; +} + + +/* + * Start, stop, and/or manipulate video transmission for the specified call. + */ +PJ_DEF(pj_status_t) pjsua_call_set_vid_strm ( + pjsua_call_id call_id, + pjsua_call_vid_strm_op op, + const pjsua_call_vid_strm_op_param *param) +{ + pjsua_call *call; + pjsua_call_vid_strm_op_param param_; + pj_status_t status; + + PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, + PJ_EINVAL); + + PJSUA_LOCK(); + + call = &pjsua_var.calls[call_id]; + + if (param) { + param_ = *param; + } else { + param_.med_idx = -1; + param_.cap_dev = PJMEDIA_VID_DEFAULT_CAPTURE_DEV; + param_.dir = PJMEDIA_DIR_ENCODING_DECODING; + } + + /* If set to PJMEDIA_VID_DEFAULT_CAPTURE_DEV, replace it with + * account default video capture device. + */ + if (param_.cap_dev == PJMEDIA_VID_DEFAULT_CAPTURE_DEV) { + pjsua_acc_config *acc_cfg = &pjsua_var.acc[call->acc_id].cfg; + param_.cap_dev = acc_cfg->vid_cap_dev; + + /* If the account default video capture device is + * PJMEDIA_VID_DEFAULT_CAPTURE_DEV, replace it with + * global default video capture device. + */ + if (param_.cap_dev == PJMEDIA_VID_DEFAULT_CAPTURE_DEV) { + pjmedia_vid_dev_info info; + pjmedia_vid_dev_get_info(param_.cap_dev, &info); + pj_assert(info.dir == PJMEDIA_DIR_CAPTURE); + param_.cap_dev = info.id; + } + } + + switch (op) { + case PJSUA_CALL_VID_STRM_ADD: + status = call_add_video(call, param_.cap_dev, param_.dir); + break; + case PJSUA_CALL_VID_STRM_REMOVE: + status = call_modify_video(call, param_.med_idx, PJMEDIA_DIR_NONE, + PJ_TRUE); + break; + case PJSUA_CALL_VID_STRM_CHANGE_DIR: + status = call_modify_video(call, param_.med_idx, param_.dir, PJ_FALSE); + break; + case PJSUA_CALL_VID_STRM_CHANGE_CAP_DEV: + status = call_change_cap_dev(call, param_.med_idx, param_.cap_dev); + break; + case PJSUA_CALL_VID_STRM_START_TRANSMIT: + status = call_set_tx_video(call, param_.med_idx, PJ_TRUE); + break; + case PJSUA_CALL_VID_STRM_STOP_TRANSMIT: + status = call_set_tx_video(call, param_.med_idx, PJ_FALSE); + break; + default: + status = PJ_EINVALIDOP; + break; + } + + PJSUA_UNLOCK(); + + return status; +} + + +/* + * Get the media stream index of the default video stream in the call. + */ +PJ_DEF(int) pjsua_call_get_vid_stream_idx(pjsua_call_id call_id) +{ + pjsua_call *call; + int first_active, first_inactive; + + PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, + PJ_EINVAL); + + PJSUA_LOCK(); + call = &pjsua_var.calls[call_id]; + call_get_vid_strm_info(call, &first_active, &first_inactive, NULL, NULL); + PJSUA_UNLOCK(); + + if (first_active == -1) + return first_inactive; + + return first_active; +} + + +#endif /* PJSUA_HAS_VIDEO */ + |