/* $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 #include #if defined(PJSUA_MEDIA_HAS_PJMEDIA) && PJSUA_MEDIA_HAS_PJMEDIA != 0 #define THIS_FILE "pjsua_vid.c" #if PJSUA_HAS_VIDEO #define ENABLE_EVENT 1 #define PJSUA_SHOW_WINDOW 1 #define PJSUA_HIDE_WINDOW 0 static pjsua_vid_win_id vid_preview_get_win(pjmedia_vid_dev_index id, pj_bool_t running_only); 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; PJ_LOG(4,(THIS_FILE, "Initializing video subsystem..")); pj_log_push_indent(); 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); goto on_error; } status = pjmedia_converter_mgr_create(pjsua_var.pool, NULL); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "Error creating PJMEDIA converter manager", status); goto on_error; } 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); goto on_error; } status = pjmedia_vid_conf_create(pjsua_var.pool, NULL, &pjsua_var.vid_conf); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "Error creating PJMEDIA video conference bridge", status); goto on_error; } #if PJMEDIA_HAS_VIDEO && PJMEDIA_HAS_VID_TOOLBOX_CODEC status = pjmedia_codec_vid_toolbox_init(NULL, &pjsua_var.cp.factory); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "Error initializing Video Toolbox codec", status); goto on_error; } #endif #if PJMEDIA_HAS_VIDEO && PJMEDIA_HAS_OPENH264_CODEC status = pjmedia_codec_openh264_vid_init(NULL, &pjsua_var.cp.factory); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "Error initializing OpenH264 library", status); goto on_error; } #endif #if PJMEDIA_HAS_VIDEO && PJMEDIA_HAS_FFMPEG_VID_CODEC status = pjmedia_codec_ffmpeg_vid_init(NULL, &pjsua_var.cp.factory); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "Error initializing ffmpeg library", status); goto on_error; } #endif #if PJMEDIA_HAS_VIDEO && PJMEDIA_HAS_VPX_CODEC status = pjmedia_codec_vpx_vid_init(NULL, &pjsua_var.cp.factory); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "Error initializing VPX library", status); goto on_error; } #endif status = pjmedia_vid_dev_subsys_init(&pjsua_var.cp.factory); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "Error creating PJMEDIA video subsystem", status); goto on_error; } for (i=0; imed_idx = -1; param->dir = PJMEDIA_DIR_ENCODING_DECODING; param->cap_dev = PJMEDIA_VID_DEFAULT_CAPTURE_DEV; } PJ_DEF(void) pjsua_vid_preview_param_default(pjsua_vid_preview_param *p) { p->rend_id = PJMEDIA_VID_DEFAULT_RENDER_DEV; p->show = PJ_TRUE; p->wnd_flags = 0; pj_bzero(&p->format, sizeof(p->format)); pj_bzero(&p->wnd, sizeof(p->wnd)); } /***************************************************************************** * 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); } /* * Check whether the video device is currently active. */ PJ_DEF(pj_bool_t) pjsua_vid_dev_is_active(pjmedia_vid_dev_index id) { pjsua_vid_win_id wid = vid_preview_get_win(id, PJ_FALSE); return (wid != PJSUA_INVALID_ID? PJ_TRUE: PJ_FALSE); } /* * Set the capability of the video device. */ PJ_DEF(pj_status_t) pjsua_vid_dev_set_setting( pjmedia_vid_dev_index id, pjmedia_vid_dev_cap cap, const void *pval, pj_bool_t keep) { pj_status_t status = PJ_SUCCESS; pjsua_vid_win_id wid; PJSUA_LOCK(); wid = vid_preview_get_win(id, PJ_FALSE); if (wid != PJSUA_INVALID_ID) { pjsua_vid_win *w; pjmedia_vid_dev_stream *cap_dev; w = &pjsua_var.win[wid]; cap_dev = pjmedia_vid_port_get_stream(w->vp_cap); status = pjmedia_vid_dev_stream_set_cap(cap_dev, cap, pval); if (status != PJ_SUCCESS) { PJSUA_UNLOCK(); return status; } } else { status = PJ_ENOTFOUND; } PJSUA_UNLOCK(); if (keep) { pjmedia_vid_dev_info info; status = pjmedia_vid_dev_get_info(id, &info); if (status != PJ_SUCCESS || (info.dir & PJMEDIA_DIR_CAPTURE) == 0) return status; /* Get real capture ID, if set to PJMEDIA_VID_DEFAULT_CAPTURE_DEV */ id = info.id; status = pjmedia_vid_dev_param_set_cap(&pjsua_var.vid_param[id], cap, pval); if (status == PJ_SUCCESS) { pjsua_var.vid_caps[id] |= cap; } } return status; } /* * Get the value of the video device capability. */ PJ_DEF(pj_status_t) pjsua_vid_dev_get_setting( pjmedia_vid_dev_index id, pjmedia_vid_dev_cap cap, void *pval) { pj_status_t status = PJ_SUCCESS; pjsua_vid_win_id wid; PJSUA_LOCK(); wid = vid_preview_get_win(id, PJ_FALSE); if (wid != PJSUA_INVALID_ID) { pjsua_vid_win *w; pjmedia_vid_dev_stream *cap_dev; w = &pjsua_var.win[wid]; cap_dev = pjmedia_vid_port_get_stream(w->vp_cap); status = pjmedia_vid_dev_stream_get_cap(cap_dev, cap, pval); PJSUA_UNLOCK(); } else { pjmedia_vid_dev_info info; PJSUA_UNLOCK(); status = pjmedia_vid_dev_get_info(id, &info); if (status != PJ_SUCCESS) return status; /* Get real device ID, if set to default device */ id = info.id; if ((pjsua_var.vid_caps[id] & cap) != 0) { status = pjmedia_vid_dev_param_get_cap(&pjsua_var.vid_param[id], cap, pval); } else { status = PJ_ENOTFOUND; } } return status; } /* * 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; ipackings & PJMEDIA_VID_PACKING_PACKETS) == 0) continue; p_info[j++] = info[i]; } *count = j; return PJ_SUCCESS; } /* * 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; islen==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 pjmedia_vid_codec_info *info[2]; unsigned count = 2; pj_status_t status; status = find_codecs_with_rtp_packing(codec_id, &count, info); 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[0], 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 = find_codecs_with_rtp_packing(codec_id, &count, info); if (status != PJ_SUCCESS) return status; if (count != 1) return (count > 1? PJ_ETOOMANY : PJ_ENOTFOUND); status = pjmedia_vid_codec_mgr_set_default_param(NULL, info[0], param); return status; } /***************************************************************************** * Preview */ static pjsua_vid_win_id vid_preview_get_win(pjmedia_vid_dev_index id, pj_bool_t running_only) { 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; itype == PJSUA_WND_TYPE_PREVIEW && w->preview_cap_id == id) { wid = i; break; } } if (wid != PJSUA_INVALID_ID && running_only) { pjsua_vid_win *w = &pjsua_var.win[wid]; wid = w->preview_running ? wid : PJSUA_INVALID_ID; } PJSUA_UNLOCK(); return wid; } /* * NOTE: internal function don't use this!!! Use vid_preview_get_win() * instead. This is because this function will only return window ID * if preview is currently running. */ PJ_DEF(pjsua_vid_win_id) pjsua_vid_preview_get_win(pjmedia_vid_dev_index id) { return vid_preview_get_win(id, PJ_TRUE); } /* * Get video conference slot ID of the specified capture device. */ PJ_DEF(pjsua_conf_port_id) pjsua_vid_preview_get_vid_conf_port( pjmedia_vid_dev_index id) { pjsua_vid_win_id wid; pjsua_vid_win *w; pjsua_conf_port_id conf_id = PJSUA_INVALID_ID; PJSUA_LOCK(); wid = vid_preview_get_win(id, PJ_TRUE); if (wid != PJSUA_INVALID_ID) { w = &pjsua_var.win[wid]; conf_id = w->cap_slot; } PJSUA_UNLOCK(); return conf_id; } void pjsua_vid_win_reset(pjsua_vid_win_id wid) { 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->ref_cnt = 0; w->pool = pool; w->preview_cap_id = PJMEDIA_VID_INVALID_DEV; } /* Allocate and initialize pjsua video window: * - If the type is preview: capture port and render port * will be instantiated, and connected via conf. * - If the type is stream: only render port will be created. */ static pj_status_t create_vid_win(pjsua_vid_win_type type, const pjmedia_format *fmt, pjmedia_vid_dev_index rend_id, pjmedia_vid_dev_index cap_id, pj_bool_t show, unsigned wnd_flags, const pjmedia_vid_dev_hwnd *wnd, pjsua_vid_win_id *id) { pj_bool_t enable_native_preview; 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; enable_native_preview = pjsua_var.media_cfg.vid_preview_enable_native; PJ_LOG(4,(THIS_FILE, "Creating video window: type=%s, cap_id=%d, rend_id=%d", pjsua_vid_win_type_name(type), cap_id, rend_id)); pj_log_push_indent(); /* If type is preview, check if it exists already */ if (type == PJSUA_WND_TYPE_PREVIEW) { wid = vid_preview_get_win(cap_id, PJ_FALSE); if (wid != PJSUA_INVALID_ID) { /* Yes, it exists */ /* Show/hide window */ pjmedia_vid_dev_stream *strm; pj_bool_t hide = !show; w = &pjsua_var.win[wid]; PJ_LOG(4,(THIS_FILE, "Window already exists for cap_dev=%d, returning wid=%d", cap_id, wid)); if (w->is_native) { strm = pjmedia_vid_port_get_stream(w->vp_cap); } else { strm = pjmedia_vid_port_get_stream(w->vp_rend); } pj_assert(strm); /* Try to apply show/hide, window flags, and output window */ status = pjmedia_vid_dev_stream_set_cap( strm, PJMEDIA_VID_DEV_CAP_OUTPUT_HIDE, &hide); if (status != PJ_SUCCESS) { PJ_PERROR(4,(THIS_FILE, status, "Ignored error on setting window visibility " "on wid=%d", wid)); } status = pjmedia_vid_dev_stream_set_cap( strm, PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW_FLAGS, &wnd_flags); if (status != PJ_SUCCESS) { PJ_PERROR(4,(THIS_FILE, status, "Ignored error on setting window flags " "on wid=%d", wid)); } if (wnd) { status = pjmedia_vid_dev_stream_set_cap( strm, PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW, wnd); if (status != PJ_SUCCESS) { PJ_PERROR(4, (THIS_FILE, status, "Ignored error on setting window handle " "on wid=%d", wid)); } } status = pjsua_vid_conf_connect(w->cap_slot, w->rend_slot, NULL); if (status != PJ_SUCCESS) { PJ_PERROR(4, (THIS_FILE, status, "Ignored error on connecting video ports " "on wid=%d", wid)); } /* Done */ *id = wid; pj_log_pop_indent(); return PJ_SUCCESS; } } /* Allocate window */ for (i=0; itype == PJSUA_WND_TYPE_NONE) { wid = i; w->type = type; break; } } if (i == PJSUA_MAX_VID_WINS) { pj_log_pop_indent(); return PJ_ETOOMANY; } /* Initialize window */ pjmedia_vid_port_param_default(&vp_param); if (w->type == PJSUA_WND_TYPE_PREVIEW) { pjmedia_vid_dev_info vdi; /* * Determine if the device supports native preview. */ status = pjmedia_vid_dev_get_info(cap_id, &vdi); if (status != PJ_SUCCESS) goto on_error; if (enable_native_preview && (vdi.caps & PJMEDIA_VID_DEV_CAP_INPUT_PREVIEW)) { /* Device supports native preview! */ w->is_native = PJ_TRUE; } status = pjmedia_vid_dev_default_param(w->pool, cap_id, &vp_param.vidparam); if (status != PJ_SUCCESS) goto on_error; if (w->is_native) { vp_param.vidparam.flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_HIDE; vp_param.vidparam.window_hide = !show; vp_param.vidparam.flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW_FLAGS; vp_param.vidparam.window_flags = wnd_flags; } /* 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_FALSE; vp_param.vidparam.dir = PJMEDIA_DIR_CAPTURE; /* Update the video setting with user preference */ #define update_param(cap, field) \ if ((pjsua_var.vid_caps[cap_id] & cap) && (vdi.caps & cap)) { \ vp_param.vidparam.flags |= cap; \ pj_memcpy(&vp_param.vidparam.field, \ &pjsua_var.vid_param[cap_id].field, \ sizeof(vp_param.vidparam.field)); \ } if (fmt) { vp_param.vidparam.fmt = *fmt; } else { update_param(PJMEDIA_VID_DEV_CAP_FORMAT, fmt); } update_param(PJMEDIA_VID_DEV_CAP_ORIENTATION, orient); #undef update_param 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_; /* Register capturer to the video conf */ status = pjsua_vid_conf_add_port( w->pool, pjmedia_vid_port_get_passive_port(w->vp_cap), NULL, &w->cap_slot); if (status != PJ_SUCCESS) goto on_error; /* If device supports native preview, enable it */ if (w->is_native) { pjmedia_vid_dev_stream *cap_dev; pj_bool_t enabled = PJ_TRUE; cap_dev = pjmedia_vid_port_get_stream(w->vp_cap); status = pjmedia_vid_dev_stream_set_cap( cap_dev, PJMEDIA_VID_DEV_CAP_INPUT_PREVIEW, &enabled); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "Error activating native preview, falling back " "to software preview..", status); w->is_native = PJ_FALSE; } } } /* Create renderer video port, only if it's not a native preview */ if (!w->is_native) { status = pjmedia_vid_dev_default_param(w->pool, rend_id, &vp_param.vidparam); if (status != PJ_SUCCESS) goto on_error; vp_param.active = PJ_FALSE; 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; vp_param.vidparam.flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW_FLAGS; vp_param.vidparam.window_flags = wnd_flags; if (wnd) { vp_param.vidparam.flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW; vp_param.vidparam.window = *wnd; } status = pjmedia_vid_port_create(w->pool, &vp_param, &w->vp_rend); if (status != PJ_SUCCESS) goto on_error; /* Register renderer to the video conf */ status = pjsua_vid_conf_add_port( w->pool, pjmedia_vid_port_get_passive_port(w->vp_rend), NULL, &w->rend_slot); if (status != PJ_SUCCESS) goto on_error; /* For preview window, connect capturer & renderer (via conf) */ if (w->type == PJSUA_WND_TYPE_PREVIEW && show) { status = pjsua_vid_conf_connect(w->cap_slot, w->rend_slot, NULL); if (status != PJ_SUCCESS) goto on_error; } PJ_LOG(4,(THIS_FILE, "%s window id %d created for cap_dev=%d rend_dev=%d", pjsua_vid_win_type_name(type), wid, cap_id, rend_id)); } else { PJ_LOG(4,(THIS_FILE, "Preview window id %d created for cap_dev %d, " "using built-in preview!", wid, cap_id)); } /* Done */ *id = wid; PJ_LOG(4,(THIS_FILE, "Window %d created", wid)); pj_log_pop_indent(); return PJ_SUCCESS; on_error: free_vid_win(wid); pj_log_pop_indent(); return status; } static void free_vid_win(pjsua_vid_win_id wid) { pjsua_vid_win *w = &pjsua_var.win[wid]; PJ_LOG(4,(THIS_FILE, "Window %d: destroying..", wid)); pj_log_push_indent(); if (w->vp_cap) { pjsua_vid_conf_remove_port(w->cap_slot); pjmedia_event_unsubscribe(NULL, &call_media_on_event, NULL, w->vp_cap); pjmedia_vid_port_stop(w->vp_cap); pjmedia_vid_port_destroy(w->vp_cap); } if (w->vp_rend) { pjsua_vid_conf_remove_port(w->rend_slot); pjmedia_event_unsubscribe(NULL, &call_media_on_event, NULL, w->vp_rend); pjmedia_vid_port_stop(w->vp_rend); pjmedia_vid_port_destroy(w->vp_rend); } pjsua_vid_win_reset(wid); pj_log_pop_indent(); } 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); } /* Initialize video call media */ pj_status_t pjsua_vid_channel_init(pjsua_call_media *call_med) { pjsua_acc *acc = &pjsua_var.acc[call_med->call->acc_id]; call_med->strm.v.rdr_dev = acc->cfg.vid_rend_dev; call_med->strm.v.cap_dev = acc->cfg.vid_cap_dev; call_med->strm.v.strm_dec_slot = PJSUA_INVALID_ID; call_med->strm.v.strm_enc_slot = PJSUA_INVALID_ID; if (call_med->strm.v.rdr_dev == PJMEDIA_VID_DEFAULT_RENDER_DEV) { pjmedia_vid_dev_info info; pjmedia_vid_dev_get_info(call_med->strm.v.rdr_dev, &info); call_med->strm.v.rdr_dev = info.id; } if (call_med->strm.v.cap_dev == PJMEDIA_VID_DEFAULT_CAPTURE_DEV) { pjmedia_vid_dev_info info; pjmedia_vid_dev_get_info(call_med->strm.v.cap_dev, &info); call_med->strm.v.cap_dev = info.id; } return PJ_SUCCESS; } static pj_status_t setup_vid_capture(pjsua_call_media *call_med) { pjsua_acc *acc_enc = &pjsua_var.acc[call_med->call->acc_id]; pjmedia_port *media_port; pjsua_vid_win *w; pjsua_vid_win_id wid; pj_bool_t just_created = PJ_FALSE; pj_status_t status; /* Retrieve stream encoding port */ status = pjmedia_vid_stream_get_port(call_med->strm.v.stream, PJMEDIA_DIR_ENCODING, &media_port); if (status != PJ_SUCCESS) return status; PJSUA_LOCK(); /* Note: calling pjsua_vid_preview_get_win() even though * create_vid_win() will automatically create the window * if it doesn't exist, because create_vid_win() will modify * existing window SHOW/HIDE value. */ wid = vid_preview_get_win(call_med->strm.v.cap_dev, PJ_FALSE); if (wid == PJSUA_INVALID_ID) { /* 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, PJSUA_HIDE_WINDOW, acc_enc->cfg.vid_wnd_flags, NULL, &wid); if (status != PJ_SUCCESS) goto on_error; just_created = PJ_TRUE; } w = &pjsua_var.win[wid]; #if ENABLE_EVENT pjmedia_event_subscribe(NULL, &call_media_on_event, call_med, w->vp_cap); #endif /* Connect capturer to stream encoding (via conf) */ status = pjsua_vid_conf_connect(w->cap_slot, call_med->strm.v.strm_enc_slot, NULL); if (status != PJ_SUCCESS) goto on_error; /* Start capturer */ if (just_created) { status = pjmedia_vid_port_start(w->vp_cap); if (status != PJ_SUCCESS) goto on_error; } /* Done */ inc_vid_win(wid); call_med->strm.v.cap_win_id = wid; PJSUA_UNLOCK(); return PJ_SUCCESS; on_error: PJSUA_UNLOCK(); return status; } /* Internal function: update video channel after SDP negotiation. * Warning: do not use temporary/flip-flop pool, e.g: inv->pool_prov, * for creating stream, etc, as after SDP negotiation and when * the SDP media is not changed, the stream should remain running * while the temporary/flip-flop pool may be released. */ pj_status_t pjsua_vid_channel_update(pjsua_call_media *call_med, pj_pool_t *tmp_pool, pjmedia_vid_stream_info *si, 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_port *media_port; pj_status_t status; PJ_UNUSED_ARG(tmp_pool); PJ_UNUSED_ARG(local_sdp); PJ_UNUSED_ARG(remote_sdp); PJ_LOG(4,(THIS_FILE, "Video channel update..")); pj_log_push_indent(); si->rtcp_sdes_bye_disabled = pjsua_var.media_cfg.no_rtcp_sdes_bye;; /* Check if no media is active */ if (si->dir != PJMEDIA_DIR_NONE) { /* 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 and CNAME */ si->ssrc = call_med->ssrc; si->cname = call->cname; /* 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; /* Set rate control config from account setting */ si->rc_cfg = acc->cfg.vid_stream_rc_cfg; /* Set send keyframe config from account setting */ si->sk_cfg = acc->cfg.vid_stream_sk_cfg; #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) goto on_error; /* 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) goto on_error; /* Subscribe to video stream events */ pjmedia_event_subscribe(NULL, &call_media_on_event, call_med, call_med->strm.v.stream); /* Start stream */ status = pjmedia_vid_stream_start(call_med->strm.v.stream); if (status != PJ_SUCCESS) goto on_error; if (call_med->prev_state == PJSUA_CALL_MEDIA_NONE) pjmedia_vid_stream_send_rtcp_sdes(call_med->strm.v.stream); /* Setup decoding direction */ if (si->dir & PJMEDIA_DIR_DECODING) { pjsua_vid_win_id wid; pjsua_vid_win *w; PJ_LOG(4,(THIS_FILE, "Setting up RX..")); pj_log_push_indent(); /* Retrieve stream decoding port */ status = pjmedia_vid_stream_get_port(call_med->strm.v.stream, PJMEDIA_DIR_DECODING, &media_port); if (status != PJ_SUCCESS) { pj_log_pop_indent(); goto on_error; } /* Create stream video window */ PJSUA_LOCK(); 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, acc->cfg.vid_wnd_flags, NULL, &wid); if (status != PJ_SUCCESS) { PJSUA_UNLOCK(); pj_log_pop_indent(); goto on_error; } w = &pjsua_var.win[wid]; #if ENABLE_EVENT /* Register to video events */ pjmedia_event_subscribe(NULL, &call_media_on_event, call_med, w->vp_rend); pjmedia_event_subscribe(NULL, &call_media_on_event, call_med, media_port); #endif /* Register renderer to stream events */ pjmedia_vid_port_subscribe_event(w->vp_rend, media_port); /* Register stream decoding to conf, using tmp_pool should be fine * as bridge will create its own pool (using tmp_pool factory). */ status = pjsua_vid_conf_add_port(tmp_pool, media_port, NULL, &call_med->strm.v.strm_dec_slot); if (status != PJ_SUCCESS) { PJSUA_UNLOCK(); pj_log_pop_indent(); goto on_error; } /* Connect stream to renderer (via conf) */ status = pjsua_vid_conf_connect(call_med->strm.v.strm_dec_slot, w->rend_slot, NULL); if (status != PJ_SUCCESS) { PJSUA_UNLOCK(); pj_log_pop_indent(); goto on_error; } /* Start renderer */ status = pjmedia_vid_port_start(w->vp_rend); if (status != PJ_SUCCESS) { PJSUA_UNLOCK(); pj_log_pop_indent(); goto on_error; } /* Done */ inc_vid_win(wid); call_med->strm.v.rdr_win_id = wid; PJSUA_UNLOCK(); pj_log_pop_indent(); } /* Setup encoding direction */ if (si->dir & PJMEDIA_DIR_ENCODING) { PJ_LOG(4,(THIS_FILE, "Setting up TX..")); pj_log_push_indent(); /* Retrieve stream encoding port */ status = pjmedia_vid_stream_get_port(call_med->strm.v.stream, PJMEDIA_DIR_ENCODING, &media_port); if (status != PJ_SUCCESS) goto on_error; /* Register stream encoding to conf, using tmp_pool should be fine * as bridge will create its own pool (using tmp_pool factory). */ status = pjsua_vid_conf_add_port(tmp_pool, media_port, NULL, &call_med->strm.v.strm_enc_slot); if (status != PJ_SUCCESS) goto on_error; if (!call->local_hold && acc->cfg.vid_out_auto_transmit) { status = setup_vid_capture(call_med); if (status != PJ_SUCCESS) goto on_error; } pj_log_pop_indent(); } } 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) goto on_error; } pj_log_pop_indent(); return PJ_SUCCESS; on_error: pj_log_pop_indent(); return status; } /* Internal function to stop video stream */ void pjsua_vid_stop_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; PJ_LOG(4,(THIS_FILE, "Stopping video stream..")); pj_log_push_indent(); /* Unregister video stream ports (encode+decode) from conference */ if (call_med->strm.v.strm_enc_slot != PJSUA_INVALID_ID) { pjsua_vid_conf_remove_port(call_med->strm.v.strm_enc_slot); call_med->strm.v.strm_enc_slot = PJSUA_INVALID_ID; } if (call_med->strm.v.strm_dec_slot != PJSUA_INVALID_ID) { pjsua_vid_conf_remove_port(call_med->strm.v.strm_dec_slot); call_med->strm.v.strm_dec_slot = PJSUA_INVALID_ID; } pjmedia_vid_stream_send_rtcp_bye(strm); PJSUA_LOCK(); if (call_med->strm.v.cap_win_id != PJSUA_INVALID_ID) { pjsua_vid_win *w = &pjsua_var.win[call_med->strm.v.cap_win_id]; /* Unsubscribe event */ pjmedia_event_unsubscribe(NULL, &call_media_on_event, call_med, w->vp_cap); /* Decrement ref count of preview video window */ dec_vid_win(call_med->strm.v.cap_win_id); call_med->strm.v.cap_win_id = PJSUA_INVALID_ID; } if (call_med->strm.v.rdr_win_id != PJSUA_INVALID_ID) { pjsua_vid_win *w = &pjsua_var.win[call_med->strm.v.rdr_win_id]; /* Unsubscribe event, but stop the render first */ pjmedia_vid_port_stop(w->vp_rend); pjmedia_event_unsubscribe(NULL, &call_media_on_event, call_med, w->vp_rend); /* Decrement ref count of stream video window */ dec_vid_win(call_med->strm.v.rdr_win_id); call_med->strm.v.rdr_win_id = PJSUA_INVALID_ID; } PJSUA_UNLOCK(); if ((call_med->dir & PJMEDIA_DIR_ENCODING) && (pjmedia_vid_stream_get_stat(strm, &stat) == PJ_SUCCESS) && stat.tx.pkt) { /* 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; } /* Unsubscribe from video stream events */ pjmedia_event_unsubscribe(NULL, &call_media_on_event, call_med, strm); pjmedia_vid_stream_destroy(strm); call_med->strm.v.stream = NULL; pj_log_pop_indent(); } /* * Does it have built-in preview support. */ PJ_DEF(pj_bool_t) pjsua_vid_preview_has_native(pjmedia_vid_dev_index id) { pjmedia_vid_dev_info vdi; return (pjmedia_vid_dev_get_info(id, &vdi)==PJ_SUCCESS) ? ((vdi.caps & PJMEDIA_VID_DEV_CAP_INPUT_PREVIEW)!=0) : PJ_FALSE; } /* * Start video preview window for the specified capture device. */ PJ_DEF(pj_status_t) pjsua_vid_preview_start(pjmedia_vid_dev_index id, const pjsua_vid_preview_param *prm) { pjsua_vid_win_id wid; pjsua_vid_win *w; pjmedia_vid_dev_index rend_id; pjsua_vid_preview_param default_param; const pjmedia_format *fmt = NULL; pj_status_t status; if (!prm) { pjsua_vid_preview_param_default(&default_param); prm = &default_param; } PJ_LOG(4,(THIS_FILE, "Starting preview for cap_dev=%d, show=%d", id, prm->show)); pj_log_push_indent(); PJSUA_LOCK(); rend_id = prm->rend_id; if (prm->format.detail_type == PJMEDIA_FORMAT_DETAIL_VIDEO) fmt = &prm->format; status = create_vid_win(PJSUA_WND_TYPE_PREVIEW, fmt, rend_id, id, prm->show, prm->wnd_flags, (prm->wnd.info.window? &prm->wnd: NULL), &wid); if (status != PJ_SUCCESS) { PJSUA_UNLOCK(); pj_log_pop_indent(); return status; } w = &pjsua_var.win[wid]; if (w->preview_running) { PJSUA_UNLOCK(); pj_log_pop_indent(); return PJ_SUCCESS; } /* Start renderer, unless it's native preview */ if (w->is_native && !pjmedia_vid_port_is_running(w->vp_cap)) { pjmedia_vid_dev_stream *cap_dev; pj_bool_t enabled = PJ_TRUE; cap_dev = pjmedia_vid_port_get_stream(w->vp_cap); status = pjmedia_vid_dev_stream_set_cap( cap_dev, PJMEDIA_VID_DEV_CAP_INPUT_PREVIEW, &enabled); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "Error activating native preview, falling back " "to software preview..", status); w->is_native = PJ_FALSE; } } if (!w->is_native && !pjmedia_vid_port_is_running(w->vp_rend)) { status = pjmedia_vid_port_start(w->vp_rend); if (status != PJ_SUCCESS) { PJSUA_UNLOCK(); pj_log_pop_indent(); return status; } } /* Start capturer */ if (!pjmedia_vid_port_is_running(w->vp_cap)) { status = pjmedia_vid_port_start(w->vp_cap); if (status != PJ_SUCCESS) { PJSUA_UNLOCK(); pj_log_pop_indent(); return status; } } inc_vid_win(wid); w->preview_running = PJ_TRUE; PJSUA_UNLOCK(); pj_log_pop_indent(); 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_vid_win *w; pj_status_t status; PJSUA_LOCK(); wid = pjsua_vid_preview_get_win(id); if (wid == PJSUA_INVALID_ID) { PJSUA_UNLOCK(); pj_log_pop_indent(); return PJ_ENOTFOUND; } PJ_LOG(4,(THIS_FILE, "Stopping preview for cap_dev=%d", id)); pj_log_push_indent(); w = &pjsua_var.win[wid]; if (w->preview_running) { if (w->is_native) { pjmedia_vid_dev_stream *cap_dev; pj_bool_t enabled = PJ_FALSE; cap_dev = pjmedia_vid_port_get_stream(w->vp_cap); status = pjmedia_vid_dev_stream_set_cap( cap_dev, PJMEDIA_VID_DEV_CAP_INPUT_PREVIEW, &enabled); } else { status = pjsua_vid_conf_disconnect(w->cap_slot, w->rend_slot); if (status != PJ_SUCCESS) { PJ_PERROR(4, (THIS_FILE, status, "Ignored error on disconnecting video ports " "on stopping preview wid=%d", wid)); } status = pjmedia_vid_port_stop(w->vp_rend); } if (status != PJ_SUCCESS) { PJ_PERROR(1,(THIS_FILE, status, "Error stopping %spreview", (w->is_native ? "native " : ""))); PJSUA_UNLOCK(); pj_log_pop_indent(); return status; } dec_vid_win(wid); w->preview_running = PJ_FALSE; } PJSUA_UNLOCK(); pj_log_pop_indent(); 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; itype != 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); pj_bzero(wi, sizeof(*wi)); PJSUA_LOCK(); w = &pjsua_var.win[wid]; wi->is_native = w->is_native; if (w->is_native) { pjmedia_vid_dev_stream *cap_strm; pjmedia_vid_dev_cap cap = PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW; if (!w->vp_cap) { status = PJ_EINVAL; } else { cap_strm = pjmedia_vid_port_get_stream(w->vp_cap); if (!cap_strm) { status = PJ_EINVAL; } else { status = pjmedia_vid_dev_stream_get_cap(cap_strm, cap, &wi->hwnd); } } PJSUA_UNLOCK(); return status; } 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->rdr_dev = vparam.rend_id; wi->slot_id = w->rend_slot; wi->hwnd = vparam.window; 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]; s = pjmedia_vid_port_get_stream(w->vp_rend? w->vp_rend: w->vp_cap); if (s == NULL) { PJSUA_UNLOCK(); return PJ_EINVAL; } /* Make sure that renderer gets started before shown up */ if (show && !pjmedia_vid_port_is_running(w->vp_rend)) status = pjmedia_vid_port_start(w->vp_rend); 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]; s = pjmedia_vid_port_get_stream(w->vp_rend? w->vp_rend: w->vp_cap); 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]; s = pjmedia_vid_port_get_stream(w->vp_rend? w->vp_rend: w->vp_cap); 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; } /* * Set output window. */ PJ_DEF(pj_status_t) pjsua_vid_win_set_win( pjsua_vid_win_id wid, const pjmedia_vid_dev_hwnd *win) { pjsua_vid_win *w; pjmedia_vid_dev_stream *s; pj_status_t status; PJ_ASSERT_RETURN(wid >= 0 && wid < PJSUA_MAX_VID_WINS && win, PJ_EINVAL); PJSUA_LOCK(); w = &pjsua_var.win[wid]; s = pjmedia_vid_port_get_stream(w->vp_rend? w->vp_rend: w->vp_cap); if (s == NULL) { PJSUA_UNLOCK(); return PJ_EINVAL; } status = pjmedia_vid_dev_stream_set_cap(s, PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW, win); PJSUA_UNLOCK(); return status; } /* * Set video orientation. */ PJ_DEF(pj_status_t) pjsua_vid_win_rotate( pjsua_vid_win_id wid, int angle) { pjsua_vid_win *w; pjmedia_vid_dev_stream *s; pjmedia_orient orient; pj_status_t status; PJ_ASSERT_RETURN(wid >= 0 && wid < PJSUA_MAX_VID_WINS, PJ_EINVAL); PJ_ASSERT_RETURN((angle % 90) == 0, PJ_EINVAL); /* Normalize angle, so it must be 0, 90, 180, or 270. */ angle %= 360; if (angle < 0) angle += 360; /* Convert angle to pjmedia_orient */ switch(angle) { case 0: /* No rotation */ return PJ_SUCCESS; case 90: orient = PJMEDIA_ORIENT_ROTATE_90DEG; break; case 180: orient = PJMEDIA_ORIENT_ROTATE_180DEG; break; case 270: orient = PJMEDIA_ORIENT_ROTATE_270DEG; break; default: pj_assert(!"Angle must have been validated"); return PJ_EBUG; } PJSUA_LOCK(); w = &pjsua_var.win[wid]; s = pjmedia_vid_port_get_stream(w->vp_rend? w->vp_rend: w->vp_cap); if (s == NULL) { PJSUA_UNLOCK(); return PJ_EINVAL; } status = pjmedia_vid_dev_stream_set_cap(s, PJMEDIA_VID_DEV_CAP_ORIENTATION, &orient); PJSUA_UNLOCK(); return status; } /* * Set video window fullscreen. */ PJ_DEF(pj_status_t) pjsua_vid_win_set_fullscreen( pjsua_vid_win_id wid, pj_bool_t enabled) { pjsua_vid_win *w; pjmedia_vid_dev_stream *s; pj_status_t status; PJ_ASSERT_RETURN(wid >= 0 && wid < PJSUA_MAX_VID_WINS, PJ_EINVAL); PJSUA_LOCK(); w = &pjsua_var.win[wid]; s = pjmedia_vid_port_get_stream(w->vp_rend? w->vp_rend: w->vp_cap); if (s == NULL) { PJSUA_UNLOCK(); return PJ_EINVAL; } status = pjmedia_vid_dev_stream_set_cap(s, PJMEDIA_VID_DEV_CAP_OUTPUT_FULLSCREEN, &enabled); 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; const pjmedia_sdp_session *current_sdp; pjmedia_sdp_session *sdp; pjmedia_sdp_media *sdp_m; pjmedia_transport_info tpinfo; unsigned options; pj_status_t status; /* Verify media slot availability */ if (call->med_cnt == PJSUA_MAX_CALL_MEDIA) return PJ_ETOOMANY; if (pjsua_call_media_is_changing(call)) { PJ_LOG(1,(THIS_FILE, "Unable to add video" ERR_MEDIA_CHANGING)); return PJ_EINVALIDOP; } /* Get active local SDP and clone it */ status = pjmedia_sdp_neg_get_active_local(call->inv->neg, ¤t_sdp); if (status != PJ_SUCCESS) return status; sdp = pjmedia_sdp_session_clone(call->inv->pool_prov, current_sdp); /* Clean up provisional media before using it */ pjsua_media_prov_clean_up(call->index); /* Update provisional media from call media */ call->med_prov_cnt = call->med_cnt; pj_memcpy(call->media_prov, call->media, sizeof(call->media[0]) * call->med_cnt); /* Initialize call media */ call_med = &call->media_prov[call->med_prov_cnt++]; status = pjsua_call_media_init(call_med, PJMEDIA_TYPE_VIDEO, &acc_cfg->rtp_cfg, call->secure_level, NULL, PJ_FALSE, NULL); if (status != PJ_SUCCESS) goto on_error; /* Override default capture device setting */ call_med->strm.v.cap_dev = cap_dev; /* Init transport media */ options = (call_med->enable_rtcp_mux? PJMEDIA_TPMED_RTCP_MUX: 0); status = pjmedia_transport_media_create(call_med->tp, pool, options, NULL, call_med->idx); if (status != PJ_SUCCESS) goto on_error; pjsua_set_media_tp_state(call_med, 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; call->opt.vid_cnt++; return PJ_SUCCESS; on_error: if (call_med->tp) { pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_NULL); pjmedia_transport_close(call_med->tp); call_med->tp = call_med->tp_orig = NULL; } call->med_prov_cnt = 0; 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; const pjmedia_sdp_session *current_sdp; pjmedia_sdp_session *sdp; pj_status_t status; if (pjsua_call_media_is_changing(call)) { PJ_LOG(1,(THIS_FILE, "Unable to modify video" ERR_MEDIA_CHANGING)); return PJ_EINVALIDOP; } /* 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; } /* Clean up provisional media before using it */ pjsua_media_prov_clean_up(call->index); /* Update provisional media from call media */ call->med_prov_cnt = call->med_cnt; pj_memcpy(call->media_prov, call->media, sizeof(call->media[0]) * call->med_cnt); call_med = &call->media_prov[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 and clone it */ status = pjmedia_sdp_neg_get_active_local(call->inv->neg, ¤t_sdp); if (status != PJ_SUCCESS) return status; sdp = pjmedia_sdp_session_clone(call->inv->pool_prov, current_sdp); 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; /* Enabling video */ if (call_med->dir == PJMEDIA_DIR_NONE) { unsigned i, vid_cnt = 0; /* Check if vid_cnt in call option needs to be increased */ for (i = 0; i < call->med_cnt; ++i) { if (call->media[i].type == PJMEDIA_TYPE_VIDEO && call->media[i].dir != PJMEDIA_DIR_NONE) { ++vid_cnt; } } if (call->opt.vid_cnt <= vid_cnt) call->opt.vid_cnt++; } status = pjsua_call_media_init(call_med, PJMEDIA_TYPE_VIDEO, &acc_cfg->rtp_cfg, call->secure_level, NULL, PJ_FALSE, NULL); if (status != PJ_SUCCESS) goto on_error; /* Init transport media */ if (call_med->tp && call_med->tp_st == PJSUA_MED_TP_IDLE) { unsigned options = (call_med->enable_rtcp_mux? PJMEDIA_TPMED_RTCP_MUX: 0); status = pjmedia_transport_media_create(call_med->tp, pool, options, 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; if (call_med->dir == PJMEDIA_DIR_NONE) { /* 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) { pjsua_media_prov_clean_up(call->index); 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. pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_DISABLED); /* Deactivate the stream */ pjmedia_sdp_media_deactivate(pool, sdp->media[med_idx]); call->opt.vid_cnt--; } 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_stream *old_dev; pjmedia_vid_dev_switch_param switch_prm; 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 == */ PJSUA_LOCK(); wid = call_med->strm.v.cap_win_id; w = &pjsua_var.win[wid]; pj_assert(w->type == PJSUA_WND_TYPE_PREVIEW && w->vp_cap); /* If the old device supports fast switching, then that's excellent! */ old_dev = pjmedia_vid_port_get_stream(w->vp_cap); pjmedia_vid_dev_switch_param_default(&switch_prm); switch_prm.target_id = cap_dev; status = pjmedia_vid_dev_stream_set_cap(old_dev, PJMEDIA_VID_DEV_CAP_SWITCH, &switch_prm); if (status == PJ_SUCCESS) { w->preview_cap_id = cap_dev; call_med->strm.v.cap_dev = cap_dev; PJSUA_UNLOCK(); /* Yay, change capturer done! */ return PJ_SUCCESS; } /* Oh no, it doesn't support fast switching. Do normal change then, * i.e: remove the old and create a new capture. */ status = pjmedia_vid_stream_get_port(call_med->strm.v.stream, PJMEDIA_DIR_ENCODING, &media_port); if (status != PJ_SUCCESS) { PJSUA_UNLOCK(); return status; } pjmedia_event_unsubscribe(NULL, &call_media_on_event, call_med, w->vp_cap); /* Disconnect the old capture device to stream encoding port */ status = pjsua_vid_conf_disconnect(w->cap_slot, call_med->strm.v.strm_enc_slot); if (status != PJ_SUCCESS) { PJSUA_UNLOCK(); return status; } /* = Attach stream port to the new capture device = */ /* Note: calling pjsua_vid_preview_get_win() even though * create_vid_win() will automatically create the window * if it doesn't exist, because create_vid_win() will modify * existing window SHOW/HIDE value. */ new_wid = vid_preview_get_win(cap_dev, PJ_FALSE); if (new_wid == PJSUA_INVALID_ID) { pjsua_acc *acc = &pjsua_var.acc[call_med->call->acc_id]; /* Create preview video window */ status = create_vid_win(PJSUA_WND_TYPE_PREVIEW, &media_port->info.fmt, call_med->strm.v.rdr_dev, cap_dev, PJSUA_HIDE_WINDOW, acc->cfg.vid_wnd_flags, NULL, &new_wid); if (status != PJ_SUCCESS) goto on_error; } inc_vid_win(new_wid); new_w = &pjsua_var.win[new_wid]; if (new_w->vp_rend) { /* Start renderer */ status = pjmedia_vid_port_start(new_w->vp_rend); if (status != PJ_SUCCESS) goto on_error; } #if ENABLE_EVENT pjmedia_event_subscribe(NULL, &call_media_on_event, call_med, new_w->vp_cap); #endif /* Start capturer */ if (!pjmedia_vid_port_is_running(new_w->vp_cap)) { status = pjmedia_vid_port_start(new_w->vp_cap); if (status != PJ_SUCCESS) goto on_error; } /* Connect capturer to stream encoding port (via conf) */ status = pjsua_vid_conf_connect(new_w->cap_slot, call_med->strm.v.strm_enc_slot, NULL); if (status != PJ_SUCCESS) goto on_error; /* Finally */ call_med->strm.v.cap_dev = cap_dev; call_med->strm.v.cap_win_id = new_wid; dec_vid_win(wid); PJSUA_UNLOCK(); return PJ_SUCCESS; on_error: PJ_PERROR(4,(THIS_FILE, status, "Call %d: error changing capture device to %d", call->index, cap_dev)); if (new_w) { /* Unsubscribe, just in case */ pjmedia_event_unsubscribe(NULL, &call_media_on_event, call_med, new_w->vp_cap); /* Release the new capturer */ dec_vid_win(new_wid); } /* Revert back to the old capturer */ status = pjsua_vid_conf_connect(w->cap_slot, call_med->strm.v.strm_enc_slot, NULL); if (status != PJ_SUCCESS) { PJSUA_UNLOCK(); return status; } #if ENABLE_EVENT /* Resubscribe */ pjmedia_event_subscribe(NULL, &call_media_on_event, call_med, w->vp_cap); #endif PJSUA_UNLOCK(); 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) { if (call_med->strm.v.cap_win_id == PJSUA_INVALID_ID) { /* Setup the video capture first */ status = setup_vid_capture(call_med); if (status != PJ_SUCCESS) return status; } /* Resume stream in encoding direction */ status = pjmedia_vid_stream_resume(call_med->strm.v.stream, PJMEDIA_DIR_ENCODING); } else { pjsua_vid_win_id wid; pjsua_vid_win *w; /* Pause stream in encoding direction */ status = pjmedia_vid_stream_pause( call_med->strm.v.stream, PJMEDIA_DIR_ENCODING); PJSUA_LOCK(); wid = vid_preview_get_win(call_med->strm.v.cap_dev, PJ_FALSE); if (wid != PJSUA_INVALID_ID) { w = &pjsua_var.win[wid]; /* Unsubscribe event */ pjmedia_event_unsubscribe(NULL, &call_media_on_event, call_med, w->vp_cap); /* Disconnect from video conference */ pjsua_vid_conf_disconnect(w->cap_slot, call_med->strm.v.strm_enc_slot); /* Decrement ref count of the video window */ dec_vid_win(call_med->strm.v.cap_win_id); call_med->strm.v.cap_win_id = PJSUA_INVALID_ID; } PJSUA_UNLOCK(); } return status; } static pj_status_t call_send_vid_keyframe(pjsua_call *call, int med_idx) { pjsua_call_media *call_med; /* 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 media type and stream instance. */ if (call_med->type != PJMEDIA_TYPE_VIDEO || !call_med->strm.v.stream) return PJ_EINVAL; return pjmedia_vid_stream_send_keyframe(call_med->strm.v.stream); } /* * 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; pjsip_dialog *dlg = NULL; 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); PJ_ASSERT_RETURN(op != PJSUA_CALL_VID_STRM_NO_OP, PJ_EINVAL); PJ_LOG(4,(THIS_FILE, "Call %d: set video stream, op=%d", call_id, op)); pj_log_push_indent(); status = acquire_call("pjsua_call_set_vid_strm()", call_id, &call, &dlg); if (status != PJ_SUCCESS) goto on_return; if (param) { param_ = *param; } else { pjsua_call_vid_strm_op_param_default(¶m_); } /* 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; case PJSUA_CALL_VID_STRM_SEND_KEYFRAME: status = call_send_vid_keyframe(call, param_.med_idx); break; default: status = PJ_EINVALIDOP; break; } on_return: if (dlg) pjsip_dlg_dec_lock(dlg); pj_log_pop_indent(); 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; } /* * Determine if video stream for the specified call is currently running * for the specified direction. */ PJ_DEF(pj_bool_t) pjsua_call_vid_stream_is_running( pjsua_call_id call_id, int med_idx, pjmedia_dir dir) { pjsua_call *call; pjsua_call_media *call_med; PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, PJ_EINVAL); /* Verify and normalize media index */ if (med_idx == -1) { med_idx = pjsua_call_get_vid_stream_idx(call_id); } call = &pjsua_var.calls[call_id]; PJ_ASSERT_RETURN(med_idx >= 0 && med_idx < (int)call->med_cnt, PJ_EINVAL); call_med = &call->media[med_idx]; /* Verify if the stream is transmitting video */ if (call_med->type != PJMEDIA_TYPE_VIDEO || (call_med->dir & dir) == 0 || !call_med->strm.v.stream) { return PJ_FALSE; } return pjmedia_vid_stream_is_running(call_med->strm.v.stream, dir); } /***************************************************************************** * Video conference */ /* * Get current number of active ports in the bridge. */ PJ_DEF(unsigned) pjsua_vid_conf_get_active_ports(void) { return pjmedia_vid_conf_get_port_count(pjsua_var.vid_conf); } /* * Enumerate all video conference ports. */ PJ_DEF(pj_status_t) pjsua_vid_conf_enum_ports( pjsua_conf_port_id id[], unsigned *count) { return pjmedia_vid_conf_enum_ports(pjsua_var.vid_conf, (unsigned*)id, count); } /* * Get information about the specified video conference port */ PJ_DEF(pj_status_t) pjsua_vid_conf_get_port_info( pjsua_conf_port_id port_id, pjsua_vid_conf_port_info *info) { pjmedia_vid_conf_port_info cinfo; unsigned i; pj_status_t status; status = pjmedia_vid_conf_get_port_info(pjsua_var.vid_conf, (unsigned)port_id, &cinfo); if (status != PJ_SUCCESS) return status; pj_bzero(info, sizeof(*info)); info->slot_id = port_id; info->name = cinfo.name; pjmedia_format_copy(&info->format, &cinfo.format); /* Build array of listeners */ info->listener_cnt = cinfo.listener_cnt; for (i=0; ilisteners[i] = cinfo.listener_slots[i]; } /* Build array of transmitters */ info->transmitter_cnt = cinfo.transmitter_cnt; for (i=0; itransmitters[i] = cinfo.transmitter_slots[i]; } return PJ_SUCCESS; } /* * Add arbitrary video media port to PJSUA's video conference bridge. */ PJ_DEF(pj_status_t) pjsua_vid_conf_add_port( pj_pool_t *pool, pjmedia_port *port, const void *param, pjsua_conf_port_id *p_id) { pj_status_t status; PJ_UNUSED_ARG(param); status = pjmedia_vid_conf_add_port(pjsua_var.vid_conf, pool, port, NULL, NULL, (unsigned*)p_id); if (status != PJ_SUCCESS) { if (p_id) *p_id = PJSUA_INVALID_ID; } return status; } /* * Remove arbitrary slot from the video conference bridge. */ PJ_DEF(pj_status_t) pjsua_vid_conf_remove_port(pjsua_conf_port_id id) { return pjmedia_vid_conf_remove_port(pjsua_var.vid_conf, (unsigned)id); } /* * Establish unidirectional video flow from souce to sink. */ PJ_DEF(pj_status_t) pjsua_vid_conf_connect( pjsua_conf_port_id source, pjsua_conf_port_id sink, const void *param) { PJ_UNUSED_ARG(param); return pjmedia_vid_conf_connect_port(pjsua_var.vid_conf, source, sink, NULL); } /* * Disconnect video flow from the source to destination port. */ PJ_DEF(pj_status_t) pjsua_vid_conf_disconnect(pjsua_conf_port_id source, pjsua_conf_port_id sink) { return pjmedia_vid_conf_disconnect_port(pjsua_var.vid_conf, source, sink); } /* * Get the video window associated with the call. */ PJ_DEF(pjsua_vid_win_id) pjsua_call_get_vid_win(pjsua_call_id call_id) { pjsua_call *call; pjsua_vid_win_id wid = PJSUA_INVALID_ID; unsigned i; PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, PJ_EINVAL); /* Use PJSUA_LOCK() instead of acquire_call(): * https://trac.pjsip.org/repos/ticket/1371 */ PJSUA_LOCK(); if (!pjsua_call_is_active(call_id)) goto on_return; call = &pjsua_var.calls[call_id]; for (i = 0; i < call->med_cnt; ++i) { if (call->media[i].type == PJMEDIA_TYPE_VIDEO && (call->media[i].dir & PJMEDIA_DIR_DECODING)) { wid = call->media[i].strm.v.rdr_win_id; break; } } on_return: PJSUA_UNLOCK(); return wid; } /* * Get the video conference port identification associated with the call. */ PJ_DEF(pjsua_conf_port_id) pjsua_call_get_vid_conf_port( pjsua_call_id call_id, pjmedia_dir dir) { pjsua_call *call; pjsua_conf_port_id port_id = PJSUA_INVALID_ID; unsigned i; PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, PJ_EINVAL); PJ_ASSERT_RETURN(dir==PJMEDIA_DIR_ENCODING || dir==PJMEDIA_DIR_DECODING, PJ_EINVAL); /* Use PJSUA_LOCK() instead of acquire_call(): * https://trac.pjsip.org/repos/ticket/1371 */ PJSUA_LOCK(); if (!pjsua_call_is_active(call_id)) goto on_return; call = &pjsua_var.calls[call_id]; for (i = 0; i < call->med_cnt; ++i) { if (call->media[i].type == PJMEDIA_TYPE_VIDEO && (call->media[i].dir & dir)) { port_id = (dir==PJMEDIA_DIR_ENCODING)? call->media[i].strm.v.strm_enc_slot : call->media[i].strm.v.strm_dec_slot; break; } } on_return: PJSUA_UNLOCK(); return port_id; } #endif /* PJSUA_HAS_VIDEO */ #endif /* PJSUA_MEDIA_HAS_PJMEDIA */