diff --git a/README.md b/README.md index e2baab9e5..aef25a1be 100644 --- a/README.md +++ b/README.md @@ -571,6 +571,8 @@ a string and determines the type of message. Currently the following commands ar * unblock DTMF * block media * unblock media +* silence media +* unsilence media * start forwarding * stop forwarding * play media @@ -751,9 +753,10 @@ Optionally included keys are: - `all` - Only relevant to the `unblock media` message. Instructs *rtpengine* to remove not only a - full-call media block, but also remove directional media blocks that were imposed on - individual participants. + Only relevant to the `unblock media` and `unsilence media` + messages. Instructs *rtpengine* to remove not only a full-call + media block, but also remove directional media blocks that were + imposed on individual participants. - `pad crypto` @@ -1805,6 +1808,13 @@ Analogous to `block DTMF` and `unblock DTMF` but blocks media packets instead of can still pass through when media blocking is enabled. Media packets can be blocked for an entire call, or directionally for individual participants. See `block DTMF` above for details. +`silence media` and `unsilence media` Messages +---------------------------------------------- + +Identical to `block media` and `unblock media` except that media packets are +not simply blocked, but rather have their payload replaced with silence audio. +This is only supported for certain trivial audio codecs (i.e. G.711, G.722). + `start forwarding` and `stop forwarding` Messages ------------------------------------------------- diff --git a/daemon/call_interfaces.c b/daemon/call_interfaces.c index 6be8a932d..3dd4356e2 100644 --- a/daemon/call_interfaces.c +++ b/daemon/call_interfaces.c @@ -2212,6 +2212,63 @@ const char *call_unblock_media_ng(bencode_item_t *input, bencode_item_t *output) } +const char *call_silence_media_ng(bencode_item_t *input, bencode_item_t *output) { + AUTO_CLEANUP_NULL(struct call *call, call_unlock_release); + struct call_monologue *monologue; + const char *errstr = NULL; + struct sdp_ng_flags flags; + + errstr = media_block_match(&call, &monologue, &flags, input, OP_OTHER); + if (errstr) + return errstr; + + if (monologue) { + ilog(LOG_INFO, "Silencing directional media (tag '" STR_FORMAT_M "')", + STR_FMT_M(&monologue->tag)); + monologue->silence_media = 1; + __monologue_unkernelize(monologue); + } + else { + ilog(LOG_INFO, "Blocking media (entire call)"); + call->silence_media = 1; + __call_unkernelize(call); + } + + return NULL; +} + +const char *call_unsilence_media_ng(bencode_item_t *input, bencode_item_t *output) { + AUTO_CLEANUP_NULL(struct call *call, call_unlock_release); + struct call_monologue *monologue; + const char *errstr = NULL; + struct sdp_ng_flags flags; + + errstr = media_block_match(&call, &monologue, &flags, input, OP_OTHER); + if (errstr) + return errstr; + + if (monologue) { + ilog(LOG_INFO, "Unsilencing directional media (tag '" STR_FORMAT_M "')", + STR_FMT_M(&monologue->tag)); + monologue->silence_media = 0; + __monologue_unkernelize(monologue); + } + else { + ilog(LOG_INFO, "Unsilencing media (entire call)"); + call->silence_media = 0; + if (flags.all) { + for (GList *l = call->monologues.head; l; l = l->next) { + monologue = l->data; + monologue->silence_media = 0; + } + } + __call_unkernelize(call); + } + + return NULL; +} + + #ifdef WITH_TRANSCODING static const char *play_media_select_party(struct call **call, GQueue *monologues, bencode_item_t *input) diff --git a/daemon/codec.c b/daemon/codec.c index 56119b2e3..4fe526685 100644 --- a/daemon/codec.c +++ b/daemon/codec.c @@ -1307,8 +1307,26 @@ void codec_add_raw_packet(struct media_packet *mp, unsigned int clockrate) { } g_queue_push_tail(&mp->packets_out, p); } -static int handler_func_passthrough(struct codec_handler *h, struct media_packet *mp) { +static bool handler_silence_block(struct codec_handler *h, struct media_packet *mp) { if (mp->call->block_media || mp->media->monologue->block_media) + return false; + if (mp->call->silence_media || mp->media->monologue->silence_media) { + if (h->source_pt.codec_def && h->source_pt.codec_def->silence_pattern.len) { + if (h->source_pt.codec_def->silence_pattern.len == 1) + memset(mp->payload.s, h->source_pt.codec_def->silence_pattern.s[0], + mp->payload.len); + else { + for (size_t pos = 0; pos < mp->payload.len; + pos += h->source_pt.codec_def->silence_pattern.len) + memcpy(&mp->payload.s[pos], h->source_pt.codec_def->silence_pattern.s, + h->source_pt.codec_def->silence_pattern.len); + } + } + } + return true; +} +static int handler_func_passthrough(struct codec_handler *h, struct media_packet *mp) { + if (!handler_silence_block(h, mp)) return 0; if (mp->rtp) @@ -1928,7 +1946,7 @@ void codec_init_payload_type(struct rtp_payload_type *pt, enum media_type type) static int handler_func_passthrough_ssrc(struct codec_handler *h, struct media_packet *mp) { if (G_UNLIKELY(!mp->rtp)) return handler_func_passthrough(h, mp); - if (mp->call->block_media || mp->media->monologue->block_media) + if (!handler_silence_block(h, mp)) return 0; if (mp->rtp) @@ -2820,7 +2838,7 @@ void codec_calc_jitter(struct ssrc_ctx *ssrc, unsigned long ts, unsigned int clo static int handler_func_transcode(struct codec_handler *h, struct media_packet *mp) { if (G_UNLIKELY(!mp->rtp)) return handler_func_passthrough(h, mp); - if (mp->call->block_media || mp->media->monologue->block_media) + if (!handler_silence_block(h, mp)) return 0; // use main codec handler for supp codecs diff --git a/daemon/control_ng.c b/daemon/control_ng.c index 36dfed003..f5ce5ae4f 100644 --- a/daemon/control_ng.c +++ b/daemon/control_ng.c @@ -38,13 +38,13 @@ const char *ng_command_strings[NGC_COUNT] = { "ping", "offer", "answer", "delete", "query", "list", "start recording", "stop recording", "start forwarding", "stop forwarding", "block DTMF", "unblock DTMF", "block media", "unblock media", "play media", "stop media", - "play DTMF", "statistics", + "play DTMF", "statistics", "silence media", "unsilence media", }; const char *ng_command_strings_short[NGC_COUNT] = { "Ping", "Offer", "Answer", "Delete", "Query", "List", "StartRec", "StopRec", "StartFwd", "StopFwd", "BlkDTMF", "UnblkDTMF", "BlkMedia", "UnblkMedia", "PlayMedia", "StopMedia", - "PlayDTMF", "Stats", + "PlayDTMF", "Stats", "SlnMedia", "UnslnMedia", }; static void timeval_update_request_time(struct request_time *request, const struct timeval *offer_diff) { @@ -282,6 +282,14 @@ int control_ng_process(str *buf, const endpoint_t *sin, char *addr, errstr = call_unblock_media_ng(dict, resp); command = NGC_UNBLOCK_MEDIA; break; + case CSH_LOOKUP("silence media"): + errstr = call_silence_media_ng(dict, resp); + command = NGC_SILENCE_MEDIA; + break; + case CSH_LOOKUP("unsilence media"): + errstr = call_unsilence_media_ng(dict, resp); + command = NGC_UNSILENCE_MEDIA; + break; case CSH_LOOKUP("play media"): errstr = call_play_media_ng(dict, resp); command = NGC_PLAY_MEDIA; diff --git a/daemon/media_socket.c b/daemon/media_socket.c index 8ad510aef..49babf989 100644 --- a/daemon/media_socket.c +++ b/daemon/media_socket.c @@ -1210,13 +1210,17 @@ static const char *kernelize_one(struct rtpengine_target_info *reti, GQueue *out rs = l->data; // only add payload types that are passthrough for all sinks bool can_kernelize = true; + bool silenced = (call->silence_media || media->monologue->silence_media) ? true : false; unsigned int clockrate = 0; + str replace_pattern = STR_NULL; for (GList *k = sinks->head; k; k = k->next) { struct sink_handler *ksh = k->data; struct packet_stream *ksink = ksh->sink; struct codec_handler *ch = codec_handler_get(media, rs->payload_type, ksink->media); clockrate = ch->source_pt.clock_rate; + if (silenced && ch->source_pt.codec_def) + replace_pattern = ch->source_pt.codec_def->silence_pattern; if (ch->kernelize) continue; can_kernelize = false; @@ -1224,9 +1228,17 @@ static const char *kernelize_one(struct rtpengine_target_info *reti, GQueue *out } if (!can_kernelize) continue; + struct rtpengine_payload_type *rpt = &reti->payload_types[reti->num_payload_types++]; rpt->pt_num = rs->payload_type; rpt->clock_rate = clockrate; + if (replace_pattern.len > sizeof(reti->payload_types->replace_pattern)) + ilog(LOG_WARNING | LOG_FLAG_LIMIT, "Payload replacement pattern too long (%zu)", + replace_pattern.len); + else { + rpt->replace_pattern_len = replace_pattern.len; + memcpy(rpt->replace_pattern, replace_pattern.s, replace_pattern.len); + } } g_list_free(values); } diff --git a/include/call.h b/include/call.h index 21a78c714..9d5ca2550 100644 --- a/include/call.h +++ b/include/call.h @@ -417,6 +417,7 @@ struct call_monologue { unsigned int block_dtmf:1; unsigned int block_media:1; + unsigned int silence_media:1; unsigned int rec_forwarding:1; unsigned int inject_dtmf:1; }; @@ -515,6 +516,7 @@ struct call { unsigned int block_dtmf:1; unsigned int block_media:1; + unsigned int silence_media:1; unsigned int recording_on:1; unsigned int rec_forwarding:1; unsigned int drop_traffic:1; diff --git a/include/call_interfaces.h b/include/call_interfaces.h index 481b04fa1..930fd1ec6 100644 --- a/include/call_interfaces.h +++ b/include/call_interfaces.h @@ -170,6 +170,8 @@ const char *call_block_dtmf_ng(bencode_item_t *, bencode_item_t *); const char *call_unblock_dtmf_ng(bencode_item_t *, bencode_item_t *); const char *call_block_media_ng(bencode_item_t *, bencode_item_t *); const char *call_unblock_media_ng(bencode_item_t *, bencode_item_t *); +const char *call_silence_media_ng(bencode_item_t *, bencode_item_t *); +const char *call_unsilence_media_ng(bencode_item_t *, bencode_item_t *); const char *call_play_media_ng(bencode_item_t *, bencode_item_t *); const char *call_stop_media_ng(bencode_item_t *, bencode_item_t *); const char *call_play_dtmf_ng(bencode_item_t *, bencode_item_t *); diff --git a/include/control_ng.h b/include/control_ng.h index c275ae45a..0ecf4144d 100644 --- a/include/control_ng.h +++ b/include/control_ng.h @@ -29,6 +29,8 @@ enum ng_command { NGC_STOP_MEDIA, NGC_PLAY_DTMF, NGC_STATISTICS, + NGC_SILENCE_MEDIA, + NGC_UNSILENCE_MEDIA, NGC_COUNT // last, number of elements }; diff --git a/kernel-module/xt_RTPENGINE.c b/kernel-module/xt_RTPENGINE.c index 5e6723600..41a9d7c18 100644 --- a/kernel-module/xt_RTPENGINE.c +++ b/kernel-module/xt_RTPENGINE.c @@ -1629,11 +1629,15 @@ static int proc_list_show(struct seq_file *f, void *v) { (unsigned long long) atomic64_read(&g->stats.bytes), (unsigned long long) atomic64_read(&g->stats.packets), (unsigned long long) atomic64_read(&g->stats.errors)); - for (i = 0; i < g->target.num_payload_types; i++) + for (i = 0; i < g->target.num_payload_types; i++) { seq_printf(f, " RTP payload type %3u: %20llu bytes, %20llu packets\n", g->target.payload_types[i].pt_num, (unsigned long long) atomic64_read(&g->rtp_stats[i].bytes), (unsigned long long) atomic64_read(&g->rtp_stats[i].packets)); + if (g->target.payload_types[i].replace_pattern_len) + seq_printf(f, " %u bytes replacement payload\n", + g->target.payload_types[i].replace_pattern_len); + } if (g->target.ssrc) seq_printf(f, " SSRC in: %lx\n", (unsigned long) ntohl(g->target.ssrc)); proc_list_crypto_print(f, &g->decrypt, &g->target.decrypt, "decryption"); @@ -4483,6 +4487,19 @@ intercept_done: } no_intercept: + // pattern rewriting + if (rtp_pt_idx >= 0 && g->target.payload_types[rtp_pt_idx].replace_pattern_len && rtp.ok) { + if (g->target.payload_types[rtp_pt_idx].replace_pattern_len == 1) + memset(rtp.payload, g->target.payload_types[rtp_pt_idx].replace_pattern[0], + rtp.payload_len); + else { + for (i = 0; i < rtp.payload_len; + i += g->target.payload_types[rtp_pt_idx].replace_pattern_len) + memcpy(&rtp.payload[i], g->target.payload_types[rtp_pt_idx].replace_pattern, + g->target.payload_types[rtp_pt_idx].replace_pattern_len); + } + } + // output for (i = 0; i < g->target.num_destinations; i++) { struct rtpengine_output *o = &g->outputs[i]; diff --git a/kernel-module/xt_RTPENGINE.h b/kernel-module/xt_RTPENGINE.h index 25e471d76..03129eec2 100644 --- a/kernel-module/xt_RTPENGINE.h +++ b/kernel-module/xt_RTPENGINE.h @@ -94,7 +94,9 @@ enum rtpengine_src_mismatch { struct rtpengine_payload_type { unsigned char pt_num; + unsigned char replace_pattern_len; uint32_t clock_rate; + char replace_pattern[16]; }; struct rtpengine_target_info { diff --git a/lib/codeclib.c b/lib/codeclib.c index 257985745..217b0696b 100644 --- a/lib/codeclib.c +++ b/lib/codeclib.c @@ -162,6 +162,7 @@ static codec_def_t __codec_defs[] = { .bits_per_sample = 8, .media_type = MT_AUDIO, .codec_type = &codec_type_avcodec, + .silence_pattern = STR_CONST_INIT("\xd5"), .dtx_methods = { [DTX_SILENCE] = &dtx_method_silence, [DTX_CN] = &dtx_method_cn, @@ -178,6 +179,7 @@ static codec_def_t __codec_defs[] = { .bits_per_sample = 8, .media_type = MT_AUDIO, .codec_type = &codec_type_avcodec, + .silence_pattern = STR_CONST_INIT("\xff"), .dtx_methods = { [DTX_SILENCE] = &dtx_method_silence, [DTX_CN] = &dtx_method_cn, @@ -210,6 +212,7 @@ static codec_def_t __codec_defs[] = { .bits_per_sample = 8, .media_type = MT_AUDIO, .codec_type = &codec_type_avcodec, + .silence_pattern = STR_CONST_INIT("\xfa"), .dtx_methods = { [DTX_SILENCE] = &dtx_method_silence, [DTX_CN] = &dtx_method_cn, diff --git a/lib/codeclib.h b/lib/codeclib.h index 9ea719d22..4ebbe1908 100644 --- a/lib/codeclib.h +++ b/lib/codeclib.h @@ -142,6 +142,7 @@ struct codec_def_s { packetizer_f * const packetizer; const int bits_per_sample; const enum media_type media_type; + const str silence_pattern; // codec-specific callbacks format_init_f *init; @@ -386,6 +387,7 @@ struct codec_def_s { int dtmf; int supplemental; format_cmp_f * const format_cmp; + const str silence_pattern; }; struct packet_sequencer_s { }; diff --git a/t/test-transcode.c b/t/test-transcode.c index 6b608fd77..03422553e 100644 --- a/t/test-transcode.c +++ b/t/test-transcode.c @@ -372,6 +372,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 PCMU_silence "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" #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 @@ -1575,6 +1576,98 @@ int main(void) { expect(B, "0/PCMU/8000 8/PCMA/8000 9/G722/8000"); end(); + // media silencing PCMA + start(); + sdp_pt(8, PCMA, 8000); + offer(); + expect(A, "8/PCMA/8000"); + expect(B, "8/PCMA/8000"); + sdp_pt(8, PCMA, 8000); + answer(); + expect(A, "8/PCMA/8000"); + expect(B, "8/PCMA/8000"); + packet_seq(A, 8, PCMA_payload, 0, 0, 8, PCMA_payload); + packet_seq(B, 8, PCMA_payload, 0, 0, 8, PCMA_payload); + packet_seq(A, 8, PCMA_payload, 160, 1, 8, PCMA_payload); + packet_seq(B, 8, PCMA_payload, 160, 1, 8, PCMA_payload); + call.silence_media = 1; + packet_seq(A, 8, PCMA_payload, 320, 2, 8, PCMA_silence); + packet_seq(B, 8, PCMA_payload, 320, 2, 8, PCMA_silence); + packet_seq(A, 8, PCMA_payload, 480, 3, 8, PCMA_silence); + packet_seq(B, 8, PCMA_payload, 480, 3, 8, PCMA_silence); + call.silence_media = 0; + packet_seq(A, 8, PCMA_payload, 640, 4, 8, PCMA_payload); + packet_seq(B, 8, PCMA_payload, 640, 4, 8, PCMA_payload); + packet_seq(A, 8, PCMA_payload, 800, 5, 8, PCMA_payload); + packet_seq(B, 8, PCMA_payload, 800, 5, 8, PCMA_payload); + ml_A.silence_media = 1; + packet_seq(A, 8, PCMA_payload, 960, 6, 8, PCMA_silence); + packet_seq(B, 8, PCMA_payload, 960, 6, 8, PCMA_payload); + packet_seq(A, 8, PCMA_payload, 1120, 7, 8, PCMA_silence); + packet_seq(B, 8, PCMA_payload, 1120, 7, 8, PCMA_payload); + ml_A.silence_media = 0; + packet_seq(A, 8, PCMA_payload, 1280, 8, 8, PCMA_payload); + packet_seq(B, 8, PCMA_payload, 1280, 8, 8, PCMA_payload); + packet_seq(A, 8, PCMA_payload, 1440, 9, 8, PCMA_payload); + packet_seq(B, 8, PCMA_payload, 1440, 9, 8, PCMA_payload); + ml_B.silence_media = 1; + packet_seq(A, 8, PCMA_payload, 1600, 10, 8, PCMA_payload); + packet_seq(B, 8, PCMA_payload, 1600, 10, 8, PCMA_silence); + packet_seq(A, 8, PCMA_payload, 1760, 11, 8, PCMA_payload); + packet_seq(B, 8, PCMA_payload, 1760, 11, 8, PCMA_silence); + ml_B.silence_media = 0; + packet_seq(A, 8, PCMA_payload, 1920, 12, 8, PCMA_payload); + packet_seq(B, 8, PCMA_payload, 1920, 12, 8, PCMA_payload); + packet_seq(A, 8, PCMA_payload, 2080, 13, 8, PCMA_payload); + packet_seq(B, 8, PCMA_payload, 2080, 13, 8, PCMA_payload); + end(); + + // media silencing PCMU + start(); + sdp_pt(0, PCMU, 8000); + offer(); + expect(A, "0/PCMU/8000"); + expect(B, "0/PCMU/8000"); + sdp_pt(0, PCMU, 8000); + answer(); + expect(A, "0/PCMU/8000"); + expect(B, "0/PCMU/8000"); + packet_seq(A, 0, PCMU_payload, 0, 0, 0, PCMU_payload); + packet_seq(B, 0, PCMU_payload, 0, 0, 0, PCMU_payload); + packet_seq(A, 0, PCMU_payload, 160, 1, 0, PCMU_payload); + packet_seq(B, 0, PCMU_payload, 160, 1, 0, PCMU_payload); + call.silence_media = 1; + packet_seq(A, 0, PCMU_payload, 320, 2, 0, PCMU_silence); + packet_seq(B, 0, PCMU_payload, 320, 2, 0, PCMU_silence); + packet_seq(A, 0, PCMU_payload, 480, 3, 0, PCMU_silence); + packet_seq(B, 0, PCMU_payload, 480, 3, 0, PCMU_silence); + call.silence_media = 0; + packet_seq(A, 0, PCMU_payload, 640, 4, 0, PCMU_payload); + packet_seq(B, 0, PCMU_payload, 640, 4, 0, PCMU_payload); + packet_seq(A, 0, PCMU_payload, 800, 5, 0, PCMU_payload); + packet_seq(B, 0, PCMU_payload, 800, 5, 0, PCMU_payload); + ml_A.silence_media = 1; + packet_seq(A, 0, PCMU_payload, 960, 6, 0, PCMU_silence); + packet_seq(B, 0, PCMU_payload, 960, 6, 0, PCMU_payload); + packet_seq(A, 0, PCMU_payload, 1120, 7, 0, PCMU_silence); + packet_seq(B, 0, PCMU_payload, 1120, 7, 0, PCMU_payload); + ml_A.silence_media = 0; + packet_seq(A, 0, PCMU_payload, 1280, 8, 0, PCMU_payload); + packet_seq(B, 0, PCMU_payload, 1280, 8, 0, PCMU_payload); + packet_seq(A, 0, PCMU_payload, 1440, 9, 0, PCMU_payload); + packet_seq(B, 0, PCMU_payload, 1440, 9, 0, PCMU_payload); + ml_B.silence_media = 1; + packet_seq(A, 0, PCMU_payload, 1600, 10, 0, PCMU_payload); + packet_seq(B, 0, PCMU_payload, 1600, 10, 0, PCMU_silence); + packet_seq(A, 0, PCMU_payload, 1760, 11, 0, PCMU_payload); + packet_seq(B, 0, PCMU_payload, 1760, 11, 0, PCMU_silence); + ml_B.silence_media = 0; + packet_seq(A, 0, PCMU_payload, 1920, 12, 0, PCMU_payload); + packet_seq(B, 0, PCMU_payload, 1920, 12, 0, PCMU_payload); + packet_seq(A, 0, PCMU_payload, 2080, 13, 0, PCMU_payload); + packet_seq(B, 0, PCMU_payload, 2080, 13, 0, PCMU_payload); + end(); + return 0; }