📄 sdp_neg.c
字号:
/* $Id: sdp_neg.c 1266 2007-05-11 15:14:34Z bennylp $ */
/*
* Copyright (C) 2003-2007 Benny Prijono <benny@prijono.org>
*
* 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 <pjmedia/sdp_neg.h>
#include <pjmedia/sdp.h>
#include <pjmedia/errno.h>
#include <pj/assert.h>
#include <pj/pool.h>
#include <pj/string.h>
#include <pj/ctype.h>
#include <pj/array.h>
/**
* This structure describes SDP media negotiator.
*/
struct pjmedia_sdp_neg
{
pjmedia_sdp_neg_state state; /**< Negotiator state. */
pj_bool_t prefer_remote_codec_order;
pj_bool_t has_remote_answer;
pj_bool_t answer_was_remote;
pjmedia_sdp_session *initial_sdp, /**< Initial local SDP */
*active_local_sdp, /**< Currently active local SDP. */
*active_remote_sdp, /**< Currently active remote's. */
*neg_local_sdp, /**< Temporary local SDP. */
*neg_remote_sdp; /**< Temporary remote SDP. */
};
static const char *state_str[] =
{
"STATE_NULL",
"STATE_LOCAL_OFFER",
"STATE_REMOTE_OFFER",
"STATE_WAIT_NEGO",
"STATE_DONE",
};
/*
* Get string representation of negotiator state.
*/
PJ_DEF(const char*) pjmedia_sdp_neg_state_str(pjmedia_sdp_neg_state state)
{
if (state >=0 && state < (pjmedia_sdp_neg_state)PJ_ARRAY_SIZE(state_str))
return state_str[state];
return "<?UNKNOWN?>";
}
/*
* Create with local offer.
*/
PJ_DEF(pj_status_t)
pjmedia_sdp_neg_create_w_local_offer( pj_pool_t *pool,
const pjmedia_sdp_session *local,
pjmedia_sdp_neg **p_neg)
{
pjmedia_sdp_neg *neg;
pj_status_t status;
/* Check arguments are valid. */
PJ_ASSERT_RETURN(pool && local && p_neg, PJ_EINVAL);
*p_neg = NULL;
/* Validate local offer. */
PJ_ASSERT_RETURN((status=pjmedia_sdp_validate(local))==PJ_SUCCESS, status);
/* Create and initialize negotiator. */
neg = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_neg);
PJ_ASSERT_RETURN(neg != NULL, PJ_ENOMEM);
neg->state = PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER;
neg->prefer_remote_codec_order = PJMEDIA_SDP_NEG_PREFER_REMOTE_CODEC_ORDER;
neg->initial_sdp = pjmedia_sdp_session_clone(pool, local);
neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, local);
*p_neg = neg;
return PJ_SUCCESS;
}
/*
* Create with remote offer and initial local offer/answer.
*/
PJ_DEF(pj_status_t)
pjmedia_sdp_neg_create_w_remote_offer(pj_pool_t *pool,
const pjmedia_sdp_session *initial,
const pjmedia_sdp_session *remote,
pjmedia_sdp_neg **p_neg)
{
pjmedia_sdp_neg *neg;
pj_status_t status;
/* Check arguments are valid. */
PJ_ASSERT_RETURN(pool && remote && p_neg, PJ_EINVAL);
*p_neg = NULL;
/* Validate remote offer and initial answer */
status = pjmedia_sdp_validate(remote);
if (status != PJ_SUCCESS)
return status;
/* Create and initialize negotiator. */
neg = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_neg);
PJ_ASSERT_RETURN(neg != NULL, PJ_ENOMEM);
neg->prefer_remote_codec_order = PJMEDIA_SDP_NEG_PREFER_REMOTE_CODEC_ORDER;
neg->neg_remote_sdp = pjmedia_sdp_session_clone(pool, remote);
if (initial) {
PJ_ASSERT_RETURN((status=pjmedia_sdp_validate(initial))==PJ_SUCCESS,
status);
neg->initial_sdp = pjmedia_sdp_session_clone(pool, initial);
neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, initial);
neg->state = PJMEDIA_SDP_NEG_STATE_WAIT_NEGO;
} else {
neg->state = PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER;
}
*p_neg = neg;
return PJ_SUCCESS;
}
/*
* Set codec order preference.
*/
PJ_DEF(pj_status_t)
pjmedia_sdp_neg_set_prefer_remote_codec_order(pjmedia_sdp_neg *neg,
pj_bool_t prefer_remote)
{
PJ_ASSERT_RETURN(neg, PJ_EINVAL);
neg->prefer_remote_codec_order = prefer_remote;
return PJ_SUCCESS;
}
/*
* Get SDP negotiator state.
*/
PJ_DEF(pjmedia_sdp_neg_state)
pjmedia_sdp_neg_get_state( pjmedia_sdp_neg *neg )
{
/* Check arguments are valid. */
PJ_ASSERT_RETURN(neg != NULL, PJMEDIA_SDP_NEG_STATE_NULL);
return neg->state;
}
PJ_DEF(pj_status_t)
pjmedia_sdp_neg_get_active_local( pjmedia_sdp_neg *neg,
const pjmedia_sdp_session **local)
{
PJ_ASSERT_RETURN(neg && local, PJ_EINVAL);
PJ_ASSERT_RETURN(neg->active_local_sdp, PJMEDIA_SDPNEG_ENOACTIVE);
*local = neg->active_local_sdp;
return PJ_SUCCESS;
}
PJ_DEF(pj_status_t)
pjmedia_sdp_neg_get_active_remote( pjmedia_sdp_neg *neg,
const pjmedia_sdp_session **remote)
{
PJ_ASSERT_RETURN(neg && remote, PJ_EINVAL);
PJ_ASSERT_RETURN(neg->active_remote_sdp, PJMEDIA_SDPNEG_ENOACTIVE);
*remote = neg->active_remote_sdp;
return PJ_SUCCESS;
}
PJ_DEF(pj_bool_t)
pjmedia_sdp_neg_was_answer_remote(pjmedia_sdp_neg *neg)
{
PJ_ASSERT_RETURN(neg, PJ_FALSE);
return neg->answer_was_remote;
}
PJ_DEF(pj_status_t)
pjmedia_sdp_neg_get_neg_remote( pjmedia_sdp_neg *neg,
const pjmedia_sdp_session **remote)
{
PJ_ASSERT_RETURN(neg && remote, PJ_EINVAL);
PJ_ASSERT_RETURN(neg->neg_remote_sdp, PJMEDIA_SDPNEG_ENONEG);
*remote = neg->neg_remote_sdp;
return PJ_SUCCESS;
}
PJ_DEF(pj_status_t)
pjmedia_sdp_neg_get_neg_local( pjmedia_sdp_neg *neg,
const pjmedia_sdp_session **local)
{
PJ_ASSERT_RETURN(neg && local, PJ_EINVAL);
PJ_ASSERT_RETURN(neg->neg_local_sdp, PJMEDIA_SDPNEG_ENONEG);
*local = neg->neg_local_sdp;
return PJ_SUCCESS;
}
/*
* Modify local SDP and wait for remote answer.
*/
PJ_DEF(pj_status_t)
pjmedia_sdp_neg_modify_local_offer( pj_pool_t *pool,
pjmedia_sdp_neg *neg,
const pjmedia_sdp_session *local)
{
/* Check arguments are valid. */
PJ_ASSERT_RETURN(pool && neg && local, PJ_EINVAL);
/* Can only do this in STATE_DONE. */
PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_DONE,
PJMEDIA_SDPNEG_EINSTATE);
/* Change state to STATE_LOCAL_OFFER */
neg->state = PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER;
neg->initial_sdp = pjmedia_sdp_session_clone(pool, local);
neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, local);
return PJ_SUCCESS;
}
PJ_DEF(pj_status_t)
pjmedia_sdp_neg_send_local_offer( pj_pool_t *pool,
pjmedia_sdp_neg *neg,
const pjmedia_sdp_session **offer)
{
/* Check arguments are valid. */
PJ_ASSERT_RETURN(neg && offer, PJ_EINVAL);
*offer = NULL;
/* Can only do this in STATE_DONE or STATE_LOCAL_OFFER. */
PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_DONE ||
neg->state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER,
PJMEDIA_SDPNEG_EINSTATE);
if (neg->state == PJMEDIA_SDP_NEG_STATE_DONE) {
/* If in STATE_DONE, set the active SDP as the offer. */
PJ_ASSERT_RETURN(neg->active_local_sdp, PJMEDIA_SDPNEG_ENOACTIVE);
neg->state = PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER;
neg->neg_local_sdp = pjmedia_sdp_session_clone(pool,
neg->active_local_sdp);
*offer = neg->active_local_sdp;
} else {
/* We assume that we're in STATE_LOCAL_OFFER.
* In this case set the neg_local_sdp as the offer.
*/
*offer = neg->neg_local_sdp;
}
return PJ_SUCCESS;
}
PJ_DEF(pj_status_t)
pjmedia_sdp_neg_set_remote_answer( pj_pool_t *pool,
pjmedia_sdp_neg *neg,
const pjmedia_sdp_session *remote)
{
/* Check arguments are valid. */
PJ_ASSERT_RETURN(pool && neg && remote, PJ_EINVAL);
/* Can only do this in STATE_LOCAL_OFFER.
* If we haven't provided local offer, then rx_remote_offer() should
* be called instead of this function.
*/
PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER,
PJMEDIA_SDPNEG_EINSTATE);
/* We're ready to negotiate. */
neg->state = PJMEDIA_SDP_NEG_STATE_WAIT_NEGO;
neg->has_remote_answer = PJ_TRUE;
neg->neg_remote_sdp = pjmedia_sdp_session_clone(pool, remote);
return PJ_SUCCESS;
}
PJ_DEF(pj_status_t)
pjmedia_sdp_neg_set_remote_offer( pj_pool_t *pool,
pjmedia_sdp_neg *neg,
const pjmedia_sdp_session *remote)
{
/* Check arguments are valid. */
PJ_ASSERT_RETURN(pool && neg && remote, PJ_EINVAL);
/* Can only do this in STATE_DONE.
* If we already provide local offer, then rx_remote_answer() should
* be called instead of this function.
*/
PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_DONE,
PJMEDIA_SDPNEG_EINSTATE);
/* State now is STATE_REMOTE_OFFER. */
neg->state = PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER;
neg->neg_remote_sdp = pjmedia_sdp_session_clone(pool, remote);
return PJ_SUCCESS;
}
PJ_DEF(pj_status_t)
pjmedia_sdp_neg_set_local_answer( pj_pool_t *pool,
pjmedia_sdp_neg *neg,
const pjmedia_sdp_session *local)
{
/* Check arguments are valid. */
PJ_ASSERT_RETURN(pool && neg && local, PJ_EINVAL);
/* Can only do this in STATE_REMOTE_OFFER.
* If we already provide local offer, then rx_remote_answer() should
* be called instead of this function.
*/
PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER,
PJMEDIA_SDPNEG_EINSTATE);
/* State now is STATE_WAIT_NEGO. */
neg->state = PJMEDIA_SDP_NEG_STATE_WAIT_NEGO;
if (local)
neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, local);
else {
PJ_ASSERT_RETURN(neg->initial_sdp, PJMEDIA_SDPNEG_ENOINITIAL);
neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, neg->initial_sdp);
}
return PJ_SUCCESS;
}
PJ_DEF(pj_bool_t) pjmedia_sdp_neg_has_local_answer(pjmedia_sdp_neg *neg)
{
pj_assert(neg && neg->state==PJMEDIA_SDP_NEG_STATE_WAIT_NEGO);
return !neg->has_remote_answer;
}
/* Swap string. */
static void str_swap(pj_str_t *str1, pj_str_t *str2)
{
pj_str_t tmp = *str1;
*str1 = *str2;
*str2 = tmp;
}
static void remove_all_media_directions(pjmedia_sdp_media *m)
{
pjmedia_sdp_media_remove_all_attr(m, "inactive");
pjmedia_sdp_media_remove_all_attr(m, "sendrecv");
pjmedia_sdp_media_remove_all_attr(m, "sendonly");
pjmedia_sdp_media_remove_all_attr(m, "recvonly");
}
/* Update media direction based on peer's media direction */
static void update_media_direction(pj_pool_t *pool,
const pjmedia_sdp_media *remote,
pjmedia_sdp_media *local)
{
pjmedia_dir old_dir = PJMEDIA_DIR_ENCODING_DECODING,
new_dir;
/* Get the media direction of local SDP */
if (pjmedia_sdp_media_find_attr2(local, "sendonly", NULL))
old_dir = PJMEDIA_DIR_ENCODING;
else if (pjmedia_sdp_media_find_attr2(local, "recvonly", NULL))
old_dir = PJMEDIA_DIR_DECODING;
else if (pjmedia_sdp_media_find_attr2(local, "inactive", NULL))
old_dir = PJMEDIA_DIR_NONE;
new_dir = old_dir;
/* Adjust local media direction based on remote media direction */
if (pjmedia_sdp_media_find_attr2(remote, "inactive", NULL) != NULL) {
/* If remote has "a=inactive", then local is inactive too */
new_dir = PJMEDIA_DIR_NONE;
} else if(pjmedia_sdp_media_find_attr2(remote, "sendonly", NULL) != NULL) {
/* If remote has "a=sendonly", then set local to "recvonly" if
* it is currently "sendrecv". Otherwise if local is NOT "recvonly",
* then set local direction to "inactive".
*/
switch (old_dir) {
case PJMEDIA_DIR_ENCODING_DECODING:
new_dir = PJMEDIA_DIR_DECODING;
break;
case PJMEDIA_DIR_DECODING:
/* No change */
break;
default:
new_dir = PJMEDIA_DIR_NONE;
break;
}
} else if(pjmedia_sdp_media_find_attr2(remote, "recvonly", NULL) != NULL) {
/* If remote has "a=recvonly", then set local to "sendonly" if
* it is currently "sendrecv". Otherwise if local is NOT "sendonly",
* then set local direction to "inactive"
*/
switch (old_dir) {
case PJMEDIA_DIR_ENCODING_DECODING:
new_dir = PJMEDIA_DIR_ENCODING;
break;
case PJMEDIA_DIR_ENCODING:
/* No change */
break;
default:
new_dir = PJMEDIA_DIR_NONE;
break;
}
} else {
/* Remote indicates "sendrecv" capability. No change to local
* direction
*/
}
if (new_dir != old_dir) {
pjmedia_sdp_attr *a = NULL;
remove_all_media_directions(local);
switch (new_dir) {
case PJMEDIA_DIR_NONE:
a = pjmedia_sdp_attr_create(pool, "inactive", NULL);
break;
case PJMEDIA_DIR_ENCODING:
a = pjmedia_sdp_attr_create(pool, "sendonly", NULL);
break;
case PJMEDIA_DIR_DECODING:
a = pjmedia_sdp_attr_create(pool, "recvonly", NULL);
break;
default:
/* sendrecv */
break;
}
if (a) {
pjmedia_sdp_media_add_attr(local, a);
}
}
}
/* Update single local media description to after receiving answer
* from remote.
*/
static pj_status_t process_m_answer( pj_pool_t *pool,
pjmedia_sdp_media *offer,
pjmedia_sdp_media *answer,
pj_bool_t allow_asym)
{
unsigned i;
/* Check that the media type match our offer. */
if (pj_strcmp(&answer->desc.media, &offer->desc.media)!=0) {
/* The media type in the answer is different than the offer! */
return PJMEDIA_SDPNEG_EINVANSMEDIA;
}
/* Chec that transport in the answer match our offer. */
if (pj_strcmp(&answer->desc.transport,
&offer->desc.transport)!=0)
{
/* The transport in the answer is different than the offer! */
return PJMEDIA_SDPNEG_EINVANSTP;
}
/* Check if remote has rejected our offer */
if (answer->desc.port == 0) {
/* Remote has rejected our offer.
* Set our port to zero too in active SDP.
*/
offer->desc.port = 0;
}
/* Process direction attributes */
update_media_direction(pool, answer, offer);
/* If asymetric media is allowed, then just check that remote answer has
* codecs that are within the offer.
*
* Otherwise if asymetric media is not allowed, then we will choose only
* one codec in our initial offer to match the answer.
*/
if (allow_asym) {
for (i=0; i<answer->desc.fmt_count; ++i) {
unsigned j;
pj_str_t *rem_fmt = &answer->desc.fmt[i];
for (j=0; j<offer->desc.fmt_count; ++j) {
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -