diff --git a/README.md b/README.md index f07fbd2d3..cd5993eec 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,7 @@ the following additional features are available: - Injection of DTMF events or PCM DTMF tones into running audio streams - Playback of pre-recorded streams/announcements - Transcoding between T.38 and PCM (G.711 or other audio codecs) +- Silence detection and comfort noise (RFC 3389) payloads *Rtpengine* does not (yet) support: @@ -811,6 +812,13 @@ Optionally included keys are: injection via the `play DTMF` control message. See `play DTMF` below for additional information. + - `generate RTCP` + + With this flag set, received RTCP packets will not simply be passed through as + usual, but instead will be consumed, and instead *rtpengine* will generate its own + RTCP packets to send to the RTP peers. This flag will be effective for both + sides of a call. + * `replace` Similar to the `flags` list. Controls which parts of the SDP body should be rewritten. @@ -1226,6 +1234,19 @@ Optionally included keys are: This option is only processed in `offer` messages and ignored otherwise. + * `consume` + + Identical to `mask` but enables the transcoding engine even if no other transcoding + related options are given. + + * `accept` + + Similar to `mask` and `consume` but doesn't remove the codec from the list of + offered codecs. This means that a codec listed under `accept` will still be offered + to the remote peer, but if the remote peer rejects it, it will still be accepted + torwards the original offerer and then used for transcoding. It is a more selective + version of what the `always transcode` flag does. + * `set` Contains a list of strings. This list makes it possible to set codec options diff --git a/daemon/call.c b/daemon/call.c index 33f10f8b9..1775126bf 100644 --- a/daemon/call.c +++ b/daemon/call.c @@ -77,6 +77,8 @@ static struct timeval add_ongoing_calls_dur_in_interval(struct timeval *interval struct timeval *interval_duration); static void __call_free(void *p); static void __call_cleanup(struct call *c); +static void __monologue_stop(struct call_monologue *ml); +static void media_stop(struct call_media *m); /* called with call->master_lock held in R */ static int call_timer_delete_monologues(struct call *c) { @@ -2122,6 +2124,11 @@ int monologue_offer_answer(struct call_monologue *other_ml, GQueue *streams, ice_restart(other_media->ice_agent); } + if (flags && flags->generate_rtcp) { + MEDIA_SET(media, RTCP_GEN); + MEDIA_SET(other_media, RTCP_GEN); + } + __update_media_protocol(media, other_media, sp, flags); __update_media_id(media, other_media, sp, flags); __endpoint_loop_protect(sp, other_media); @@ -2388,13 +2395,13 @@ static void __call_cleanup(struct call *c) { for (GList *l = c->medias.head; l; l = l->next) { struct call_media *md = l->data; ice_shutdown(&md->ice_agent); - t38_gateway_stop(md->t38_gateway); + media_stop(md); t38_gateway_put(&md->t38_gateway); } for (GList *l = c->monologues.head; l; l = l->next) { struct call_monologue *ml = l->data; - media_player_stop(ml->player); + __monologue_stop(ml); media_player_put(&ml->player); } @@ -3042,13 +3049,18 @@ struct call_monologue *call_get_mono_dialogue(struct call *call, const str *from -static void monologue_stop(struct call_monologue *ml) { +static void media_stop(struct call_media *m) { + t38_gateway_stop(m->t38_gateway); + codec_handlers_stop(&m->codec_handlers_store); + m->rtcp_timer.tv_sec = 0; +} +static void __monologue_stop(struct call_monologue *ml) { media_player_stop(ml->player); - for (GList *l = ml->medias.head; l; l = l->next) { - struct call_media *m = l->data; - t38_gateway_stop(m->t38_gateway); - codec_handlers_stop(&m->codec_handlers_store); - } +} +static void monologue_stop(struct call_monologue *ml) { + __monologue_stop(ml); + for (GList *l = ml->medias.head; l; l = l->next) + media_stop(l->data); } @@ -3115,6 +3127,8 @@ do_delete: ng_call_stats(c, fromtag, totag, output, NULL); monologue_stop(ml); + if (ml->active_dialogue && ml->active_dialogue->active_dialogue == ml) + monologue_stop(ml->active_dialogue); if (delete_delay > 0) { ilog(LOG_INFO, "Scheduling deletion of call branch '" STR_FORMAT_M "' " diff --git a/daemon/call_interfaces.c b/daemon/call_interfaces.c index 7c5b32497..f8f5719f2 100644 --- a/daemon/call_interfaces.c +++ b/daemon/call_interfaces.c @@ -801,14 +801,18 @@ static void call_ng_flags_flags(struct sdp_ng_flags *out, str *s, void *dummy) { case CSH_LOOKUP("full-rtcp-attribute"): out->full_rtcp_attr = 1; break; + case CSH_LOOKUP("generate-RTCP"): + out->generate_rtcp = 1; + break; case CSH_LOOKUP("loop-protect"): out->loop_protect = 1; break; case CSH_LOOKUP("original-sendrecv"): out->original_sendrecv = 1; break; - case CSH_LOOKUP("always-transcode"): - out->always_transcode = 1; + case CSH_LOOKUP("always-transcode"):; + static const str str_all = STR_CONST_INIT("all"); + call_ng_flags_str_ht(out, (str *) &str_all, &out->codec_accept); break; case CSH_LOOKUP("asymmetric-codecs"): out->asymmetric_codecs = 1; @@ -862,14 +866,20 @@ static void call_ng_flags_flags(struct sdp_ng_flags *out, str *s, void *dummy) { if (call_ng_flags_prefix(out, s, "codec-mask-", call_ng_flags_str_ht, &out->codec_mask)) return; - if (call_ng_flags_prefix(out, s, "codec-set-", call_ng_flags_str_ht_split, - &out->codec_set)) - return; if (call_ng_flags_prefix(out, s, "T38-", ng_t38_option, NULL)) return; if (call_ng_flags_prefix(out, s, "T.38-", ng_t38_option, NULL)) return; } + if (call_ng_flags_prefix(out, s, "codec-set-", call_ng_flags_str_ht_split, + &out->codec_set)) + return; + if (call_ng_flags_prefix(out, s, "codec-accept-", call_ng_flags_str_ht, + &out->codec_accept)) + return; + if (call_ng_flags_prefix(out, s, "codec-consume-", call_ng_flags_str_ht, + &out->codec_consume)) + return; #endif ilog(LOG_WARN, "Unknown flag encountered: '" STR_FORMAT "'", @@ -1082,6 +1092,8 @@ static void call_ng_process_flags(struct sdp_ng_flags *out, bencode_item_t *inpu call_ng_flags_list(out, dict, "transcode", call_ng_flags_codec_list, &out->codec_transcode); call_ng_flags_list(out, dict, "mask", call_ng_flags_str_ht, &out->codec_mask); call_ng_flags_list(out, dict, "set", call_ng_flags_str_ht_split, &out->codec_set); + call_ng_flags_list(out, dict, "accept", call_ng_flags_str_ht, &out->codec_accept); + call_ng_flags_list(out, dict, "consume", call_ng_flags_str_ht, &out->codec_consume); } #endif } @@ -1095,6 +1107,10 @@ static void call_ng_free_flags(struct sdp_ng_flags *flags) { g_hash_table_destroy(flags->codec_mask); if (flags->codec_set) g_hash_table_destroy(flags->codec_set); + if (flags->codec_accept) + g_hash_table_destroy(flags->codec_accept); + if (flags->codec_consume) + g_hash_table_destroy(flags->codec_consume); if (flags->sdes_no) g_hash_table_destroy(flags->sdes_no); g_queue_clear_full(&flags->codec_offer, free); diff --git a/daemon/codec.c b/daemon/codec.c index 6bd28c29b..164cc1f11 100644 --- a/daemon/codec.c +++ b/daemon/codec.c @@ -85,6 +85,11 @@ struct dtx_entry { void *ssrc_ptr; // opaque pointer, doesn't hold a reference }; +struct silence_event { + uint64_t start; + uint64_t end; +}; + struct codec_ssrc_handler { struct ssrc_entry h; // must be first struct codec_handler *handler; @@ -109,6 +114,9 @@ struct codec_ssrc_handler { GQueue dtmf_events; struct dtmf_event dtmf_event; + // silence detection + GQueue silence_events; + uint64_t skip_pts; int rtp_mark:1; @@ -131,9 +139,19 @@ struct codec_tracker { GHashTable *supp_codecs; // telephone-event etc => hash table of clock rates }; +struct rtcp_timer_queue { + struct timerthread_queue ttq; +}; +struct rtcp_timer { + struct timerthread_queue_entry ttq_entry; + struct call *call; + struct call_media *media; +}; + static struct timerthread codec_timers_thread; +static struct rtcp_timer_queue *rtcp_timer_queue; static codec_handler_func handler_func_passthrough_ssrc; @@ -176,6 +194,7 @@ static void __handler_shutdown(struct codec_handler *handler) { handler->dtmf_scaler = 0; handler->output_handler = handler; // reset to default handler->dtmf_payload_type = -1; + handler->cn_payload_type = -1; handler->pcm_dtmf_detect = 0; if (handler->stats_entry) { @@ -203,6 +222,7 @@ static struct codec_handler *__handler_new(const struct rtp_payload_type *pt, st handler->source_pt = *pt; handler->output_handler = handler; // default handler->dtmf_payload_type = -1; + handler->cn_payload_type = -1; handler->packet_encoded = packet_encoded_rtp; handler->packet_decoded = packet_decoded_fifo; handler->media = media; @@ -405,6 +425,7 @@ static struct rtp_payload_type *__check_dest_codecs(struct call_media *receiver, const struct sdp_ng_flags *flags, GHashTable *supplemental_sinks, int *sink_transcoding) { struct rtp_payload_type *pref_dest_codec = NULL; + struct rtp_payload_type *first_tc_codec = NULL; for (GList *l = sink->codecs_prefs_send.head; l; l = l->next) { struct rtp_payload_type *pt = l->data; @@ -418,28 +439,53 @@ static struct rtp_payload_type *__check_dest_codecs(struct call_media *receiver, if (sink->ptime) pt->ptime = sink->ptime; - if (!pref_dest_codec && !pt->codec_def->supplemental) { - ilog(LOG_DEBUG, "Default sink codec is " STR_FORMAT, STR_FMT(&pt->encoding_with_params)); + if (!pref_dest_codec && !pt->codec_def->supplemental) pref_dest_codec = pt; - } + // also check if this is a transcoding codec: if we can send a codec to the sink, // but can't receive it on the receiver side, then it's transcoding. this is to check // whether transcoding on the sink side is actually needed. if transcoding has been // previously enabled on the sink, but no transcoding codecs are actually present, // we can disable the transcoding engine. + struct rtp_payload_type *recv_pt = g_hash_table_lookup(receiver->codecs_send, + &pt->payload_type); + if (recv_pt && rtp_payload_type_cmp(pt, recv_pt)) + recv_pt = NULL; + //ilog(LOG_DEBUG, "XXXXXXXXXXXX old flag is %i", *sink_transcoding); + //ilog(LOG_DEBUG, "XXXXXXXXXXXX checking dest codec " STR_FORMAT " is %i", + //STR_FMT(&pt->encoding_with_params), + //pt->for_transcoding); + //if (recv_pt) + //ilog(LOG_DEBUG, "XXXXXXXXXXXX checking dest codec reverse " STR_FORMAT " is %i", + //STR_FMT(&recv_pt->encoding_with_params), + //recv_pt->for_transcoding); if (MEDIA_ISSET(sink, TRANSCODE)) { - struct rtp_payload_type *recv_pt = g_hash_table_lookup(receiver->codecs_send, - &pt->payload_type); - if (!recv_pt || rtp_payload_type_cmp(pt, recv_pt)) { - // can the sink receive supplemental codec but the receiver can't send it? + if (!recv_pt) { + // can the sink receive codec but the receiver can't send it? *sink_transcoding |= 0x3; } } + if (pt->for_transcoding) { + // codec is explicitly marked for transcoding. enable transcoding engine + MEDIA_SET(receiver, TRANSCODE); + *sink_transcoding |= 0x3; + if (!first_tc_codec && !pt->codec_def->supplemental) + first_tc_codec = pt; + if (pt->codec_def->supplemental) + *sink_transcoding |= 0x4; + } + //ilog(LOG_DEBUG, "XXXXXXXXXXXX new flag is %i", *sink_transcoding); __track_supp_codec(supplemental_sinks, pt); } + if (first_tc_codec) + pref_dest_codec = first_tc_codec; + if (pref_dest_codec) + ilog(LOG_DEBUG, "Default sink codec is " STR_FORMAT, + STR_FMT(&pref_dest_codec->encoding_with_params)); + return pref_dest_codec; } @@ -454,10 +500,21 @@ static void __check_send_codecs(struct call_media *receiver, struct call_media * struct rtp_payload_type *recv_pt = g_hash_table_lookup(receiver->codecs_send, &pt->payload_type); int tc_flag = 0; + //ilog(LOG_DEBUG, "XXXXXXXXXXXX old flag is %i", *sink_transcoding); + //ilog(LOG_DEBUG, "XXXXXXXXXXXX checking send codec " STR_FORMAT " is %i", + //STR_FMT(&pt->encoding_with_params), + //pt->for_transcoding); + //if (recv_pt) + //ilog(LOG_DEBUG, "XXXXXXXXXXXX checking send codec reverse " STR_FORMAT " is %i", + //STR_FMT(&recv_pt->encoding_with_params), + //recv_pt->for_transcoding); if (!recv_pt || rtp_payload_type_cmp(pt, recv_pt)) tc_flag |= 0x3; if (flags && flags->inject_dtmf) tc_flag |= 0x1; + if (pt->for_transcoding) + tc_flag |= 0x3; + //ilog(LOG_DEBUG, "XXXXXXXXXXXX set flag is %i", *sink_transcoding); if (tc_flag) { // can the sink receive codec but the receiver can't send it? *sink_transcoding |= tc_flag; @@ -466,11 +523,12 @@ static void __check_send_codecs(struct call_media *receiver, struct call_media * // even if the receiver can receive the same codec that the sink can // send, we might still have it configured as a transcoder due to - // always-transcode in the offer + // force accepted codec in the offer struct codec_handler *ch_recv = g_hash_table_lookup(sink->codec_handlers, GINT_TO_POINTER(recv_pt->payload_type)); if (!ch_recv) continue; + //ilog(LOG_DEBUG, "XXXXXXXXXXXX handler transcoder %i", ch_recv->transcoder); if (ch_recv->transcoder) *sink_transcoding |= 0x3; } @@ -571,8 +629,28 @@ static void __single_codec(struct call_media *media, const struct sdp_ng_flags * } } +static int __check_receiver_codecs(struct call_media *receiver) { + int ret = 0; + // if some codecs were explicitly marked for transcoding, then we accept only those. + // otherwise we accept all that we can. + for (GList *l = receiver->codecs_prefs_send.head; l; l = l->next) { + struct rtp_payload_type *pt = l->data; + ensure_codec_def(pt, receiver); + if (!pt->codec_def) + continue; + //ilog(LOG_DEBUG, "XXXXXXXXXXXX checking recv send " STR_FORMAT " %i %i", STR_FMT(&pt->encoding_with_params), pt->for_transcoding, pt->codec_def->supplemental); + if (pt->for_transcoding) { + if (pt->codec_def->supplemental) + ret |= 0x2 | 0x4; + else + ret |= 0x1 | 0x2; + } + } + return ret; +} + static void __accept_transcode_codecs(struct call_media *receiver, struct call_media *sink, - const struct sdp_ng_flags *flags) + const struct sdp_ng_flags *flags, int accept_only_tc) { // if the other side is transcoding, we need to accept codecs that were // originally offered (recv->send) if we support them, even if the @@ -583,6 +661,9 @@ static void __accept_transcode_codecs(struct call_media *receiver, struct call_m ensure_codec_def(pt, receiver); if (!pt->codec_def) continue; + if (accept_only_tc && !pt->for_transcoding) + continue; + //ilog(LOG_DEBUG, "XXXXXXXXXXX accept codec " STR_FORMAT " flag %i", STR_FMT(&pt->encoding_with_params), pt->for_transcoding); struct rtp_payload_type *existing_pt = g_hash_table_lookup(receiver->codecs_recv, &pt->payload_type); if (existing_pt && !rtp_payload_type_cmp_nf(existing_pt, pt)) { @@ -625,6 +706,7 @@ static void __accept_transcode_codecs(struct call_media *receiver, struct call_m g_hash_table_insert(receiver->codecs_recv, &existing_pt->payload_type, existing_pt); } + //ilog(LOG_DEBUG, "XXXXXXXXXXXXX offered codec %i", pt->for_transcoding); ilog(LOG_DEBUG, "Accepting offered codec " STR_FORMAT " due to transcoding", STR_FMT(&pt->encoding_with_params)); MEDIA_SET(receiver, TRANSCODE); @@ -714,6 +796,7 @@ static GHashTable *__payload_type_queue_hash(GQueue *prefs, GQueue *order) { static void __symmetric_codecs(struct call_media *receiver, struct call_media *sink, int *sink_transcoding) { + //ilog(LOG_DEBUG, "XXXXXXXXXXXXXXX symm codec flags %i %i", MEDIA_ISSET(sink, TRANSCODE), *sink_transcoding); if (!MEDIA_ISSET(sink, TRANSCODE)) return; if (!*sink_transcoding) @@ -735,28 +818,62 @@ static void __symmetric_codecs(struct call_media *receiver, struct call_media *s // reconstruct list based on other side's preference. int transcoding = 0; + // keep track of our reconstruction order. there might be some codecs that have been force accepted + // that aren't present in sink->codecs_prefs_send. we must add them our output (receiver->send/recv) + // in order. + GList *prefix_pt_pos = prefs_send_order.head; + for (GList *l = sink->codecs_prefs_send.head; l; l = l->next) { struct rtp_payload_type *pt = l->data; + //ilog(LOG_DEBUG, "XXXXXXXXXXXXXXXX symm codec check " STR_FORMAT, STR_FMT(&pt->encoding_with_params)); // do we have a matching output? struct rtp_payload_type *out_pt = g_hash_table_lookup(prefs_recv, GINT_TO_POINTER(pt->payload_type)); - if (out_pt && g_hash_table_lookup(prefs_send, GINT_TO_POINTER(pt->payload_type))) { + struct rtp_payload_type *send_pt; + if (!out_pt || !(send_pt = g_hash_table_lookup(prefs_send, GINT_TO_POINTER(pt->payload_type)))) { + // we must transcode after all. + ilog(LOG_DEBUG, "RTP payload type %i is not symmetric and must be transcoded", + pt->payload_type); + transcoding = 1; + continue; + } + + // seek forward in our prefix list and check any PTs to see if they're force accepted + while (prefix_pt_pos) { + void *ptype = prefix_pt_pos->data; + struct rtp_payload_type *prefix_pt = g_hash_table_lookup(prefs_send, ptype); + prefix_pt_pos = prefix_pt_pos->next; + if (!prefix_pt) + continue; // bug? + if (prefix_pt == send_pt) + break; // caught up + //ilog(LOG_DEBUG, "XXXXXXXXXXXXXXXX prefix codec check " STR_FORMAT " %i", STR_FMT(&prefix_pt->encoding_with_params), prefix_pt->for_transcoding); + if (!prefix_pt->for_transcoding) + continue; // not interesting + // 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); - // and our send leg - out_pt = g_hash_table_lookup(prefs_send, GINT_TO_POINTER(pt->payload_type)); - if (out_pt) { - g_hash_table_steal(prefs_send, GINT_TO_POINTER(pt->payload_type)); - __rtp_payload_type_add_send(receiver, out_pt); + ilog(LOG_DEBUG, "Adding force-accepted RTP payload type %i", prefix_pt->payload_type); + g_hash_table_steal(prefs_send, ptype); + __rtp_payload_type_add_send(receiver, prefix_pt); + // and our receive leg + struct rtp_payload_type *in_pt = g_hash_table_lookup(prefs_recv, ptype); + if (in_pt) { + g_hash_table_steal(prefs_recv, ptype); + __rtp_payload_type_add_recv(receiver, in_pt, 1); } - continue; + transcoding = 1; + } + + // 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); + // and our send leg + out_pt = g_hash_table_lookup(prefs_send, GINT_TO_POINTER(pt->payload_type)); + if (out_pt) { + g_hash_table_steal(prefs_send, GINT_TO_POINTER(pt->payload_type)); + __rtp_payload_type_add_send(receiver, out_pt); } - // we must transcode after all. - ilog(LOG_DEBUG, "RTP payload type %i is not symmetric and must be transcoded", - pt->payload_type); - transcoding = 1; } if (!transcoding) @@ -983,6 +1100,111 @@ static int codec_handler_non_rtp_update(struct call_media *receiver, struct call } +static void __rtcp_timer_free(void *p) { + struct rtcp_timer *rt = p; + if (rt->call) + obj_put(rt->call); + g_slice_free1(sizeof(*rt), rt); +} +// master lock held in W +static void __codec_rtcp_timer_schedule(struct call_media *media) { + struct rtcp_timer *rt = g_slice_alloc0(sizeof(*rt)); + rt->ttq_entry.when = media->rtcp_timer; + rt->call = obj_get(media->call); + rt->media = media; + + timerthread_queue_push(&rtcp_timer_queue->ttq, &rt->ttq_entry); +} +// no lock held +static void __rtcp_timer_run(struct timerthread_queue *q, void *p) { + struct rtcp_timer *rt = p; + + // check scheduling + rwlock_lock_w(&rt->call->master_lock); + struct call_media *media = rt->media; + struct timeval rtcp_timer = media->rtcp_timer; + + log_info_call(rt->call); + + if (!rtcp_timer.tv_sec || timeval_diff(&rtpe_now, &rtcp_timer) < 0 || !proto_is_rtp(media->protocol)) { + __rtcp_timer_free(rt); + rwlock_unlock_w(&rt->call->master_lock); + goto out; + } + timeval_add_usec(&rtcp_timer, 5000000 + (random() % 2000000)); + media->rtcp_timer = rtcp_timer; + __codec_rtcp_timer_schedule(media); + + // switch locks to be more graceful + rwlock_unlock_w(&rt->call->master_lock); + + rwlock_lock_r(&rt->call->master_lock); + + struct ssrc_ctx *ssrc_out = NULL; + if (media->streams.head) { + struct packet_stream *ps = media->streams.head->data; + mutex_lock(&ps->out_lock); + ssrc_out = ps->ssrc_out; + if (ssrc_out) + obj_hold(&ssrc_out->parent->h); + mutex_unlock(&ps->out_lock); + } + + if (ssrc_out) + rtcp_send_report(media, ssrc_out); + + rwlock_unlock_r(&rt->call->master_lock); + + if (ssrc_out) + obj_put(&ssrc_out->parent->h); + + __rtcp_timer_free(rt); + +out: + log_info_clear(); +} +// master lock held in W +static void __codec_rtcp_timer(struct call_media *receiver) { + if (receiver->rtcp_timer.tv_sec) // already scheduled + return; + + receiver->rtcp_timer = rtpe_now; + timeval_add_usec(&receiver->rtcp_timer, 5000000 + (random() % 2000000)); + __codec_rtcp_timer_schedule(receiver); + // XXX unify with media player into a generic RTCP player +} + + +// returns: 0 = supp codec not present; 1 = sink has codec but receiver does not, 2 = both have codec +int __supp_codec_match(struct call_media *receiver, struct call_media *sink, int pt, + struct rtp_payload_type **sink_pt, struct rtp_payload_type **recv_pt) +{ + if (pt == -1) + return 0; + //ilog(LOG_DEBUG, "XXXXXXXXX checking supp PT match %i", pt); + + struct rtp_payload_type *sink_pt_stor = NULL; + struct rtp_payload_type *recv_pt_stor = NULL; + if (!sink_pt) + sink_pt = &sink_pt_stor; + if (!recv_pt) + recv_pt = &recv_pt_stor; + + // find a matching output payload type + *sink_pt = g_hash_table_lookup(sink->codecs_send, &pt); + if (!*sink_pt) + return 0; + //ilog(LOG_DEBUG, "XXXXXXXXX sink has supp PT %i", pt); + // XXX should go by codec name/params, not payload type number + *recv_pt = g_hash_table_lookup(receiver->codecs_recv, &pt); + if (!*recv_pt) + return 1; + //ilog(LOG_DEBUG, "XXXXXXXXX recv has supp PT %i", pt); + if (rtp_payload_type_cmp(*sink_pt, *recv_pt)) + return 1; + //ilog(LOG_DEBUG, "XXXXXXXXX recv has matching supp PT %i", pt); + return 2; +} // call must be locked in W void codec_handlers_update(struct call_media *receiver, struct call_media *sink, @@ -1027,7 +1249,11 @@ void codec_handlers_update(struct call_media *receiver, struct call_media *sink, // if we transcode, we transcode to the highest-preference supported codec // that the sink specified. determine this first. struct rtp_payload_type *pref_dest_codec = NULL; - int sink_transcoding = 0; // 0x1 = any transcoder present, 0x2 = non pseudo transcoder present + + // 0x1 = any transcoder present, 0x2 = non pseudo transcoder present, + // 0x4 = supplemental codec for transcoding + int sink_transcoding = 0; + // keep track of supplemental payload types. we hash them by clock rate // in case there's several of them. the clock rates of the destination // codec and the supplemental codec must match. @@ -1044,33 +1270,32 @@ void codec_handlers_update(struct call_media *receiver, struct call_media *sink, // similarly, if the sink can receive a codec that the receiver can't send, it's also transcoding __check_send_codecs(receiver, sink, flags, supplemental_sinks, &sink_transcoding); + // 0x1 = accept only codecs marked for transcoding, 0x2 = some codecs marked for transcoding + // present, 0x4 = supplemental codec for transcoding + int receiver_transcoding = __check_receiver_codecs(receiver); + if (flags && flags->opmode == OP_ANSWER && flags->symmetric_codecs) __symmetric_codecs(receiver, sink, &sink_transcoding); int dtmf_payload_type = __dtmf_payload_type(supplemental_sinks, pref_dest_codec); + int cn_payload_type = __supp_payload_type(supplemental_sinks, pref_dest_codec, "CN"); g_hash_table_destroy(supplemental_sinks); supplemental_sinks = NULL; struct rtp_payload_type *dtmf_pt = NULL; struct rtp_payload_type *reverse_dtmf_pt = NULL; - - if (dtmf_payload_type != -1) { - // find a matching output DTMF payload type - dtmf_pt = g_hash_table_lookup(sink->codecs_send, &dtmf_payload_type); - reverse_dtmf_pt = g_hash_table_lookup(receiver->codecs_recv, &dtmf_payload_type); - if (reverse_dtmf_pt && dtmf_pt && rtp_payload_type_cmp(reverse_dtmf_pt, dtmf_pt)) - reverse_dtmf_pt = NULL; - } + int dtmf_pt_match = __supp_codec_match(receiver, sink, dtmf_payload_type, &dtmf_pt, &reverse_dtmf_pt); + int cn_pt_match = __supp_codec_match(receiver, sink, cn_payload_type, NULL, NULL); // stop transcoding if we've determined that we don't need it - if (MEDIA_ISSET(sink, TRANSCODE) && !sink_transcoding) { + if (MEDIA_ISSET(sink, TRANSCODE) && !sink_transcoding && !(receiver_transcoding & 0x2)) { ilog(LOG_DEBUG, "Disabling transcoding engine (not needed)"); MEDIA_CLEAR(sink, TRANSCODE); } if (MEDIA_ISSET(sink, TRANSCODE) && (sink_transcoding & 0x2)) - __accept_transcode_codecs(receiver, sink, flags); + __accept_transcode_codecs(receiver, sink, flags, (receiver_transcoding & 0x1)); else __eliminate_rejected_codecs(receiver, sink, flags); @@ -1080,11 +1305,12 @@ void codec_handlers_update(struct call_media *receiver, struct call_media *sink, GHashTable *output_transcoders = g_hash_table_new(g_direct_hash, g_direct_equal); int transcode_supplemental = 0; // is one of our source codecs a supplemental one? + if ((sink_transcoding & 0x4)) + transcode_supplemental = 1; // do we need to detect PCM DTMF tones? int pcm_dtmf_detect = 0; - if (reverse_dtmf_pt) - if ((MEDIA_ISSET(sink, TRANSCODE) || (flags && flags->always_transcode)) + if ((MEDIA_ISSET(sink, TRANSCODE) || (sink_transcoding & 0x2)) && dtmf_payload_type != -1 && dtmf_pt && (!reverse_dtmf_pt || reverse_dtmf_pt->for_transcoding || !g_hash_table_lookup(receiver->codecs_send, &dtmf_payload_type))) @@ -1130,23 +1356,27 @@ void codec_handlers_update(struct call_media *receiver, struct call_media *sink, goto next; } + //ilog(LOG_DEBUG, "XXXXXXXXXXXX pref dest codec " STR_FORMAT " is %i", + //STR_FMT(&pref_dest_codec->encoding_with_params), + //pref_dest_codec->for_transcoding); + struct rtp_payload_type *dest_pt; // transcode to this GQueue *dest_codecs = NULL; - if (!flags || !flags->always_transcode) { - // we ignore output codec matches if we must transcode DTMF - if (dtmf_pt && !reverse_dtmf_pt) + if (pref_dest_codec->for_transcoding) { + // with force accepted codec, we still accept DTMF payloads if possible + if (pt->codec_def && pt->codec_def->supplemental) + dest_codecs = g_hash_table_lookup(sink->codec_names_send, &pt->encoding); + } + else { + // we ignore output codec matches if we must transcode supp codecs + if ((dtmf_pt_match == 1 || cn_pt_match == 1) && MEDIA_ISSET(sink, TRANSCODE)) ; else if (pcm_dtmf_detect) ; else dest_codecs = g_hash_table_lookup(sink->codec_names_send, &pt->encoding); } - else if (flags->always_transcode) { - // with always-transcode, we still accept DTMF payloads if possible - if (pt->codec_def && pt->codec_def->supplemental) - dest_codecs = g_hash_table_lookup(sink->codec_names_send, &pt->encoding); - } if (dest_codecs) { // the sink supports this codec - check offered formats dest_pt = NULL; @@ -1187,6 +1417,11 @@ void codec_handlers_update(struct call_media *receiver, struct call_media *sink, if (rtp_payload_type_cmp_nf(pt, dest_pt)) goto transcode; + // do we need silence detection? + if (cn_pt_match == 2 && MEDIA_ISSET(sink, TRANSCODE)) + goto transcode; + + // XXX check format parameters as well ilog(LOG_DEBUG, "Sink supports codec " STR_FORMAT, STR_FMT(&pt->encoding_with_params)); __make_passthrough_gsl(handler, &passthrough_handlers); if (pt->codec_def && pt->codec_def->dtmf) @@ -1214,6 +1449,7 @@ transcode:; } MEDIA_SET(receiver, TRANSCODE); __make_transcoder(handler, dest_pt, output_transcoders, dtmf_payload_type, pcm_dtmf_detect); + handler->cn_payload_type = cn_payload_type; next: l = l->next; @@ -1253,16 +1489,24 @@ next: // if the sink does not support DTMF but we can receive it, we must transcode // DTMF event packets to PCM. this requires all codecs to be transcoded to the // sink's preferred destination codec. + //ilog(LOG_DEBUG, "XXXXXXXXXXXXX tc supp %i DTMF PT %i DTMF PT match %i PCM detect %i", + //transcode_supplemental, dtmf_payload_type, dtmf_pt_match, pcm_dtmf_detect); + //ilog(LOG_DEBUG, "XXXXXXXXXXXXX tc supp %i CN PT %i CN PT match %i", + //transcode_supplemental, cn_payload_type, cn_pt_match); + //ilog(LOG_DEBUG, "XXXXXXXXXXXXX %p %p %p", + //pref_dest_codec, handler->source_pt.codec_def, pref_dest_codec->codec_def); if (!transcode_supplemental && !pcm_dtmf_detect) __make_passthrough_ssrc(handler); - else if (dtmf_pt && reverse_dtmf_pt) + else if (dtmf_pt_match == 2) __make_passthrough_ssrc(handler); else if (!pref_dest_codec || !handler->source_pt.codec_def || !pref_dest_codec->codec_def) __make_passthrough_ssrc(handler); - else + else { __make_transcoder(handler, pref_dest_codec, output_transcoders, dtmf_payload_type, pcm_dtmf_detect); + handler->cn_payload_type = cn_payload_type; + } passthrough_handlers = g_slist_delete_link(passthrough_handlers, passthrough_handlers); } @@ -1272,6 +1516,11 @@ next: } g_hash_table_destroy(output_transcoders); + + if (MEDIA_ISSET(receiver, RTCP_GEN)) { + receiver->rtcp_handler = rtcp_sink_handler; + __codec_rtcp_timer(receiver); + } } @@ -2086,6 +2335,104 @@ void codec_handlers_stop(GQueue *q) { } + + +static void silence_event_free(void *p) { + g_slice_free1(sizeof(struct silence_event), p); +} + +#define __silence_detect_type(type) \ +static void __silence_detect_ ## type(struct codec_ssrc_handler *ch, AVFrame *frame, type thres) { \ + type *s = (void *) frame->data[0]; \ + struct silence_event *last = g_queue_peek_tail(&ch->silence_events); \ + \ + if (last && last->end) /* last event finished? */ \ + last = NULL; \ + \ + for (unsigned int i = 0; i < frame->nb_samples; i++) { \ + /* ilog(LOG_DEBUG, "XXXXXXXXXXXX checking %u %i vs %i", i, (int) s[i], (int) thres); */ \ + if (s[i] <= thres && s[1] >= -thres) { \ + /* silence */ \ + if (!last) { \ + /* new event */ \ + last = g_slice_alloc0(sizeof(*last)); \ + last->start = frame->pts + i; \ + g_queue_push_tail(&ch->silence_events, last); \ + } \ + } \ + else { \ + /* not silence */ \ + if (last && !last->end) { \ + /* close off event */ \ + last->end = frame->pts + i; \ + last = NULL; \ + } \ + } \ + } \ +} + +__silence_detect_type(double) +__silence_detect_type(float) +__silence_detect_type(int32_t) +__silence_detect_type(int16_t) + +static void __silence_detect(struct codec_ssrc_handler *ch, AVFrame *frame) { + //ilog(LOG_DEBUG, "XXXXXXXXXXXXXXXXXXXX silence detect %i %i", rtpe_config.silence_detect_int, ch->handler->cn_payload_type); + if (!rtpe_config.silence_detect_int) + return; + if (ch->handler->cn_payload_type < 0) + return; + switch (frame->format) { + case AV_SAMPLE_FMT_DBL: + __silence_detect_double(ch, frame, rtpe_config.silence_detect_double); + break; + case AV_SAMPLE_FMT_FLT: + __silence_detect_float(ch, frame, rtpe_config.silence_detect_double); + break; + case AV_SAMPLE_FMT_S32: + __silence_detect_int32_t(ch, frame, rtpe_config.silence_detect_int); + break; + case AV_SAMPLE_FMT_S16: + __silence_detect_int16_t(ch, frame, rtpe_config.silence_detect_int >> 16); + break; + default: + ilog(LOG_WARN | LOG_FLAG_LIMIT, "Unsupported sample format %i for silence detection", + frame->format); + } +} +static int is_silence_event(str *inout, GQueue *events, uint64_t pts, uint64_t duration) { + uint64_t end = pts + duration; + + while (events->length) { + struct silence_event *first = g_queue_peek_head(events); + if (first->start > pts) // future event + return 0; + if (!first->end) // ongoing event + goto silence; + if (first->end > end) // event finished with end in the future + goto silence; + // event has ended: remove it + g_queue_pop_head(events); + // does the event fill the entire span? + if (first->end == end) { + silence_event_free(first); + goto silence; + } + // keep going, there might be more + silence_event_free(first); + } + return 0; + +silence: + // replace with CN payload + inout->len = rtpe_config.cn_payload.len; + memcpy(inout->s, rtpe_config.cn_payload.s, inout->len); + return 1; +} + + + + static struct ssrc_entry *__ssrc_handler_transcode_new(void *p) { struct codec_handler *h = p; @@ -2184,6 +2531,7 @@ static void __free_ssrc_handler(void *chp) { dtmf_rx_free(ch->dtmf_dsp); resample_shutdown(&ch->dtmf_resampler); g_queue_clear_full(&ch->dtmf_events, dtmf_event_free); + g_queue_clear_full(&ch->silence_events, silence_event_free); if (ch->dtx_buffer) obj_put(&ch->dtx_buffer->ttq.tt_obj); } @@ -2224,13 +2572,26 @@ static int packet_encoded_rtp(encoder_t *enc, void *u1, void *u2) { ilog(LOG_DEBUG, "Received packet of %i bytes from packetizer", inout.len); + // check special payloads + unsigned int repeats = 0; + int payload_type = -1; + int is_dtmf = dtmf_event_payload(&inout, (uint64_t *) &enc->avpkt.pts, enc->avpkt.duration, &ch->dtmf_event, &ch->dtmf_events); - if (is_dtmf == 1) - ch->rtp_mark = 1; // DTMF start event - else if (is_dtmf == 3) - repeats = 2; // DTMF end event + if (is_dtmf) { + payload_type = ch->handler->dtmf_payload_type; + if (is_dtmf == 1) + ch->rtp_mark = 1; // DTMF start event + else if (is_dtmf == 3) + repeats = 2; // DTMF end event + } + else { + if (is_silence_event(&inout, &ch->silence_events, enc->avpkt.pts, enc->avpkt.duration)) + payload_type = ch->handler->cn_payload_type; + } + + // ready to send do { char *send_buf = buf; @@ -2242,7 +2603,7 @@ static int packet_encoded_rtp(encoder_t *enc, void *u1, void *u2) { __output_rtp(mp, ch, ch->handler, send_buf, inout.len, ch->first_ts + enc->avpkt.pts / enc->def->clockrate_mult, ch->rtp_mark ? 1 : 0, -1, 0, - is_dtmf ? ch->handler->dtmf_payload_type : -1); + payload_type); mp->ssrc_out->parent->seq_diff++; //mp->iter_out++; ch->rtp_mark = 0; @@ -2349,6 +2710,7 @@ static int packet_decoded_common(decoder_t *decoder, AVFrame *frame, void *u1, v } __dtmf_detect(ch, frame); + __silence_detect(ch, frame); // locking deliberately ignored if (mp->media_out) @@ -2414,6 +2776,27 @@ static int packet_decode(struct codec_ssrc_handler *ch, struct transcode_packet return ret; } + +static void codec_calc_jitter(struct media_packet *mp, unsigned int clockrate) { + if (!mp->ssrc_in) + return; + struct ssrc_entry_call *sec = mp->ssrc_in->parent; + + // RFC 3550 A.8 + uint32_t transit = (((timeval_us(&mp->tv) / 1000) * clockrate) / 1000) + - ntohl(mp->rtp->timestamp); + mutex_lock(&sec->h.lock); + int32_t d = 0; + if (sec->transit) + d = transit - sec->transit; + sec->transit = transit; + if (d < 0) + d = -d; + sec->jitter += d - ((sec->jitter + 8) >> 4); + mutex_unlock(&sec->h.lock); +} + + static int handler_func_transcode(struct codec_handler *h, struct media_packet *mp) { if (G_UNLIKELY(!mp->rtp)) return handler_func_passthrough(h, mp); @@ -2426,6 +2809,8 @@ static int handler_func_transcode(struct codec_handler *h, struct media_packet * ntohl(mp->rtp->ssrc), mp->rtp->m_pt, ntohs(mp->rtp->seq_num), ntohl(mp->rtp->timestamp), mp->payload.len); + codec_calc_jitter(mp, h->source_pt.clock_rate); + if (h->stats_entry) { unsigned int idx = rtpe_now.tv_sec & 1; int last_tv_sec = g_atomic_int_get(&h->stats_entry->last_tv_sec[idx]); @@ -2586,9 +2971,7 @@ static void __insert_codec_tracker(struct call_media *media, GList *link) { } } #endif -static void __queue_insert_supp(GQueue *q, struct rtp_payload_type *pt, int supp_check, - struct codec_tracker *sct) -{ +static void __queue_insert_supp(GQueue *q, struct rtp_payload_type *pt, int supp_check) { // do we care at all? if (!supp_check) { g_queue_push_tail(q, pt); @@ -2632,7 +3015,7 @@ void __rtp_payload_type_add_recv(struct call_media *media, struct rtp_payload_ty 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, media->codec_tracker); + __queue_insert_supp(&media->codecs_prefs_recv, pt, supp_check); } // consumes 'pt' void __rtp_payload_type_add_send(struct call_media *other_media, @@ -2862,7 +3245,7 @@ int __codec_ht_except(int all_flag, GHashTable *yes_ht, GHashTable *no_ht, struc else if (g_hash_table_lookup(yes_ht, &pt->encoding_with_params)) do_this = 1; } - if (no_ht) { + if (no_ht && all_flag) { if (g_hash_table_lookup(no_ht, &pt->encoding)) do_this = 0; else if (g_hash_table_lookup(no_ht, &pt->encoding_with_params)) @@ -2870,6 +3253,19 @@ int __codec_ht_except(int all_flag, GHashTable *yes_ht, GHashTable *no_ht, struc } return do_this; } +void __ht_merge(GHashTable **dst, GHashTable *src) { + if (!src) + return; + if (!*dst) + *dst = g_hash_table_new_full(str_case_hash, str_case_equal, free, NULL); + GHashTableIter iter; + g_hash_table_iter_init(&iter, src); + void *key; + while (g_hash_table_iter_next(&iter, &key, NULL)) { + str *dup = str_dup(key); + g_hash_table_replace(*dst, dup, dup); + } +} void codec_rtp_payload_types(struct call_media *media, struct call_media *other_media, GQueue *types, struct sdp_ng_flags *flags) { @@ -2883,7 +3279,7 @@ void codec_rtp_payload_types(struct call_media *media, struct call_media *other_ static const str str_full = STR_CONST_INIT("full"); GHashTable *stripped = g_hash_table_new_full(str_case_hash, str_case_equal, free, __payload_queue_free); GHashTable *masked = g_hash_table_new_full(str_case_hash, str_case_equal, free, __payload_queue_free); - int strip_all = 0, mask_all = 0; + int strip_all = 0, mask_all = 0, consume_all = 0, accept_all = 0; // start fresh if (!proto_is_rtp(other_media->protocol) && proto_is_rtp(media->protocol) && flags->opmode == OP_OFFER) { @@ -2906,12 +3302,23 @@ void codec_rtp_payload_types(struct call_media *media, struct call_media *other_ if (flags->codec_strip && g_hash_table_lookup(flags->codec_strip, &str_all)) strip_all = 1; - if (flags->codec_strip && g_hash_table_lookup(flags->codec_strip, &str_all)) + else if (flags->codec_strip && g_hash_table_lookup(flags->codec_strip, &str_full)) strip_all = 2; if (flags->codec_mask && g_hash_table_lookup(flags->codec_mask, &str_all)) mask_all = 1; else if (flags->codec_mask && g_hash_table_lookup(flags->codec_mask, &str_full)) mask_all = 2; + if (flags->codec_consume && g_hash_table_lookup(flags->codec_consume, &str_all)) + consume_all = 1; + else if (flags->codec_consume && g_hash_table_lookup(flags->codec_consume, &str_full)) + consume_all = 2; + if (flags->codec_accept && g_hash_table_lookup(flags->codec_accept, &str_all)) + accept_all = 1; + + __ht_merge(&flags->codec_except, flags->codec_consume); + __ht_merge(&flags->codec_except, flags->codec_accept); + __ht_merge(&flags->codec_except, flags->codec_strip); + __ht_merge(&flags->codec_except, flags->codec_mask); /* we steal the entire list to avoid duplicate allocs */ while ((pt = g_queue_pop_head(types))) { @@ -2940,12 +3347,39 @@ void codec_rtp_payload_types(struct call_media *media, struct call_media *other_ #ifdef WITH_TRANSCODING codec_touched(pt, media); #endif + // special case for handling of the legacy always-transcode flag (= accept-all) + // in combination with codec-mask + if (accept_all) + pt->for_transcoding = 1; + GQueue *q = g_hash_table_lookup_queue_new(masked, str_dup(&pt->encoding), free); g_queue_push_tail(q, __rtp_payload_type_copy(pt)); q = g_hash_table_lookup_queue_new(masked, str_dup(&pt->encoding_with_params), free); g_queue_push_tail(q, __rtp_payload_type_copy(pt)); __rtp_payload_type_add_send(other_media, pt); } + else if (__codec_ht_except(consume_all, flags->codec_consume, flags->codec_except, pt)) { + ilog(LOG_DEBUG, "Consuming codec '" STR_FORMAT "'", + STR_FMT(&pt->encoding_with_params)); +#ifdef WITH_TRANSCODING + codec_touched(pt, media); +#endif + pt->for_transcoding = 1; + GQueue *q = g_hash_table_lookup_queue_new(masked, str_dup(&pt->encoding), free); + g_queue_push_tail(q, __rtp_payload_type_copy(pt)); + q = g_hash_table_lookup_queue_new(masked, str_dup(&pt->encoding_with_params), free); + g_queue_push_tail(q, __rtp_payload_type_copy(pt)); + __rtp_payload_type_add_send(other_media, pt); + } + else if (__codec_ht_except(accept_all, flags->codec_accept, NULL, pt)) { + ilog(LOG_DEBUG, "Accepting codec '" STR_FORMAT "'", + STR_FMT(&pt->encoding_with_params)); +#ifdef WITH_TRANSCODING + codec_touched(pt, media); +#endif + pt->for_transcoding = 1; + __rtp_payload_type_add(media, other_media, pt); + } else __rtp_payload_type_add(media, other_media, pt); } @@ -3067,6 +3501,8 @@ void codec_rtp_payload_types(struct call_media *media, struct call_media *other_ void codecs_init(void) { #ifdef WITH_TRANSCODING timerthread_init(&codec_timers_thread, timerthread_queue_run); + rtcp_timer_queue = timerthread_queue_new("rtcp_timer_queue", sizeof(*rtcp_timer_queue), + &codec_timers_thread, NULL, __rtcp_timer_run, NULL, __rtcp_timer_free); #endif } void codecs_cleanup(void) { diff --git a/daemon/kernel.c b/daemon/kernel.c index 7ce3fefca..07d849d5a 100644 --- a/daemon/kernel.c +++ b/daemon/kernel.c @@ -240,3 +240,27 @@ unsigned int kernel_add_intercept_stream(unsigned int call_idx, const char *id) return UNINIT_IDX; return msg.u.stream.stream_idx; } + +int kernel_update_stats(const struct re_address *a, uint32_t ssrc, struct rtpengine_ssrc_stats *out) { + struct rtpengine_message msg; + int ret; + + if (!kernel.is_open) + return -1; + + ZERO(msg); + msg.cmd = REMG_GET_RESET_STATS; + msg.u.stats.local = *a; + + ret = read(kernel.fd, &msg, sizeof(msg)); + if (ret <= 0) { + ilog(LOG_ERROR, "Failed to get stream stats from kernel: %s", strerror(errno)); + return -1; + } + if (msg.u.stats.ssrc != ssrc) + return -1; + + *out = msg.u.stats.ssrc_stats; + + return 0; +} diff --git a/daemon/main.c b/daemon/main.c index c8c76f0cf..536316b6a 100644 --- a/daemon/main.c +++ b/daemon/main.c @@ -380,6 +380,8 @@ static void options(int *argc, char ***argv) { AUTO_CLEANUP_GBUF(dtmf_udp_ep); AUTO_CLEANUP_GBUF(endpoint_learning); AUTO_CLEANUP_GBUF(dtls_sig); + double silence_detect = 0; + AUTO_CLEANUP_GVBUF(cn_payload); GOptionEntry e[] = { { "table", 't', 0, G_OPTION_ARG_INT, &rtpe_config.kernel_table, "Kernel table to use", "INT" }, @@ -465,6 +467,8 @@ static void options(int *argc, char ***argv) { #ifdef WITH_TRANSCODING { "dtx-delay", 0,0, G_OPTION_ARG_INT, &rtpe_config.dtx_delay, "Delay in milliseconds to trigger DTX handling","INT"}, { "max-dtx", 0,0, G_OPTION_ARG_INT, &rtpe_config.max_dtx, "Maximum duration of DTX handling", "INT"}, + { "silence-detect",0,0, G_OPTION_ARG_DOUBLE, &silence_detect, "Audio level threshold in percent for silence detection","FLOAT"}, + { "cn-payload",0,0, G_OPTION_ARG_STRING_ARRAY,&cn_payload, "Comfort noise parameters to replace silence with","INT INT INT ..."}, #endif { NULL, } @@ -684,6 +688,32 @@ static void options(int *argc, char ***argv) { if (rtpe_config.jb_length < 0) die("Invalid negative jitter buffer size"); + + if (silence_detect > 0) { + rtpe_config.silence_detect_double = silence_detect / 100.0; + rtpe_config.silence_detect_int = (int) ((silence_detect / 100.0) * UINT32_MAX); + } + + if (!cn_payload) + str_init_dup(&rtpe_config.cn_payload, "\x20"); + else { + int len = g_strv_length(cn_payload); + if (len < 1) + die("Invalid CN payload specified"); + rtpe_config.cn_payload.s = malloc(len); + for (int i = 0; i < len; i++) { + char *endp; + long p = strtol(cn_payload[i], &endp, 0); + if (endp == cn_payload[i] || *endp != '\0') + die("Invalid CN payload specified"); + if (p < 0 || p > 254) + die("Invalid CN payload specified"); + if (i == 0 && p > 127) + die("Invalid CN payload specified"); + rtpe_config.cn_payload.s[i] = p; + } + rtpe_config.cn_payload.len = len; + } } void fill_initial_rtpe_cfg(struct rtpengine_config* ini_rtpe_cfg) { diff --git a/daemon/media_player.c b/daemon/media_player.c index 3e4b30794..de531e161 100644 --- a/daemon/media_player.c +++ b/daemon/media_player.c @@ -163,51 +163,17 @@ struct send_timer *send_timer_new(struct packet_stream *ps) { return st; } - -// call is locked in R -static void send_timer_send_rtcp(struct ssrc_ctx *ssrc_out, struct call *call, struct packet_stream *ps) { - GQueue rrs = G_QUEUE_INIT; - rtcp_receiver_reports(&rrs, call->ssrc_hash, ps->media->monologue); - - ilog(LOG_DEBUG, "Generating and sending RTCP SR for %x and up to %i source(s)", - ssrc_out->parent->h.ssrc, rrs.length); - - GString *sr = rtcp_sender_report(ssrc_out->parent->h.ssrc, - atomic64_get(&ssrc_out->last_ts), - atomic64_get(&ssrc_out->packets), - atomic64_get(&ssrc_out->octets), - &rrs); - - socket_sendto(&ps->selected_sfd->socket, sr->str, sr->len, &ps->endpoint); - g_string_free(sr, TRUE); -} - // call is locked in R static void send_timer_rtcp(struct send_timer *st, struct ssrc_ctx *ssrc_out) { struct call_media *media = st->sink ? st->sink->media : NULL; if (!media) return; - struct call *call = media->call; - - // figure out where to send it - struct packet_stream *ps = media->streams.head->data; - if (MEDIA_ISSET(media, RTCP_MUX)) - ; - else if (!media->streams.head->next) - ; - else { - struct packet_stream *next_ps = media->streams.head->next->data; - if (PS_ISSET(next_ps, RTCP)) - ps = next_ps; - } - - log_info_stream_fd(ps->selected_sfd); - send_timer_send_rtcp(ssrc_out, call, ps); + rtcp_send_report(media, ssrc_out); // XXX missing locking? ssrc_out->next_rtcp = rtpe_now; - timeval_add_usec(&ssrc_out->next_rtcp, 5000000); + timeval_add_usec(&ssrc_out->next_rtcp, 5000000 + (random() % 2000000)); } diff --git a/daemon/media_socket.c b/daemon/media_socket.c index d1730c198..b991fc9dd 100644 --- a/daemon/media_socket.c +++ b/daemon/media_socket.c @@ -1137,6 +1137,7 @@ void kernelize(struct packet_stream *stream) { reti.dtls = MEDIA_ISSET(media, DTLS); reti.stun = media->ice_agent ? 1 : 0; reti.non_forwarding = non_forwarding; + reti.rtp_stats = MEDIA_ISSET(media, RTCP_GEN) ? 1 : 0; __re_address_translate_ep(&reti.dst_addr, &sink->endpoint); __re_address_translate_ep(&reti.src_addr, &sink->selected_sfd->socket.local); @@ -1179,7 +1180,9 @@ void kernelize(struct packet_stream *stream) { struct codec_handler *ch = codec_handler_get(media, rs->payload_type); if (!ch->kernelize) continue; - reti.payload_types[reti.num_payload_types++] = rs->payload_type; + reti.payload_types[reti.num_payload_types] = rs->payload_type; + reti.clock_rates[reti.num_payload_types] = ch->source_pt.clock_rate; + reti.num_payload_types++; } g_list_free(values); } @@ -1202,6 +1205,60 @@ no_kernel: PS_SET(stream, NO_KERNEL_SUPPORT); } +// must be called with appropriate locks (master lock and/or in_lock) +static void __stream_update_stats(struct packet_stream *ps, int have_in_lock) { + struct re_address local; + + if (!have_in_lock) + mutex_lock(&ps->in_lock); + + struct ssrc_ctx *ssrc_ctx = ps->ssrc_in; + struct ssrc_entry_call *parent = ssrc_ctx->parent; + + __re_address_translate_ep(&local, &ps->selected_sfd->socket.local); + struct rtpengine_ssrc_stats stats; + if (kernel_update_stats(&local, htonl(parent->h.ssrc), &stats)) { + if (!have_in_lock) + mutex_unlock(&ps->in_lock); + return; + } + + if (!stats.basic_stats.packets) { + // no change + if (!have_in_lock) + mutex_unlock(&ps->in_lock); + return; + } + + atomic64_add(&ssrc_ctx->packets, stats.basic_stats.packets); + atomic64_add(&ssrc_ctx->octets, stats.basic_stats.bytes); + atomic64_add(&ssrc_ctx->packets_lost, stats.total_lost); + atomic64_set(&ssrc_ctx->last_seq, stats.ext_seq); + atomic64_set(&ssrc_ctx->last_ts, stats.timestamp); + parent->jitter = stats.jitter; + + uint32_t ssrc_map_out = ssrc_ctx->ssrc_map_out; + + if (!have_in_lock) + mutex_unlock(&ps->in_lock); + + // update opposite outgoing SSRC + if (!have_in_lock) + mutex_lock(&ps->out_lock); + else { + if (mutex_trylock(&ps->out_lock)) + return; // will have to skip this + } + ssrc_ctx = ps->ssrc_out; + parent = ssrc_ctx->parent; + if (parent->h.ssrc == ssrc_map_out) { + atomic64_add(&ssrc_ctx->packets, stats.basic_stats.packets); + atomic64_add(&ssrc_ctx->octets, stats.basic_stats.bytes); + } + mutex_unlock(&ps->out_lock); +} + + /* must be called with in_lock held or call->master_lock held in W */ void __unkernelize(struct packet_stream *p) { struct re_address rea; @@ -1212,6 +1269,7 @@ void __unkernelize(struct packet_stream *p) { return; if (kernel.is_open) { + __stream_update_stats(p, 1); __re_address_translate_ep(&rea, &p->selected_sfd->socket.local); kernel_del_stream(&rea); } @@ -1241,6 +1299,24 @@ void unkernelize(struct packet_stream *ps) { mutex_unlock(&ps->in_lock); } +// master lock held in R +void media_update_stats(struct call_media *m) { + if (!proto_is_rtp(m->protocol)) + return; + if (!kernel.is_open) + return; + + for (GList *l = m->streams.head; l; l = l->next) { + struct packet_stream *ps = l->data; + if (!PS_ISSET(ps, RTP)) + continue; + if (!PS_ISSET(ps, KERNELIZED)) + continue; + + __stream_update_stats(ps, 0); + } +} + const struct streamhandler *determine_handler(const struct transport_protocol *in_proto, @@ -1764,16 +1840,19 @@ static int do_rtcp(struct packet_handler_ctx *phc) { int ret = -1; GQueue rtcp_list = G_QUEUE_INIT; - if (rtcp_parse(&rtcp_list, &phc->mp)) + int rtcp_ret = rtcp_parse(&rtcp_list, &phc->mp); + if (rtcp_ret < 0) goto out; + if (rtcp_ret == 1) + goto ok; if (phc->rtcp_filter) if (phc->rtcp_filter(&phc->mp, &rtcp_list)) goto out; // queue for output codec_add_raw_packet(&phc->mp); +ok: ret = 0; - out: rtcp_list_free(&rtcp_list); return ret; diff --git a/daemon/rtcp.c b/daemon/rtcp.c index 211e97a7e..64909ddcd 100644 --- a/daemon/rtcp.c +++ b/daemon/rtcp.c @@ -16,6 +16,7 @@ #include "rtcplib.h" #include "ssrc.h" #include "sdp.h" +#include "log_funcs.h" @@ -246,6 +247,9 @@ struct rtcp_process_ctx { // Homer stats GString *json; int json_init_len; + + // verdict + int discard:1; }; // all available methods struct rtcp_handler { @@ -307,6 +311,9 @@ static void transcode_common_wrap(struct rtcp_process_ctx *, struct rtcp_packet static void transcode_rr_wrap(struct rtcp_process_ctx *, struct report_block *); static void transcode_sr_wrap(struct rtcp_process_ctx *, struct sender_report_packet *); +// RTCP sinks for local RTCP generation +static void sink_common(struct rtcp_process_ctx *, struct rtcp_packet *); + // homer functions static void homer_init(struct rtcp_process_ctx *); static void homer_sr(struct rtcp_process_ctx *, struct sender_report_packet *); @@ -358,6 +365,9 @@ static struct rtcp_handler transcode_handlers = { .rr = transcode_rr, .sr = transcode_sr, }; +static struct rtcp_handler sink_handlers = { + .common = sink_common, +}; static struct rtcp_handler transcode_handlers_wrap = { .common = transcode_common_wrap, .rr = transcode_rr_wrap, @@ -472,6 +482,7 @@ static const int min_xr_packet_sizes[] = { struct rtcp_handler *rtcp_transcode_handler = &transcode_handlers; +struct rtcp_handler *rtcp_sink_handler = &sink_handlers; @@ -645,6 +656,7 @@ void rtcp_list_free(GQueue *q) { +// returns: 0 = ok, forward, -1 = error, drop, 1 = ok, but discard (no forward) int rtcp_parse(GQueue *q, struct media_packet *mp) { struct rtcp_header *hdr; struct rtcp_chain_element *el; @@ -713,7 +725,7 @@ next: CAH(finish, c, &mp->fsin, &mp->sfd->socket.local, &mp->tv); CAH(destroy); - return 0; + return log_ctx->discard ? 1 : 0; error: CAH(finish, c, &mp->fsin, &mp->sfd->socket.local, &mp->tv); @@ -1357,17 +1369,20 @@ static void transcode_sr(struct rtcp_process_ctx *ctx, struct sender_report_pack static void transcode_common_wrap(struct rtcp_process_ctx *ctx, struct rtcp_packet *common) { if (!ctx->mp->media->rtcp_handler) return; - ctx->mp->media->rtcp_handler->common(ctx, common); + if (ctx->mp->media->rtcp_handler->common) + ctx->mp->media->rtcp_handler->common(ctx, common); } static void transcode_rr_wrap(struct rtcp_process_ctx *ctx, struct report_block *rr) { if (!ctx->mp->media->rtcp_handler) return; - ctx->mp->media->rtcp_handler->rr(ctx, rr); + if (ctx->mp->media->rtcp_handler->rr) + ctx->mp->media->rtcp_handler->rr(ctx, rr); } static void transcode_sr_wrap(struct rtcp_process_ctx *ctx, struct sender_report_packet *sr) { if (!ctx->mp->media->rtcp_handler) return; - ctx->mp->media->rtcp_handler->sr(ctx, sr); + if (ctx->mp->media->rtcp_handler->sr) + ctx->mp->media->rtcp_handler->sr(ctx, sr); } @@ -1398,8 +1413,8 @@ GString *rtcp_sender_report(uint32_t ssrc, uint32_t ts, uint32_t packets, uint32 // receiver reports int i = 0, n = 0; - for (GList *l = rrs->head; l; l = l->next) { - struct ssrc_ctx *s = l->data; + while (rrs->length) { + struct ssrc_ctx *s = g_queue_pop_head(rrs); if (i < 30) { struct report_block *rr = (void *) ret->str + ret->len; g_string_set_size(ret, ret->len + sizeof(*rr)); @@ -1416,6 +1431,7 @@ GString *rtcp_sender_report(uint32_t ssrc, uint32_t ts, uint32_t packets, uint32 tv_diff = timeval_diff(&rtpe_now, &si->received); ntp_middle_bits = si->ntp_middle_bits; } + uint32_t jitter = se->jitter; mutex_unlock(&se->h.lock); uint64_t lost = atomic64_get(&s->packets_lost); @@ -1430,8 +1446,8 @@ GString *rtcp_sender_report(uint32_t ssrc, uint32_t ts, uint32_t packets, uint32 .high_seq_received = htonl(atomic64_get(&s->last_seq)), .lsr = htonl(ntp_middle_bits), .dlsr = htonl(tv_diff * 65536 / 1000000), + .jitter = htonl(jitter >> 4), }; - // XXX jitter n++; } ssrc_ctx_put(&s); @@ -1478,7 +1494,7 @@ void rtcp_receiver_reports(GQueue *out, struct ssrc_hash *hash, struct call_mono rwlock_lock_r(&hash->lock); for (GList *l = hash->q.head; l; l = l->next) { struct ssrc_entry_call *e = l->data; - ilog(LOG_DEBUG, "xxxxx %x %i %i %p %p %p", e->h.ssrc, (int) atomic64_get(&e->input_ctx.packets), (int) atomic64_get(&e->output_ctx.packets), ml, e->input_ctx.ref, e->output_ctx.ref); + //ilog(LOG_DEBUG, "xxxxx %x %i %i %p %p %p", e->h.ssrc, (int) atomic64_get(&e->input_ctx.packets), (int) atomic64_get(&e->output_ctx.packets), ml, e->input_ctx.ref, e->output_ctx.ref); struct ssrc_ctx *i = &e->input_ctx; if (i->ref != ml) continue; @@ -1489,3 +1505,44 @@ void rtcp_receiver_reports(GQueue *out, struct ssrc_hash *hash, struct call_mono } rwlock_unlock_r(&hash->lock); } + + +// call must be locked in R +void rtcp_send_report(struct call_media *media, struct ssrc_ctx *ssrc_out) { + struct call *call = media->call; + + // figure out where to send it + struct packet_stream *ps = media->streams.head->data; + if (MEDIA_ISSET(media, RTCP_MUX)) + ; + else if (!media->streams.head->next) + ; + else { + struct packet_stream *next_ps = media->streams.head->next->data; + if (PS_ISSET(next_ps, RTCP)) + ps = next_ps; + } + + log_info_stream_fd(ps->selected_sfd); + + GQueue rrs = G_QUEUE_INIT; + rtcp_receiver_reports(&rrs, call->ssrc_hash, ps->media->monologue); + + ilog(LOG_DEBUG, "Generating and sending RTCP SR for %x and up to %i source(s)", + ssrc_out->parent->h.ssrc, rrs.length); + + GString *sr = rtcp_sender_report(ssrc_out->parent->h.ssrc, + atomic64_get(&ssrc_out->last_ts), + atomic64_get(&ssrc_out->packets), + atomic64_get(&ssrc_out->octets), + &rrs); + + socket_sendto(&ps->selected_sfd->socket, sr->str, sr->len, &ps->endpoint); + g_string_free(sr, TRUE); +} + + + +static void sink_common(struct rtcp_process_ctx *ctx, struct rtcp_packet *common) { + ctx->discard = 1; +} diff --git a/daemon/rtpengine.pod b/daemon/rtpengine.pod index a2dd9cfc6..a2e73ea78 100644 --- a/daemon/rtpengine.pod +++ b/daemon/rtpengine.pod @@ -745,6 +745,51 @@ received within this time frame, then DTX processing will stop. Can be set to zero or negative to disable and keep DTX processing on indefinitely. Defaults to 30 seconds. +=item B<--silence-detect=>I + +Enable silence detection and specify threshold in percent. This option is +applicable to transcoded stream only and defaults to zero (disabled). + +When enabled, silence detection will be performed on all transcoded audio +streams. The threshold specified here is the sensitivity for detecting silence: +higher thresholds result in more audio to be detected as silence, while lower +thresholds result in less audio to be detected as silence. The threshold is +specified as percent between zero and 100. If set to 100, then all audio would +be detected as silence; if set to 50, then any audio that is quieter than 50% +of the maximum volume would be detected as silence; and so on. Setting it to +zero disables silence detection. To only detect silence that is very near or +equal to absolute silence, set this value to a low number such as 0.01. (For +certain codecs such as PCMA, a higher minimum threshold is required to detect +complete silence, as their compressed payloads don't decode to actual silence +but instead have a residual DC offset. For PCMA the minimum value is 0.013.) + +Audio that is detected as silence will be replaced by comfort noise as +specified by the B option (see below). Currently this is applicable +only to RTP peers that have advertised support for the B RTP payload type, +in which case the silence audio frames will be replaced by B RTP frames. + +=item B<--cn-payload=>I + +Specify one comfort noise parameter. This option can be given multiple times +and the format follows RFC 3389. When specified at the command line, list the +B<--cn-payload=> option multiple times, each one specifying a single CN +parameter. When used in the config file, list the option only a single time and +list multiple CN parameters separated by semicolons (e.g. +I). + +The first CN payload value given is the noise level, specified as -dBov as per +RFC 3389. This means that a noise level of zero corresponds to maximum volume, +while higher numbers correspond to lower volumes. The highest allowable number +is 127, corresponding to -127 dBov, which is near silence. + +Subsequent CN payload values carry spectral information (reflection +coefficients) as per RFC 3389. Allowable values for each coefficient are +between 0 and 254. Specifying spectral information is optional and the number +of coefficients listed (model order) is variable. + +The default values are 32 (-32 dBov) for the noise level and no spectral +information. + =back =head1 INTERFACES diff --git a/daemon/stun.c b/daemon/stun.c index 83580847d..63852a10f 100644 --- a/daemon/stun.c +++ b/daemon/stun.c @@ -630,11 +630,13 @@ int stun(const str *b, struct stream_fd *sfd, const endpoint_t *sin) { bad_req: ilog(LOG_NOTICE | LOG_FLAG_LIMIT, "Received invalid STUN packet" SLF ": %s", SLP, err); - stun_error(sfd, sin, req, 400, "Bad request"); + if (class == STUN_CLASS_REQUEST) + stun_error(sfd, sin, req, 400, "Bad request"); return 0; unauth: ilog(LOG_NOTICE | LOG_FLAG_LIMIT, "STUN authentication mismatch" SLF, SLP); - stun_error(sfd, sin, req, 401, "Unauthorized"); + if (class == STUN_CLASS_REQUEST) + stun_error(sfd, sin, req, 401, "Unauthorized"); return 0; ignore: ilog(LOG_NOTICE | LOG_FLAG_LIMIT, "Not handling potential STUN packet" SLF ": %s", SLP, err); diff --git a/include/call.h b/include/call.h index 28c9228ff..4d255007c 100644 --- a/include/call.h +++ b/include/call.h @@ -149,6 +149,7 @@ enum call_stream_state { #define MEDIA_FLAG_RTCP_FB SHARED_FLAG_RTCP_FB #define MEDIA_FLAG_GENERATOR 0x02000000 #define MEDIA_FLAG_ICE_LITE_SELF 0x04000000 +#define MEDIA_FLAG_RTCP_GEN 0x08000000 /* access macros */ #define SP_ISSET(p, f) bf_isset(&(p)->sp_flags, SP_FLAG_ ## f) @@ -331,6 +332,7 @@ struct call_media { GQueue codec_handlers_store; // storage for struct codec_handler struct codec_handler *codec_handler_cache; struct rtcp_handler *rtcp_handler; + struct timeval rtcp_timer; // master lock for scheduling purposes struct codec_handler *dtmf_injector; struct t38_gateway *t38_gateway; struct codec_handler *t38_handler; diff --git a/include/call_interfaces.h b/include/call_interfaces.h index ed94d99fb..62ae6f7a2 100644 --- a/include/call_interfaces.h +++ b/include/call_interfaces.h @@ -42,6 +42,8 @@ struct sdp_ng_flags { GQueue codec_offer; GQueue codec_transcode; GHashTable *codec_mask; + GHashTable *codec_accept; + GHashTable *codec_consume; GHashTable *codec_set; int ptime, rev_ptime; @@ -75,6 +77,7 @@ struct sdp_ng_flags { rtcp_mux_reject:1, no_rtcp_attr:1, full_rtcp_attr:1, + generate_rtcp:1, generate_mid:1, strict_source:1, media_handover:1, @@ -88,7 +91,6 @@ struct sdp_ng_flags { record_call:1, loop_protect:1, original_sendrecv:1, - always_transcode:1, asymmetric_codecs:1, symmetric_codecs:1, single_codec:1, diff --git a/include/codec.h b/include/codec.h index c3aa41adb..a45f4333c 100644 --- a/include/codec.h +++ b/include/codec.h @@ -29,6 +29,7 @@ struct codec_handler { struct rtp_payload_type source_pt; // source_pt.payload_type = hashtable index struct rtp_payload_type dest_pt; int dtmf_payload_type; + int cn_payload_type; codec_handler_func *func; int kernelize:1; int transcoder:1; diff --git a/include/kernel.h b/include/kernel.h index 96f123d70..6dc7b54d4 100644 --- a/include/kernel.h +++ b/include/kernel.h @@ -17,6 +17,7 @@ struct rtpengine_target_info; struct re_address; +struct rtpengine_ssrc_stats; @@ -35,6 +36,7 @@ int kernel_setup_table(unsigned int); int kernel_add_stream(struct rtpengine_target_info *, int); int kernel_del_stream(const struct re_address *); GList *kernel_list(void); +int kernel_update_stats(const struct re_address *a, uint32_t ssrc, struct rtpengine_ssrc_stats *out); unsigned int kernel_add_call(const char *id); int kernel_del_call(unsigned int); diff --git a/include/main.h b/include/main.h index ab8d6f53c..df6df7407 100644 --- a/include/main.h +++ b/include/main.h @@ -108,6 +108,9 @@ struct rtpengine_config { int http_threads; int dtx_delay; int max_dtx; + double silence_detect_double; + uint32_t silence_detect_int; + str cn_payload; }; diff --git a/include/media_socket.h b/include/media_socket.h index 36439cf49..94c93af47 100644 --- a/include/media_socket.h +++ b/include/media_socket.h @@ -176,6 +176,8 @@ void __unkernelize(struct packet_stream *); void unkernelize(struct packet_stream *); void __stream_unconfirm(struct packet_stream *); +void media_update_stats(struct call_media *m); + void media_packet_copy(struct media_packet *, const struct media_packet *); void media_packet_release(struct media_packet *); int media_socket_dequeue(struct media_packet *mp, struct packet_stream *sink); diff --git a/include/rtcp.h b/include/rtcp.h index 73edc6276..d177d3f47 100644 --- a/include/rtcp.h +++ b/include/rtcp.h @@ -22,6 +22,7 @@ struct rtcp_parse_ctx { extern struct rtcp_handler *rtcp_transcode_handler; +extern struct rtcp_handler *rtcp_sink_handler; int rtcp_avp2savp(str *, struct crypto_context *, struct ssrc_ctx *); @@ -39,5 +40,6 @@ void rtcp_init(void); GString *rtcp_sender_report(uint32_t ssrc, uint32_t ts, uint32_t packets, uint32_t octets, GQueue *rrs); void rtcp_receiver_reports(GQueue *out, struct ssrc_hash *hash, struct call_monologue *ml); +void rtcp_send_report(struct call_media *media, struct ssrc_ctx *ssrc_out); #endif diff --git a/include/ssrc.h b/include/ssrc.h index 6698d7c22..fd203bdc1 100644 --- a/include/ssrc.h +++ b/include/ssrc.h @@ -98,6 +98,7 @@ struct ssrc_entry_call { // for transcoding // input only packet_sequencer_t sequencer; + uint32_t jitter, transit; // output only uint16_t seq_diff; }; diff --git a/kernel-module/xt_RTPENGINE.c b/kernel-module/xt_RTPENGINE.c index 962c02dcf..77106a0d9 100644 --- a/kernel-module/xt_RTPENGINE.c +++ b/kernel-module/xt_RTPENGINE.c @@ -277,6 +277,8 @@ struct rtpengine_target { struct rtpengine_stats_a stats; struct rtpengine_rtp_stats_a rtp_stats[NUM_PAYLOAD_TYPES]; + spinlock_t ssrc_stats_lock; + struct rtpengine_ssrc_stats ssrc_stats; struct re_crypto_context decrypt; struct re_crypto_context encrypt; @@ -1573,6 +1575,8 @@ static int proc_list_show(struct seq_file *f, void *v) { seq_printf(f, " option: transcoding\n"); if (g->target.non_forwarding) seq_printf(f, " option: non forwarding\n"); + if (g->target.rtp_stats) + seq_printf(f, " option: RTP stats\n"); target_put(g); @@ -1658,6 +1662,32 @@ static struct re_dest_addr *find_dest_addr(const struct re_dest_addr_hash *h, co +static int table_get_target_stats(struct rtpengine_table *t, struct rtpengine_stats_info *i, int reset) { + struct rtpengine_target *g; + + g = get_target(t, &i->local); + if (!g) + return -ENOENT; + + i->ssrc = g->target.ssrc; + spin_lock(&g->ssrc_stats_lock); + i->ssrc_stats = g->ssrc_stats; + + if (reset) { + g->ssrc_stats.basic_stats.packets = 0; + g->ssrc_stats.basic_stats.bytes = 0; + g->ssrc_stats.total_lost = 0; + } + + spin_unlock(&g->ssrc_stats_lock); + + target_put(g); + + return 0; +} + + + static int table_del_target(struct rtpengine_table *t, const struct re_address *local) { unsigned char hi, lo; struct re_dest_addr *rda; @@ -2131,6 +2161,8 @@ static int table_new_target(struct rtpengine_table *t, struct rtpengine_target_i memcpy(&g->target, i, sizeof(*i)); crypto_context_init(&g->decrypt, &g->target.decrypt); crypto_context_init(&g->encrypt, &g->target.encrypt); + spin_lock_init(&g->ssrc_stats_lock); + g->ssrc_stats.lost_bits = -1; err = gen_session_keys(&g->decrypt, &g->target.decrypt); if (err) @@ -3244,6 +3276,20 @@ static inline ssize_t proc_control_read_write(struct file *file, char __user *ub err = table_new_target(t, &msg->u.target, 1); break; + case REMG_GET_STATS: + err = -EINVAL; + if (!writeable) + goto err; + err = table_get_target_stats(t, &msg->u.stats, 0); + break; + + case REMG_GET_RESET_STATS: + err = -EINVAL; + if (!writeable) + goto err; + err = table_get_target_stats(t, &msg->u.stats, 1); + break; + case REMG_ADD_CALL: err = -EINVAL; if (!writeable) @@ -3925,6 +3971,88 @@ static struct sk_buff *intercept_skb_copy(struct sk_buff *oskb, const struct re_ +static void rtp_stats(struct rtpengine_target *g, struct rtp_parsed *rtp, s64 arrival_time, int pt_idx) { + unsigned long flags; + struct rtpengine_ssrc_stats *s = &g->ssrc_stats; + u_int16_t old_seq_trunc; + u_int32_t last_seq; + u_int16_t seq_diff; + u_int32_t clockrate; + u_int32_t transit; + int32_t d; + + u_int16_t seq = ntohs(rtp->header->seq_num); + u_int32_t ts = ntohl(rtp->header->timestamp); + + spin_lock_irqsave(&g->ssrc_stats_lock, flags); + + s->basic_stats.packets++; + s->basic_stats.bytes += rtp->payload_len; + s->timestamp = ts; + + // track sequence numbers and lost frames + + last_seq = s->ext_seq; + + // old seq or seq reset? + old_seq_trunc = last_seq & 0xffff; + seq_diff = seq - old_seq_trunc; + if (seq_diff == 0 || seq_diff >= 0xfeff) // old/dup seq - ignore + ; + else if (seq_diff > 0x100) { + // reset seq and loss tracker + s->ext_seq = seq; + s->lost_bits = -1; + } + else { + // seq wrap? + u_int32_t new_seq = (last_seq & 0xffff0000) | seq; + while (new_seq < last_seq) { + new_seq += 0x10000; + if ((new_seq & 0xffff0000) == 0) // ext seq wrapped + break; + } + seq_diff = new_seq - s->ext_seq; + s->ext_seq = new_seq; + + // shift loss tracker bit field and count losses + if (seq_diff >= (sizeof(s->lost_bits) * 8)) { + // complete loss + s->total_lost += sizeof(s->lost_bits) * 8; + s->lost_bits = -1; + } + else { + while (seq_diff) { + // shift out one bit and see if we lost it + if ((s->lost_bits & 0x80000000) == 0) + s->total_lost++; + s->lost_bits <<= 1; + seq_diff--; + } + } + } + + // track this frame as being seen + seq_diff = (s->ext_seq & 0xffff) - seq; + if (seq_diff < (sizeof(s->lost_bits) * 8)) + s->lost_bits |= (1 << seq_diff); + + // jitter + // RFC 3550 A.8 + clockrate = g->target.clock_rates[pt_idx]; + transit = (((arrival_time / 1000) * clockrate) / 1000) - ts; + d = 0; + if (s->transit) + d = transit - s->transit; + s->transit = transit; + if (d < 0) + d = -d; + s->jitter += d - ((s->jitter + 8) >> 4); + + spin_unlock_irqrestore(&g->ssrc_stats_lock, flags); +} + + static unsigned int rtpengine46(struct sk_buff *skb, struct rtpengine_table *t, struct re_address *src, struct re_address *dst, u_int8_t in_tos, const struct xt_action_param *par) { @@ -4040,6 +4168,9 @@ src_check_ok: skb_trim(skb, rtp.header_len + rtp.payload_len); + if (g->target.rtp_stats) + rtp_stats(g, &rtp, ktime_to_us(skb->tstamp), rtp_pt_idx); + DBG("packet payload decrypted as %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x...\n", rtp.payload[0], rtp.payload[1], rtp.payload[2], rtp.payload[3], rtp.payload[4], rtp.payload[5], rtp.payload[6], rtp.payload[7], diff --git a/kernel-module/xt_RTPENGINE.h b/kernel-module/xt_RTPENGINE.h index f553e6740..00b0922c7 100644 --- a/kernel-module/xt_RTPENGINE.h +++ b/kernel-module/xt_RTPENGINE.h @@ -24,6 +24,15 @@ struct rtpengine_rtp_stats { u_int64_t packets; u_int64_t bytes; }; +struct rtpengine_ssrc_stats { + struct rtpengine_rtp_stats basic_stats; + u_int32_t timestamp; + u_int32_t ext_seq; + u_int32_t lost_bits; // sliding bitfield, [0] = ext_seq + u_int32_t total_lost; + u_int32_t transit; + u_int32_t jitter; +}; struct re_address { int family; @@ -95,6 +104,7 @@ struct rtpengine_target_info { u_int32_t ssrc_out; // Rewrite SSRC unsigned char payload_types[NUM_PAYLOAD_TYPES]; /* must be sorted */ + u_int32_t clock_rates[NUM_PAYLOAD_TYPES]; unsigned int num_payload_types; unsigned char tos; @@ -105,7 +115,8 @@ struct rtpengine_target_info { rtp_only:1, do_intercept:1, transcoding:1, // SSRC subst and RTP PT filtering - non_forwarding:1; // empty src/dst addr + non_forwarding:1, // empty src/dst addr + rtp_stats:1; // requires SSRC and clock_rates to be set }; struct rtpengine_call_info { @@ -125,6 +136,12 @@ struct rtpengine_packet_info { unsigned int stream_idx; }; +struct rtpengine_stats_info { + struct re_address local; // input + u_int32_t ssrc; // output + struct rtpengine_ssrc_stats ssrc_stats; // output +}; + struct rtpengine_message { enum { REMG_NOOP = 1, @@ -145,6 +162,10 @@ struct rtpengine_message { /* packet_info: */ REMG_PACKET, + /* stats_info: */ + REMG_GET_STATS, + REMG_GET_RESET_STATS, + __REMG_LAST } cmd; @@ -153,6 +174,7 @@ struct rtpengine_message { struct rtpengine_call_info call; struct rtpengine_stream_info stream; struct rtpengine_packet_info packet; + struct rtpengine_stats_info stats; } u; unsigned char data[]; diff --git a/lib/codeclib.c b/lib/codeclib.c index 76b92ad3d..c75b8762c 100644 --- a/lib/codeclib.c +++ b/lib/codeclib.c @@ -63,6 +63,8 @@ static int ilbc_decoder_input(decoder_t *dec, const str *data, GQueue *out); static const char *dtmf_decoder_init(decoder_t *, const str *, const str *); static int dtmf_decoder_input(decoder_t *dec, const str *data, GQueue *out); +static int cn_decoder_input(decoder_t *dec, const str *data, GQueue *out); + static int format_cmp_ignore(const struct rtp_payload_type *, const struct rtp_payload_type *); static int amr_packet_lost(decoder_t *, GQueue *); @@ -102,6 +104,12 @@ static const codec_type_t codec_type_dtmf = { .decoder_init = dtmf_decoder_init, .decoder_input = dtmf_decoder_input, }; +static const codec_type_t codec_type_cn = { + .def_init = avc_def_init, + .decoder_init = avc_decoder_init, + .decoder_input = cn_decoder_input, + .decoder_close = avc_decoder_close, +}; #ifdef HAVE_BCG729 static packetizer_f packetizer_g729; // aggregate some frames into packets @@ -407,6 +415,20 @@ static codec_def_t __codec_defs[] = { .support_encoding = 1, .support_decoding = 1, }, + { + .rtpname = "CN", + .avcodec_id = AV_CODEC_ID_COMFORT_NOISE, + .avcodec_name_enc = "comfortnoise", + .avcodec_name_dec = "comfortnoise", + .packetizer = packetizer_passthrough, + .media_type = MT_AUDIO, + .supplemental = 1, + .default_clockrate = 8000, + .default_channels = 1, + .default_ptime = 20, + .format_cmp = format_cmp_ignore, + .codec_type = &codec_type_cn, + }, // for file reading and writing { .rtpname = "PCM-S16LE", @@ -2314,3 +2336,20 @@ static int dtmf_decoder_input(decoder_t *dec, const str *data, GQueue *out) { static int format_cmp_ignore(const struct rtp_payload_type *a, const struct rtp_payload_type *b) { return 0; } + + + +static int cn_decoder_input(decoder_t *dec, const str *data, GQueue *out) { + // generate one set of ptime worth of samples + int ptime = dec->ptime; + if (!ptime) + ptime = 20; // ? + int samples = dec->in_format.clockrate * ptime / 1000; + dec->u.avc.avcctx->frame_size = samples; + int ret = avc_decoder_input(dec, data, out); + if (ret) + return ret; + if (!out->length) + return -1; + return 0; +} diff --git a/t/auto-daemon-tests.pl b/t/auto-daemon-tests.pl index 1f5a35968..3dd827d67 100755 --- a/t/auto-daemon-tests.pl +++ b/t/auto-daemon-tests.pl @@ -10,7 +10,7 @@ use NGCP::Rtpclient::ICE; autotest_start(qw(--config-file=none -t -1 -i 203.0.113.1 -i 2001:db8:4321::1 - -n 2223 -c 12345 -f -L 7 -E -u 2222)) + -n 2223 -c 12345 -f -L 7 -E -u 2222 --silence-detect=1)) or die; @@ -36,6 +36,1393 @@ my ($sock_a, $sock_b, $sock_c, $sock_d, $port_a, $port_b, $ssrc, $resp, + + +# simple codec masking + +new_call; + +offer('simple codec neg', + { }, < { accept => ['PCMU'] } }, < { consume => ['PCMU'] } }, < { mask => ['PCMA'] } }, < { mask => ['PCMU'] } }, < { mask => ['PCMA'] } }, < { mask => ['PCMU'] } }, < { mask => ['PCMA'] } }, < { mask => ['PCMU'] } }, < { mask => ['telephone-event'] } }, < { mask => ['telephone-event'] } }, < { + strip => ['all'], + consume => ['CN'], + offer => ['PCMA', 'PCMU', 'telephone-event'], + } }, < { + strip => ['all'], + consume => ['CN'], + offer => ['PCMA', 'PCMU', 'telephone-event'], + }, + flags => ['symmetric codecs'], + }, < ['symmetric codecs'], + }, < 'remove', replace => ['origin'], codec => { transcode => ['CN'] } }, < 'remove', replace => ['origin'] }, < 'remove', replace => ['origin'], flags => ['always transcode'] }, < 'remove', replace => ['origin'] }, < 'remove', replace => ['origin'], + codec => { + strip => ['all'], + consume => ['CN'], + offer => ['PCMA','PCMU','telephone-event'], + } }, < 'remove', replace => ['origin'] }, < 'remove', replace => ['origin'], codec => { transcode => ['G722'] } }, < 'remove', replace => ['origin'] }, < 'remove', replace => ['origin'], codec => { transcode => ['G722'] } }, < 'remove', replace => ['origin'], flags => ['symmetric-codecs'] }, < 'remove', replace => ['origin'], codec => { transcode => ['CN'] } }, < 'remove', replace => ['origin'] }, < 'remove', replace => ['origin'], codec => { transcode => ['CN'] } }, < 'remove', replace => ['origin'] }, < 'remove', replace => ['origin'], codec => { transcode => ['PCMU', 'CN'] } }, < 'remove', replace => ['origin'] }, <monologue = &ml_B; media_B->protocol = &transport_protocols[PROTO_RTP_AVP]; - g_queue_init(&rtp_types); // parsed from received SDP - flags.codec_strip = g_hash_table_new_full(str_case_hash, str_case_equal, free, NULL); - flags.codec_mask = g_hash_table_new_full(str_case_hash, str_case_equal, free, NULL); - flags.codec_except = g_hash_table_new_full(str_case_hash, str_case_equal, free, NULL); - flags.codec_set = g_hash_table_new_full(str_case_hash, str_case_equal, free, free); + __init(); } #define transcode(codec) g_queue_push_tail(&flags.codec_transcode, sdup(#codec)) @@ -92,6 +97,12 @@ static void codec_set(char *c) { } #endif +static void __ht_set(GHashTable *h, char *x) { + str *d = sdup(x); + g_hash_table_insert(h, d, d); +} +#define ht_set(ht, s) __ht_set(flags.ht, #s) + #define sdp_pt_fmt(num, codec, clockrate, fmt) \ __sdp_pt_fmt(num, (str) STR_CONST_INIT(#codec), clockrate, (str) STR_CONST_INIT(#codec "/" #clockrate), \ (str) STR_CONST_INIT(fmt)) @@ -112,8 +123,7 @@ static void offer(void) { codec_rtp_payload_types(media_B, media_A, &rtp_types, &flags); codec_handlers_update(media_B, media_A, &flags, NULL); codec_tracker_finish(media_B); - g_queue_clear(&rtp_types); - memset(&flags, 0, sizeof(flags)); + __init(); } static void answer(void) { @@ -123,8 +133,7 @@ static void answer(void) { codec_rtp_payload_types(media_A, media_B, &rtp_types, &flags); codec_handlers_update(media_A, media_B, &flags, NULL); codec_tracker_finish(media_A); - g_queue_clear(&rtp_types); - memset(&flags, 0, sizeof(flags)); + __init(); } #define expect(side, dir, codecs) \ @@ -318,6 +327,7 @@ static void dtmf(const char *s) { #define PCMU_payload "\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00" #define PCMA_payload "\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a" +#define PCMA_silence "\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5" #define G722_payload "\x23\x84\x20\x84\x20\x84\x04\x84\x04\x04\x84\x04\x84\x04\x84\x05\x85\x46\x87\x48\xc8\x48\x88\x48\xc8\x49\x8a\x4b\xcc\x4c\x8c\x4c\xcc\x4c\x8c\x4d\xce\x50\xcf\x51\x90\x50\xcf\x12\xd1\x52\xd2\x54\x91\x52\xd2\x54\x92\x54\xd3\x56\x93\xd6\x94\xd4\x93\xd7\xd5\x55\x94\x55\xd5\x55\xd4\x56\xd5\x17\xd7\x5a\x95\xd7\x97\xd9\xd4\x16\x58\x57\x98\xd5\xd7\x5b\x96\xda\xd6\x1b\x57\x5a\xd6\x1a\x57\x5b\x98\xd6\xd8\x56\x98\xd7\xd9\x5a\x95\xdb\xd6\x1c\x52\x5e\xd7\x5c\x93\xdf\x99\xd5\xd7\x5f\xd9\x14\x56\x7f\x92\xda\xd9\x5c\x92\xdd\xd7\x5d\x92\xff\xd6\x5a\x96\xdc\xd5\x18\x56\x7e\xd2\x5e\x96\xde\x94\xd8\xd8\x58\xd3\x79\x93\xfb\x90\xdc\xd6\x5b\xdd\x58\x96\xff" #define AMR_WB_payload "\xf0\x1c\xf3\x06\x08\x10\x77\x32\x23\x20\xd3\x50\x62\x12\xc7\x7c\xe2\xea\x84\x0e\x6e\xf4\x4d\xe4\x7f\xc9\x4c\xcc\x58\x5d\xed\xcc\x5d\x7c\x6c\x14\x7d\xc0" // octet aligned #define AMR_WB_payload_noe "\xf1\xfc\xc1\x82\x04\x1d\xcc\x88\xc8\x34\xd4\x18\x84\xb1\xdf\x38\xba\xa1\x03\x9b\xbd\x13\x79\x1f\xf2\x53\x33\x16\x17\x7b\x73\x17\x5f\x1b\x05\x1f\x70" // bandwidth efficient @@ -410,7 +420,7 @@ int main(void) { // plain with two offered and two answered + always-transcode one way start(); - flags.always_transcode = 1; + ht_set(codec_accept, all); sdp_pt(0, PCMU, 8000); sdp_pt(8, PCMA, 8000); offer(); @@ -433,7 +443,7 @@ int main(void) { // plain with two offered and two answered + always-transcode both ways start(); - flags.always_transcode = 1; + ht_set(codec_accept, all); sdp_pt(0, PCMU, 8000); sdp_pt(8, PCMA, 8000); offer(); @@ -441,7 +451,7 @@ int main(void) { expect(A, send, "0/PCMU/8000 8/PCMA/8000"); expect(B, recv, "0/PCMU/8000 8/PCMA/8000"); expect(B, send, ""); - flags.always_transcode = 1; + ht_set(codec_accept, all); sdp_pt(0, PCMU, 8000); sdp_pt(8, PCMA, 8000); answer(); @@ -1199,5 +1209,484 @@ int main(void) { packet_seq(A, 0, PCMU_payload, 1002240, 220, 0, PCMU_payload); end(); + // codec-mask/accept/consume tests + // control - plain in/out + start(); + sdp_pt(104, SILK, 16000); + sdp_pt(9, G722, 8000); + sdp_pt(0, PCMU, 8000); + sdp_pt(8, PCMA, 8000); + sdp_pt(101, telephone-event, 8000); + sdp_pt(13, CN, 8000); + sdp_pt(118, CN, 16000); + offer(); + expect(A, recv, ""); + expect(A, send, "104/SILK/16000 9/G722/8000 0/PCMU/8000 8/PCMA/8000 101/telephone-event/8000 13/CN/8000 118/CN/16000"); + expect(B, recv, "104/SILK/16000 9/G722/8000 0/PCMU/8000 8/PCMA/8000 101/telephone-event/8000 13/CN/8000 118/CN/16000"); + expect(B, send, ""); + sdp_pt(8, PCMA, 8000); + sdp_pt(101, telephone-event, 8000); + answer(); + expect(A, recv, "8/PCMA/8000 101/telephone-event/8000"); + expect(A, send, "8/PCMA/8000 101/telephone-event/8000"); + expect(B, recv, "8/PCMA/8000 101/telephone-event/8000"); + expect(B, send, "8/PCMA/8000 101/telephone-event/8000"); + end(); + // codec-mask only + start(); + sdp_pt(104, SILK, 16000); + sdp_pt(9, G722, 8000); + sdp_pt(0, PCMU, 8000); + sdp_pt(8, PCMA, 8000); + sdp_pt(101, telephone-event, 8000); + sdp_pt(13, CN, 8000); + sdp_pt(118, CN, 16000); + ht_set(codec_mask, PCMU); + offer(); + expect(A, recv, ""); + expect(A, send, "104/SILK/16000 9/G722/8000 0/PCMU/8000 8/PCMA/8000 101/telephone-event/8000 13/CN/8000 118/CN/16000"); + expect(B, recv, "104/SILK/16000 9/G722/8000 8/PCMA/8000 101/telephone-event/8000 13/CN/8000 118/CN/16000"); + expect(B, send, ""); + sdp_pt(8, PCMA, 8000); + sdp_pt(101, telephone-event, 8000); + answer(); + expect(A, recv, "8/PCMA/8000 101/telephone-event/8000"); + expect(A, send, "8/PCMA/8000 101/telephone-event/8000"); + expect(B, recv, "8/PCMA/8000 101/telephone-event/8000"); + expect(B, send, "8/PCMA/8000 101/telephone-event/8000"); + end(); + // codec-mask + transcode + reject transcoded codec + start(); + sdp_pt(104, SILK, 16000); + sdp_pt(9, G722, 8000); + sdp_pt(0, PCMU, 8000); + sdp_pt(8, PCMA, 8000); + sdp_pt(101, telephone-event, 8000); + sdp_pt(13, CN, 8000); + sdp_pt(118, CN, 16000); + ht_set(codec_mask, PCMU); + transcode(GSM); + offer(); + expect(A, recv, ""); + expect(A, send, "104/SILK/16000 9/G722/8000 0/PCMU/8000 8/PCMA/8000 101/telephone-event/8000 13/CN/8000 118/CN/16000"); + expect(B, recv, "9/G722/8000 8/PCMA/8000 3/GSM/8000 101/telephone-event/8000 13/CN/8000"); + expect(B, send, ""); + sdp_pt(8, PCMA, 8000); + sdp_pt(101, telephone-event, 8000); + answer(); + expect(A, recv, "8/PCMA/8000 101/telephone-event/8000"); + expect(A, send, "8/PCMA/8000 101/telephone-event/8000"); + expect(B, recv, "8/PCMA/8000 101/telephone-event/8000"); + expect(B, send, "8/PCMA/8000 101/telephone-event/8000"); + end(); + // codec-mask + transcode + accept transcoded codec + start(); + sdp_pt(104, SILK, 16000); + sdp_pt(9, G722, 8000); + sdp_pt(0, PCMU, 8000); + sdp_pt(8, PCMA, 8000); + sdp_pt(101, telephone-event, 8000); + sdp_pt(13, CN, 8000); + sdp_pt(118, CN, 16000); + ht_set(codec_mask, PCMU); + transcode(GSM); + offer(); + expect(A, recv, ""); + expect(A, send, "104/SILK/16000 9/G722/8000 0/PCMU/8000 8/PCMA/8000 101/telephone-event/8000 13/CN/8000 118/CN/16000"); + expect(B, recv, "9/G722/8000 8/PCMA/8000 3/GSM/8000 101/telephone-event/8000 13/CN/8000"); + expect(B, send, ""); + sdp_pt(8, PCMA, 8000); + sdp_pt(3, GSM, 8000); + sdp_pt(101, telephone-event, 8000); + answer(); + expect(A, recv, "9/G722/8000 0/PCMU/8000 8/PCMA/8000 101/telephone-event/8000 13/CN/8000"); + expect(A, send, "104/SILK/16000 9/G722/8000 0/PCMU/8000 8/PCMA/8000 101/telephone-event/8000 13/CN/8000 118/CN/16000"); + expect(B, recv, "8/PCMA/8000 3/GSM/8000 101/telephone-event/8000"); + expect(B, send, "8/PCMA/8000 3/GSM/8000 101/telephone-event/8000"); + // G.722 > PCMA + packet_seq(A, 9, G722_payload, 0, 0, -1, ""); // nothing due to resampling + packet_seq_nf(A, 9, G722_payload, 160, 1, 8, PCMA_payload); + packet_seq_ts(A, 9, G722_payload, 320, 2, 8, PCMA_payload, 160, 0); + // asymmetric codec + packet(B, 8, PCMA_payload, 8, PCMA_payload); // nothing due to resampling + end(); + // codec-mask + transcode + accept transcoded codec + symmetric codecs + start(); + sdp_pt(104, SILK, 16000); + sdp_pt(9, G722, 8000); + sdp_pt(0, PCMU, 8000); + sdp_pt(8, PCMA, 8000); + sdp_pt(101, telephone-event, 8000); + sdp_pt(13, CN, 8000); + sdp_pt(118, CN, 16000); + ht_set(codec_mask, PCMU); + transcode(GSM); + offer(); + expect(A, recv, ""); + expect(A, send, "104/SILK/16000 9/G722/8000 0/PCMU/8000 8/PCMA/8000 101/telephone-event/8000 13/CN/8000 118/CN/16000"); + expect(B, recv, "9/G722/8000 8/PCMA/8000 3/GSM/8000 101/telephone-event/8000 13/CN/8000"); + expect(B, send, ""); + sdp_pt(8, PCMA, 8000); + sdp_pt(3, GSM, 8000); + sdp_pt(101, telephone-event, 8000); + flags.symmetric_codecs = 1; + answer(); + expect(A, recv, "8/PCMA/8000 9/G722/8000 0/PCMU/8000 13/CN/8000 101/telephone-event/8000"); + expect(A, send, "8/PCMA/8000 101/telephone-event/8000 104/SILK/16000 9/G722/8000 0/PCMU/8000 13/CN/8000 118/CN/16000"); + expect(B, recv, "8/PCMA/8000 3/GSM/8000 101/telephone-event/8000"); + expect(B, send, "8/PCMA/8000 3/GSM/8000 101/telephone-event/8000"); + end(); + // codec-consume only + start(); + sdp_pt(104, SILK, 16000); + sdp_pt(9, G722, 8000); + sdp_pt(0, PCMU, 8000); + sdp_pt(8, PCMA, 8000); + sdp_pt(101, telephone-event, 8000); + sdp_pt(13, CN, 8000); + sdp_pt(118, CN, 16000); + ht_set(codec_consume, PCMU); + offer(); + expect(A, recv, ""); + expect(A, send, "104/SILK/16000 9/G722/8000 0/PCMU/8000 8/PCMA/8000 101/telephone-event/8000 13/CN/8000 118/CN/16000"); + expect(B, recv, "9/G722/8000 8/PCMA/8000 101/telephone-event/8000 13/CN/8000"); + expect(B, send, ""); + sdp_pt(8, PCMA, 8000); + sdp_pt(101, telephone-event, 8000); + answer(); + expect(A, recv, "0/PCMU/8000 8/PCMA/8000 101/telephone-event/8000"); + expect(A, send, "104/SILK/16000 9/G722/8000 0/PCMU/8000 8/PCMA/8000 101/telephone-event/8000 13/CN/8000 118/CN/16000"); + expect(B, recv, "8/PCMA/8000 101/telephone-event/8000"); + expect(B, send, "8/PCMA/8000 101/telephone-event/8000"); + end(); + // codec-consume w symmetric codecs + start(); + sdp_pt(104, SILK, 16000); + sdp_pt(9, G722, 8000); + sdp_pt(0, PCMU, 8000); + sdp_pt(8, PCMA, 8000); + sdp_pt(101, telephone-event, 8000); + sdp_pt(13, CN, 8000); + sdp_pt(118, CN, 16000); + ht_set(codec_consume, PCMU); + offer(); + expect(A, recv, ""); + expect(A, send, "104/SILK/16000 9/G722/8000 0/PCMU/8000 8/PCMA/8000 101/telephone-event/8000 13/CN/8000 118/CN/16000"); + expect(B, recv, "9/G722/8000 8/PCMA/8000 101/telephone-event/8000 13/CN/8000"); + expect(B, send, ""); + sdp_pt(8, PCMA, 8000); + sdp_pt(101, telephone-event, 8000); + flags.symmetric_codecs = 1; + answer(); + expect(A, recv, "0/PCMU/8000 8/PCMA/8000 101/telephone-event/8000"); + expect(A, send, "0/PCMU/8000 8/PCMA/8000 101/telephone-event/8000 104/SILK/16000 9/G722/8000 13/CN/8000 118/CN/16000"); + expect(B, recv, "8/PCMA/8000 101/telephone-event/8000"); + expect(B, send, "8/PCMA/8000 101/telephone-event/8000"); + end(); + // codec-consume + transcode + reject transcoded codec + start(); + sdp_pt(104, SILK, 16000); + sdp_pt(9, G722, 8000); + sdp_pt(0, PCMU, 8000); + sdp_pt(8, PCMA, 8000); + sdp_pt(101, telephone-event, 8000); + sdp_pt(13, CN, 8000); + sdp_pt(118, CN, 16000); + ht_set(codec_consume, PCMU); + transcode(GSM); + offer(); + expect(A, recv, ""); + expect(A, send, "104/SILK/16000 9/G722/8000 0/PCMU/8000 8/PCMA/8000 101/telephone-event/8000 13/CN/8000 118/CN/16000"); + expect(B, recv, "9/G722/8000 8/PCMA/8000 3/GSM/8000 101/telephone-event/8000 13/CN/8000"); + expect(B, send, ""); + sdp_pt(8, PCMA, 8000); + sdp_pt(101, telephone-event, 8000); + answer(); + expect(A, recv, "0/PCMU/8000 8/PCMA/8000 101/telephone-event/8000"); + expect(A, send, "104/SILK/16000 9/G722/8000 0/PCMU/8000 8/PCMA/8000 101/telephone-event/8000 13/CN/8000 118/CN/16000"); + expect(B, recv, "8/PCMA/8000 101/telephone-event/8000"); + expect(B, send, "8/PCMA/8000 101/telephone-event/8000"); + end(); + // codec-consume + transcode + accept transcoded codec + start(); + sdp_pt(104, SILK, 16000); + sdp_pt(9, G722, 8000); + sdp_pt(0, PCMU, 8000); + sdp_pt(8, PCMA, 8000); + sdp_pt(101, telephone-event, 8000); + sdp_pt(13, CN, 8000); + sdp_pt(118, CN, 16000); + ht_set(codec_consume, PCMU); + transcode(GSM); + offer(); + expect(A, recv, ""); + expect(A, send, "104/SILK/16000 9/G722/8000 0/PCMU/8000 8/PCMA/8000 101/telephone-event/8000 13/CN/8000 118/CN/16000"); + expect(B, recv, "9/G722/8000 8/PCMA/8000 3/GSM/8000 101/telephone-event/8000 13/CN/8000"); + expect(B, send, ""); + sdp_pt(8, PCMA, 8000); + sdp_pt(3, GSM, 8000); + sdp_pt(101, telephone-event, 8000); + answer(); + expect(A, recv, "0/PCMU/8000 8/PCMA/8000 101/telephone-event/8000"); + expect(A, send, "104/SILK/16000 9/G722/8000 0/PCMU/8000 8/PCMA/8000 101/telephone-event/8000 13/CN/8000 118/CN/16000"); + expect(B, recv, "8/PCMA/8000 3/GSM/8000 101/telephone-event/8000"); + expect(B, send, "8/PCMA/8000 3/GSM/8000 101/telephone-event/8000"); + end(); + // codec-consume + transcode + accept transcoded codec + symmetric codecs + start(); + sdp_pt(104, SILK, 16000); + sdp_pt(9, G722, 8000); + sdp_pt(0, PCMU, 8000); + sdp_pt(8, PCMA, 8000); + sdp_pt(101, telephone-event, 8000); + sdp_pt(13, CN, 8000); + sdp_pt(118, CN, 16000); + ht_set(codec_consume, PCMU); + transcode(GSM); + offer(); + expect(A, recv, ""); + expect(A, send, "104/SILK/16000 9/G722/8000 0/PCMU/8000 8/PCMA/8000 101/telephone-event/8000 13/CN/8000 118/CN/16000"); + expect(B, recv, "9/G722/8000 8/PCMA/8000 3/GSM/8000 101/telephone-event/8000 13/CN/8000"); + expect(B, send, ""); + sdp_pt(8, PCMA, 8000); + sdp_pt(3, GSM, 8000); + sdp_pt(101, telephone-event, 8000); + flags.symmetric_codecs = 1; + answer(); + expect(A, recv, "0/PCMU/8000 8/PCMA/8000 101/telephone-event/8000"); + expect(A, send, "0/PCMU/8000 8/PCMA/8000 101/telephone-event/8000 104/SILK/16000 9/G722/8000 13/CN/8000 118/CN/16000"); + expect(B, recv, "8/PCMA/8000 3/GSM/8000 101/telephone-event/8000"); + expect(B, send, "8/PCMA/8000 3/GSM/8000 101/telephone-event/8000"); + end(); + // codec-accept only + start(); + sdp_pt(104, SILK, 16000); + sdp_pt(9, G722, 8000); + sdp_pt(0, PCMU, 8000); + sdp_pt(8, PCMA, 8000); + sdp_pt(101, telephone-event, 8000); + sdp_pt(13, CN, 8000); + sdp_pt(118, CN, 16000); + ht_set(codec_accept, PCMU); + offer(); + expect(A, recv, ""); + expect(A, send, "104/SILK/16000 9/G722/8000 0/PCMU/8000 8/PCMA/8000 101/telephone-event/8000 13/CN/8000 118/CN/16000"); + expect(B, recv, "9/G722/8000 0/PCMU/8000 8/PCMA/8000 101/telephone-event/8000 13/CN/8000"); + expect(B, send, ""); + sdp_pt(8, PCMA, 8000); + sdp_pt(101, telephone-event, 8000); + answer(); + expect(A, recv, "0/PCMU/8000 8/PCMA/8000 101/telephone-event/8000"); + expect(A, send, "104/SILK/16000 9/G722/8000 0/PCMU/8000 8/PCMA/8000 101/telephone-event/8000 13/CN/8000 118/CN/16000"); + expect(B, recv, "8/PCMA/8000 101/telephone-event/8000"); + expect(B, send, "8/PCMA/8000 101/telephone-event/8000"); + end(); + // codec-accept w symmetric codecs + start(); + sdp_pt(104, SILK, 16000); + sdp_pt(9, G722, 8000); + sdp_pt(0, PCMU, 8000); + sdp_pt(8, PCMA, 8000); + sdp_pt(101, telephone-event, 8000); + sdp_pt(13, CN, 8000); + sdp_pt(118, CN, 16000); + ht_set(codec_accept, PCMU); + offer(); + expect(A, recv, ""); + expect(A, send, "104/SILK/16000 9/G722/8000 0/PCMU/8000 8/PCMA/8000 101/telephone-event/8000 13/CN/8000 118/CN/16000"); + expect(B, recv, "9/G722/8000 0/PCMU/8000 8/PCMA/8000 101/telephone-event/8000 13/CN/8000"); + expect(B, send, ""); + sdp_pt(8, PCMA, 8000); + sdp_pt(101, telephone-event, 8000); + flags.symmetric_codecs = 1; + answer(); + expect(A, recv, "0/PCMU/8000 8/PCMA/8000 101/telephone-event/8000"); + expect(A, send, "0/PCMU/8000 8/PCMA/8000 101/telephone-event/8000 104/SILK/16000 9/G722/8000 13/CN/8000 118/CN/16000"); + expect(B, recv, "8/PCMA/8000 101/telephone-event/8000"); + expect(B, send, "8/PCMA/8000 101/telephone-event/8000"); + end(); + // codec-accept + transcode + reject transcoded codec + start(); + sdp_pt(104, SILK, 16000); + sdp_pt(9, G722, 8000); + sdp_pt(0, PCMU, 8000); + sdp_pt(8, PCMA, 8000); + sdp_pt(101, telephone-event, 8000); + sdp_pt(13, CN, 8000); + sdp_pt(118, CN, 16000); + ht_set(codec_accept, PCMU); + transcode(GSM); + offer(); + expect(A, recv, ""); + expect(A, send, "104/SILK/16000 9/G722/8000 0/PCMU/8000 8/PCMA/8000 101/telephone-event/8000 13/CN/8000 118/CN/16000"); + expect(B, recv, "9/G722/8000 0/PCMU/8000 8/PCMA/8000 3/GSM/8000 101/telephone-event/8000 13/CN/8000"); + expect(B, send, ""); + sdp_pt(8, PCMA, 8000); + sdp_pt(101, telephone-event, 8000); + answer(); + expect(A, recv, "0/PCMU/8000 8/PCMA/8000 101/telephone-event/8000"); + expect(A, send, "104/SILK/16000 9/G722/8000 0/PCMU/8000 8/PCMA/8000 101/telephone-event/8000 13/CN/8000 118/CN/16000"); + expect(B, recv, "8/PCMA/8000 101/telephone-event/8000"); + expect(B, send, "8/PCMA/8000 101/telephone-event/8000"); + end(); + // codec-accept + transcode + accept transcoded codec + start(); + sdp_pt(104, SILK, 16000); + sdp_pt(9, G722, 8000); + sdp_pt(0, PCMU, 8000); + sdp_pt(8, PCMA, 8000); + sdp_pt(101, telephone-event, 8000); + sdp_pt(13, CN, 8000); + sdp_pt(118, CN, 16000); + ht_set(codec_accept, PCMU); + transcode(GSM); + offer(); + expect(A, recv, ""); + expect(A, send, "104/SILK/16000 9/G722/8000 0/PCMU/8000 8/PCMA/8000 101/telephone-event/8000 13/CN/8000 118/CN/16000"); + expect(B, recv, "9/G722/8000 0/PCMU/8000 8/PCMA/8000 3/GSM/8000 101/telephone-event/8000 13/CN/8000"); + expect(B, send, ""); + sdp_pt(8, PCMA, 8000); + sdp_pt(3, GSM, 8000); + sdp_pt(101, telephone-event, 8000); + answer(); + expect(A, recv, "0/PCMU/8000 8/PCMA/8000 101/telephone-event/8000"); + expect(A, send, "104/SILK/16000 9/G722/8000 0/PCMU/8000 8/PCMA/8000 101/telephone-event/8000 13/CN/8000 118/CN/16000"); + expect(B, recv, "8/PCMA/8000 3/GSM/8000 101/telephone-event/8000"); + expect(B, send, "8/PCMA/8000 3/GSM/8000 101/telephone-event/8000"); + end(); + // codec-accept + transcode + accept transcoded codec + symmetric codecs + start(); + sdp_pt(104, SILK, 16000); + sdp_pt(9, G722, 8000); + sdp_pt(0, PCMU, 8000); + sdp_pt(8, PCMA, 8000); + sdp_pt(101, telephone-event, 8000); + sdp_pt(13, CN, 8000); + sdp_pt(118, CN, 16000); + ht_set(codec_accept, PCMU); + transcode(GSM); + offer(); + expect(A, recv, ""); + expect(A, send, "104/SILK/16000 9/G722/8000 0/PCMU/8000 8/PCMA/8000 101/telephone-event/8000 13/CN/8000 118/CN/16000"); + expect(B, recv, "9/G722/8000 0/PCMU/8000 8/PCMA/8000 3/GSM/8000 101/telephone-event/8000 13/CN/8000"); + expect(B, send, ""); + sdp_pt(8, PCMA, 8000); + sdp_pt(3, GSM, 8000); + sdp_pt(101, telephone-event, 8000); + flags.symmetric_codecs = 1; + answer(); + expect(A, recv, "0/PCMU/8000 8/PCMA/8000 101/telephone-event/8000"); + expect(A, send, "0/PCMU/8000 8/PCMA/8000 101/telephone-event/8000 104/SILK/16000 9/G722/8000 13/CN/8000 118/CN/16000"); + expect(B, recv, "8/PCMA/8000 3/GSM/8000 101/telephone-event/8000"); + expect(B, send, "8/PCMA/8000 3/GSM/8000 101/telephone-event/8000"); + end(); + // codec-accept first codec + start(); + sdp_pt(104, SILK, 16000); + sdp_pt(9, G722, 8000); + sdp_pt(0, PCMU, 8000); + sdp_pt(8, PCMA, 8000); + sdp_pt(101, telephone-event, 8000); + sdp_pt(13, CN, 8000); + sdp_pt(118, CN, 16000); + ht_set(codec_accept, G722); + offer(); + expect(A, recv, ""); + expect(A, send, "104/SILK/16000 9/G722/8000 0/PCMU/8000 8/PCMA/8000 101/telephone-event/8000 13/CN/8000 118/CN/16000"); + expect(B, recv, "9/G722/8000 0/PCMU/8000 8/PCMA/8000 101/telephone-event/8000 13/CN/8000"); + expect(B, send, ""); + sdp_pt(8, PCMA, 8000); + sdp_pt(101, telephone-event, 8000); + answer(); + expect(A, recv, "9/G722/8000 8/PCMA/8000 101/telephone-event/8000"); + expect(A, send, "104/SILK/16000 9/G722/8000 0/PCMU/8000 8/PCMA/8000 101/telephone-event/8000 13/CN/8000 118/CN/16000"); + expect(B, recv, "8/PCMA/8000 101/telephone-event/8000"); + expect(B, send, "8/PCMA/8000 101/telephone-event/8000"); + end(); + // gh 664 codec masking a/t + start(); + sdp_pt(120, opus, 48000); + sdp_pt(8, PCMA, 8000); + sdp_pt(0, PCMU, 8000); + sdp_pt(101, telephone-event, 8000); + ht_set(codec_mask, opus); + ht_set(codec_mask, G722); + ht_set(codec_mask, G7221); + ht_set(codec_accept, all); + offer(); + expect(B, recv, "8/PCMA/8000 0/PCMU/8000 101/telephone-event/8000"); + sdp_pt(8, PCMA, 8000); + sdp_pt(101, telephone-event, 8000); + answer(); + expect(A, recv, "120/opus/48000 8/PCMA/8000 0/PCMU/8000 101/telephone-event/8000 96/telephone-event/48000/0-15"); + // gh 664 codec masking accept=all + start(); + sdp_pt(120, opus, 48000); + sdp_pt(8, PCMA, 8000); + sdp_pt(0, PCMU, 8000); + sdp_pt(101, telephone-event, 8000); + ht_set(codec_mask, opus); + ht_set(codec_mask, G722); + ht_set(codec_mask, G7221); + ht_set(codec_accept, all); + offer(); + expect(B, recv, "8/PCMA/8000 0/PCMU/8000 101/telephone-event/8000"); + sdp_pt(8, PCMA, 8000); + sdp_pt(101, telephone-event, 8000); + answer(); + expect(A, recv, "120/opus/48000 8/PCMA/8000 0/PCMU/8000 101/telephone-event/8000 96/telephone-event/48000/0-15"); + + // CN transcoding + rtpe_config.silence_detect_int = 10 << 16; + str_init_len(&rtpe_config.cn_payload, "\x40", 1); + // CN transcoding - forward + start(); + sdp_pt(8, PCMA, 8000); + sdp_pt(0, PCMU, 8000); + transcode(CN); + offer(); + expect(B, recv, "8/PCMA/8000 0/PCMU/8000 13/CN/8000"); + sdp_pt(8, PCMA, 8000); + sdp_pt(0, PCMU, 8000); + sdp_pt(13, CN, 8000); + answer(); + expect(A, recv, "8/PCMA/8000 0/PCMU/8000"); + packet_seq(A, 8, PCMA_payload, 160, 1, 8, PCMA_payload); + packet_seq(B, 8, PCMA_payload, 160, 1, 8, PCMA_payload); + packet_seq(B, 13, "\x20", 320, 2, 8, "\xf5\x5c\x4b\xc2\xde\xf4\x5e\xd4\x47\x70\x5d\x77\x45\x51\xc5\xcd\xd7\x77\x5a\xf5\xcf\x4a\x4c\x40\xc3\x47\x74\x49\x59\xc4\x76\x57\x71\x57\x40\xc5\xf4\x5a\x47\xd6\xc4\xf6\xc7\xf3\x40\x58\x74\x54\x4b\xd7\x5c\xc7\x41\x49\xf5\x5b\x53\xd9\x70\x44\xcd\xc4\xce\xcb\xc7\x58\xcd\x45\xc6\x71\xf5\x70\x43\xca\x43\xd5\x52\x5c\x75\x74\xc6\xc3\x4f\xda\x56\xc3\x46\xf5\x49\xdf\x56\x4f\x71\x5b\x52\xc6\x4e\xd0\x43\xc2\xcd\xd5\xdf\x40\x43\x4a\xf7\xf6\xd9\xdf\xde\x45\xc9\xd9\xc2\xf0\xc1\x4a\x40\x52\xd1\x5b\xd0\x54\xc9\x5e\xde\xd5\x74\x5c\x5d\x59\x71\xc1\xc1\x71\xd2\xcb\x50\x50\x54\x53\x75\xdc\x4b\xcf\xc2\xd7\x4a\xcc\x58\xc7\xdb\xd8\x48\x4a\xd6\x58\xf0\x46"); + packet_seq(A, 8, PCMA_silence, 320, 2, 13, "\x40"); + // CN transcoding - reverse 1 + start(); + sdp_pt(8, PCMA, 8000); + sdp_pt(0, PCMU, 8000); + sdp_pt(13, CN, 8000); + ht_set(codec_consume, CN); + offer(); + expect(B, recv, "8/PCMA/8000 0/PCMU/8000"); + sdp_pt(8, PCMA, 8000); + sdp_pt(0, PCMU, 8000); + answer(); + expect(A, recv, "8/PCMA/8000 0/PCMU/8000 13/CN/8000"); + packet_seq(A, 8, PCMA_payload, 160, 1, 8, PCMA_payload); + packet_seq(B, 8, PCMA_payload, 160, 1, 8, PCMA_payload); + packet_seq(A, 13, "\x20", 320, 2, 8, "\xf5\x5c\x4b\xc2\xde\xf4\x5e\xd4\x47\x70\x5d\x77\x45\x51\xc5\xcd\xd7\x77\x5a\xf5\xcf\x4a\x4c\x40\xc3\x47\x74\x49\x59\xc4\x76\x57\x71\x57\x40\xc5\xf4\x5a\x47\xd6\xc4\xf6\xc7\xf3\x40\x58\x74\x54\x4b\xd7\x5c\xc7\x41\x49\xf5\x5b\x53\xd9\x70\x44\xcd\xc4\xce\xcb\xc7\x58\xcd\x45\xc6\x71\xf5\x70\x43\xca\x43\xd5\x52\x5c\x75\x74\xc6\xc3\x4f\xda\x56\xc3\x46\xf5\x49\xdf\x56\x4f\x71\x5b\x52\xc6\x4e\xd0\x43\xc2\xcd\xd5\xdf\x40\x43\x4a\xf7\xf6\xd9\xdf\xde\x45\xc9\xd9\xc2\xf0\xc1\x4a\x40\x52\xd1\x5b\xd0\x54\xc9\x5e\xde\xd5\x74\x5c\x5d\x59\x71\xc1\xc1\x71\xd2\xcb\x50\x50\x54\x53\x75\xdc\x4b\xcf\xc2\xd7\x4a\xcc\x58\xc7\xdb\xd8\x48\x4a\xd6\x58\xf0\x46"); + packet_seq(B, 8, PCMA_silence, 320, 2, 13, "\x40"); + // CN transcoding - reverse 2 + start(); + sdp_pt(8, PCMA, 8000); + sdp_pt(0, PCMU, 8000); + sdp_pt(13, CN, 8000); + ht_set(codec_accept, CN); + offer(); + expect(B, recv, "8/PCMA/8000 0/PCMU/8000 13/CN/8000"); + sdp_pt(8, PCMA, 8000); + sdp_pt(0, PCMU, 8000); + answer(); + expect(A, recv, "8/PCMA/8000 0/PCMU/8000 13/CN/8000"); + packet_seq(A, 8, PCMA_payload, 160, 1, 8, PCMA_payload); + packet_seq(B, 8, PCMA_payload, 160, 1, 8, PCMA_payload); + packet_seq(A, 13, "\x20", 320, 2, 8, "\xf5\x5c\x4b\xc2\xde\xf4\x5e\xd4\x47\x70\x5d\x77\x45\x51\xc5\xcd\xd7\x77\x5a\xf5\xcf\x4a\x4c\x40\xc3\x47\x74\x49\x59\xc4\x76\x57\x71\x57\x40\xc5\xf4\x5a\x47\xd6\xc4\xf6\xc7\xf3\x40\x58\x74\x54\x4b\xd7\x5c\xc7\x41\x49\xf5\x5b\x53\xd9\x70\x44\xcd\xc4\xce\xcb\xc7\x58\xcd\x45\xc6\x71\xf5\x70\x43\xca\x43\xd5\x52\x5c\x75\x74\xc6\xc3\x4f\xda\x56\xc3\x46\xf5\x49\xdf\x56\x4f\x71\x5b\x52\xc6\x4e\xd0\x43\xc2\xcd\xd5\xdf\x40\x43\x4a\xf7\xf6\xd9\xdf\xde\x45\xc9\xd9\xc2\xf0\xc1\x4a\x40\x52\xd1\x5b\xd0\x54\xc9\x5e\xde\xd5\x74\x5c\x5d\x59\x71\xc1\xc1\x71\xd2\xcb\x50\x50\x54\x53\x75\xdc\x4b\xcf\xc2\xd7\x4a\xcc\x58\xc7\xdb\xd8\x48\x4a\xd6\x58\xf0\x46"); + packet_seq(B, 8, PCMA_silence, 320, 2, 13, "\x40"); + return 0; } diff --git a/utils/rtpengine-ng-client b/utils/rtpengine-ng-client index 7a08dc743..03d1eb20a 100755 --- a/utils/rtpengine-ng-client +++ b/utils/rtpengine-ng-client @@ -52,6 +52,8 @@ GetOptions( 'codec-offer=s@' => \$options{'codec-offer'}, 'codec-transcode=s@' => \$options{'codec-transcode'}, 'codec-mask=s@' => \$options{'codec-mask'}, + 'codec-consume=s@' => \$options{'codec-consume'}, + 'codec-accept=s@' => \$options{'codec-accept'}, 'codec-set=s@' => \$options{'codec-set'}, 'ptime=i' => \$options{'ptime'}, 'flags=s@' => \$options{'flags'}, @@ -79,14 +81,15 @@ GetOptions( 'inject-DTMF' => \$options{'inject DTMF'}, 'DTLS-fingerprint=s' => \$options{'DTLS-fingerprint'}, 'ICE-lite=s' => \$options{'ICE-lite'}, - 'no-jitter-buffer' => \$options{'no jitter buffer'}, + 'no-jitter-buffer' => \$options{'no jitter buffer'}, + 'generate-RTCP' => \$options{'generate RTCP'}, ) or die; my $cmd = shift(@ARGV) or die; my %packet = (command => $cmd); -for my $x (split(/,/, 'from-tag,to-tag,call-id,transport protocol,media address,ICE,address family,DTLS,via-branch,media address,ptime,xmlrpc-callback,metadata,address,file,db-id,code,DTLS-fingerprint,ICE-lite')) { +for my $x (split(/,/, 'from-tag,to-tag,call-id,transport protocol,media address,ICE,address family,DTLS,via-branch,media address,ptime,xmlrpc-callback,metadata,address,file,db-id,code,DTLS-fingerprint,ICE-lite,generate RTCP')) { defined($options{$x}) and $packet{$x} = \$options{$x}; } for my $x (split(/,/, 'TOS,delete-delay')) { @@ -106,7 +109,7 @@ if (defined($options{direction})) { $options{direction} =~ /(.*),(.*)/ or die; $packet{direction} = [$1,$2]; } -for my $x (qw(strip offer transcode mask set)) { +for my $x (qw(strip offer transcode mask set consume accept)) { if ($options{'codec-'.$x} && @{$options{'codec-'.$x}}) { if (!$options{'codec options flag'}) { $packet{codec}{$x} = $options{'codec-'.$x};