From a4e73c90e846350963f4ccb4adc552e367b9fe6c Mon Sep 17 00:00:00 2001 From: Richard Fuchs Date: Thu, 25 Jan 2018 10:57:54 -0500 Subject: [PATCH] TT#30404 accept and reject codecs according to transcoding preference Change-Id: I7e4d1d834289433ae4a42d78b92cbc745884d5d1 --- daemon/call.c | 16 +++-- daemon/call.h | 13 ++-- daemon/codec.c | 173 +++++++++++++++++++++++++++++++++++---------- daemon/recording.c | 2 +- daemon/redis.c | 8 +-- daemon/ssrc.c | 2 +- 6 files changed, 160 insertions(+), 54 deletions(-) diff --git a/daemon/call.c b/daemon/call.c index 45750034a..5a0e5d523 100644 --- a/daemon/call.c +++ b/daemon/call.c @@ -683,8 +683,9 @@ static struct call_media *__get_media(struct call_monologue *ml, GList **it, con med->call = ml->call; med->index = sp->index; call_str_cpy(ml->call, &med->type, &sp->type); - med->codecs = g_hash_table_new_full(g_int_hash, g_int_equal, NULL, (GDestroyNotify) payload_type_free); - med->codec_names = g_hash_table_new_full(str_hash, str_equal, NULL, (void (*)(void*)) g_queue_free); + med->codecs_recv = g_hash_table_new_full(g_int_hash, g_int_equal, NULL, NULL); + med->codec_names_recv = g_hash_table_new_full(str_hash, str_equal, NULL, (void (*)(void*)) g_queue_free); + med->codec_names_send = g_hash_table_new_full(str_hash, str_equal, NULL, (void (*)(void*)) g_queue_free); g_queue_push_tail(&ml->medias, med); @@ -1019,7 +1020,7 @@ static int __init_streams(struct call_media *A, struct call_media *B, const stru a->rtp_sink = b; PS_SET(a, RTP); /* XXX technically not correct, could be udptl too */ - __rtp_stats_update(a->rtp_stats, A->codecs); + __rtp_stats_update(a->rtp_stats, A->codecs_recv); if (sp) { __fill_stream(a, &sp->rtp_endpoint, port_off, sp); @@ -1720,7 +1721,7 @@ const struct rtp_payload_type *__rtp_stats_codec(struct call_media *m) { if (atomic64_get(&rtp_s->packets) == 0) goto out; - rtp_pt = rtp_payload_type(rtp_s->payload_type, m->codecs); + rtp_pt = rtp_payload_type(rtp_s->payload_type, m->codecs_recv); out: g_list_free(values); @@ -1983,9 +1984,10 @@ static void __call_free(void *p) { crypto_params_cleanup(&md->sdes_out.params); g_queue_clear(&md->streams); g_queue_clear(&md->endpoint_maps); - g_hash_table_destroy(md->codecs); - g_hash_table_destroy(md->codec_names); - g_queue_clear(&md->codecs_prefs_recv); + g_hash_table_destroy(md->codecs_recv); + g_hash_table_destroy(md->codec_names_recv); + g_hash_table_destroy(md->codec_names_send); + g_queue_clear_full(&md->codecs_prefs_recv, (GDestroyNotify) payload_type_free); g_queue_clear_full(&md->codecs_prefs_send, (GDestroyNotify) payload_type_free); codec_handlers_free(md); g_slice_free1(sizeof(*md), md); diff --git a/daemon/call.h b/daemon/call.h index b4f6a43a8..f12024065 100644 --- a/daemon/call.h +++ b/daemon/call.h @@ -326,10 +326,15 @@ struct call_media { GQueue streams; /* normally RTP + RTCP */ GQueue endpoint_maps; - GHashTable *codecs; // int payload type -> struct rtp_payload_type; storage container - GHashTable *codec_names; // codec name -> GQueue of int payload types; storage container - GQueue codecs_prefs_recv, // preference by order in SDP; values shared with 'codecs' - codecs_prefs_send; // ditto for outgoing media; storage container + // what we say we can receive (outgoing SDP): + GHashTable *codecs_recv; // int payload type -> struct rtp_payload_type + GHashTable *codec_names_recv; // codec name -> GQueue of int payload types; storage container + GQueue codecs_prefs_recv; // preference by order in SDP; storage container + + // what we can send, taken from received SDP: + GHashTable *codec_names_send; // codec name -> GQueue of int payload types; storage container + GQueue codecs_prefs_send; // storage container + GHashTable *codec_handlers; // int payload type -> struct codec_handler // XXX combine this with 'codecs' hash table? volatile struct codec_handler *codec_handler_cache; diff --git a/daemon/codec.c b/daemon/codec.c index d1ad62667..21673aac0 100644 --- a/daemon/codec.c +++ b/daemon/codec.c @@ -31,6 +31,9 @@ static void __ssrc_handler_free(struct codec_ssrc_handler *p); static void __transcode_packet_free(struct transcode_packet *); +static struct rtp_payload_type *__rtp_payload_type_copy(struct rtp_payload_type *pt); +static void __rtp_payload_type_add_name(GHashTable *, struct rtp_payload_type *pt); + static struct codec_handler codec_handler_stub = { .source_pt.payload_type = -1, @@ -83,6 +86,22 @@ static void __make_transcoder(struct codec_handler *handler, struct rtp_payload_ } +static void __ensure_codec_def(struct rtp_payload_type *pt) { + if (!pt->codec_def) + pt->codec_def = codec_find(&pt->encoding); +} +static GList *__delete_receiver_codec(struct call_media *receiver, GList *link) { + struct rtp_payload_type *pt = link->data; + + g_hash_table_remove(receiver->codecs_recv, &pt->payload_type); + g_hash_table_remove(receiver->codec_names_recv, &pt->encoding); + + GList *next = link->next; + g_queue_delete_link(&receiver->codecs_prefs_recv, link); + payload_type_free(pt); + return next; +} + // call must be locked in W void codec_handlers_update(struct call_media *receiver, struct call_media *sink) { if (!receiver->codec_handlers) @@ -100,8 +119,7 @@ void codec_handlers_update(struct call_media *receiver, struct call_media *sink) struct rtp_payload_type *pref_dest_codec = NULL; for (GList *l = sink->codecs_prefs_send.head; l; l = l->next) { struct rtp_payload_type *pt = l->data; - if (!pt->codec_def) - pt->codec_def = codec_find(&pt->encoding); + __ensure_codec_def(pt); if (!pt->codec_def) // not supported, next continue; ilog(LOG_DEBUG, "Default sink codec is " STR_FORMAT, STR_FMT(&pt->encoding)); @@ -109,9 +127,71 @@ void codec_handlers_update(struct call_media *receiver, struct call_media *sink) break; } - for (GList *l = receiver->codecs_prefs_recv.head; l; l = l->next) { + if (MEDIA_ISSET(sink, TRANSCODE)) { + // if the other side is transcoding, we need to accept codecs that were + // originally offered (recv->send) if we support them, even if the + // response (sink->send) doesn't include them + GList *insert_pos = NULL; + for (GList *l = receiver->codecs_prefs_send.head; l; l = l->next) { + struct rtp_payload_type *pt = l->data; + __ensure_codec_def(pt); + if (!pt->codec_def) + continue; + if (g_hash_table_lookup(receiver->codecs_recv, &pt->payload_type)) { + // already present. + // to keep the order intact, we seek the list for the position + // of this codec entry. all newly added codecs must come after + // this entry. + if (!insert_pos) + insert_pos = receiver->codecs_prefs_recv.head; + while (insert_pos) { + if (!insert_pos->next) + break; // end of list - we insert everything after + struct rtp_payload_type *test_pt = insert_pos->data; + if (test_pt->payload_type == pt->payload_type) + break; + insert_pos = insert_pos->next; + } + continue; + } + + ilog(LOG_DEBUG, "Accepting offered codec " STR_FORMAT " due to transcoding", + STR_FMT(&pt->encoding)); + MEDIA_SET(receiver, TRANSCODE); + + // we need a new pt entry + pt = __rtp_payload_type_copy(pt); + // 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); + if (!insert_pos) { + g_queue_push_head(&receiver->codecs_prefs_recv, pt); + insert_pos = receiver->codecs_prefs_recv.head; + } + else { + g_queue_insert_after(&receiver->codecs_prefs_recv, insert_pos, pt); + insert_pos = insert_pos->next; + } + } + } + + for (GList *l = receiver->codecs_prefs_recv.head; l; ) { struct rtp_payload_type *pt = l->data; + if (MEDIA_ISSET(sink, TRANSCODE)) { + // if the other side is transcoding, we may come across a receiver entry + // (recv->recv) that wasn't originally offered (recv->send). we must eliminate + // those + // XXX sufficient to check against payload type? + if (!g_hash_table_lookup(receiver->codec_names_send, &pt->encoding)) { + ilog(LOG_DEBUG, "Eliminating transcoded codec " STR_FORMAT, + STR_FMT(&pt->encoding)); + + l = __delete_receiver_codec(receiver, l); + continue; + } + } + // first, make sure we have a codec_handler struct for this struct codec_handler *handler; handler = g_hash_table_lookup(receiver->codec_handlers, &pt->payload_type); @@ -122,6 +202,9 @@ void codec_handlers_update(struct call_media *receiver, struct call_media *sink) handler); } + // check our own support for this codec + __ensure_codec_def(pt); + // if the sink's codec preferences are unknown (empty), or there are // no supported codecs to transcode to, then we have nothing // to do. most likely this is an initial offer without a received answer. @@ -129,21 +212,43 @@ void codec_handlers_update(struct call_media *receiver, struct call_media *sink) if (!pref_dest_codec) { ilog(LOG_DEBUG, "No known/supported sink codec for " STR_FORMAT, STR_FMT(&pt->encoding)); __make_passthrough(handler); - continue; + goto next; } - if (g_hash_table_lookup(sink->codec_names, &pt->encoding)) { + if (g_hash_table_lookup(sink->codec_names_send, &pt->encoding)) { // the sink supports this codec. forward without transcoding. // XXX check format parameters as well ilog(LOG_DEBUG, "Sink supports codec " STR_FORMAT, STR_FMT(&pt->encoding)); __make_passthrough(handler); - continue; + goto next; } // the sink does not support this codec -> transcode ilog(LOG_DEBUG, "Sink does not support codec " STR_FORMAT, STR_FMT(&pt->encoding)); MEDIA_SET(receiver, TRANSCODE); __make_transcoder(handler, pt, pref_dest_codec); + +next: + l = l->next; + } + + // if we've determined that we transcode, we must remove all unsupported codecs from + // the list, as we must expect to potentially receive media in that codec, which we + // then could not transcode. + if (MEDIA_ISSET(receiver, TRANSCODE)) { + for (GList *l = receiver->codecs_prefs_recv.head; l; ) { + struct rtp_payload_type *pt = l->data; + + if (pt->codec_def) { + // supported + l = l->next; + continue; + } + + ilog(LOG_DEBUG, "Stripping unsupported codec " STR_FORMAT " due to active transcoding", + STR_FMT(&pt->encoding)); + l = __delete_receiver_codec(receiver, l); + } } } @@ -312,7 +417,7 @@ static struct rtp_payload_type *codec_add_payload_type(const str *codec, struct if (pt->payload_type < 0) pt->payload_type = 96; // default first dynamic payload type number while (1) { - if (!g_hash_table_lookup(media->codecs, &pt->payload_type)) + if (!g_hash_table_lookup(media->codecs_recv, &pt->payload_type)) break; // OK pt->payload_type++; if (pt->payload_type < 96) // if an RFC type was taken already @@ -343,44 +448,35 @@ static void __rtp_payload_type_dup(struct call *call, struct rtp_payload_type *p call_str_cpy(call, &pt->encoding_parameters, &pt->encoding_parameters); call_str_cpy(call, &pt->format_parameters, &pt->format_parameters); } +static struct rtp_payload_type *__rtp_payload_type_copy(struct rtp_payload_type *pt) { + struct rtp_payload_type *pt_copy = g_slice_alloc(sizeof(*pt)); + *pt_copy = *pt; + return pt_copy; +} +static void __rtp_payload_type_add_name(GHashTable *ht, struct rtp_payload_type *pt) +{ + GQueue *q = g_hash_table_lookup_queue_new(ht, &pt->encoding); + g_queue_push_tail(q, GUINT_TO_POINTER(pt->payload_type)); +} // consumes 'pt' -static struct rtp_payload_type *__rtp_payload_type_add_recv(struct call_media *media, +static void __rtp_payload_type_add_recv(struct call_media *media, struct rtp_payload_type *pt) { - struct rtp_payload_type *existing_pt; - if ((existing_pt = g_hash_table_lookup(media->codecs, &pt->payload_type))) { - // collision/duplicate - ignore - payload_type_free(pt); - return existing_pt; - } - g_hash_table_replace(media->codecs, &pt->payload_type, pt); - - GQueue *q = g_hash_table_lookup_queue_new(media->codec_names, &pt->encoding); - g_queue_push_tail(q, GUINT_TO_POINTER(pt->payload_type)); - + g_hash_table_insert(media->codecs_recv, &pt->payload_type, pt); + __rtp_payload_type_add_name(media->codec_names_recv, pt); g_queue_push_tail(&media->codecs_prefs_recv, pt); - - return pt; } // duplicates 'pt' static void __rtp_payload_type_add_send(struct call_media *other_media, struct rtp_payload_type *pt) { - // for the other side, we need a new 'pt' struct - struct rtp_payload_type *pt_copy = g_slice_alloc(sizeof(*pt)); - *pt_copy = *pt; - g_queue_push_tail(&other_media->codecs_prefs_send, pt_copy); - - // make sure we have at least an empty queue here to indicate support for this code. - // don't add anything to the queue as we don't know the reverse RTP payload type. - g_hash_table_lookup_queue_new(other_media->codec_names, &pt->encoding); + pt = __rtp_payload_type_copy(pt); + __rtp_payload_type_add_name(other_media->codec_names_send, pt); + g_queue_push_tail(&other_media->codecs_prefs_send, pt); } // consumes 'pt' static void __rtp_payload_type_add(struct call_media *media, struct call_media *other_media, struct rtp_payload_type *pt) { - // if this payload type is already present in the 'codec' table, the _recv - // function frees its argument and returns the existing entry instead. - // otherwise it returns its argument. - pt = __rtp_payload_type_add_recv(media, pt); + __rtp_payload_type_add_recv(media, pt); __rtp_payload_type_add_send(other_media, pt); } @@ -415,10 +511,13 @@ void codec_rtp_payload_types(struct call_media *media, struct call_media *other_ int remove_all = 0; // start fresh - g_queue_clear(&media->codecs_prefs_recv); + // receiving part for 'media' + g_queue_clear_full(&media->codecs_prefs_recv, (GDestroyNotify) payload_type_free); + g_hash_table_remove_all(media->codecs_recv); + g_hash_table_remove_all(media->codec_names_recv); + // and sending part for 'other_media' g_queue_clear_full(&other_media->codecs_prefs_send, (GDestroyNotify) payload_type_free); - g_hash_table_remove_all(media->codecs); - g_hash_table_remove_all(media->codec_names); + g_hash_table_remove_all(other_media->codec_names_send); if (strip && g_hash_table_lookup(strip, &str_all)) remove_all = 1; @@ -454,7 +553,7 @@ void codec_rtp_payload_types(struct call_media *media, struct call_media *other_ if (__revert_codec_strip(removed, codec, media, other_media)) continue; // also check if maybe the codec was never stripped - if (g_hash_table_lookup(media->codec_names, codec)) { + if (g_hash_table_lookup(media->codec_names_recv, codec)) { ilog(LOG_DEBUG, "Codec '" STR_FORMAT "' requested for transcoding is already present", STR_FMT(codec)); continue; diff --git a/daemon/recording.c b/daemon/recording.c index 2b490faa0..8b0cf5f36 100644 --- a/daemon/recording.c +++ b/daemon/recording.c @@ -713,7 +713,7 @@ static void setup_media_proc(struct call_media *media) { if (!recording) return; - GList *pltypes = g_hash_table_get_values(media->codecs); + GList *pltypes = g_hash_table_get_values(media->codecs_recv); for (GList *l = pltypes; l; l = l->next) { struct rtp_payload_type *pt = l->data; diff --git a/daemon/redis.c b/daemon/redis.c index fc2a907bb..413fe0fc2 100644 --- a/daemon/redis.c +++ b/daemon/redis.c @@ -1227,7 +1227,7 @@ static int rbl_cb_plts(str *s, GQueue *q, struct redis_list *list, void *ptr) { pt->clock_rate = str_to_ui(&clock, 0); call_str_cpy(call, &pt->encoding_parameters, &enc_parms); call_str_cpy(call, &pt->format_parameters, &fmt_parms); - g_hash_table_replace(med->codecs, &pt->payload_type, pt); + g_hash_table_replace(med->codecs_recv, &pt->payload_type, pt); return 0; } static int json_medias(struct call *c, struct redis_list *medias, JsonReader *root_reader) { @@ -1242,7 +1242,7 @@ static int json_medias(struct call *c, struct redis_list *medias, JsonReader *ro /* from call.c:__get_media() */ med = uid_slice_alloc0(med, &c->medias); med->call = c; - med->codecs = g_hash_table_new_full(g_int_hash, g_int_equal, NULL, + med->codecs_recv = g_hash_table_new_full(g_int_hash, g_int_equal, NULL, (GDestroyNotify) payload_type_free); if (redis_hash_get_unsigned(&med->index, rh, "index")) @@ -1387,7 +1387,7 @@ static int json_link_streams(struct call *c, struct redis_list *streams, return -1; if (ps->media) - __rtp_stats_update(ps->rtp_stats, ps->media->codecs); + __rtp_stats_update(ps->rtp_stats, ps->media->codecs_recv); } return 0; @@ -2009,7 +2009,7 @@ char* redis_encode_json(struct call *c) { } json_builder_end_array (builder); - k = g_hash_table_get_values(media->codecs); + k = g_hash_table_get_values(media->codecs_recv); snprintf(tmp, sizeof(tmp), "payload_types-%u", media->unique_id); json_builder_set_member_name(builder, tmp); json_builder_begin_array (builder); diff --git a/daemon/ssrc.c b/daemon/ssrc.c index c63b25106..cb38fbc99 100644 --- a/daemon/ssrc.c +++ b/daemon/ssrc.c @@ -292,7 +292,7 @@ void ssrc_receiver_report(struct call_media *m, const struct ssrc_receiver_repor } } - const struct rtp_payload_type *rpt = rtp_payload_type(pt, m->codecs); + const struct rtp_payload_type *rpt = rtp_payload_type(pt, m->codecs_recv); if (!rpt) { ilog(LOG_INFO, "Invalid RTP payload type %i, discarding RTCP RR", pt); goto out_nl;