diff --git a/README.md b/README.md index 11b5bf0ab..68a6d2ad9 100644 --- a/README.md +++ b/README.md @@ -1188,6 +1188,24 @@ Optionally included keys are: which means that it's applied only to the one side doing the signalling that is being handled (i.e. the side doing the `offer` or the `answer`). + - asymmetric codecs + + This flag is relevant to transcoding scenarios. By default, if an RTP client rejects a + codec that was offered to it (by not including it in the answer SDP), *rtpengine* will + assume that this client will also not send this codec (in addition to not wishing to + receive it). With this flag given, *rtpengine* will not make this assumption, meaning + that *rtpengine* will expect to potentially receive a codec from an RTP client even if + that RTP client rejected this codec in its answer SDP. + + The effective difference is that when *rtpengine* is instructed to offer a new codec for + transcoding to an RTP client, and then this RTP client rejects this codec, by default + *rtpengine* is then able to shut down its transcoding engine and revert to non-transcoding + operation for this call. With this flag given however, *rtpengine* would not be able + to shut down its transcoding engine in this case, resulting in potentially different media + flow, and potentially transcoding media when it otherwise would not have to. + + This flag should be given as part of the `answer` message. + * `replace` Similar to the `flags` list. Controls which parts of the SDP body should be rewritten. diff --git a/daemon/call_interfaces.c b/daemon/call_interfaces.c index ce9693405..2741051d8 100644 --- a/daemon/call_interfaces.c +++ b/daemon/call_interfaces.c @@ -618,6 +618,8 @@ static void call_ng_flags_flags(struct sdp_ng_flags *out, str *s, void *dummy) { out->loop_protect = 1; else if (!str_cmp(s, "always-transcode")) out->always_transcode = 1; + else if (!str_cmp(s, "asymmetric-codecs")) + out->asymmetric_codecs = 1; else { // handle values aliases from other dictionaries if (call_ng_flags_prefix(out, s, "SDES-", ng_sdes_option, NULL)) diff --git a/daemon/codec.c b/daemon/codec.c index a66cf5815..d51243f84 100644 --- a/daemon/codec.c +++ b/daemon/codec.c @@ -145,18 +145,27 @@ static void __ensure_codec_def(struct rtp_payload_type *pt, struct call_media *m if (!pt->codec_def->pseudocodec && (!pt->codec_def->support_encoding || !pt->codec_def->support_decoding)) pt->codec_def = NULL; } -static GList *__delete_receiver_codec(struct call_media *receiver, GList *link) { + +static GList *__delete_x_codec(GList *link, GHashTable *codecs, GHashTable *codec_names, GQueue *codecs_prefs) { 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); - g_hash_table_remove(receiver->codec_names_recv, &pt->encoding_with_params); + g_hash_table_remove(codecs, &pt->payload_type); + g_hash_table_remove(codec_names, &pt->encoding); + g_hash_table_remove(codec_names, &pt->encoding_with_params); GList *next = link->next; - g_queue_delete_link(&receiver->codecs_prefs_recv, link); + g_queue_delete_link(codecs_prefs, link); payload_type_free(pt); return next; } +static GList *__delete_receiver_codec(struct call_media *receiver, GList *link) { + return __delete_x_codec(link, receiver->codecs_recv, receiver->codec_names_recv, + &receiver->codecs_prefs_recv); +} +static GList *__delete_send_codec(struct call_media *sender, GList *link) { + return __delete_x_codec(link, sender->codecs_send, sender->codec_names_send, + &sender->codecs_prefs_send); +} // call must be locked in W void codec_handlers_update(struct call_media *receiver, struct call_media *sink, @@ -244,6 +253,22 @@ void codec_handlers_update(struct call_media *receiver, struct call_media *sink, } } } + else { + if (!flags || !flags->asymmetric_codecs) { + // in the other case (not transcoding), we can eliminate rejected codecs from our + // `send` list if the receiver cannot receive it. + for (GList *l = receiver->codecs_prefs_send.head; l;) { + struct rtp_payload_type *pt = l->data; + if (g_hash_table_lookup(receiver->codec_names_recv, &pt->encoding)) { + l = l->next; + continue; + } + ilog(LOG_DEBUG, "Eliminating asymmetric outbound codec " STR_FORMAT, + STR_FMT(&pt->encoding_with_params)); + l = __delete_send_codec(receiver, l); + } + } + } for (GList *l = receiver->codecs_prefs_recv.head; l; ) { struct rtp_payload_type *pt = l->data; @@ -980,6 +1005,21 @@ void codec_rtp_payload_types(struct call_media *media, struct call_media *other_ __revert_codec_strip(removed, codec, media, other_media); } + if (!flags->asymmetric_codecs) { + // eliminate rejected codecs from the reverse direction. a rejected codec is missing + // from the `send` list. also remove it from the `receive` list. + for (GList *l = other_media->codecs_prefs_recv.head; l;) { + pt = l->data; + if (g_hash_table_lookup(other_media->codec_names_send, &pt->encoding)) { + l = l->next; + continue; + } + ilog(LOG_DEBUG, "Eliminating asymmetric inbound codec " STR_FORMAT, + STR_FMT(&pt->encoding_with_params)); + l = __delete_receiver_codec(other_media, l); + } + } + #ifdef WITH_TRANSCODING // add transcode codecs for (GList *l = flags->codec_transcode.head; l; l = l->next) { diff --git a/include/call_interfaces.h b/include/call_interfaces.h index a59eea1e1..824e0cc18 100644 --- a/include/call_interfaces.h +++ b/include/call_interfaces.h @@ -60,6 +60,7 @@ struct sdp_ng_flags { record_call:1, loop_protect:1, always_transcode:1, + asymmetric_codecs:1, supports_load_limit:1, dtls_off:1, sdes_off:1, diff --git a/t/transcode-test.c b/t/transcode-test.c index 74f2960ba..d96823f4d 100644 --- a/t/transcode-test.c +++ b/t/transcode-test.c @@ -72,6 +72,7 @@ static void __sdp_pt_fmt(int num, str codec, int clockrate, str full_codec, str #define sdp_pt(num, codec, clockrate) sdp_pt_fmt(num, codec, clockrate, "") static void offer() { + printf("offer\n"); codec_rtp_payload_types(media_B, media_A, &rtp_types, &flags); codec_handlers_update(media_B, media_A, &flags); g_queue_clear(&rtp_types); @@ -79,6 +80,7 @@ static void offer() { } static void answer() { + printf("answer\n"); codec_rtp_payload_types(media_A, media_B, &rtp_types, &flags); codec_handlers_update(media_A, media_B, &flags); } @@ -259,6 +261,46 @@ int main() { packet(B, 8, PCMA_payload, 8, PCMA_payload); end(); + // plain with two offered and one answered + start(); + sdp_pt(0, PCMU, 8000); + sdp_pt(8, PCMA, 8000); + offer(); + expect(A, recv, ""); + expect(A, send, "0/PCMU/8000 8/PCMA/8000"); + expect(B, recv, "0/PCMU/8000 8/PCMA/8000"); + expect(B, send, ""); + sdp_pt(8, PCMA, 8000); + answer(); + expect(A, recv, "8/PCMA/8000"); + expect(A, send, "8/PCMA/8000"); + expect(B, recv, "8/PCMA/8000"); + expect(B, send, "8/PCMA/8000"); + packet(A, 8, PCMA_payload, 8, PCMA_payload); + packet(B, 8, PCMA_payload, 8, PCMA_payload); + end(); + + // plain with two offered and one answered + asymmetric codecs + start(); + sdp_pt(0, PCMU, 8000); + sdp_pt(8, PCMA, 8000); + offer(); + expect(A, recv, ""); + expect(A, send, "0/PCMU/8000 8/PCMA/8000"); + expect(B, recv, "0/PCMU/8000 8/PCMA/8000"); + expect(B, send, ""); + sdp_pt(8, PCMA, 8000); + flags.asymmetric_codecs = 1; + answer(); + expect(A, recv, "8/PCMA/8000"); + expect(A, send, "0/PCMU/8000 8/PCMA/8000"); + expect(B, recv, "0/PCMU/8000 8/PCMA/8000"); + expect(B, send, "8/PCMA/8000"); + packet(A, 0, PCMU_payload, 0, PCMU_payload); + packet(A, 8, PCMA_payload, 8, PCMA_payload); + packet(B, 8, PCMA_payload, 8, PCMA_payload); + end(); + // plain with two offered and two answered + always-transcode one way start(); flags.always_transcode = 1; @@ -340,10 +382,31 @@ int main() { answer(); expect(A, recv, "0/PCMU/8000"); expect(A, send, "0/PCMU/8000"); + expect(B, recv, "8/PCMA/8000"); + expect(B, send, "8/PCMA/8000"); + packet(A, 0, PCMU_payload, 8, PCMA_payload); + packet(B, 8, PCMA_payload, 0, PCMU_payload); + end(); + + // same as above, but allow asymmetric codecs + start(); + sdp_pt(0, PCMU, 8000); + transcode(PCMA); + offer(); + expect(A, recv, ""); + expect(A, send, "0/PCMU/8000"); + expect(B, recv, "0/PCMU/8000 8/PCMA/8000"); + expect(B, send, ""); + sdp_pt(8, PCMA, 8000); + flags.asymmetric_codecs = 1; + answer(); + expect(A, recv, "0/PCMU/8000"); + expect(A, send, "0/PCMU/8000"); expect(B, recv, "0/PCMU/8000 8/PCMA/8000"); expect(B, send, "8/PCMA/8000"); packet(A, 0, PCMU_payload, 8, PCMA_payload); packet(B, 8, PCMA_payload, 0, PCMU_payload); + packet(B, 0, PCMU_payload, 0, PCMU_payload); end(); { @@ -364,7 +427,7 @@ int main() { answer(); expect(A, recv, "0/PCMU/8000"); expect(A, send, "0/PCMU/8000"); - expect(B, recv, "0/PCMU/8000 96/AMR-WB/16000/octet-align=1"); + expect(B, recv, "96/AMR-WB/16000/octet-align=1"); expect(B, send, "96/AMR-WB/16000/octet-align=1"); packet_seq(A, 0, PCMU_payload, 0, 0, -1, ""); // nothing due to resampling buffer packet_seq_nf(A, 0, PCMU_payload, 160, 1, 96, AMR_WB_payload); @@ -385,7 +448,7 @@ int main() { answer(); expect(A, recv, "96/AMR-WB/16000/octet-align=1"); expect(A, send, "96/AMR-WB/16000/octet-align=1"); - expect(B, recv, "96/AMR-WB/16000/octet-align=1 0/PCMU/8000"); + expect(B, recv, "0/PCMU/8000"); expect(B, send, "0/PCMU/8000"); packet_seq(B, 0, PCMU_payload, 0, 0, -1, ""); // nothing due to resampling buffer packet_seq_nf(B, 0, PCMU_payload, 160, 1, 96, AMR_WB_payload); @@ -406,7 +469,7 @@ int main() { answer(); expect(A, recv, "96/AMR-WB/16000"); expect(A, send, "96/AMR-WB/16000"); - expect(B, recv, "96/AMR-WB/16000 0/PCMU/8000"); + expect(B, recv, "0/PCMU/8000"); expect(B, send, "0/PCMU/8000"); packet_seq(B, 0, PCMU_payload, 0, 0, -1, ""); // nothing due to resampling buffer packet_seq_nf(B, 0, PCMU_payload, 160, 1, 96, AMR_WB_payload_noe); @@ -429,7 +492,7 @@ int main() { answer(); expect(A, recv, "8/PCMA/8000"); expect(A, send, "8/PCMA/8000"); - expect(B, recv, "8/PCMA/8000 9/G722/8000"); + expect(B, recv, "9/G722/8000"); expect(B, send, "9/G722/8000"); packet_seq(A, 8, PCMA_payload, 0, 0, -1, ""); // nothing due to resampling packet_seq_nf(A, 8, PCMA_payload, 160, 1, 9, G722_payload);