/* Common initialization for both audio and video SDP media line */ static pj_status_t init_sdp_media(pjmedia_sdp_media *m, pj_pool_t *pool, const pj_str_t *media_type, const pjmedia_sock_info *sock_info) { char tmp_addr[PJ_INET6_ADDRSTRLEN]; pjmedia_sdp_attr *attr; const pj_sockaddr *addr; pj_strdup(pool, &m->desc.media, media_type); addr = &sock_info->rtp_addr_name; /* Validate address family */ PJ_ASSERT_RETURN(addr->addr.sa_family == pj_AF_INET() || addr->addr.sa_family == pj_AF_INET6(), PJ_EAFNOTSUP); /* SDP connection line */ m->conn = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_conn); m->conn->net_type = STR_IN; m->conn->addr_type = (addr->addr.sa_family==pj_AF_INET())? STR_IP4:STR_IP6; pj_sockaddr_print(addr, tmp_addr, sizeof(tmp_addr), 0); pj_strdup2(pool, &m->conn->addr, tmp_addr); /* Port and transport in media description */ m->desc.port = pj_sockaddr_get_port(addr); m->desc.port_count = 1; pj_strdup (pool, &m->desc.transport, &STR_RTP_AVP); /* Add "rtcp" attribute */ #if defined(PJMEDIA_HAS_RTCP_IN_SDP) && PJMEDIA_HAS_RTCP_IN_SDP!=0 if (sock_info->rtcp_addr_name.addr.sa_family != 0) { attr = pjmedia_sdp_attr_create_rtcp(pool, &sock_info->rtcp_addr_name); if (attr) pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr); } #endif /* Add sendrecv attribute. */ attr = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_attr); attr->name = STR_SENDRECV; m->attr[m->attr_count++] = attr; return PJ_SUCCESS; }
/** * Create a SDP session description that describes the endpoint * capability. */ PJ_DEF(pj_status_t) pjmedia_endpt_create_sdp( pjmedia_endpt *endpt, pj_pool_t *pool, unsigned stream_cnt, const pjmedia_sock_info sock_info[], pjmedia_sdp_session **p_sdp ) { pj_time_val tv; unsigned i; const pj_sockaddr *addr0; pjmedia_sdp_session *sdp; pjmedia_sdp_media *m; pjmedia_sdp_attr *attr; /* Sanity check arguments */ PJ_ASSERT_RETURN(endpt && pool && p_sdp && stream_cnt, PJ_EINVAL); /* Check that there are not too many codecs */ PJ_ASSERT_RETURN(endpt->codec_mgr.codec_cnt <= PJMEDIA_MAX_SDP_FMT, PJ_ETOOMANY); /* Create and initialize basic SDP session */ sdp = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_session); addr0 = &sock_info[0].rtp_addr_name; pj_gettimeofday(&tv); sdp->origin.user = pj_str("-"); sdp->origin.version = sdp->origin.id = tv.sec + 2208988800UL; sdp->origin.net_type = STR_IN; if (addr0->addr.sa_family == pj_AF_INET()) { sdp->origin.addr_type = STR_IP4; pj_strdup2(pool, &sdp->origin.addr, pj_inet_ntoa(addr0->ipv4.sin_addr)); } else if (addr0->addr.sa_family == pj_AF_INET6()) { char tmp_addr[PJ_INET6_ADDRSTRLEN]; sdp->origin.addr_type = STR_IP6; pj_strdup2(pool, &sdp->origin.addr, pj_sockaddr_print(addr0, tmp_addr, sizeof(tmp_addr), 0)); } else { pj_assert(!"Invalid address family"); return PJ_EAFNOTSUP; } sdp->name = STR_SDP_NAME; /* Since we only support one media stream at present, put the * SDP connection line in the session level. */ sdp->conn = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_conn); sdp->conn->net_type = sdp->origin.net_type; sdp->conn->addr_type = sdp->origin.addr_type; sdp->conn->addr = sdp->origin.addr; /* SDP time and attributes. */ sdp->time.start = sdp->time.stop = 0; sdp->attr_count = 0; /* Create media stream 0: */ sdp->media_count = 1; m = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_media); sdp->media[0] = m; /* Standard media info: */ pj_strdup(pool, &m->desc.media, &STR_AUDIO); m->desc.port = pj_sockaddr_get_port(addr0); m->desc.port_count = 1; pj_strdup (pool, &m->desc.transport, &STR_RTP_AVP); /* Init media line and attribute list. */ m->desc.fmt_count = 0; m->attr_count = 0; /* Add "rtcp" attribute */ #if defined(PJMEDIA_HAS_RTCP_IN_SDP) && PJMEDIA_HAS_RTCP_IN_SDP!=0 if (sock_info->rtcp_addr_name.addr.sa_family != 0) { attr = pjmedia_sdp_attr_create_rtcp(pool, &sock_info->rtcp_addr_name); if (attr) pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr); } #endif /* Add format, rtpmap, and fmtp (when applicable) for each codec */ for (i=0; i<endpt->codec_mgr.codec_cnt; ++i) { pjmedia_codec_info *codec_info; pjmedia_sdp_rtpmap rtpmap; char tmp_param[3]; pjmedia_sdp_attr *attr; pjmedia_codec_param codec_param; pj_str_t *fmt; if (endpt->codec_mgr.codec_desc[i].prio == PJMEDIA_CODEC_PRIO_DISABLED) break; codec_info = &endpt->codec_mgr.codec_desc[i].info; pjmedia_codec_mgr_get_default_param(&endpt->codec_mgr, codec_info, &codec_param); fmt = &m->desc.fmt[m->desc.fmt_count++]; fmt->ptr = (char*) pj_pool_alloc(pool, 8); fmt->slen = pj_utoa(codec_info->pt, fmt->ptr); rtpmap.pt = *fmt; rtpmap.enc_name = codec_info->encoding_name; #if defined(PJMEDIA_HANDLE_G722_MPEG_BUG) && (PJMEDIA_HANDLE_G722_MPEG_BUG != 0) if (codec_info->pt == PJMEDIA_RTP_PT_G722) rtpmap.clock_rate = 8000; else rtpmap.clock_rate = codec_info->clock_rate; #else rtpmap.clock_rate = codec_info->clock_rate; #endif /* For audio codecs, rtpmap parameters denotes the number * of channels, which can be omited if the value is 1. */ if (codec_info->type == PJMEDIA_TYPE_AUDIO && codec_info->channel_cnt > 1) { /* Can only support one digit channel count */ pj_assert(codec_info->channel_cnt < 10); tmp_param[0] = (char)('0' + codec_info->channel_cnt); rtpmap.param.ptr = tmp_param; rtpmap.param.slen = 1; } else { rtpmap.param.slen = 0; } if (codec_info->pt >= 96 || pjmedia_add_rtpmap_for_static_pt) { pjmedia_sdp_rtpmap_to_attr(pool, &rtpmap, &attr); m->attr[m->attr_count++] = attr; } /* Add fmtp params */ if (codec_param.setting.dec_fmtp.cnt > 0) { enum { MAX_FMTP_STR_LEN = 160 }; char buf[MAX_FMTP_STR_LEN]; unsigned buf_len = 0, i; pjmedia_codec_fmtp *dec_fmtp = &codec_param.setting.dec_fmtp; /* Print codec PT */ buf_len += pj_ansi_snprintf(buf, MAX_FMTP_STR_LEN - buf_len, "%d", codec_info->pt); for (i = 0; i < dec_fmtp->cnt; ++i) { unsigned test_len = 2; /* Check if buf still available */ test_len = dec_fmtp->param[i].val.slen + dec_fmtp->param[i].name.slen; if (test_len + buf_len >= MAX_FMTP_STR_LEN) return PJ_ETOOBIG; /* Print delimiter */ buf_len += pj_ansi_snprintf(&buf[buf_len], MAX_FMTP_STR_LEN - buf_len, (i == 0?" ":";")); /* Print an fmtp param */ if (dec_fmtp->param[i].name.slen) buf_len += pj_ansi_snprintf( &buf[buf_len], MAX_FMTP_STR_LEN - buf_len, "%.*s=%.*s", (int)dec_fmtp->param[i].name.slen, dec_fmtp->param[i].name.ptr, (int)dec_fmtp->param[i].val.slen, dec_fmtp->param[i].val.ptr); else buf_len += pj_ansi_snprintf(&buf[buf_len], MAX_FMTP_STR_LEN - buf_len, "%.*s", (int)dec_fmtp->param[i].val.slen, dec_fmtp->param[i].val.ptr); } attr = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_attr); attr->name = pj_str("fmtp"); attr->value = pj_strdup3(pool, buf); m->attr[m->attr_count++] = attr; } } /* Add sendrecv attribute. */ attr = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_attr); attr->name = STR_SENDRECV; m->attr[m->attr_count++] = attr; #if defined(PJMEDIA_RTP_PT_TELEPHONE_EVENTS) && \ PJMEDIA_RTP_PT_TELEPHONE_EVENTS != 0 /* * Add support telephony event */ m->desc.fmt[m->desc.fmt_count++] = pj_str(PJMEDIA_RTP_PT_TELEPHONE_EVENTS_STR); /* Add rtpmap. */ attr = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_attr); attr->name = pj_str("rtpmap"); attr->value = pj_str(PJMEDIA_RTP_PT_TELEPHONE_EVENTS_STR " telephone-event/8000"); m->attr[m->attr_count++] = attr; /* Add fmtp */ attr = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_attr); attr->name = pj_str("fmtp"); attr->value = pj_str(PJMEDIA_RTP_PT_TELEPHONE_EVENTS_STR " 0-15"); m->attr[m->attr_count++] = attr; #endif /* Done */ *p_sdp = sdp; return PJ_SUCCESS; }
/* Encode ICE information in SDP */ static pj_status_t encode_session_in_sdp(struct transport_ice *tp_ice, pj_pool_t *sdp_pool, pjmedia_sdp_session *sdp_local, unsigned media_index, unsigned comp_cnt, pj_bool_t restart_session) { enum { ATTR_BUF_LEN = 160, /* Max len of a=candidate attr */ RATTR_BUF_LEN= 160 /* Max len of a=remote-candidates attr */ }; pjmedia_sdp_media *m = sdp_local->media[media_index]; pj_str_t local_ufrag, local_pwd; pjmedia_sdp_attr *attr; pj_status_t status; /* Must have a session */ PJ_ASSERT_RETURN(pj_ice_strans_has_sess(tp_ice->ice_st), PJ_EBUG); /* Get ufrag and pwd from current session */ pj_ice_strans_get_ufrag_pwd(tp_ice->ice_st, &local_ufrag, &local_pwd, NULL, NULL); /* The listing of candidates depends on whether ICE has completed * or not. When ICE has completed: * * 9.1.2.2: Existing Media Streams with ICE Completed * The agent MUST include a candidate attributes for candidates * matching the default destination for each component of the * media stream, and MUST NOT include any other candidates. * * When ICE has not completed, we shall include all candidates. * * Except when we have detected that remote is offering to restart * the session, in this case we will answer with full ICE SDP and * new ufrag/pwd pair. */ if (!restart_session && pj_ice_strans_sess_is_complete(tp_ice->ice_st)) { const pj_ice_sess_check *check; char *attr_buf; pjmedia_sdp_conn *conn; pjmedia_sdp_attr *a_rtcp; pj_str_t rem_cand; unsigned comp; /* Encode ice-ufrag attribute */ attr = pjmedia_sdp_attr_create(sdp_pool, STR_ICE_UFRAG.ptr, &local_ufrag); pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr); /* Encode ice-pwd attribute */ attr = pjmedia_sdp_attr_create(sdp_pool, STR_ICE_PWD.ptr, &local_pwd); pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr); /* Prepare buffer */ attr_buf = (char*) pj_pool_alloc(sdp_pool, ATTR_BUF_LEN); rem_cand.ptr = (char*) pj_pool_alloc(sdp_pool, RATTR_BUF_LEN); rem_cand.slen = 0; /* 9.1.2.2: Existing Media Streams with ICE Completed * The default destination for media (i.e., the values of * the IP addresses and ports in the m and c line used for * that media stream) MUST be the local candidate from the * highest priority nominated pair in the valid list for each * component. */ check = pj_ice_strans_get_valid_pair(tp_ice->ice_st, 1); if (check == NULL) { pj_assert(!"Shouldn't happen"); return PJ_EBUG; } /* Override connection line address and media port number */ conn = m->conn; if (conn == NULL) conn = sdp_local->conn; conn->addr.ptr = (char*) pj_pool_alloc(sdp_pool, PJ_INET6_ADDRSTRLEN); pj_sockaddr_print(&check->lcand->addr, conn->addr.ptr, PJ_INET6_ADDRSTRLEN, 0); conn->addr.slen = pj_ansi_strlen(conn->addr.ptr); m->desc.port = pj_sockaddr_get_port(&check->lcand->addr); /* Override address RTCP attribute if it's present */ if (comp_cnt == 2 && (check = pj_ice_strans_get_valid_pair(tp_ice->ice_st, COMP_RTCP)) != NULL && (a_rtcp = pjmedia_sdp_attr_find(m->attr_count, m->attr, &STR_RTCP, 0)) != NULL) { pjmedia_sdp_attr_remove(&m->attr_count, m->attr, a_rtcp); a_rtcp = pjmedia_sdp_attr_create_rtcp(sdp_pool, &check->lcand->addr); if (a_rtcp) pjmedia_sdp_attr_add(&m->attr_count, m->attr, a_rtcp); } /* Encode only candidates matching the default destination * for each component */ for (comp=0; comp < comp_cnt; ++comp) { int len; pj_str_t value; /* Get valid pair for this component */ check = pj_ice_strans_get_valid_pair(tp_ice->ice_st, comp+1); if (check == NULL) continue; /* Print and add local candidate in the pair */ value.ptr = attr_buf; value.slen = print_sdp_cand_attr(attr_buf, ATTR_BUF_LEN, check->lcand); if (value.slen < 0) { pj_assert(!"Not enough attr_buf to print candidate"); return PJ_EBUG; } attr = pjmedia_sdp_attr_create(sdp_pool, STR_CANDIDATE.ptr, &value); pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr); /* Append to a=remote-candidates attribute */ if (pj_ice_strans_get_role(tp_ice->ice_st) == PJ_ICE_SESS_ROLE_CONTROLLING) { char rem_addr[PJ_INET6_ADDRSTRLEN]; pj_sockaddr_print(&check->rcand->addr, rem_addr, sizeof(rem_addr), 0); len = pj_ansi_snprintf( rem_cand.ptr + rem_cand.slen, RATTR_BUF_LEN - rem_cand.slen, "%s%u %s %u", (rem_cand.slen==0? "" : " "), comp+1, rem_addr, pj_sockaddr_get_port(&check->rcand->addr) ); if (len < 1 || len >= RATTR_BUF_LEN) { pj_assert(!"Not enough buffer to print " "remote-candidates"); return PJ_EBUG; } rem_cand.slen += len; } } /* 9.1.2.2: Existing Media Streams with ICE Completed * In addition, if the agent is controlling, it MUST include * the a=remote-candidates attribute for each media stream * whose check list is in the Completed state. The attribute * contains the remote candidates from the highest priority * nominated pair in the valid list for each component of that * media stream. */ if (pj_ice_strans_get_role(tp_ice->ice_st) == PJ_ICE_SESS_ROLE_CONTROLLING) { attr = pjmedia_sdp_attr_create(sdp_pool, STR_REM_CAND.ptr, &rem_cand); pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr); } } else if (pj_ice_strans_has_sess(tp_ice->ice_st)) { /* Encode all candidates to SDP media */ char *attr_buf; unsigned comp; /* If ICE is not restarted, encode current ICE ufrag/pwd. * Otherwise generate new one. */ if (!restart_session) { attr = pjmedia_sdp_attr_create(sdp_pool, STR_ICE_UFRAG.ptr, &local_ufrag); pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr); attr = pjmedia_sdp_attr_create(sdp_pool, STR_ICE_PWD.ptr, &local_pwd); pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr); } else { pj_str_t str; str.slen = PJ_ICE_UFRAG_LEN; str.ptr = (char*) pj_pool_alloc(sdp_pool, str.slen); pj_create_random_string(str.ptr, str.slen); attr = pjmedia_sdp_attr_create(sdp_pool, STR_ICE_UFRAG.ptr, &str); pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr); str.ptr = (char*) pj_pool_alloc(sdp_pool, str.slen); pj_create_random_string(str.ptr, str.slen); attr = pjmedia_sdp_attr_create(sdp_pool, STR_ICE_PWD.ptr, &str); pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr); } /* Create buffer to encode candidates as SDP attribute */ attr_buf = (char*) pj_pool_alloc(sdp_pool, ATTR_BUF_LEN); for (comp=0; comp < comp_cnt; ++comp) { unsigned cand_cnt; pj_ice_sess_cand cand[PJ_ICE_ST_MAX_CAND]; unsigned i; cand_cnt = PJ_ARRAY_SIZE(cand); status = pj_ice_strans_enum_cands(tp_ice->ice_st, comp+1, &cand_cnt, cand); if (status != PJ_SUCCESS) return status; for (i=0; i<cand_cnt; ++i) { pj_str_t value; value.slen = print_sdp_cand_attr(attr_buf, ATTR_BUF_LEN, &cand[i]); if (value.slen < 0) { pj_assert(!"Not enough attr_buf to print candidate"); return PJ_EBUG; } value.ptr = attr_buf; attr = pjmedia_sdp_attr_create(sdp_pool, STR_CANDIDATE.ptr, &value); pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr); } } } else { /* ICE has failed, application should have terminated this call */ } /* Removing a=rtcp line when there is only one component. */ if (comp_cnt == 1) { attr = pjmedia_sdp_attr_find(m->attr_count, m->attr, &STR_RTCP, NULL); if (attr) pjmedia_sdp_attr_remove(&m->attr_count, m->attr, attr); } return PJ_SUCCESS; }