1
/* $Id: sdp_neg.c 4367 2013-02-21 20:49:19Z nanang $ */
3
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
4
* Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
6
* This program is free software; you can redistribute it and/or modify
7
* it under the terms of the GNU General Public License as published by
8
* the Free Software Foundation; either version 2 of the License, or
9
* (at your option) any later version.
11
* This program is distributed in the hope that it will be useful,
12
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
* GNU General Public License for more details.
16
* You should have received a copy of the GNU General Public License
17
* along with this program; if not, write to the Free Software
18
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20
#include <pjmedia/sdp_neg.h>
21
#include <pjmedia/sdp.h>
22
#include <pjmedia/errno.h>
23
#include <pj/assert.h>
25
#include <pj/string.h>
30
* This structure describes SDP media negotiator.
32
struct pjmedia_sdp_neg
34
pjmedia_sdp_neg_state state; /**< Negotiator state. */
35
pj_bool_t prefer_remote_codec_order;
36
pj_bool_t has_remote_answer;
37
pj_bool_t answer_was_remote;
39
pjmedia_sdp_session *initial_sdp, /**< Initial local SDP */
40
*active_local_sdp, /**< Currently active local SDP. */
41
*active_remote_sdp, /**< Currently active remote's. */
42
*neg_local_sdp, /**< Temporary local SDP. */
43
*neg_remote_sdp; /**< Temporary remote SDP. */
46
static const char *state_str[] =
55
/* Definition of customized SDP format negotiation callback */
59
pjmedia_sdp_neg_fmt_match_cb cb;
62
/* Number of registered customized SDP format negotiation callbacks */
63
static unsigned fmt_match_cb_cnt;
65
/* The registered customized SDP format negotiation callbacks */
66
static struct fmt_match_cb_t
67
fmt_match_cb[PJMEDIA_SDP_NEG_MAX_CUSTOM_FMT_NEG_CB];
69
/* Redefining a very long identifier name, just for convenience */
70
#define ALLOW_MODIFY_ANSWER PJMEDIA_SDP_NEG_FMT_MATCH_ALLOW_MODIFY_ANSWER
72
static pj_status_t custom_fmt_match( pj_pool_t *pool,
73
const pj_str_t *fmt_name,
74
pjmedia_sdp_media *offer,
76
pjmedia_sdp_media *answer,
82
* Get string representation of negotiator state.
84
PJ_DEF(const char*) pjmedia_sdp_neg_state_str(pjmedia_sdp_neg_state state)
86
if (state >=0 && state < (pjmedia_sdp_neg_state)PJ_ARRAY_SIZE(state_str))
87
return state_str[state];
94
* Create with local offer.
96
PJ_DEF(pj_status_t) pjmedia_sdp_neg_create_w_local_offer( pj_pool_t *pool,
97
const pjmedia_sdp_session *local,
98
pjmedia_sdp_neg **p_neg)
100
pjmedia_sdp_neg *neg;
103
/* Check arguments are valid. */
104
PJ_ASSERT_RETURN(pool && local && p_neg, PJ_EINVAL);
108
/* Validate local offer. */
109
PJ_ASSERT_RETURN((status=pjmedia_sdp_validate(local))==PJ_SUCCESS, status);
111
/* Create and initialize negotiator. */
112
neg = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_neg);
113
PJ_ASSERT_RETURN(neg != NULL, PJ_ENOMEM);
115
neg->state = PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER;
116
neg->prefer_remote_codec_order = PJMEDIA_SDP_NEG_PREFER_REMOTE_CODEC_ORDER;
117
neg->initial_sdp = pjmedia_sdp_session_clone(pool, local);
118
neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, local);
125
* Create with remote offer and initial local offer/answer.
127
PJ_DEF(pj_status_t) pjmedia_sdp_neg_create_w_remote_offer(pj_pool_t *pool,
128
const pjmedia_sdp_session *initial,
129
const pjmedia_sdp_session *remote,
130
pjmedia_sdp_neg **p_neg)
132
pjmedia_sdp_neg *neg;
135
/* Check arguments are valid. */
136
PJ_ASSERT_RETURN(pool && remote && p_neg, PJ_EINVAL);
140
/* Validate remote offer and initial answer */
141
status = pjmedia_sdp_validate2(remote, PJ_FALSE);
142
if (status != PJ_SUCCESS)
145
/* Create and initialize negotiator. */
146
neg = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_neg);
147
PJ_ASSERT_RETURN(neg != NULL, PJ_ENOMEM);
149
neg->prefer_remote_codec_order = PJMEDIA_SDP_NEG_PREFER_REMOTE_CODEC_ORDER;
150
neg->neg_remote_sdp = pjmedia_sdp_session_clone(pool, remote);
153
PJ_ASSERT_RETURN((status=pjmedia_sdp_validate(initial))==PJ_SUCCESS,
156
neg->initial_sdp = pjmedia_sdp_session_clone(pool, initial);
157
neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, initial);
159
neg->state = PJMEDIA_SDP_NEG_STATE_WAIT_NEGO;
163
neg->state = PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER;
173
* Set codec order preference.
175
PJ_DEF(pj_status_t) pjmedia_sdp_neg_set_prefer_remote_codec_order(
176
pjmedia_sdp_neg *neg,
177
pj_bool_t prefer_remote)
179
PJ_ASSERT_RETURN(neg, PJ_EINVAL);
180
neg->prefer_remote_codec_order = prefer_remote;
186
* Get SDP negotiator state.
188
PJ_DEF(pjmedia_sdp_neg_state) pjmedia_sdp_neg_get_state( pjmedia_sdp_neg *neg )
190
/* Check arguments are valid. */
191
PJ_ASSERT_RETURN(neg != NULL, PJMEDIA_SDP_NEG_STATE_NULL);
196
PJ_DEF(pj_status_t) pjmedia_sdp_neg_get_active_local( pjmedia_sdp_neg *neg,
197
const pjmedia_sdp_session **local)
199
PJ_ASSERT_RETURN(neg && local, PJ_EINVAL);
200
PJ_ASSERT_RETURN(neg->active_local_sdp, PJMEDIA_SDPNEG_ENOACTIVE);
202
*local = neg->active_local_sdp;
207
PJ_DEF(pj_status_t) pjmedia_sdp_neg_get_active_remote( pjmedia_sdp_neg *neg,
208
const pjmedia_sdp_session **remote)
210
PJ_ASSERT_RETURN(neg && remote, PJ_EINVAL);
211
PJ_ASSERT_RETURN(neg->active_remote_sdp, PJMEDIA_SDPNEG_ENOACTIVE);
213
*remote = neg->active_remote_sdp;
218
PJ_DEF(pj_bool_t) pjmedia_sdp_neg_was_answer_remote(pjmedia_sdp_neg *neg)
220
PJ_ASSERT_RETURN(neg, PJ_FALSE);
222
return neg->answer_was_remote;
226
PJ_DEF(pj_status_t) pjmedia_sdp_neg_get_neg_remote( pjmedia_sdp_neg *neg,
227
const pjmedia_sdp_session **remote)
229
PJ_ASSERT_RETURN(neg && remote, PJ_EINVAL);
230
PJ_ASSERT_RETURN(neg->neg_remote_sdp, PJMEDIA_SDPNEG_ENONEG);
232
*remote = neg->neg_remote_sdp;
236
PJ_DEF(pj_status_t) pjmedia_sdp_neg_get_neg_local( pjmedia_sdp_neg *neg,
237
const pjmedia_sdp_session **local)
239
PJ_ASSERT_RETURN(neg && local, PJ_EINVAL);
240
PJ_ASSERT_RETURN(neg->neg_local_sdp, PJMEDIA_SDPNEG_ENONEG);
242
*local = neg->neg_local_sdp;
246
static pjmedia_sdp_media *sdp_media_clone_deactivate(
248
const pjmedia_sdp_media *rem_med,
249
const pjmedia_sdp_media *local_med,
250
const pjmedia_sdp_session *local_sess)
252
pjmedia_sdp_media *res;
254
res = pjmedia_sdp_media_clone_deactivate(pool, rem_med);
258
if (!res->conn && (!local_sess || !local_sess->conn)) {
259
if (local_med && local_med->conn)
260
res->conn = pjmedia_sdp_conn_clone(pool, local_med->conn);
262
res->conn = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_conn);
263
res->conn->net_type = pj_str("IN");
264
res->conn->addr_type = pj_str("IP4");
265
res->conn->addr = pj_str("127.0.0.1");
273
* Modify local SDP and wait for remote answer.
275
PJ_DEF(pj_status_t) pjmedia_sdp_neg_modify_local_offer( pj_pool_t *pool,
276
pjmedia_sdp_neg *neg,
277
const pjmedia_sdp_session *local)
279
pjmedia_sdp_session *new_offer;
280
pjmedia_sdp_session *old_offer;
281
char media_used[PJMEDIA_MAX_SDP_MEDIA];
282
unsigned oi; /* old offer media index */
285
/* Check arguments are valid. */
286
PJ_ASSERT_RETURN(pool && neg && local, PJ_EINVAL);
288
/* Can only do this in STATE_DONE. */
289
PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_DONE,
290
PJMEDIA_SDPNEG_EINSTATE);
292
/* Validate the new offer */
293
status = pjmedia_sdp_validate(local);
294
if (status != PJ_SUCCESS)
297
/* Change state to STATE_LOCAL_OFFER */
298
neg->state = PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER;
301
pj_bzero(media_used, sizeof(media_used));
302
old_offer = neg->active_local_sdp;
303
new_offer = pjmedia_sdp_session_clone(pool, local);
305
/* RFC 3264 Section 8: When issuing an offer that modifies the session,
306
* the "o=" line of the new SDP MUST be identical to that in the
307
* previous SDP, except that the version in the origin field MUST
308
* increment by one from the previous SDP.
310
pj_strdup(pool, &new_offer->origin.user, &old_offer->origin.user);
311
new_offer->origin.id = old_offer->origin.id;
312
new_offer->origin.version = old_offer->origin.version + 1;
313
pj_strdup(pool, &new_offer->origin.net_type, &old_offer->origin.net_type);
314
pj_strdup(pool, &new_offer->origin.addr_type,&old_offer->origin.addr_type);
315
pj_strdup(pool, &new_offer->origin.addr, &old_offer->origin.addr);
317
/* Generating the new offer, in the case media lines doesn't match the
318
* active SDP (e.g. current/active SDP's have m=audio and m=video lines,
319
* and the new offer only has m=audio line), the negotiator will fix
320
* the new offer by reordering and adding the missing media line with
321
* port number set to zero.
323
for (oi = 0; oi < old_offer->media_count; ++oi) {
324
pjmedia_sdp_media *om;
325
pjmedia_sdp_media *nm;
326
unsigned ni; /* new offer media index */
327
pj_bool_t found = PJ_FALSE;
329
om = old_offer->media[oi];
330
for (ni = oi; ni < new_offer->media_count; ++ni) {
331
nm = new_offer->media[ni];
332
if (pj_strcmp(&nm->desc.media, &om->desc.media) == 0) {
334
/* The same media found but the position unmatched to the
335
* old offer, so let's put this media in the right place,
336
* and keep the order of the rest.
338
pj_array_insert(new_offer->media, /* array */
339
sizeof(new_offer->media[0]), /* elmt size*/
349
pjmedia_sdp_media *m;
351
m = sdp_media_clone_deactivate(pool, om, om, local);
353
pj_array_insert(new_offer->media, sizeof(new_offer->media[0]),
354
new_offer->media_count++, oi, &m);
358
/* New_offer fixed */
359
neg->initial_sdp = new_offer;
360
neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, new_offer);
366
PJ_DEF(pj_status_t) pjmedia_sdp_neg_send_local_offer( pj_pool_t *pool,
367
pjmedia_sdp_neg *neg,
368
const pjmedia_sdp_session **offer)
370
/* Check arguments are valid. */
371
PJ_ASSERT_RETURN(neg && offer, PJ_EINVAL);
375
/* Can only do this in STATE_DONE or STATE_LOCAL_OFFER. */
376
PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_DONE ||
377
neg->state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER,
378
PJMEDIA_SDPNEG_EINSTATE);
380
if (neg->state == PJMEDIA_SDP_NEG_STATE_DONE) {
381
/* If in STATE_DONE, set the active SDP as the offer. */
382
PJ_ASSERT_RETURN(neg->active_local_sdp, PJMEDIA_SDPNEG_ENOACTIVE);
384
neg->state = PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER;
385
neg->neg_local_sdp = pjmedia_sdp_session_clone(pool,
386
neg->active_local_sdp);
387
*offer = neg->active_local_sdp;
390
/* We assume that we're in STATE_LOCAL_OFFER.
391
* In this case set the neg_local_sdp as the offer.
393
*offer = neg->neg_local_sdp;
401
PJ_DEF(pj_status_t) pjmedia_sdp_neg_set_remote_answer( pj_pool_t *pool,
402
pjmedia_sdp_neg *neg,
403
const pjmedia_sdp_session *remote)
405
/* Check arguments are valid. */
406
PJ_ASSERT_RETURN(pool && neg && remote, PJ_EINVAL);
408
/* Can only do this in STATE_LOCAL_OFFER.
409
* If we haven't provided local offer, then rx_remote_offer() should
410
* be called instead of this function.
412
PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER,
413
PJMEDIA_SDPNEG_EINSTATE);
415
/* We're ready to negotiate. */
416
neg->state = PJMEDIA_SDP_NEG_STATE_WAIT_NEGO;
417
neg->has_remote_answer = PJ_TRUE;
418
neg->neg_remote_sdp = pjmedia_sdp_session_clone(pool, remote);
423
PJ_DEF(pj_status_t) pjmedia_sdp_neg_set_remote_offer( pj_pool_t *pool,
424
pjmedia_sdp_neg *neg,
425
const pjmedia_sdp_session *remote)
427
/* Check arguments are valid. */
428
PJ_ASSERT_RETURN(pool && neg && remote, PJ_EINVAL);
430
/* Can only do this in STATE_DONE.
431
* If we already provide local offer, then rx_remote_answer() should
432
* be called instead of this function.
434
PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_DONE,
435
PJMEDIA_SDPNEG_EINSTATE);
437
/* State now is STATE_REMOTE_OFFER. */
438
neg->state = PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER;
439
neg->neg_remote_sdp = pjmedia_sdp_session_clone(pool, remote);
444
PJ_DEF(pj_status_t) pjmedia_sdp_neg_set_local_answer( pj_pool_t *pool,
445
pjmedia_sdp_neg *neg,
446
const pjmedia_sdp_session *local)
448
/* Check arguments are valid. */
449
PJ_ASSERT_RETURN(pool && neg && local, PJ_EINVAL);
451
/* Can only do this in STATE_REMOTE_OFFER.
452
* If we already provide local offer, then rx_remote_answer() should
453
* be called instead of this function.
455
PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER,
456
PJMEDIA_SDPNEG_EINSTATE);
458
/* State now is STATE_WAIT_NEGO. */
459
neg->state = PJMEDIA_SDP_NEG_STATE_WAIT_NEGO;
461
neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, local);
462
if (neg->initial_sdp) {
463
/* I don't think there is anything in RFC 3264 that mandates
464
* answerer to place the same origin (and increment version)
465
* in the answer, but probably it won't hurt either.
466
* Note that the version will be incremented in
467
* pjmedia_sdp_neg_negotiate()
469
neg->neg_local_sdp->origin.id = neg->initial_sdp->origin.id;
471
neg->initial_sdp = pjmedia_sdp_session_clone(pool, local);
474
PJ_ASSERT_RETURN(neg->initial_sdp, PJMEDIA_SDPNEG_ENOINITIAL);
475
neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, neg->initial_sdp);
481
PJ_DEF(pj_bool_t) pjmedia_sdp_neg_has_local_answer(pjmedia_sdp_neg *neg)
483
pj_assert(neg && neg->state==PJMEDIA_SDP_NEG_STATE_WAIT_NEGO);
484
return !neg->has_remote_answer;
489
static void str_swap(pj_str_t *str1, pj_str_t *str2)
491
pj_str_t tmp = *str1;
496
static void remove_all_media_directions(pjmedia_sdp_media *m)
498
pjmedia_sdp_media_remove_all_attr(m, "inactive");
499
pjmedia_sdp_media_remove_all_attr(m, "sendrecv");
500
pjmedia_sdp_media_remove_all_attr(m, "sendonly");
501
pjmedia_sdp_media_remove_all_attr(m, "recvonly");
504
/* Update media direction based on peer's media direction */
505
static void update_media_direction(pj_pool_t *pool,
506
const pjmedia_sdp_media *remote,
507
pjmedia_sdp_media *local)
509
pjmedia_dir old_dir = PJMEDIA_DIR_ENCODING_DECODING,
512
/* Get the media direction of local SDP */
513
if (pjmedia_sdp_media_find_attr2(local, "sendonly", NULL))
514
old_dir = PJMEDIA_DIR_ENCODING;
515
else if (pjmedia_sdp_media_find_attr2(local, "recvonly", NULL))
516
old_dir = PJMEDIA_DIR_DECODING;
517
else if (pjmedia_sdp_media_find_attr2(local, "inactive", NULL))
518
old_dir = PJMEDIA_DIR_NONE;
522
/* Adjust local media direction based on remote media direction */
523
if (pjmedia_sdp_media_find_attr2(remote, "inactive", NULL) != NULL) {
524
/* If remote has "a=inactive", then local is inactive too */
526
new_dir = PJMEDIA_DIR_NONE;
528
} else if(pjmedia_sdp_media_find_attr2(remote, "sendonly", NULL) != NULL) {
529
/* If remote has "a=sendonly", then set local to "recvonly" if
530
* it is currently "sendrecv". Otherwise if local is NOT "recvonly",
531
* then set local direction to "inactive".
534
case PJMEDIA_DIR_ENCODING_DECODING:
535
new_dir = PJMEDIA_DIR_DECODING;
537
case PJMEDIA_DIR_DECODING:
541
new_dir = PJMEDIA_DIR_NONE;
545
} else if(pjmedia_sdp_media_find_attr2(remote, "recvonly", NULL) != NULL) {
546
/* If remote has "a=recvonly", then set local to "sendonly" if
547
* it is currently "sendrecv". Otherwise if local is NOT "sendonly",
548
* then set local direction to "inactive"
552
case PJMEDIA_DIR_ENCODING_DECODING:
553
new_dir = PJMEDIA_DIR_ENCODING;
555
case PJMEDIA_DIR_ENCODING:
559
new_dir = PJMEDIA_DIR_NONE;
564
/* Remote indicates "sendrecv" capability. No change to local
569
if (new_dir != old_dir) {
570
pjmedia_sdp_attr *a = NULL;
572
remove_all_media_directions(local);
575
case PJMEDIA_DIR_NONE:
576
a = pjmedia_sdp_attr_create(pool, "inactive", NULL);
578
case PJMEDIA_DIR_ENCODING:
579
a = pjmedia_sdp_attr_create(pool, "sendonly", NULL);
581
case PJMEDIA_DIR_DECODING:
582
a = pjmedia_sdp_attr_create(pool, "recvonly", NULL);
590
pjmedia_sdp_media_add_attr(local, a);
596
/* Update single local media description to after receiving answer
599
static pj_status_t process_m_answer( pj_pool_t *pool,
600
pjmedia_sdp_media *offer,
601
pjmedia_sdp_media *answer,
602
pj_bool_t allow_asym)
606
/* Check that the media type match our offer. */
608
if (pj_strcmp(&answer->desc.media, &offer->desc.media)!=0) {
609
/* The media type in the answer is different than the offer! */
610
return PJMEDIA_SDPNEG_EINVANSMEDIA;
614
/* Check that transport in the answer match our offer. */
616
/* At this point, transport type must be compatible,
617
* the transport instance will do more validation later.
619
if (pjmedia_sdp_transport_cmp(&answer->desc.transport,
620
&offer->desc.transport)
623
return PJMEDIA_SDPNEG_EINVANSTP;
627
/* Check if remote has rejected our offer */
628
if (answer->desc.port == 0) {
630
/* Remote has rejected our offer.
631
* Deactivate our media too.
633
pjmedia_sdp_media_deactivate(pool, offer);
635
/* Don't need to proceed */
639
/* Ticket #1148: check if remote answer does not set port to zero when
640
* offered with port zero. Let's just tolerate it.
642
if (offer->desc.port == 0) {
643
/* Don't need to proceed */
647
/* Process direction attributes */
648
update_media_direction(pool, answer, offer);
650
/* If asymetric media is allowed, then just check that remote answer has
651
* codecs that are within the offer.
653
* Otherwise if asymetric media is not allowed, then we will choose only
654
* one codec in our initial offer to match the answer.
657
for (i=0; i<answer->desc.fmt_count; ++i) {
659
pj_str_t *rem_fmt = &answer->desc.fmt[i];
661
for (j=0; j<offer->desc.fmt_count; ++j) {
662
if (pj_strcmp(rem_fmt, &answer->desc.fmt[j])==0)
666
if (j != offer->desc.fmt_count) {
667
/* Found at least one common codec. */
672
if (i == answer->desc.fmt_count) {
673
/* No common codec in the answer! */
674
return PJMEDIA_SDPNEG_EANSNOMEDIA;
677
PJ_TODO(CHECK_SDP_NEGOTIATION_WHEN_ASYMETRIC_MEDIA_IS_ALLOWED);
680
/* Offer format priority based on answer format index/priority */
681
unsigned offer_fmt_prior[PJMEDIA_MAX_SDP_FMT];
683
/* Remove all format in the offer that has no matching answer */
684
for (i=0; i<offer->desc.fmt_count;) {
687
pj_str_t *fmt = &offer->desc.fmt[i];
690
/* Find matching answer */
691
pt = pj_strtoul(fmt);
694
for (j=0; j<answer->desc.fmt_count; ++j) {
695
if (pj_strcmp(fmt, &answer->desc.fmt[j])==0)
699
/* This is dynamic payload type.
700
* For dynamic payload type, we must look the rtpmap and
701
* compare the encoding name.
703
const pjmedia_sdp_attr *a;
704
pjmedia_sdp_rtpmap or_;
706
/* Get the rtpmap for the payload type in the offer. */
707
a = pjmedia_sdp_media_find_attr2(offer, "rtpmap", fmt);
709
pj_assert(!"Bug! Offer should have been validated");
712
pjmedia_sdp_attr_get_rtpmap(a, &or_);
714
/* Find paylaod in answer SDP with matching
715
* encoding name and clock rate.
717
for (j=0; j<answer->desc.fmt_count; ++j) {
718
a = pjmedia_sdp_media_find_attr2(answer, "rtpmap",
719
&answer->desc.fmt[j]);
721
pjmedia_sdp_rtpmap ar;
722
pjmedia_sdp_attr_get_rtpmap(a, &ar);
724
/* See if encoding name, clock rate, and channel
727
if (!pj_stricmp(&or_.enc_name, &ar.enc_name) &&
728
or_.clock_rate == ar.clock_rate &&
729
(pj_stricmp(&or_.param, &ar.param)==0 ||
730
(ar.param.slen==1 && *ar.param.ptr=='1')))
732
/* Call custom format matching callbacks */
733
if (custom_fmt_match(pool, &or_.enc_name,
734
offer, i, answer, j, 0) ==
745
if (j == answer->desc.fmt_count) {
746
/* This format has no matching answer.
747
* Remove it from our offer.
751
/* Remove rtpmap associated with this format */
752
a = pjmedia_sdp_media_find_attr2(offer, "rtpmap", fmt);
754
pjmedia_sdp_media_remove_attr(offer, a);
756
/* Remove fmtp associated with this format */
757
a = pjmedia_sdp_media_find_attr2(offer, "fmtp", fmt);
759
pjmedia_sdp_media_remove_attr(offer, a);
761
/* Remove this format from offer's array */
762
pj_array_erase(offer->desc.fmt, sizeof(offer->desc.fmt[0]),
763
offer->desc.fmt_count, i);
764
--offer->desc.fmt_count;
767
offer_fmt_prior[i] = j;
772
if (0 == offer->desc.fmt_count) {
773
/* No common codec in the answer! */
774
return PJMEDIA_SDPNEG_EANSNOMEDIA;
778
* - Resort offer formats so the order match to the answer.
779
* - Remove answer formats that unmatches to the offer.
782
/* Resort offer formats */
783
for (i=0; i<offer->desc.fmt_count; ++i) {
785
for (j=i+1; j<offer->desc.fmt_count; ++j) {
786
if (offer_fmt_prior[i] > offer_fmt_prior[j]) {
787
unsigned tmp = offer_fmt_prior[i];
788
offer_fmt_prior[i] = offer_fmt_prior[j];
789
offer_fmt_prior[j] = tmp;
790
str_swap(&offer->desc.fmt[i], &offer->desc.fmt[j]);
795
/* Remove unmatched answer formats */
797
unsigned del_cnt = 0;
798
for (i=0; i<answer->desc.fmt_count;) {
799
/* The offer is ordered now, also the offer_fmt_prior */
800
if (i >= offer->desc.fmt_count ||
801
offer_fmt_prior[i]-del_cnt != i)
803
pj_str_t *fmt = &answer->desc.fmt[i];
806
/* Remove rtpmap associated with this format */
807
a = pjmedia_sdp_media_find_attr2(answer, "rtpmap", fmt);
809
pjmedia_sdp_media_remove_attr(answer, a);
811
/* Remove fmtp associated with this format */
812
a = pjmedia_sdp_media_find_attr2(answer, "fmtp", fmt);
814
pjmedia_sdp_media_remove_attr(answer, a);
816
/* Remove this format from answer's array */
817
pj_array_erase(answer->desc.fmt,
818
sizeof(answer->desc.fmt[0]),
819
answer->desc.fmt_count, i);
820
--answer->desc.fmt_count;
835
/* Update local media session (offer) to create active local session
836
* after receiving remote answer.
838
static pj_status_t process_answer(pj_pool_t *pool,
839
pjmedia_sdp_session *offer,
840
pjmedia_sdp_session *answer,
841
pj_bool_t allow_asym,
842
pjmedia_sdp_session **p_active)
844
unsigned omi = 0; /* Offer media index */
845
unsigned ami = 0; /* Answer media index */
846
pj_bool_t has_active = PJ_FALSE;
849
/* Check arguments. */
850
PJ_ASSERT_RETURN(pool && offer && answer && p_active, PJ_EINVAL);
852
/* Check that media count match between offer and answer */
853
// Ticket #527, different media count is allowed for more interoperability,
854
// however, the media order must be same between offer and answer.
855
// if (offer->media_count != answer->media_count)
856
// return PJMEDIA_SDPNEG_EMISMEDIA;
858
/* Now update each media line in the offer with the answer. */
859
for (; omi<offer->media_count; ++omi) {
860
if (ami == answer->media_count) {
861
/* The answer has less media than the offer */
862
pjmedia_sdp_media *am;
864
/* Generate matching-but-disabled-media for the answer */
865
am = sdp_media_clone_deactivate(pool, offer->media[omi],
866
offer->media[omi], offer);
867
answer->media[answer->media_count++] = am;
870
/* Deactivate our media offer too */
871
pjmedia_sdp_media_deactivate(pool, offer->media[omi]);
873
/* No answer media to be negotiated */
877
status = process_m_answer(pool, offer->media[omi], answer->media[ami],
880
/* If media type is mismatched, just disable the media. */
881
if (status == PJMEDIA_SDPNEG_EINVANSMEDIA) {
882
pjmedia_sdp_media_deactivate(pool, offer->media[omi]);
885
/* No common format in the answer media. */
886
else if (status == PJMEDIA_SDPNEG_EANSNOMEDIA) {
887
pjmedia_sdp_media_deactivate(pool, offer->media[omi]);
888
pjmedia_sdp_media_deactivate(pool, answer->media[ami]);
890
/* Return the error code, for other errors. */
891
else if (status != PJ_SUCCESS) {
895
if (offer->media[omi]->desc.port != 0)
896
has_active = PJ_TRUE;
903
return has_active ? PJ_SUCCESS : PJMEDIA_SDPNEG_ENOMEDIA;
907
/* Internal function to rewrite the format string in SDP attribute rtpmap
910
PJ_INLINE(void) rewrite_pt(pj_pool_t *pool, pj_str_t *attr_val,
911
const pj_str_t *old_pt, const pj_str_t *new_pt)
913
int len_diff = new_pt->slen - old_pt->slen;
915
/* Note that attribute value should be null-terminated. */
918
new_val.ptr = (char*)pj_pool_alloc(pool, attr_val->slen+len_diff+1);
919
new_val.slen = attr_val->slen + len_diff;
920
pj_memcpy(new_val.ptr + len_diff, attr_val->ptr, attr_val->slen + 1);
922
} else if (len_diff < 0) {
923
attr_val->slen += len_diff;
924
pj_memmove(attr_val->ptr, attr_val->ptr - len_diff,
927
pj_memcpy(attr_val->ptr, new_pt->ptr, new_pt->slen);
931
/* Internal function to apply symmetric PT for the local answer. */
932
static void apply_answer_symmetric_pt(pj_pool_t *pool,
933
pjmedia_sdp_media *answer,
935
const pj_str_t pt_offer[],
936
const pj_str_t pt_answer[])
938
pjmedia_sdp_attr *a_tmp[PJMEDIA_MAX_SDP_ATTR];
939
unsigned i, a_tmp_cnt = 0;
941
/* Rewrite the payload types in the answer if different to
942
* the ones in the offer.
944
for (i = 0; i < pt_cnt; ++i) {
947
/* Skip if the PTs are the same already, e.g: static PT. */
948
if (pj_strcmp(&pt_answer[i], &pt_offer[i]) == 0)
951
/* Rewrite payload type in the answer to match to the offer */
952
pj_strdup(pool, &answer->desc.fmt[i], &pt_offer[i]);
954
/* Also update payload type in rtpmap */
955
a = pjmedia_sdp_media_find_attr2(answer, "rtpmap", &pt_answer[i]);
957
rewrite_pt(pool, &a->value, &pt_answer[i], &pt_offer[i]);
958
/* Temporarily remove the attribute in case the new payload
959
* type is being used by another format in the media.
961
pjmedia_sdp_media_remove_attr(answer, a);
962
a_tmp[a_tmp_cnt++] = a;
965
/* Also update payload type in fmtp */
966
a = pjmedia_sdp_media_find_attr2(answer, "fmtp", &pt_answer[i]);
968
rewrite_pt(pool, &a->value, &pt_answer[i], &pt_offer[i]);
969
/* Temporarily remove the attribute in case the new payload
970
* type is being used by another format in the media.
972
pjmedia_sdp_media_remove_attr(answer, a);
973
a_tmp[a_tmp_cnt++] = a;
977
/* Return back 'rtpmap' and 'fmtp' attributes */
978
for (i = 0; i < a_tmp_cnt; ++i)
979
pjmedia_sdp_media_add_attr(answer, a_tmp[i]);
983
/* Try to match offer with answer. */
984
static pj_status_t match_offer(pj_pool_t *pool,
985
pj_bool_t prefer_remote_codec_order,
986
const pjmedia_sdp_media *offer,
987
const pjmedia_sdp_media *preanswer,
988
const pjmedia_sdp_session *preanswer_sdp,
989
pjmedia_sdp_media **p_answer)
992
pj_bool_t master_has_codec = 0,
993
master_has_telephone_event = 0,
994
master_has_other = 0,
995
found_matching_codec = 0,
996
found_matching_telephone_event = 0,
997
found_matching_other = 0;
998
unsigned pt_answer_count = 0;
999
pj_str_t pt_answer[PJMEDIA_MAX_SDP_FMT];
1000
pj_str_t pt_offer[PJMEDIA_MAX_SDP_FMT];
1001
pjmedia_sdp_media *answer;
1002
const pjmedia_sdp_media *master, *slave;
1004
/* If offer has zero port, just clone the offer */
1005
if (offer->desc.port == 0) {
1006
answer = sdp_media_clone_deactivate(pool, offer, preanswer,
1012
/* If the preanswer define zero port, this media is being rejected,
1013
* just clone the preanswer.
1015
if (preanswer->desc.port == 0) {
1016
answer = pjmedia_sdp_media_clone(pool, preanswer);
1021
/* Set master/slave negotiator based on prefer_remote_codec_order. */
1022
if (prefer_remote_codec_order) {
1030
/* With the addition of telephone-event and dodgy MS RTC SDP,
1031
* the answer generation algorithm looks really shitty...
1033
for (i=0; i<master->desc.fmt_count; ++i) {
1036
if (pj_isdigit(*master->desc.fmt[i].ptr)) {
1037
/* This is normal/standard payload type, where it's identified
1038
* by payload number.
1042
pt = pj_strtoul(&master->desc.fmt[i]);
1045
/* For static payload type, it's enough to compare just
1046
* the payload number.
1049
master_has_codec = 1;
1051
/* We just need to select one codec.
1052
* Continue if we have selected matching codec for previous
1055
if (found_matching_codec)
1058
/* Find matching codec in local descriptor. */
1059
for (j=0; j<slave->desc.fmt_count; ++j) {
1061
p = pj_strtoul(&slave->desc.fmt[j]);
1062
if (p == pt && pj_isdigit(*slave->desc.fmt[j].ptr)) {
1063
found_matching_codec = 1;
1064
pt_offer[pt_answer_count] = slave->desc.fmt[j];
1065
pt_answer[pt_answer_count++] = slave->desc.fmt[j];
1071
/* This is dynamic payload type.
1072
* For dynamic payload type, we must look the rtpmap and
1073
* compare the encoding name.
1075
const pjmedia_sdp_attr *a;
1076
pjmedia_sdp_rtpmap or_;
1079
/* Get the rtpmap for the payload type in the master. */
1080
a = pjmedia_sdp_media_find_attr2(master, "rtpmap",
1081
&master->desc.fmt[i]);
1083
pj_assert(!"Bug! Offer should have been validated");
1084
return PJMEDIA_SDP_EMISSINGRTPMAP;
1086
pjmedia_sdp_attr_get_rtpmap(a, &or_);
1088
if (!pj_stricmp2(&or_.enc_name, "telephone-event")) {
1089
master_has_telephone_event = 1;
1090
if (found_matching_telephone_event)
1094
master_has_codec = 1;
1095
if (found_matching_codec)
1100
/* Find paylaod in our initial SDP with matching
1101
* encoding name and clock rate.
1103
for (j=0; j<slave->desc.fmt_count; ++j) {
1104
a = pjmedia_sdp_media_find_attr2(slave, "rtpmap",
1105
&slave->desc.fmt[j]);
1107
pjmedia_sdp_rtpmap lr;
1108
pjmedia_sdp_attr_get_rtpmap(a, &lr);
1110
/* See if encoding name, clock rate, and
1111
* channel count match
1113
if (!pj_stricmp(&or_.enc_name, &lr.enc_name) &&
1114
or_.clock_rate == lr.clock_rate &&
1115
(pj_stricmp(&or_.param, &lr.param)==0 ||
1116
(lr.param.slen==0 && or_.param.slen==1 &&
1117
*or_.param.ptr=='1') ||
1118
(or_.param.slen==0 && lr.param.slen==1 &&
1119
*lr.param.ptr=='1')))
1123
pjmedia_sdp_media *o, *a;
1124
unsigned o_fmt_idx, a_fmt_idx;
1126
o = (pjmedia_sdp_media*)offer;
1127
a = (pjmedia_sdp_media*)preanswer;
1128
o_fmt_idx = prefer_remote_codec_order? i:j;
1129
a_fmt_idx = prefer_remote_codec_order? j:i;
1131
/* Call custom format matching callbacks */
1132
if (custom_fmt_match(pool, &or_.enc_name,
1135
ALLOW_MODIFY_ANSWER) !=
1140
found_matching_codec = 1;
1142
found_matching_telephone_event = 1;
1145
pt_offer[pt_answer_count] =
1146
prefer_remote_codec_order?
1149
pt_answer[pt_answer_count++] =
1150
prefer_remote_codec_order?
1151
preanswer->desc.fmt[j]:
1152
preanswer->desc.fmt[i];
1160
/* This is a non-standard, brain damaged SDP where the payload
1161
* type is non-numeric. It exists e.g. in Microsoft RTC based
1162
* UA, to indicate instant messaging capability.
1164
* - m=x-ms-message 5060 sip null
1166
master_has_other = 1;
1167
if (found_matching_other)
1170
for (j=0; j<slave->desc.fmt_count; ++j) {
1171
if (!pj_strcmp(&master->desc.fmt[i], &slave->desc.fmt[j])) {
1173
found_matching_other = 1;
1174
pt_offer[pt_answer_count] = prefer_remote_codec_order?
1177
pt_answer[pt_answer_count++] = prefer_remote_codec_order?
1178
preanswer->desc.fmt[j]:
1179
preanswer->desc.fmt[i];
1186
/* See if all types of master can be matched. */
1187
if (master_has_codec && !found_matching_codec) {
1188
return PJMEDIA_SDPNEG_NOANSCODEC;
1191
/* If this comment is removed, negotiation will fail if remote has offered
1192
telephone-event and local is not configured with telephone-event
1194
if (offer_has_telephone_event && !found_matching_telephone_event) {
1195
return PJMEDIA_SDPNEG_NOANSTELEVENT;
1199
if (master_has_other && !found_matching_other) {
1200
return PJMEDIA_SDPNEG_NOANSUNKNOWN;
1203
/* Seems like everything is in order.
1204
* Build the answer by cloning from preanswer, but rearrange the payload
1205
* to suit the offer.
1207
answer = pjmedia_sdp_media_clone(pool, preanswer);
1208
for (i=0; i<pt_answer_count; ++i) {
1210
for (j=i; j<answer->desc.fmt_count; ++j) {
1211
if (!pj_strcmp(&answer->desc.fmt[j], &pt_answer[i]))
1214
pj_assert(j != answer->desc.fmt_count);
1215
str_swap(&answer->desc.fmt[i], &answer->desc.fmt[j]);
1218
/* Remove unwanted local formats. */
1219
for (i=pt_answer_count; i<answer->desc.fmt_count; ++i) {
1220
pjmedia_sdp_attr *a;
1222
/* Remove rtpmap for this format */
1223
a = pjmedia_sdp_media_find_attr2(answer, "rtpmap",
1224
&answer->desc.fmt[i]);
1226
pjmedia_sdp_media_remove_attr(answer, a);
1229
/* Remove fmtp for this format */
1230
a = pjmedia_sdp_media_find_attr2(answer, "fmtp",
1231
&answer->desc.fmt[i]);
1233
pjmedia_sdp_media_remove_attr(answer, a);
1236
answer->desc.fmt_count = pt_answer_count;
1238
#if PJMEDIA_SDP_NEG_ANSWER_SYMMETRIC_PT
1239
apply_answer_symmetric_pt(pool, answer, pt_answer_count,
1240
pt_offer, pt_answer);
1243
/* Update media direction. */
1244
update_media_direction(pool, offer, answer);
1250
/* Create complete answer for remote's offer. */
1251
static pj_status_t create_answer( pj_pool_t *pool,
1252
pj_bool_t prefer_remote_codec_order,
1253
const pjmedia_sdp_session *initial,
1254
const pjmedia_sdp_session *offer,
1255
pjmedia_sdp_session **p_answer)
1257
pj_status_t status = PJMEDIA_SDPNEG_ENOMEDIA;
1258
pj_bool_t has_active = PJ_FALSE;
1259
pjmedia_sdp_session *answer;
1260
char media_used[PJMEDIA_MAX_SDP_MEDIA];
1263
/* Validate remote offer.
1264
* This should have been validated before.
1266
PJ_ASSERT_RETURN((status=pjmedia_sdp_validate(offer))==PJ_SUCCESS, status);
1268
/* Create initial answer by duplicating initial SDP,
1269
* but clear all media lines. The media lines will be filled up later.
1271
answer = pjmedia_sdp_session_clone(pool, initial);
1272
PJ_ASSERT_RETURN(answer != NULL, PJ_ENOMEM);
1274
answer->media_count = 0;
1276
pj_bzero(media_used, sizeof(media_used));
1278
/* For each media line, create our answer based on our initial
1281
for (i=0; i<offer->media_count; ++i) {
1282
const pjmedia_sdp_media *om; /* offer */
1283
const pjmedia_sdp_media *im; /* initial media */
1284
pjmedia_sdp_media *am = NULL; /* answer/result */
1287
om = offer->media[i];
1289
/* Find media description in our initial capability that matches
1290
* the media type and transport type of offer's media, has
1291
* matching codec, and has not been used to answer other offer.
1293
for (im=NULL, j=0; j<initial->media_count; ++j) {
1294
im = initial->media[j];
1295
if (pj_strcmp(&om->desc.media, &im->desc.media)==0 &&
1296
pj_strcmp(&om->desc.transport, &im->desc.transport)==0 &&
1299
pj_status_t status2;
1301
/* See if it has matching codec. */
1302
status2 = match_offer(pool, prefer_remote_codec_order,
1303
om, im, initial, &am);
1304
if (status2 == PJ_SUCCESS) {
1305
/* Mark media as used. */
1314
if (j==initial->media_count) {
1315
/* No matching media.
1316
* Reject the offer by setting the port to zero in the answer.
1318
/* For simplicity in the construction of the answer, we'll
1319
* just clone the media from the offer. Anyway receiver will
1320
* ignore anything in the media once it sees that the port
1323
am = sdp_media_clone_deactivate(pool, om, om, answer);
1325
/* The answer is in am */
1326
pj_assert(am != NULL);
1329
/* Add the media answer */
1330
answer->media[answer->media_count++] = am;
1332
/* Check if this media is active.*/
1333
if (am->desc.port != 0)
1334
has_active = PJ_TRUE;
1339
return has_active ? PJ_SUCCESS : status;
1343
PJ_DEF(pj_status_t) pjmedia_sdp_neg_cancel_offer(pjmedia_sdp_neg *neg)
1345
PJ_ASSERT_RETURN(neg, PJ_EINVAL);
1347
/* Must be in LOCAL_OFFER state. */
1348
PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER ||
1349
neg->state == PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER,
1350
PJMEDIA_SDPNEG_EINSTATE);
1352
/* Reset state to done */
1353
neg->state = PJMEDIA_SDP_NEG_STATE_DONE;
1355
/* Clear temporary SDP */
1356
neg->neg_local_sdp = neg->neg_remote_sdp = NULL;
1357
neg->has_remote_answer = PJ_FALSE;
1363
/* The best bit: SDP negotiation function! */
1364
PJ_DEF(pj_status_t) pjmedia_sdp_neg_negotiate( pj_pool_t *pool,
1365
pjmedia_sdp_neg *neg,
1366
pj_bool_t allow_asym)
1370
/* Check arguments are valid. */
1371
PJ_ASSERT_RETURN(pool && neg, PJ_EINVAL);
1373
/* Must be in STATE_WAIT_NEGO state. */
1374
PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_WAIT_NEGO,
1375
PJMEDIA_SDPNEG_EINSTATE);
1377
/* Must have remote offer. */
1378
PJ_ASSERT_RETURN(neg->neg_remote_sdp, PJ_EBUG);
1380
if (neg->has_remote_answer) {
1381
pjmedia_sdp_session *active;
1382
status = process_answer(pool, neg->neg_local_sdp, neg->neg_remote_sdp,
1383
allow_asym, &active);
1384
if (status == PJ_SUCCESS) {
1385
/* Only update active SDPs when negotiation is successfull */
1386
neg->active_local_sdp = active;
1387
neg->active_remote_sdp = neg->neg_remote_sdp;
1390
pjmedia_sdp_session *answer = NULL;
1392
status = create_answer(pool, neg->prefer_remote_codec_order,
1393
neg->neg_local_sdp, neg->neg_remote_sdp,
1395
if (status == PJ_SUCCESS) {
1396
pj_uint32_t active_ver;
1398
if (neg->active_local_sdp)
1399
active_ver = neg->active_local_sdp->origin.version;
1401
active_ver = neg->initial_sdp->origin.version;
1403
/* Only update active SDPs when negotiation is successfull */
1404
neg->active_local_sdp = answer;
1405
neg->active_remote_sdp = neg->neg_remote_sdp;
1407
/* Increment SDP version */
1408
neg->active_local_sdp->origin.version = ++active_ver;
1412
/* State is DONE regardless */
1413
neg->state = PJMEDIA_SDP_NEG_STATE_DONE;
1416
neg->answer_was_remote = neg->has_remote_answer;
1418
/* Clear temporary SDP */
1419
neg->neg_local_sdp = neg->neg_remote_sdp = NULL;
1420
neg->has_remote_answer = PJ_FALSE;
1426
static pj_status_t custom_fmt_match(pj_pool_t *pool,
1427
const pj_str_t *fmt_name,
1428
pjmedia_sdp_media *offer,
1430
pjmedia_sdp_media *answer,
1436
for (i = 0; i < fmt_match_cb_cnt; ++i) {
1437
if (pj_stricmp(fmt_name, &fmt_match_cb[i].fmt_name) == 0) {
1438
pj_assert(fmt_match_cb[i].cb);
1439
return (*fmt_match_cb[i].cb)(pool, offer, o_fmt_idx,
1445
/* Not customized format matching found, should be matched */
1449
/* Register customized SDP format negotiation callback function. */
1450
PJ_DECL(pj_status_t) pjmedia_sdp_neg_register_fmt_match_cb(
1451
const pj_str_t *fmt_name,
1452
pjmedia_sdp_neg_fmt_match_cb cb)
1454
struct fmt_match_cb_t *f = NULL;
1457
PJ_ASSERT_RETURN(fmt_name, PJ_EINVAL);
1459
/* Check if the callback for the format name has been registered */
1460
for (i = 0; i < fmt_match_cb_cnt; ++i) {
1461
if (pj_stricmp(fmt_name, &fmt_match_cb[i].fmt_name) == 0)
1465
/* Unregistration */
1468
if (i == fmt_match_cb_cnt)
1469
return PJ_ENOTFOUND;
1471
pj_array_erase(fmt_match_cb, sizeof(fmt_match_cb[0]),
1472
fmt_match_cb_cnt, i);
1480
if (i < fmt_match_cb_cnt) {
1481
/* The same format name has been registered before */
1482
if (cb != fmt_match_cb[i].cb)
1488
if (fmt_match_cb_cnt >= PJ_ARRAY_SIZE(fmt_match_cb))
1491
f = &fmt_match_cb[fmt_match_cb_cnt++];
1492
f->fmt_name = *fmt_name;
1499
/* Match format in the SDP media offer and answer. */
1500
PJ_DEF(pj_status_t) pjmedia_sdp_neg_fmt_match(pj_pool_t *pool,
1501
pjmedia_sdp_media *offer,
1503
pjmedia_sdp_media *answer,
1507
const pjmedia_sdp_attr *attr;
1508
pjmedia_sdp_rtpmap o_rtpmap, a_rtpmap;
1512
o_pt = pj_strtoul(&offer->desc.fmt[o_fmt_idx]);
1513
a_pt = pj_strtoul(&answer->desc.fmt[a_fmt_idx]);
1515
if (o_pt < 96 || a_pt < 96) {
1519
return PJMEDIA_SDP_EFORMATNOTEQUAL;
1522
/* Get the format rtpmap from the offer. */
1523
attr = pjmedia_sdp_media_find_attr2(offer, "rtpmap",
1524
&offer->desc.fmt[o_fmt_idx]);
1526
pj_assert(!"Bug! Offer haven't been validated");
1529
pjmedia_sdp_attr_get_rtpmap(attr, &o_rtpmap);
1531
/* Get the format rtpmap from the answer. */
1532
attr = pjmedia_sdp_media_find_attr2(answer, "rtpmap",
1533
&answer->desc.fmt[a_fmt_idx]);
1535
pj_assert(!"Bug! Answer haven't been validated");
1538
pjmedia_sdp_attr_get_rtpmap(attr, &a_rtpmap);
1540
if (pj_stricmp(&o_rtpmap.enc_name, &a_rtpmap.enc_name) != 0 ||
1541
o_rtpmap.clock_rate != a_rtpmap.clock_rate)
1543
return PJMEDIA_SDP_EFORMATNOTEQUAL;
1546
return custom_fmt_match(pool, &o_rtpmap.enc_name,
1547
offer, o_fmt_idx, answer, a_fmt_idx, option);