From 68ccc52b2b5daf929a3895627f4397f34be2543b Mon Sep 17 00:00:00 2001 From: Richard Fuchs Date: Wed, 14 Oct 2020 15:19:56 -0400 Subject: [PATCH] TT#92250 postpone codec_tracker to final stage Change-Id: I222f705e53fc95f56da40cf927197afcb2ee5107 --- daemon/call.c | 3 + daemon/codec.c | 159 ++++++++++++++++++++++++----------------- daemon/redis.c | 2 +- include/call.h | 2 + include/codec.h | 7 +- t/auto-daemon-tests.pl | 12 +++- 6 files changed, 112 insertions(+), 73 deletions(-) diff --git a/daemon/call.c b/daemon/call.c index 63a97ed3d..bb9cc4818 100644 --- a/daemon/call.c +++ b/daemon/call.c @@ -2116,8 +2116,11 @@ int monologue_offer_answer(struct call_monologue *other_ml, GQueue *streams, if (media->protocol == other_media->protocol) call_str_cpy(call, &media->format_str, &sp->format_str); } + + codec_tracker_init(media); codec_rtp_payload_types(media, other_media, &sp->rtp_payload_types, flags); codec_handlers_update(media, other_media, flags, sp); + codec_tracker_finish(media); /* send and recv are from our POV */ bf_copy_same(&media->media_flags, &sp->sp_flags, diff --git a/daemon/codec.c b/daemon/codec.c index 0a0699f05..49bc7d73f 100644 --- a/daemon/codec.c +++ b/daemon/codec.c @@ -100,7 +100,7 @@ struct transcode_packet { int (*dup_func)(struct codec_ssrc_handler *, struct transcode_packet *, struct media_packet *); struct rtp_header rtp; }; -struct supp_codec_tracker { +struct codec_tracker { GHashTable *clockrates; // 8000, 16000, etc, for each real audio codec that is present GHashTable *touched; // 8000, 16000, etc, for each audio codec that was touched (added, removed, etc) int all_touched; @@ -125,6 +125,8 @@ static int packet_encoded_rtp(encoder_t *enc, void *u1, void *u2); static int packet_decoded_fifo(decoder_t *decoder, AVFrame *frame, void *u1, void *u2); static int packet_decoded_direct(decoder_t *decoder, AVFrame *frame, void *u1, void *u2); +static void codec_touched(struct rtp_payload_type *pt, struct call_media *media); + static struct codec_handler codec_handler_stub_ssrc = { .source_pt.payload_type = -1, @@ -501,6 +503,7 @@ static void __single_codec(struct call_media *media, const struct sdp_ng_flags * } ilog(LOG_DEBUG, "Removing codec '" STR_FORMAT "' due to 'single codec' flag", STR_FMT(&pt->encoding_with_params)); + codec_touched(pt, media); l = __delete_receiver_codec(media, l); } } @@ -565,6 +568,8 @@ static void __accept_transcode_codecs(struct call_media *receiver, struct call_m // we need a new pt entry pt = __rtp_payload_type_copy(pt); + pt->for_transcoding = 1; + codec_touched(pt, receiver); // this somewhat duplicates __rtp_payload_type_add_recv g_hash_table_insert(receiver->codecs_recv, &pt->payload_type, pt); __rtp_payload_type_add_name(receiver->codec_names_recv, pt); @@ -597,6 +602,7 @@ static void __eliminate_rejected_codecs(struct call_media *receiver, struct call } ilog(LOG_DEBUG, "Eliminating asymmetric outbound codec " STR_FORMAT, STR_FMT(&pt->encoding_with_params)); + codec_touched(pt, receiver); l = __delete_send_codec(receiver, l); } } @@ -657,7 +663,7 @@ static void __symmetric_codecs(struct call_media *receiver, struct call_media *s // add it to the list ilog(LOG_DEBUG, "Adding symmetric RTP payload type %i", pt->payload_type); g_hash_table_steal(prefs_recv, GINT_TO_POINTER(pt->payload_type)); - __rtp_payload_type_add_recv(receiver, out_pt, 1, NULL); + __rtp_payload_type_add_recv(receiver, out_pt, 1); // and our send leg out_pt = g_hash_table_lookup(prefs_send, GINT_TO_POINTER(pt->payload_type)); if (out_pt) { @@ -682,7 +688,7 @@ static void __symmetric_codecs(struct call_media *receiver, struct call_media *s if (!out_pt) continue; g_hash_table_steal(prefs_recv, ptype); - __rtp_payload_type_add_recv(receiver, out_pt, 1, NULL); + __rtp_payload_type_add_recv(receiver, out_pt, 1); } while (prefs_send_order.length) { void *ptype = g_queue_pop_head(&prefs_send_order); @@ -1009,6 +1015,7 @@ void codec_handlers_update(struct call_media *receiver, struct call_media *sink, ilog(LOG_DEBUG, "Eliminating transcoded codec " STR_FORMAT, STR_FMT(&pt->encoding_with_params)); + codec_touched(pt, receiver); l = __delete_receiver_codec(receiver, l); continue; } @@ -1130,6 +1137,7 @@ next: ilog(LOG_DEBUG, "Stripping unsupported codec " STR_FORMAT " due to active transcoding", STR_FMT(&pt->encoding)); + codec_touched(pt, receiver); l = __delete_receiver_codec(receiver, l); } @@ -2219,21 +2227,41 @@ static void __rtp_payload_type_add_name(GHashTable *ht, struct rtp_payload_type q = g_hash_table_lookup_queue_new(ht, &pt->encoding_with_params); g_queue_push_tail(q, GUINT_TO_POINTER(pt->payload_type)); } +static void __insert_codec_tracker(struct call_media *media, GList *link) { + struct rtp_payload_type *pt = link->data; + struct codec_tracker *sct = media->codec_tracker; + + ensure_codec_def(pt, media); + + if (!pt->codec_def || !pt->codec_def->supplemental) + g_hash_table_replace(sct->clockrates, GUINT_TO_POINTER(pt->clock_rate), + GUINT_TO_POINTER(GPOINTER_TO_UINT( + g_hash_table_lookup(sct->clockrates, + GUINT_TO_POINTER(pt->clock_rate))) + 1)); + else { + GHashTable *clockrates = g_hash_table_lookup(sct->supp_codecs, &pt->encoding); + if (!clockrates) { + clockrates = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, + (GDestroyNotify) g_queue_free); + g_hash_table_replace(sct->supp_codecs, str_dup(&pt->encoding), clockrates); + } + GQueue *entries = g_hash_table_lookup_queue_new(clockrates, GUINT_TO_POINTER(pt->clock_rate)); + g_queue_push_tail(entries, link); + } +} static void __queue_insert_supp(GQueue *q, struct rtp_payload_type *pt, int supp_check, - struct supp_codec_tracker *sct) + struct codec_tracker *sct) { - int is_supp = pt->codec_def && pt->codec_def->supplemental; - // do we care at all? if (!supp_check) { g_queue_push_tail(q, pt); - goto do_sct; + return; } // all new supp codecs go last - if (is_supp) { + if (pt->codec_def && pt->codec_def->supplemental) { g_queue_push_tail(q, pt); - goto do_sct; + return; } // find the cut-off point between non-supp and supp codecs @@ -2250,28 +2278,9 @@ static void __queue_insert_supp(GQueue *q, struct rtp_payload_type *pt, int supp g_queue_push_head(q, pt); else g_queue_insert_after(q, insert_pos, pt); - -do_sct: - if (!sct) - return; - if (!is_supp) - g_hash_table_replace(sct->clockrates, GUINT_TO_POINTER(pt->clock_rate), (void *) 0x1); - else { - GHashTable *clockrates = g_hash_table_lookup(sct->supp_codecs, &pt->encoding); - if (!clockrates) { - clockrates = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, - (GDestroyNotify) g_queue_free); - g_hash_table_replace(sct->supp_codecs, &pt->encoding, clockrates); - } - GQueue *entries = g_hash_table_lookup_queue_new(clockrates, GUINT_TO_POINTER(pt->clock_rate)); - // new supp entries are always last - g_queue_push_tail(entries, q->tail); - } } // consumes 'pt' -void __rtp_payload_type_add_recv(struct call_media *media, - struct rtp_payload_type *pt, int supp_check, struct supp_codec_tracker *sct) -{ +void __rtp_payload_type_add_recv(struct call_media *media, struct rtp_payload_type *pt, int supp_check) { if (!pt) return; ensure_codec_def(pt, media); @@ -2284,7 +2293,7 @@ void __rtp_payload_type_add_recv(struct call_media *media, pt->ptime = media->ptime; g_hash_table_insert(media->codecs_recv, &pt->payload_type, pt); __rtp_payload_type_add_name(media->codec_names_recv, pt); - __queue_insert_supp(&media->codecs_prefs_recv, pt, supp_check, sct); + __queue_insert_supp(&media->codecs_prefs_recv, pt, supp_check, media->codec_tracker); } // consumes 'pt' void __rtp_payload_type_add_send(struct call_media *other_media, @@ -2314,10 +2323,10 @@ void __rtp_payload_type_add_send_dup(struct call_media *other_media, } // consumes 'pt' static void __rtp_payload_type_add(struct call_media *media, struct call_media *other_media, - struct rtp_payload_type *pt, struct supp_codec_tracker *sct) + struct rtp_payload_type *pt) { __rtp_payload_type_add_send_dup(other_media, pt); - __rtp_payload_type_add_recv(media, pt, 0, sct); + __rtp_payload_type_add_recv(media, pt, 0); } static void __payload_queue_free(void *qq) { @@ -2325,7 +2334,7 @@ static void __payload_queue_free(void *qq) { g_queue_free_full(q, (GDestroyNotify) payload_type_free); } static int __revert_codec_strip(GHashTable *stripped, GHashTable *masked, const str *codec, - struct call_media *media, struct call_media *other_media, struct supp_codec_tracker *sct) + struct call_media *media, struct call_media *other_media) { int ret = 0; @@ -2336,7 +2345,7 @@ static int __revert_codec_strip(GHashTable *stripped, GHashTable *masked, const g_hash_table_steal(stripped, codec); for (GList *l = q->head; l; l = l->next) { struct rtp_payload_type *pt = l->data; - __rtp_payload_type_add(media, other_media, pt, sct); + __rtp_payload_type_add(media, other_media, pt); } g_queue_free(q); ret = 1; @@ -2350,7 +2359,7 @@ static int __revert_codec_strip(GHashTable *stripped, GHashTable *masked, const for (GList *l = q->head; l; l = l->next) { struct rtp_payload_type *pt = l->data; pt->for_transcoding = 1; - __rtp_payload_type_add_recv(media, pt, 1, sct); + __rtp_payload_type_add_recv(media, pt, 1); } g_queue_free(q); ret = 1; @@ -2388,25 +2397,32 @@ static void __codec_options_set(struct call *call, struct rtp_payload_type *pt, if (__codec_options_set1(call, pt, &pt->encoding, codec_set)) return; } -static void supp_codec_tracker_init(struct supp_codec_tracker *sct) { - ZERO(*sct); - sct->clockrates = g_hash_table_new(g_direct_hash, g_direct_equal); - sct->touched = g_hash_table_new(g_direct_hash, g_direct_equal); - sct->supp_codecs = g_hash_table_new_full(str_case_hash, str_case_equal, NULL, +static void codec_tracker_destroy(struct codec_tracker **sct) { + if (!*sct) + return; + g_hash_table_destroy((*sct)->clockrates); + g_hash_table_destroy((*sct)->touched); + g_hash_table_destroy((*sct)->supp_codecs); + g_slice_free1(sizeof(*sct), *sct); + *sct = NULL; +} +void codec_tracker_init(struct call_media *m) { + codec_tracker_destroy(&m->codec_tracker); + m->codec_tracker = g_slice_alloc0(sizeof(*m->codec_tracker)); + m->codec_tracker->clockrates = g_hash_table_new(g_direct_hash, g_direct_equal); + m->codec_tracker->touched = g_hash_table_new(g_direct_hash, g_direct_equal); + m->codec_tracker->supp_codecs = g_hash_table_new_full(str_case_hash, str_case_equal, free, (GDestroyNotify) g_hash_table_destroy); } -static void supp_codec_tracker_destroy(struct supp_codec_tracker *sct) { - g_hash_table_destroy(sct->clockrates); - g_hash_table_destroy(sct->touched); - g_hash_table_destroy(sct->supp_codecs); -} -static void codec_touched(struct rtp_payload_type *pt, struct supp_codec_tracker *sct, struct call_media *media) { +static void codec_touched(struct rtp_payload_type *pt, struct call_media *media) { + if (!media->codec_tracker) + return; ensure_codec_def(pt, media); if (pt->codec_def && pt->codec_def->supplemental) { - sct->all_touched = 1; + media->codec_tracker->all_touched = 1; return; } - g_hash_table_replace(sct->touched, GUINT_TO_POINTER(pt->clock_rate), (void *) 0x1); + g_hash_table_replace(media->codec_tracker->touched, GUINT_TO_POINTER(pt->clock_rate), (void *) 0x1); } static int ptr_cmp(const void *a, const void *b) { if (a < b) @@ -2415,8 +2431,16 @@ static int ptr_cmp(const void *a, const void *b) { return 1; return 0; } -static void supp_codecs_fixup(struct supp_codec_tracker *sct, struct call_media *media) { - // get all supported audio cloc krates +void codec_tracker_finish(struct call_media *media) { + struct codec_tracker *sct = media->codec_tracker; + if (!sct) + return; + + // build our tables + for (GList *l = media->codecs_prefs_recv.head; l; l = l->next) + __insert_codec_tracker(media, l); + + // get all supported audio clock rates GList *clockrates = g_hash_table_get_keys(sct->clockrates); // and to ensure consistent results clockrates = g_list_sort(clockrates, ptr_cmp); @@ -2433,6 +2457,10 @@ static void supp_codecs_fixup(struct supp_codec_tracker *sct, struct call_media for (GList *k = clockrates; k; k = k->next) { unsigned int clockrate = GPOINTER_TO_UINT(k->data); + // has it been removed? + if (!g_hash_table_lookup(sct->clockrates, GUINT_TO_POINTER(clockrate))) + continue; + // is this already supported? if (g_hash_table_lookup(supp_clockrates, GUINT_TO_POINTER(clockrate))) { // good, remember this @@ -2455,7 +2483,7 @@ static void supp_codecs_fixup(struct supp_codec_tracker *sct, struct call_media continue; pt->for_transcoding = 1; - __rtp_payload_type_add_recv(media, pt, 1, NULL); + __rtp_payload_type_add_recv(media, pt, 1); g_free(pt_s); } @@ -2484,6 +2512,7 @@ static void supp_codecs_fixup(struct supp_codec_tracker *sct, struct call_media g_list_free(supp_codecs); g_list_free(clockrates); + codec_tracker_destroy(&media->codec_tracker); } int __codec_ht_except(int all_flag, GHashTable *yes_ht, GHashTable *no_ht, struct rtp_payload_type *pt) { int do_this = 0; @@ -2546,9 +2575,6 @@ void codec_rtp_payload_types(struct call_media *media, struct call_media *other_ else if (flags->codec_mask && g_hash_table_lookup(flags->codec_mask, &str_full)) mask_all = 2; - struct supp_codec_tracker sct; - supp_codec_tracker_init(&sct); - /* we steal the entire list to avoid duplicate allocs */ while ((pt = g_queue_pop_head(types))) { __rtp_payload_type_dup(call, pt); // this takes care of string allocation @@ -2557,7 +2583,7 @@ void codec_rtp_payload_types(struct call_media *media, struct call_media *other_ if (__codec_ht_except(strip_all, flags->codec_strip, flags->codec_except, pt)) { ilog(LOG_DEBUG, "Stripping codec '" STR_FORMAT "'", STR_FMT(&pt->encoding_with_params)); - codec_touched(pt, &sct, media); + codec_touched(pt, media); GQueue *q = g_hash_table_lookup_queue_new(stripped, &pt->encoding); g_queue_push_tail(q, __rtp_payload_type_copy(pt)); q = g_hash_table_lookup_queue_new(stripped, &pt->encoding_with_params); @@ -2571,7 +2597,7 @@ void codec_rtp_payload_types(struct call_media *media, struct call_media *other_ if (__codec_ht_except(mask_all, flags->codec_mask, flags->codec_except, pt)) { ilog(LOG_DEBUG, "Masking codec '" STR_FORMAT "'", STR_FMT(&pt->encoding_with_params)); - codec_touched(pt, &sct, other_media); + codec_touched(pt, media); GQueue *q = g_hash_table_lookup_queue_new(masked, &pt->encoding); g_queue_push_tail(q, __rtp_payload_type_copy(pt)); q = g_hash_table_lookup_queue_new(stripped, &pt->encoding_with_params); @@ -2579,13 +2605,13 @@ void codec_rtp_payload_types(struct call_media *media, struct call_media *other_ __rtp_payload_type_add_send(other_media, pt); } else - __rtp_payload_type_add(media, other_media, pt, &sct); + __rtp_payload_type_add(media, other_media, pt); } // now restore codecs that have been removed, but should be offered for (GList *l = flags->codec_offer.head; l; l = l->next) { str *codec = l->data; - __revert_codec_strip(stripped, masked, codec, media, other_media, &sct); + __revert_codec_strip(stripped, masked, codec, media, other_media); } if (!flags->asymmetric_codecs) { @@ -2599,6 +2625,7 @@ void codec_rtp_payload_types(struct call_media *media, struct call_media *other_ } ilog(LOG_DEBUG, "Eliminating asymmetric inbound codec " STR_FORMAT, STR_FMT(&pt->encoding_with_params)); + codec_touched(pt, other_media); l = __delete_receiver_codec(other_media, l); } } @@ -2614,7 +2641,7 @@ void codec_rtp_payload_types(struct call_media *media, struct call_media *other_ // simply restore it from the original list and handle it the same way // as 'offer' if ((strip_all == 1 || mask_all == 1) - && __revert_codec_strip(stripped, masked, codec, media, other_media, &sct)) + && __revert_codec_strip(stripped, masked, codec, media, other_media)) continue; // also check if maybe the codec was never stripped if (g_hash_table_lookup(media->codec_names_recv, codec)) { @@ -2628,11 +2655,11 @@ void codec_rtp_payload_types(struct call_media *media, struct call_media *other_ if (!pt) continue; pt->for_transcoding = 1; - codec_touched(pt, &sct, media); + codec_touched(pt, media); ilog(LOG_DEBUG, "Codec '" STR_FORMAT "' added for transcoding with payload type %u", STR_FMT(&pt->encoding_with_params), pt->payload_type); - __rtp_payload_type_add_recv(media, pt, 1, &sct); + __rtp_payload_type_add_recv(media, pt, 1); } if (media->type_id == MT_AUDIO && other_media->type_id == MT_IMAGE) { @@ -2648,7 +2675,7 @@ void codec_rtp_payload_types(struct call_media *media, struct call_media *other_ if (media->t38_gateway && media->t38_gateway->pcm_player && media->t38_gateway->pcm_player->handler) __rtp_payload_type_add_recv(media, - __rtp_payload_type_copy(&media->t38_gateway->pcm_player->handler->dest_pt), 1, &sct); + __rtp_payload_type_copy(&media->t38_gateway->pcm_player->handler->dest_pt), 1); } else if (flags->opmode == OP_OFFER) { // T.38 -> audio transcoder, initial offer, and no codecs have been given. @@ -2658,10 +2685,10 @@ void codec_rtp_payload_types(struct call_media *media, struct call_media *other_ static const str PCMA_str = STR_CONST_INIT("PCMA"); pt = codec_add_payload_type(&PCMU_str, media); assert(pt != NULL); - __rtp_payload_type_add_recv(media, pt, 1, &sct); + __rtp_payload_type_add_recv(media, pt, 1); pt = codec_add_payload_type(&PCMA_str, media); assert(pt != NULL); - __rtp_payload_type_add_recv(media, pt, 1, &sct); + __rtp_payload_type_add_recv(media, pt, 1); ilog(LOG_DEBUG, "Using default codecs PCMU and PCMA for T.38 gateway"); } @@ -2680,15 +2707,13 @@ void codec_rtp_payload_types(struct call_media *media, struct call_media *other_ } ilog(LOG_DEBUG, "Eliminating unsupported codec " STR_FORMAT, STR_FMT(&pt->encoding_with_params)); + codec_touched(pt, media); l = __delete_receiver_codec(media, l); } } } #endif - supp_codecs_fixup(&sct, media); - g_hash_table_destroy(stripped); g_hash_table_destroy(masked); - supp_codec_tracker_destroy(&sct); } diff --git a/daemon/redis.c b/daemon/redis.c index d9d8359f6..b8a759f8f 100644 --- a/daemon/redis.c +++ b/daemon/redis.c @@ -1427,7 +1427,7 @@ static struct rtp_payload_type *rbl_cb_plts_g(str *s, GQueue *q, struct redis_li } static int rbl_cb_plts_r(str *s, GQueue *q, struct redis_list *list, void *ptr) { struct call_media *med = ptr; - __rtp_payload_type_add_recv(med, rbl_cb_plts_g(s, q, list, ptr), 0, NULL); + __rtp_payload_type_add_recv(med, rbl_cb_plts_g(s, q, list, ptr), 0); return 0; } static int rbl_cb_plts_s(str *s, GQueue *q, struct redis_list *list, void *ptr) { diff --git a/include/call.h b/include/call.h index c4c18d191..743d17185 100644 --- a/include/call.h +++ b/include/call.h @@ -196,6 +196,7 @@ struct media_player; struct send_timer; struct transport_protocol; struct jitter_buffer; +struct codec_tracker; typedef bencode_buffer_t call_buffer_t; @@ -325,6 +326,7 @@ struct call_media { GHashTable *codecs_send; // int payload type -> struct rtp_payload_type GHashTable *codec_names_send; // codec name -> GQueue of int payload types; storage container GQueue codecs_prefs_send; // storage container + struct codec_tracker *codec_tracker; GQueue sdp_attributes; // str_sprintf() diff --git a/include/codec.h b/include/codec.h index acfbbc926..2ed37cedb 100644 --- a/include/codec.h +++ b/include/codec.h @@ -79,8 +79,7 @@ void codec_init_payload_type(struct rtp_payload_type *, struct call_media *); // used by redis -void __rtp_payload_type_add_recv(struct call_media *media, struct rtp_payload_type *pt, int supp_check, - struct supp_codec_tracker *sct); +void __rtp_payload_type_add_recv(struct call_media *media, struct rtp_payload_type *pt, int supp_check); void __rtp_payload_type_add_send(struct call_media *other_media, struct rtp_payload_type *pt); @@ -95,12 +94,16 @@ uint64_t codec_last_dtmf_event(struct codec_ssrc_handler *ch); uint64_t codec_encoder_pts(struct codec_ssrc_handler *ch); void codec_decoder_skip_pts(struct codec_ssrc_handler *ch, uint64_t); uint64_t codec_decoder_unskip_pts(struct codec_ssrc_handler *ch); +void codec_tracker_init(struct call_media *); +void codec_tracker_finish(struct call_media *); #else INLINE void codec_handlers_update(struct call_media *receiver, struct call_media *sink, const struct sdp_ng_flags *flags, const struct stream_params *sp) { } INLINE void codec_handler_free(struct codec_handler **handler) { } +INLINE void codec_tracker_init(struct call_media *m) { } +INLINE void codec_tracker_finish(struct call_media *m) { } #endif diff --git a/t/auto-daemon-tests.pl b/t/auto-daemon-tests.pl index 2644b5128..f0f909109 100755 --- a/t/auto-daemon-tests.pl +++ b/t/auto-daemon-tests.pl @@ -4081,14 +4081,16 @@ s=pjmedia b=AS:117 t=0 0 a=X-nat:0 -m=audio PORT RTP/AVP 8 107 101 +m=audio PORT RTP/AVP 8 107 101 96 c=IN IP4 203.0.113.1 b=TIAS:96000 a=ssrc:243811319 cname:04389d431bdd5c52 a=rtpmap:8 PCMA/8000 a=rtpmap:107 opus/48000/2 a=rtpmap:101 telephone-event/8000 +a=rtpmap:96 telephone-event/48000 a=fmtp:101 0-16 +a=fmtp:96 0-15 a=sendrecv a=rtcp:PORT a=ptime:20 @@ -4151,14 +4153,16 @@ s=pjmedia b=AS:117 t=0 0 a=X-nat:0 -m=audio PORT RTP/AVP 8 107 101 +m=audio PORT RTP/AVP 8 107 101 96 c=IN IP4 203.0.113.1 b=TIAS:96000 a=ssrc:243811319 cname:04389d431bdd5c52 a=rtpmap:8 PCMA/8000 a=rtpmap:107 opus/48000/2 a=rtpmap:101 telephone-event/8000 +a=rtpmap:96 telephone-event/48000 a=fmtp:101 0-16 +a=fmtp:96 0-15 a=sendrecv a=rtcp:PORT a=ptime:20 @@ -6946,13 +6950,15 @@ o=- 1545997027 1 IN IP4 203.0.113.1 s=tester c=IN IP4 203.0.113.1 t=0 0 -m=audio PORT RTP/AVP 120 8 0 101 +m=audio PORT RTP/AVP 120 8 0 101 96 a=rtpmap:120 opus/48000/2 a=rtpmap:8 PCMA/8000 a=rtpmap:0 PCMU/8000 a=rtpmap:101 telephone-event/8000 +a=rtpmap:96 telephone-event/48000 a=fmtp:120 useinbandfec=1; usedtx=1; maxaveragebitrate=64000 a=fmtp:101 0-15 +a=fmtp:96 0-15 a=sendrecv a=rtcp:PORT SDP