From 15b1199c4a6f57de38e714aba9fb99b6986b1cde Mon Sep 17 00:00:00 2001 From: Richard Fuchs Date: Tue, 6 Oct 2020 14:29:51 -0400 Subject: [PATCH] TT#92250 better handle supplemental codecs Change-Id: Id6bac3892642d6ae58c71a1781b5d282c48a3a1c (cherry picked from commit 2ce7b6476e2c7729c6613d215fd2bca1ffb4f412) --- daemon/codec.c | 215 +++++++++++++++++++++++++++++------------ daemon/redis.c | 2 +- include/codec.h | 4 +- lib/codeclib.c | 8 +- lib/codeclib.h | 2 + t/auto-daemon-tests.pl | 100 ++++++++++++++++++- 6 files changed, 266 insertions(+), 65 deletions(-) diff --git a/daemon/codec.c b/daemon/codec.c index 34691ac57..936d47adb 100644 --- a/daemon/codec.c +++ b/daemon/codec.c @@ -100,6 +100,12 @@ struct transcode_packet { int (*dup_func)(struct codec_ssrc_handler *, struct transcode_packet *, struct media_packet *); struct rtp_header rtp; }; +struct supp_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; + GHashTable *supp_codecs; // telephone-event etc => hash table of clock rates +}; static codec_handler_func handler_func_passthrough_ssrc; @@ -623,7 +629,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); + __rtp_payload_type_add_recv(receiver, out_pt, 1, NULL); // and our send leg out_pt = g_hash_table_lookup(prefs_send, GINT_TO_POINTER(pt->payload_type)); if (out_pt) { @@ -648,7 +654,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); + __rtp_payload_type_add_recv(receiver, out_pt, 1, NULL); } while (prefs_send_order.length) { void *ptype = g_queue_pop_head(&prefs_send_order); @@ -2126,41 +2132,6 @@ static struct rtp_payload_type *codec_add_payload_type(const str *codec, struct return pt; } -// handle special meaning "clock rate == 1": add one instance of this PT for each clock rate -// that is already present -static int __codec_synth_transcode_options(struct rtp_payload_type *pt, struct sdp_ng_flags *flags, - struct call_media *media) -{ - if (pt->clock_rate != 1) - return 0; - - struct call *call = media->call; - GHashTable *clockrates = g_hash_table_new(g_direct_hash, g_direct_equal); - - // special handling - add one instance for each clock rate that is present - for (GList *k = media->codecs_prefs_recv.head; k; k = k->next) { - struct rtp_payload_type *pt_r = k->data; - if (g_hash_table_lookup(clockrates, GUINT_TO_POINTER(pt_r->clock_rate))) - continue; - char *pt_s; - if (asprintf(&pt_s, STR_FORMAT "/%u", STR_FMT(&pt->encoding), pt_r->clock_rate) < 0) - continue; - pt_s = call_strdup(call, pt_s); - // XXX optimise this -^ call buffer can probably be replaced with a gstringchunk - // and made lock free - g_hash_table_insert(clockrates, GUINT_TO_POINTER(pt_r->clock_rate), (void *) 1); - str pt_str; - str_init(&pt_str, pt_s); - ilog(LOG_DEBUG, "Synthesised transcoding option for '%s'", pt_s); - g_queue_push_tail(&flags->codec_transcode, str_slice_dup(&pt_str)); - } - - payload_type_free(pt); - g_hash_table_destroy(clockrates); - - return 1; -} - #endif @@ -2187,17 +2158,21 @@ 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 __queue_insert_supp(GQueue *q, struct rtp_payload_type *pt, int supp_check) { +static void __queue_insert_supp(GQueue *q, struct rtp_payload_type *pt, int supp_check, + struct supp_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); - return; + goto do_sct; } // all new supp codecs go last - if (pt->codec_def && pt->codec_def->supplemental) { + if (is_supp) { g_queue_push_tail(q, pt); - return; + goto do_sct; } // find the cut-off point between non-supp and supp codecs @@ -2210,15 +2185,31 @@ static void __queue_insert_supp(GQueue *q, struct rtp_payload_type *pt, int supp } } // do we have any non-supp codecs? - if (!insert_pos) { + if (!insert_pos) 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); } - g_queue_insert_after(q, insert_pos, pt); } // consumes 'pt' void __rtp_payload_type_add_recv(struct call_media *media, - struct rtp_payload_type *pt, int supp_check) + struct rtp_payload_type *pt, int supp_check, struct supp_codec_tracker *sct) { if (!pt) return; @@ -2232,7 +2223,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); + __queue_insert_supp(&media->codecs_prefs_recv, pt, supp_check, sct); } // consumes 'pt' void __rtp_payload_type_add_send(struct call_media *other_media, @@ -2262,10 +2253,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 rtp_payload_type *pt, struct supp_codec_tracker *sct) { __rtp_payload_type_add_send_dup(other_media, pt); - __rtp_payload_type_add_recv(media, pt, 0); + __rtp_payload_type_add_recv(media, pt, 0, sct); } static void __payload_queue_free(void *qq) { @@ -2273,7 +2264,7 @@ static void __payload_queue_free(void *qq) { g_queue_free_full(q, (GDestroyNotify) payload_type_free); } static int __revert_codec_strip(GHashTable *removed, const str *codec, - struct call_media *media, struct call_media *other_media) + struct call_media *media, struct call_media *other_media, struct supp_codec_tracker *sct) { GQueue *q = g_hash_table_lookup(removed, codec); if (!q) @@ -2283,7 +2274,7 @@ static int __revert_codec_strip(GHashTable *removed, const str *codec, g_hash_table_steal(removed, 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); + __rtp_payload_type_add(media, other_media, pt, sct); } g_queue_free(q); return 1; @@ -2314,6 +2305,103 @@ static void __codec_options_set(struct rtp_payload_type *pt, GHashTable *codec_s if (__codec_options_set1(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, + (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) { + ensure_codec_def(pt, media); + if (pt->codec_def && pt->codec_def->supplemental) { + sct->all_touched = 1; + return; + } + g_hash_table_replace(sct->touched, GUINT_TO_POINTER(pt->clock_rate), (void *) 0x1); +} +static int ptr_cmp(const void *a, const void *b) { + if (a < b) + return -1; + if (a > 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 + GList *clockrates = g_hash_table_get_keys(sct->clockrates); + // and to ensure consistent results + clockrates = g_list_sort(clockrates, ptr_cmp); + + // for each supplemental codec supported ... + GList *supp_codecs = g_hash_table_get_keys(sct->supp_codecs); + + for (GList *l = supp_codecs; l; l = l->next) { + // ... compare the list of clock rates against the clock rates supported by the audio codecs + str *supp_codec = l->data; + GHashTable *supp_clockrates = g_hash_table_lookup(sct->supp_codecs, supp_codec); + + // iterate audio clock rates and check against supp clockrates + for (GList *k = clockrates; k; k = k->next) { + unsigned int clockrate = GPOINTER_TO_UINT(k->data); + + // is this already supported? + if (g_hash_table_lookup(supp_clockrates, GUINT_TO_POINTER(clockrate))) { + // good, remember this + g_hash_table_remove(supp_clockrates, GUINT_TO_POINTER(clockrate)); + continue; + } + + // ignore if we haven't touched anything with that clock rate + if (!sct->all_touched && !g_hash_table_lookup(sct->touched, GUINT_TO_POINTER(clockrate))) + continue; + + ilog(LOG_DEBUG, "Adding supplemental codec " STR_FORMAT " for clock rate %u", STR_FMT(supp_codec), clockrate); + + char *pt_s = g_strdup_printf(STR_FORMAT "/%u", STR_FMT(supp_codec), clockrate); + str pt_str; + str_init(&pt_str, pt_s); + + struct rtp_payload_type *pt = codec_add_payload_type(&pt_str, media); + if (!pt) + continue; + pt->for_transcoding = 1; + + __rtp_payload_type_add_recv(media, pt, 1, NULL); + + g_free(pt_s); + } + + // finally check which clock rates are left over and remove those + GList *to_remove = g_hash_table_get_keys(supp_clockrates); + for (GList *k = to_remove; k; k = k->next) { + unsigned int clockrate = GPOINTER_TO_UINT(k->data); + + // ignore if we haven't touched anything with that clock rate + if (!sct->all_touched && !g_hash_table_lookup(sct->touched, GUINT_TO_POINTER(clockrate))) + continue; + + GQueue *entries = g_hash_table_lookup(supp_clockrates, GUINT_TO_POINTER(clockrate)); + for (GList *j = entries->head; j; j = j->next) { + GList *link = j->data; + struct rtp_payload_type *pt = link->data; + + ilog(LOG_DEBUG, "Eliminating supplemental codec " STR_FORMAT " with stray clock rate %u", + STR_FMT(&pt->encoding), clockrate); + + __delete_receiver_codec(media, link); + } + } + } + + g_list_free(supp_codecs); + g_list_free(clockrates); +} void codec_rtp_payload_types(struct call_media *media, struct call_media *other_media, GQueue *types, struct sdp_ng_flags *flags) { @@ -2351,6 +2439,9 @@ void codec_rtp_payload_types(struct call_media *media, struct call_media *other_ if (flags->codec_mask && g_hash_table_lookup(flags->codec_mask, &str_all)) mask_all = 1; + 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 @@ -2362,6 +2453,7 @@ void codec_rtp_payload_types(struct call_media *media, struct call_media *other_ { ilog(LOG_DEBUG, "Stripping codec '" STR_FORMAT "'", STR_FMT(&pt->encoding_with_params)); + codec_touched(pt, &sct, media); GQueue *q = g_hash_table_lookup_queue_new(removed, &pt->encoding); g_queue_push_tail(q, __rtp_payload_type_copy(pt)); q = g_hash_table_lookup_queue_new(removed, &pt->encoding_with_params); @@ -2372,15 +2464,17 @@ void codec_rtp_payload_types(struct call_media *media, struct call_media *other_ __codec_options_set(pt, flags->codec_set); if (!mask_all && (!flags->codec_mask || !g_hash_table_lookup(flags->codec_mask, &pt->encoding)) && (!flags->codec_mask || !g_hash_table_lookup(flags->codec_mask, &pt->encoding_with_params))) - __rtp_payload_type_add(media, other_media, pt); - else + __rtp_payload_type_add(media, other_media, pt, &sct); + else { + codec_touched(pt, &sct, other_media); __rtp_payload_type_add_send(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(removed, codec, media, other_media); + __revert_codec_strip(removed, codec, media, other_media, &sct); } if (!flags->asymmetric_codecs) { @@ -2406,7 +2500,7 @@ void codec_rtp_payload_types(struct call_media *media, struct call_media *other_ // and removed by a strip=all option, // simply restore it from the original list and handle it the same way // as 'offer' - if (strip_all && __revert_codec_strip(removed, codec, media, other_media)) + if (strip_all && __revert_codec_strip(removed, codec, media, other_media, &sct)) continue; // also check if maybe the codec was never stripped if (g_hash_table_lookup(media->codec_names_recv, codec)) { @@ -2420,13 +2514,11 @@ void codec_rtp_payload_types(struct call_media *media, struct call_media *other_ if (!pt) continue; pt->for_transcoding = 1; - - if (__codec_synth_transcode_options(pt, flags, media)) - continue; + codec_touched(pt, &sct, 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); + __rtp_payload_type_add_recv(media, pt, 1, &sct); } if (media->type_id == MT_AUDIO && other_media->type_id == MT_IMAGE) { @@ -2442,7 +2534,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); + __rtp_payload_type_copy(&media->t38_gateway->pcm_player->handler->dest_pt), 1, &sct); } else if (flags->opmode == OP_OFFER) { // T.38 -> audio transcoder, initial offer, and no codecs have been given. @@ -2452,10 +2544,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); + __rtp_payload_type_add_recv(media, pt, 1, &sct); pt = codec_add_payload_type(&PCMA_str, media); assert(pt != NULL); - __rtp_payload_type_add_recv(media, pt, 1); + __rtp_payload_type_add_recv(media, pt, 1, &sct); ilog(LOG_DEBUG, "Using default codecs PCMU and PCMA for T.38 gateway"); } @@ -2480,5 +2572,8 @@ void codec_rtp_payload_types(struct call_media *media, struct call_media *other_ } #endif + supp_codecs_fixup(&sct, media); + g_hash_table_destroy(removed); + supp_codec_tracker_destroy(&sct); } diff --git a/daemon/redis.c b/daemon/redis.c index a92dfd1d4..95b197f46 100644 --- a/daemon/redis.c +++ b/daemon/redis.c @@ -1271,7 +1271,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); + __rtp_payload_type_add_recv(med, rbl_cb_plts_g(s, q, list, ptr), 0, NULL); return 0; } static int rbl_cb_plts_s(str *s, GQueue *q, struct redis_list *list, void *ptr) { diff --git a/include/codec.h b/include/codec.h index 262d2eeee..8d7b6ac38 100644 --- a/include/codec.h +++ b/include/codec.h @@ -19,6 +19,7 @@ struct sdp_ng_flags; struct codec_ssrc_handler; struct rtp_header; struct stream_params; +struct supp_codec_tracker; typedef int codec_handler_func(struct codec_handler *, struct media_packet *); @@ -77,7 +78,8 @@ 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); +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_send(struct call_media *other_media, struct rtp_payload_type *pt); diff --git a/lib/codeclib.c b/lib/codeclib.c index c34cce3e8..82246b34b 100644 --- a/lib/codeclib.c +++ b/lib/codeclib.c @@ -390,7 +390,7 @@ static codec_def_t __codec_defs[] = { .media_type = MT_AUDIO, .supplemental = 1, .dtmf = 1, - .default_clockrate = 1, // special handling + .default_clockrate = 8000, .default_channels = 1, .default_fmtp = "0-15", .codec_type = &codec_type_dtmf, @@ -424,6 +424,9 @@ static codec_def_t __codec_defs[] = { }, }; +static GQueue __supplemental_codecs = G_QUEUE_INIT; +const GQueue * const codec_supplemental_codecs = &__supplemental_codecs; + static GHashTable *codecs_ht; @@ -835,6 +838,9 @@ void codeclib_init(int print) { ilog(LOG_DEBUG, "Codec %s is only supported for encoding " "by codec library", def->rtpname); } + + if (def->supplemental) + g_queue_push_tail(&__supplemental_codecs, def); } } diff --git a/lib/codeclib.h b/lib/codeclib.h index 01ee890b6..91b98bcb6 100644 --- a/lib/codeclib.h +++ b/lib/codeclib.h @@ -209,6 +209,8 @@ struct packet_sequencer_s { }; +extern const GQueue * const codec_supplemental_codecs; + void codeclib_init(int); void codeclib_free(void); diff --git a/t/auto-daemon-tests.pl b/t/auto-daemon-tests.pl index 9cd79d42e..0954433ab 100755 --- a/t/auto-daemon-tests.pl +++ b/t/auto-daemon-tests.pl @@ -7313,8 +7313,8 @@ a=rtpmap:8 PCMA/8000 a=rtpmap:97 speex/16000 a=rtpmap:9 G722/8000 a=rtpmap:98 telephone-event/8000 -a=rtpmap:99 telephone-event/48000 -a=rtpmap:100 telephone-event/16000 +a=rtpmap:99 telephone-event/16000 +a=rtpmap:100 telephone-event/48000 a=fmtp:98 0-15 a=fmtp:99 0-15 a=fmtp:100 0-15 @@ -7325,6 +7325,102 @@ SDP +new_call; + +offer('DTMF PT already present, add one codec', + { ICE => 'remove', replace => ['origin'], + codec => { transcode => ['opus'] } }, < 'remove', replace => ['origin'], + codec => { strip => ['PCMA'] } }, < 'remove', replace => ['origin'], + codec => { transcode => ['opus'], mask => ['PCMA'] } }, < 'remove', replace => ['origin'],