diff --git a/README.md b/README.md index 12b6a4420..edff85a05 100644 --- a/README.md +++ b/README.md @@ -1180,6 +1180,19 @@ Optionally included keys are: See the `--recording-dir` option above. +* `codec` + + Contains a dictionary controlling various aspects of codecs (or RTP payload types). + The following keys are understood: + + * `strip` + + Contains a list of strings. Each string is the name of a codec or RTP payload + type that should be removed from the SDP. Codec names are case sensitive, and + can be either from the list of codecs explicitly defined by the SDP through + an `a=rtpmap` attribute, or can be from the list of RFC-defined codecs. Examples + are `PCMU`, `opus`, or `telephone-event`. + An example of a complete `offer` request dictionary could be (SDP body abbreviated): diff --git a/daemon/call.c b/daemon/call.c index e92214d7b..a9da7d5c5 100644 --- a/daemon/call.c +++ b/daemon/call.c @@ -1419,17 +1419,30 @@ static void __dtls_logic(const struct sdp_ng_flags *flags, MEDIA_SET(other_media, DTLS); } -static void __rtp_payload_types(struct call_media *media, GQueue *types) { +static void __rtp_payload_types(struct call_media *media, GQueue *types, GHashTable *strip) { struct rtp_payload_type *pt; struct call *call = media->call; + static const str __all = STR_CONST_INIT("all"); + + // start fresh + g_queue_clear(&media->rtp_payload_types_prefs); /* we steal the entire list to avoid duplicate allocs */ while ((pt = g_queue_pop_head(types))) { - /* but we must duplicate the contents */ + // codec stripping + if (strip) { + if (g_hash_table_lookup(strip, &pt->encoding) + || g_hash_table_lookup(strip, &__all)) { + __payload_type_free(pt); + continue; + } + } + /* we must duplicate the contents */ call_str_cpy(call, &pt->encoding_with_params, &pt->encoding_with_params); call_str_cpy(call, &pt->encoding, &pt->encoding); call_str_cpy(call, &pt->encoding_parameters, &pt->encoding_parameters); g_hash_table_replace(media->rtp_payload_types, &pt->payload_type, pt); + g_queue_push_tail(&media->rtp_payload_types_prefs, pt); } } @@ -1553,7 +1566,7 @@ int monologue_offer_answer(struct call_monologue *other_ml, GQueue *streams, MEDIA_SET(other_media, SDES); } - __rtp_payload_types(media, &sp->rtp_payload_types); + __rtp_payload_types(media, &sp->rtp_payload_types, flags->codec_strip); /* send and recv are from our POV */ bf_copy_same(&media->media_flags, &sp->sp_flags, @@ -1963,6 +1976,7 @@ static void __call_free(void *p) { g_queue_clear(&md->streams); g_queue_clear(&md->endpoint_maps); g_hash_table_destroy(md->rtp_payload_types); + g_queue_clear(&md->rtp_payload_types_prefs); g_slice_free1(sizeof(*md), md); } diff --git a/daemon/call.h b/daemon/call.h index 8a8cfe105..ccb679203 100644 --- a/daemon/call.h +++ b/daemon/call.h @@ -322,7 +322,9 @@ struct call_media { GQueue streams; /* normally RTP + RTCP */ GQueue endpoint_maps; + GHashTable *rtp_payload_types; + GQueue rtp_payload_types_prefs; volatile unsigned int media_flags; }; diff --git a/daemon/call_interfaces.c b/daemon/call_interfaces.c index 7f80b7810..b01b096ff 100644 --- a/daemon/call_interfaces.c +++ b/daemon/call_interfaces.c @@ -528,18 +528,18 @@ INLINE void ng_sdes_option(struct sdp_ng_flags *out, bencode_item_t *it, unsigne static void call_ng_flags_list(struct sdp_ng_flags *out, bencode_item_t *input, const char *key, - void (*callback)(struct sdp_ng_flags *out, bencode_item_t *input)) + void (*callback)(struct sdp_ng_flags *, bencode_item_t *, void *), void *parm) { bencode_item_t *list, *it; if ((list = bencode_dictionary_get_expect(input, key, BENCODE_LIST))) { for (it = list->child; it; it = it->sibling) - callback(out, it); + callback(out, it, parm); } } -static void call_ng_flags_sdes(struct sdp_ng_flags *out, bencode_item_t *it) { +static void call_ng_flags_sdes(struct sdp_ng_flags *out, bencode_item_t *it, void *dummy) { ng_sdes_option(out, it, 0); } -static void call_ng_flags_rtcp_mux(struct sdp_ng_flags *out, bencode_item_t *it) { +static void call_ng_flags_rtcp_mux(struct sdp_ng_flags *out, bencode_item_t *it, void *dummy) { if (!bencode_strcmp(it, "offer")) out->rtcp_mux_offer = 1; else if (!bencode_strcmp(it, "require")) @@ -554,7 +554,7 @@ static void call_ng_flags_rtcp_mux(struct sdp_ng_flags *out, bencode_item_t *it) ilog(LOG_WARN, "Unknown 'rtcp-mux' flag encountered: '"BENCODE_FORMAT"'", BENCODE_FMT(it)); } -static void call_ng_flags_replace(struct sdp_ng_flags *out, bencode_item_t *it) { +static void call_ng_flags_replace(struct sdp_ng_flags *out, bencode_item_t *it, void *dummy) { str_hyphenate(it); if (!bencode_strcmp(it, "origin")) out->replace_origin = 1; @@ -564,7 +564,29 @@ static void call_ng_flags_replace(struct sdp_ng_flags *out, bencode_item_t *it) ilog(LOG_WARN, "Unknown 'replace' flag encountered: '"BENCODE_FORMAT"'", BENCODE_FMT(it)); } -static void call_ng_flags_flags(struct sdp_ng_flags *out, bencode_item_t *it) { +static void call_ng_flags_codec_list(struct sdp_ng_flags *out, bencode_item_t *it, void *qp) { + str *s; + s = g_slice_alloc(sizeof(*s)); + if (!bencode_get_str(it, s)) { + g_slice_free1(sizeof(*s), s); + return; + } + g_queue_push_tail((GQueue *) qp, s); +} +static void call_ng_flags_codec_ht_strip(bencode_item_t *it, GHashTable *ht, unsigned int strip) { + str *s; + s = g_slice_alloc(sizeof(*s)); + if (!bencode_get_str(it, s)) { + g_slice_free1(sizeof(*s), s); + return; + } + str_shift(s, strip); + g_hash_table_replace(ht, s, s); +} +static void call_ng_flags_codec_ht(struct sdp_ng_flags *out, bencode_item_t *it, void *htp) { + call_ng_flags_codec_ht_strip(it, htp, 0); +} +static void call_ng_flags_flags(struct sdp_ng_flags *out, bencode_item_t *it, void *dummy) { if (it->type != BENCODE_STRING) return; @@ -586,8 +608,11 @@ static void call_ng_flags_flags(struct sdp_ng_flags *out, bencode_item_t *it) { out->media_handover = 1; else if (!bencode_strcmp(it, "reset")) out->reset = 1; + // XXX unify these else if (it->iov[1].iov_len >= 5 && !memcmp(it->iov[1].iov_base, "SDES-", 5)) ng_sdes_option(out, it, 5); + else if (it->iov[1].iov_len >= 12 && !memcmp(it->iov[1].iov_base, "codec-strip-", 12)) + call_ng_flags_codec_ht_strip(it, out->codec_strip, 12); else if (!bencode_strcmp(it, "port-latching")) out->port_latching = 1; else if (!bencode_strcmp(it, "record-call")) @@ -599,17 +624,18 @@ static void call_ng_flags_flags(struct sdp_ng_flags *out, bencode_item_t *it) { BENCODE_FMT(it)); } static void call_ng_process_flags(struct sdp_ng_flags *out, bencode_item_t *input) { - bencode_item_t *list, *it; + bencode_item_t *list, *it, *dict; int diridx; str s; ZERO(*out); + out->codec_strip = g_hash_table_new_full(str_hash, str_equal, str_slice_free, NULL); out->trust_address = trust_address_def; out->dtls_passive = dtls_passive_def; - call_ng_flags_list(out, input, "flags", call_ng_flags_flags); - call_ng_flags_list(out, input, "replace", call_ng_flags_replace); + call_ng_flags_list(out, input, "flags", call_ng_flags_flags, NULL); + call_ng_flags_list(out, input, "replace", call_ng_flags_replace, NULL); diridx = 0; if ((list = bencode_dictionary_get_expect(input, "direction", BENCODE_LIST))) { @@ -649,10 +675,10 @@ static void call_ng_process_flags(struct sdp_ng_flags *out, bencode_item_t *inpu STR_FMT(&s)); } - call_ng_flags_list(out, input, "rtcp-mux", call_ng_flags_rtcp_mux); + call_ng_flags_list(out, input, "rtcp-mux", call_ng_flags_rtcp_mux, NULL); /* XXX module still needs to support this list */ - call_ng_flags_list(out, input, "SDES", call_ng_flags_sdes); + call_ng_flags_list(out, input, "SDES", call_ng_flags_sdes, NULL); bencode_get_alt(input, "transport-protocol", "transport protocol", &out->transport_protocol_str); out->transport_protocol = transport_protocol(&out->transport_protocol_str); @@ -662,6 +688,15 @@ static void call_ng_process_flags(struct sdp_ng_flags *out, bencode_item_t *inpu out->tos = bencode_dictionary_get_integer(input, "TOS", 256); bencode_get_alt(input, "record-call", "record call", &out->record_call_str); bencode_dictionary_get_str(input, "metadata", &out->metadata); + + if ((dict = bencode_dictionary_get_expect(input, "codec", BENCODE_DICTIONARY))) { + call_ng_flags_list(out, dict, "strip", call_ng_flags_codec_ht, out->codec_strip); + call_ng_flags_list(out, dict, "offer", call_ng_flags_codec_list, &out->codec_offer); + } +} +static void call_ng_free_flags(struct sdp_ng_flags *flags) { + g_hash_table_destroy(flags->codec_strip); + g_queue_clear_full(&flags->codec_offer, str_slice_free); } static const char *call_offer_answer_ng(bencode_item_t *input, @@ -798,6 +833,7 @@ static const char *call_offer_answer_ng(bencode_item_t *input, if (ret == ERROR_NO_FREE_PORTS || ret == ERROR_NO_FREE_LOGS) { ilog(LOG_ERR, "Destroying call"); + errstr = "Ran out of ports"; call_destroy(call); } @@ -811,6 +847,7 @@ static const char *call_offer_answer_ng(bencode_item_t *input, out: sdp_free(&parsed); streams_free(&streams); + call_ng_free_flags(&flags); return errstr; } diff --git a/daemon/call_interfaces.h b/daemon/call_interfaces.h index 1cff0b1ce..7114bd9eb 100644 --- a/daemon/call_interfaces.h +++ b/daemon/call_interfaces.h @@ -31,6 +31,8 @@ struct sdp_ng_flags { int tos; str record_call_str; str metadata; + GHashTable *codec_strip; + GQueue codec_offer; int asymmetric:1, no_redis_update:1, unidirectional:1, diff --git a/daemon/sdp.c b/daemon/sdp.c index 4583f0a8d..b0315e2c4 100644 --- a/daemon/sdp.c +++ b/daemon/sdp.c @@ -162,6 +162,13 @@ struct attribute_rtpmap { struct rtp_payload_type rtp_pt; }; +struct attribute_fmtp { + str payload_type_str; + str format_parms_str; + + unsigned int payload_type; +}; + struct sdp_attribute { str full_line, /* including a= and \r\n */ line_value, /* without a= and without \r\n */ @@ -192,6 +199,7 @@ struct sdp_attribute { ATTR_FINGERPRINT, ATTR_SETUP, ATTR_RTPMAP, + ATTR_FMTP, ATTR_IGNORE, ATTR_END_OF_CANDIDATES, } attr; @@ -205,6 +213,7 @@ struct sdp_attribute { struct attribute_fingerprint fingerprint; struct attribute_setup setup; struct attribute_rtpmap rtpmap; + struct attribute_fmtp fmtp; } u; }; @@ -742,6 +751,26 @@ static int parse_attribute_rtpmap(struct sdp_attribute *output) { return 0; } +static int parse_attribute_fmtp(struct sdp_attribute *output) { + PARSE_DECL; + char *ep; + struct attribute_fmtp *a; + + output->attr = ATTR_FMTP; + + PARSE_INIT; + EXTRACT_TOKEN(u.fmtp.payload_type_str); + EXTRACT_TOKEN(u.fmtp.format_parms_str); + + a = &output->u.fmtp; + + a->payload_type = strtoul(a->payload_type_str.s, &ep, 10); + if (ep == a->payload_type_str.s) + return -1; + + return 0; +} + static int parse_attribute(struct sdp_attribute *a) { int ret; @@ -777,6 +806,8 @@ static int parse_attribute(struct sdp_attribute *a) { ret = parse_attribute_rtcp(a); else if (!str_cmp(&a->name, "ssrc")) ret = parse_attribute_ssrc(a); + else if (!str_cmp(&a->name, "fmtp")) + ret = parse_attribute_fmtp(a); break; case 5: if (!str_cmp(&a->name, "group")) @@ -1408,6 +1439,21 @@ static int replace_transport_protocol(struct sdp_chopper *chop, return 0; } +static int replace_codec_list(struct sdp_chopper *chop, + struct sdp_media *media, struct call_media *cm) +{ + if (cm->rtp_payload_types_prefs.length == 0) + return 0; // legacy protocol or usage error + + for (GList *l = cm->rtp_payload_types_prefs.head; l; l = l->next) { + struct rtp_payload_type *pt = l->data; + chopper_append_printf(chop, " %u", pt->payload_type); + } + if (skip_over(chop, &media->formats)) + return -1; + return 0; +} + static int replace_media_port(struct sdp_chopper *chop, struct sdp_media *media, struct packet_stream *ps) { str *port = &media->port; unsigned int p; @@ -1643,6 +1689,22 @@ static int process_media_attributes(struct sdp_chopper *chop, struct sdp_media * goto strip; // hack/workaround: always remove a=mid break; + case ATTR_RTPMAP: + if (media->rtp_payload_types_prefs.length == 0) + break; // legacy protocol or usage error + if (!g_hash_table_lookup(media->rtp_payload_types, + &attr->u.rtpmap.rtp_pt.payload_type)) + goto strip; + break; + + case ATTR_FMTP: + if (media->rtp_payload_types_prefs.length == 0) + break; // legacy protocol or usage error + if (!g_hash_table_lookup(media->rtp_payload_types, + &attr->u.fmtp.payload_type)) + goto strip; + break; + default: break; } @@ -1951,6 +2013,8 @@ int sdp_replace(struct sdp_chopper *chop, GQueue *sessions, struct call_monologu goto error; if (replace_transport_protocol(chop, sdp_media, call_media)) goto error; + if (replace_codec_list(chop, sdp_media, call_media)) + goto error; if (sdp_media->connection.parsed) { if (replace_network_address(chop, &sdp_media->connection.address, ps, diff --git a/utils/rtpengine-ng-client b/utils/rtpengine-ng-client index b99dd6025..f778d5ce2 100755 --- a/utils/rtpengine-ng-client +++ b/utils/rtpengine-ng-client @@ -44,6 +44,8 @@ GetOptions( 'reset' => \$options{'reset'}, 'port-latching' => \$options{'port latching'}, 'media-address=s' => \$options{'media address'}, + 'codec-strip=s@' => \$options{'codec-strip'}, + 'flags=s@' => \$options{'flags'}, ) or die; my $cmd = shift(@ARGV) or die; @@ -70,6 +72,12 @@ if (defined($options{direction})) { $options{direction} =~ /(.*),(.*)/ or die; $packet{direction} = [$1,$2]; } +if ($options{'codec-strip'} && @{$options{'codec-strip'}}) { + $packet{codec}{strip} = $options{'codec-strip'}; +} +if ($options{'flags'} && @{$options{'flags'}}) { + push(@{$packet{flags}}, @{$options{'flags'}}); +} if (defined($options{sdp})) { $packet{sdp} = $options{sdp};