创建基于现有SDP媒体频道
/* Create SDP based on the current media channel. Note that, this function
* will not modify the media channel, so when receiving new offer or
* updating media count (via call setting), media channel must be reinit'd
* (using pjsua_media_channel_init()) first before calling this function.
创建基于现有SDP媒体频道。请注意,此功能
*不会修改媒体频道,所以在接受新的报价或
*更新媒体计数(通过调用设置),媒体必须勒会
*(用pjsua_media_channel_init())第一次调用这个函数之前。 */
pj_status_t pjsua_media_channel_create_sdp(pjsua_call_id call_id,
pj_pool_t *pool,
const pjmedia_sdp_session *rem_sdp,
pjmedia_sdp_session **p_sdp,
int *sip_err_code)
{
enum { MAX_MEDIA = PJSUA_MAX_CALL_MEDIA };
pjmedia_sdp_session *sdp;
pj_sockaddr origin;
pjsua_call *call = &pjsua_var.calls[call_id];
pjmedia_sdp_neg_state sdp_neg_state = PJMEDIA_SDP_NEG_STATE_NULL;
unsigned mi;
unsigned tot_bandw_tias = 0;
pj_status_t status;
if (pjsua_get_state() != PJSUA_STATE_RUNNING)
return PJ_EBUSY;
#if 0
// This function should not really change the media channel.
if (rem_sdp) {
/* If this is a re-offer, let's re-initialize media as remote may
* add or remove media
*/
if (call->inv && call->inv->state == PJSIP_INV_STATE_CONFIRMED) {
status = pjsua_media_channel_init(call_id, PJSIP_ROLE_UAS,
call->secure_level, pool,
rem_sdp, sip_err_code,
PJ_FALSE, NULL);
if (status != PJ_SUCCESS)
return status;
}
} else {
/* Audio is first in our offer, by convention */
// The audio_idx should not be changed here, as this function may be
// called in generating re-offer and the current active audio index
// can be anywhere.
//call->audio_idx = 0;
}
#endif
#if 0
// Since r3512, old-style hold should have got transport, created by
// pjsua_media_channel_init() in initial offer/answer or remote reoffer.
/* Create media if it's not created. This could happen when call is
* currently on-hold (with the old style hold)
*/
if (call->media[call->audio_idx].tp == NULL) {
pjsip_role_e role;
role = (rem_sdp ? PJSIP_ROLE_UAS : PJSIP_ROLE_UAC);
status = pjsua_media_channel_init(call_id, role, call->secure_level,
pool, rem_sdp, sip_err_code);
if (status != PJ_SUCCESS)
return status;
}
#endif
/* Get SDP negotiator state */
if (call->inv && call->inv->neg)
sdp_neg_state = pjmedia_sdp_neg_get_state(call->inv->neg);
/* Get one address to use in the origin field */
pj_bzero(&origin, sizeof(origin));
for (mi=0; mi<call->med_prov_cnt; ++mi) {
pjmedia_transport_info tpinfo;
if (call->media_prov[mi].tp == NULL)
continue;
pjmedia_transport_info_init(&tpinfo);
pjmedia_transport_get_info(call->media_prov[mi].tp, &tpinfo);
pj_sockaddr_cp(&origin, &tpinfo.sock_info.rtp_addr_name);
break;
}
/* Create the base (blank) SDP */
status = pjmedia_endpt_create_base_sdp(pjsua_var.med_endpt, pool, NULL,
&origin, &sdp);
if (status != PJ_SUCCESS)
return status;
/* Process each media line */
for (mi=0; mi<call->med_prov_cnt; ++mi) {
pjsua_call_media *call_med = &call->media_prov[mi];
pjmedia_sdp_media *m = NULL;
pjmedia_transport_info tpinfo;
unsigned i;
if (rem_sdp && mi >= rem_sdp->media_count) {
/* Remote might have removed some media lines. */
/* Note that we must not modify the current active media
* (e.g: stop stream, close/cleanup media transport), as if
* SDP nego fails, the current active media should be maintained.
* Also note that our media count should never decrease, even when
* remote removed some media lines.
*/
break;
}
if (call_med->tp == NULL || call_med->tp_st == PJSUA_MED_TP_DISABLED)
{
/*
* This media is disabled. Just create a valid SDP with zero
* port.
*/
if (rem_sdp) {
/* Just clone the remote media and deactivate it */
m = pjmedia_sdp_media_clone_deactivate(pool,
rem_sdp->media[mi]);
} else {
m = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_media);
m->desc.transport = pj_str("RTP/AVP");
m->desc.fmt_count = 1;
switch (call_med->type) {
case PJMEDIA_TYPE_AUDIO:
m->desc.media = pj_str("audio");
m->desc.fmt[0] = pj_str("0");
break;
case PJMEDIA_TYPE_VIDEO:
m->desc.media = pj_str("video");
m->desc.fmt[0] = pj_str("31");
break;
default:
/* This must be us generating re-offer, and some unknown
* media may exist, so just clone from active local SDP
* (and it should have been deactivated already).
*/
pj_assert(call->inv && call->inv->neg &&
sdp_neg_state == PJMEDIA_SDP_NEG_STATE_DONE);
{
const pjmedia_sdp_session *s_;
pjmedia_sdp_neg_get_active_local(call->inv->neg, &s_);
pj_assert(mi < s_->media_count);
m = pjmedia_sdp_media_clone(pool, s_->media[mi]);
m->desc.port = 0;
}
break;
}
}
/* Add connection line, if none */
if (m->conn == NULL && sdp->conn == NULL) {
pj_bool_t use_ipv6;
use_ipv6 = (pjsua_var.acc[call->acc_id].cfg.ipv6_media_use !=
PJSUA_IPV6_DISABLED);
m->conn = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_conn);
m->conn->net_type = pj_str("IN");
if (use_ipv6) {
m->conn->addr_type = pj_str("IP6");
m->conn->addr = pj_str("::1");
} else {
m->conn->addr_type = pj_str("IP4");
m->conn->addr = pj_str("127.0.0.1");
}
}
sdp->media[sdp->media_count++] = m;
continue;
}
/* Get transport address info */
pjmedia_transport_info_init(&tpinfo);
pjmedia_transport_get_info(call_med->tp, &tpinfo);
/* Ask pjmedia endpoint to create SDP media line */
switch (call_med->type) {
case PJMEDIA_TYPE_AUDIO:
status = pjmedia_endpt_create_audio_sdp(pjsua_var.med_endpt, pool,
&tpinfo.sock_info, 0, &m);
break;
#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
case PJMEDIA_TYPE_VIDEO:
status = pjmedia_endpt_create_video_sdp(pjsua_var.med_endpt, pool,
&tpinfo.sock_info, 0, &m);
break;
#endif
default:
pj_assert(!"Invalid call_med media type");
return PJ_EBUG;
}
if (status != PJ_SUCCESS)
return status;
sdp->media[sdp->media_count++] = m;
/* Give to transport */
status = pjmedia_transport_encode_sdp(call_med->tp, pool,
sdp, rem_sdp, mi);
if (status != PJ_SUCCESS) {
if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE;
return status;
}
#if PJSUA_SDP_SESS_HAS_CONN
/* Copy c= line of the first media to session level,
* if there's none.
*/
if (sdp->conn == NULL) {
sdp->conn = pjmedia_sdp_conn_clone(pool, m->conn);
}
#endif
/* Find media bandwidth info */
for (i = 0; i < m->bandw_count; ++i) {
const pj_str_t STR_BANDW_MODIFIER_TIAS = { "TIAS", 4 };
if (!pj_stricmp(&m->bandw[i]->modifier, &STR_BANDW_MODIFIER_TIAS))
{
tot_bandw_tias += m->bandw[i]->value;
break;
}
}
}
/* Add NAT info in the SDP */
if (pjsua_var.ua_cfg.nat_type_in_sdp) {
pjmedia_sdp_attr *a;
pj_str_t value;
char nat_info[80];
value.ptr = nat_info;
if (pjsua_var.ua_cfg.nat_type_in_sdp == 1) {
value.slen = pj_ansi_snprintf(nat_info, sizeof(nat_info),
"%d", pjsua_var.nat_type);
} else {
const char *type_name = pj_stun_get_nat_name(pjsua_var.nat_type);
value.slen = pj_ansi_snprintf(nat_info, sizeof(nat_info),
"%d %s",
pjsua_var.nat_type,
type_name);
}
a = pjmedia_sdp_attr_create(pool, "X-nat", &value);
pjmedia_sdp_attr_add(&sdp->attr_count, sdp->attr, a);
}
/* Add bandwidth info in session level using bandwidth modifier "AS". */
if (tot_bandw_tias) {
unsigned bandw;
const pj_str_t STR_BANDW_MODIFIER_AS = { "AS", 2 };
pjmedia_sdp_bandw *b;
/* AS bandwidth = RTP bitrate + RTCP bitrate.
* RTP bitrate = payload bitrate (total TIAS) + overheads (~16kbps).
* RTCP bitrate = est. 5% of RTP bitrate.
* Note that AS bandwidth is in kbps.
*/
bandw = tot_bandw_tias + 16000;
bandw += bandw * 5 / 100;
b = PJ_POOL_ALLOC_T(pool, pjmedia_sdp_bandw);
b->modifier = STR_BANDW_MODIFIER_AS;
b->value = bandw / 1000;
sdp->bandw[sdp->bandw_count++] = b;
}
#if DISABLED_FOR_TICKET_1185 && defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
/* Check if SRTP is in optional mode and configured to use duplicated
* media, i.e: secured and unsecured version, in the SDP offer.
*/
if (!rem_sdp &&
pjsua_var.acc[call->acc_id].cfg.use_srtp == PJMEDIA_SRTP_OPTIONAL &&
pjsua_var.acc[call->acc_id].cfg.srtp_optional_dup_offer)
{
unsigned i;
for (i = 0; i < sdp->media_count; ++i) {
pjmedia_sdp_media *m = sdp->media[i];
/* Check if this media is unsecured but has SDP "crypto"
* attribute.
*/
if (pj_stricmp2(&m->desc.transport, "RTP/AVP") == 0 &&
pjmedia_sdp_media_find_attr2(m, "crypto", NULL) != NULL)
{
if (i == (unsigned)call->audio_idx &&
sdp_neg_state == PJMEDIA_SDP_NEG_STATE_DONE)
{
/* This is a session update, and peer has chosen the
* unsecured version, so let's make this unsecured too.
*/
pjmedia_sdp_media_remove_all_attr(m, "crypto");
} else {
/* This is new offer, duplicate media so we'll have
* secured (with "RTP/SAVP" transport) and and unsecured
* versions.
*/
pjmedia_sdp_media *new_m;
/* Duplicate this media and apply secured transport */
new_m = pjmedia_sdp_media_clone(pool, m);
pj_strdup2(pool, &new_m->desc.transport, "RTP/SAVP");
/* Remove the "crypto" attribute in the unsecured media */
pjmedia_sdp_media_remove_all_attr(m, "crypto");
/* Insert the new media before the unsecured media */
if (sdp->media_count < PJMEDIA_MAX_SDP_MEDIA) {
pj_array_insert(sdp->media, sizeof(new_m),
sdp->media_count, i, &new_m);
++sdp->media_count;
++i;
}
}
}
}
}
#endif
call->rem_offerer = (rem_sdp != NULL);
/* Notify application */
if (pjsua_var.ua_cfg.cb.on_call_sdp_created) {
(*pjsua_var.ua_cfg.cb.on_call_sdp_created)(call_id, sdp,
pool, rem_sdp);
}
*p_sdp = sdp;
return PJ_SUCCESS;
}