From cf12ffc264d1b81333e3cdf88620c0909af3467d Mon Sep 17 00:00:00 2001 From: Richard Fuchs Date: Tue, 20 Dec 2022 13:54:27 -0500 Subject: [PATCH] MT#55283 support buffered media player Change-Id: I9e935b8561bb8710933fa11de383458896c0a5d9 --- daemon/codec.c | 7 + daemon/main.c | 1 + daemon/media_player.c | 445 +++++++++++++++++++- daemon/rtpengine.pod | 22 + include/codec.h | 1 + include/main.h | 1 + include/media_player.h | 13 + include/media_socket.h | 2 + t/Makefile | 12 +- t/auto-daemon-tests-player-cache.pl | 619 ++++++++++++++++++++++++++++ 10 files changed, 1109 insertions(+), 14 deletions(-) create mode 100755 t/auto-daemon-tests-player-cache.pl diff --git a/daemon/codec.c b/daemon/codec.c index d4090ffe4..c5d53df1a 100644 --- a/daemon/codec.c +++ b/daemon/codec.c @@ -520,6 +520,13 @@ struct codec_handler *codec_handler_make_playback(const struct rtp_payload_type return handler; } +struct codec_handler *codec_handler_make_dummy(const struct rtp_payload_type *dst_pt, struct call_media *media) +{ + struct codec_handler *handler = __handler_new(NULL, media, NULL); + rtp_payload_type_copy(&handler->dest_pt, dst_pt); + return handler; +} + // does not init/parse a=fmtp static void ensure_codec_def_type(struct rtp_payload_type *pt, enum media_type type) { diff --git a/daemon/main.c b/daemon/main.c index 58ae17ee1..5078fdd0c 100644 --- a/daemon/main.c +++ b/daemon/main.c @@ -555,6 +555,7 @@ static void options(int *argc, char ***argv) { { "amr-dtx", 0,0, G_OPTION_ARG_STRING, &amr_dtx, "DTX mechanism to use for AMR and AMR-WB","native|CN"}, { "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 ..."}, + { "player-cache",0,0, G_OPTION_ARG_NONE, &rtpe_config.player_cache,"Cache media files for playback in memory",NULL}, #endif #ifdef HAVE_MQTT { "mqtt-host",0,0, G_OPTION_ARG_STRING, &rtpe_config.mqtt_host, "Mosquitto broker host or address", "HOST|IP"}, diff --git a/daemon/media_player.c b/daemon/media_player.c index 17097ede1..6eb6ab1c3 100644 --- a/daemon/media_player.c +++ b/daemon/media_player.c @@ -30,6 +30,35 @@ static struct timerthread media_player_thread; static __thread MYSQL *mysql_conn; + +struct media_player_cache_index { + struct media_player_content_index index; + struct rtp_payload_type dst_pt; +}; +struct media_player_cache_entry { + bool finished; + // "unfinished" elements, only used while decoding is active: + mutex_t lock; + cond_t cond; // to wait for more data to be decoded + + GPtrArray *packets; // read-only except for decoder thread, which uses finished flags and locks + + struct codec_scheduler csch; + struct media_player_coder coder; // de/encoder data + + char *info_str; // for logging +}; +struct media_player_cache_packet { + char *buf; + str s; + long long pts; + long long duration; + long long duration_ts; +}; + +static mutex_t media_player_cache_lock; +static GHashTable *media_player_cache; // keys and values only ever freed at shutdown + static void media_player_read_packet(struct media_player *mp); #endif @@ -85,6 +114,13 @@ static void media_player_shutdown(struct media_player *mp) { mp->media = NULL; media_player_coder_shutdown(&mp->coder); + + mp->cache_index.type = MP_OTHER; + if (mp->cache_index.file.s) + g_free(mp->cache_index.file.s); + mp->cache_index.file = STR_NULL; + mp->cache_entry = NULL; + mp->cache_read_idx = 0; } #endif @@ -103,13 +139,13 @@ long long media_player_stop(struct media_player *mp) { static void __media_player_free(void *p) { struct media_player *mp = p; - //ilog(LOG_DEBUG, "freeing media_player"); - media_player_shutdown(mp); ssrc_ctx_put(&mp->ssrc_out); mutex_destroy(&mp->lock); obj_put(mp->call); av_packet_free(&mp->coder.pkt); + if (mp->cache_index.file.s) + g_free(mp->cache_index.file.s); } #endif @@ -134,6 +170,7 @@ struct media_player *media_player_new(struct call_monologue *ml) { mp->call = obj_get(ml->call); mp->ml = ml; mp->seq = ssl_random(); + mp->buffer_ts = ssl_random(); mp->ssrc_out = ssrc_ctx; mp->coder.pkt = av_packet_alloc(); @@ -149,9 +186,6 @@ struct media_player *media_player_new(struct call_monologue *ml) { static void __send_timer_free(void *p) { struct send_timer *st = p; - - //ilog(LOG_DEBUG, "freeing send_timer"); - obj_put(st->call); } @@ -165,8 +199,6 @@ static void __send_timer_send_later(struct timerthread_queue *ttq, void *p) { // call->master_lock held in W struct send_timer *send_timer_new(struct packet_stream *ps) { - //ilog(LOG_DEBUG, "creating send_timer"); - struct send_timer *st = timerthread_queue_new("send_timer", sizeof(*st), &send_timer_thread, __send_timer_send_now, @@ -321,6 +353,296 @@ static void media_player_coder_add_packet(struct media_player_coder *c, } +static void media_player_read_decoded_packet(struct media_player *mp) { + struct media_player_cache_entry *entry = mp->cache_entry; + + unsigned int read_idx = mp->cache_read_idx; + ilog(LOG_DEBUG, "Buffered media player reading packet #%u", read_idx); + +retry:; + bool finished = entry->finished; // hold lock or not + + if (!finished) { + // slow track with locking + mutex_lock(&entry->lock); + // confirm that we are indeed not finished + if (entry->finished) { + // preempted, good to go + mutex_unlock(&entry->lock); + finished = true; + } + } + + if (read_idx >= entry->packets->len) { + if (!finished) { + // wait for more + cond_wait(&entry->cond, &entry->lock); + mutex_unlock(&entry->lock); + goto retry; + } + + // EOF + + if (mp->repeat <= 1) { + ilog(LOG_DEBUG, "EOF reading from media buffer (%s), stopping playback", + entry->info_str); + return; + } + + ilog(LOG_DEBUG, "EOF reading from media buffer (%s) but will repeat %li time", + entry->info_str, mp->repeat); + mp->repeat--; + read_idx = mp->cache_read_idx = 0; + goto retry; + } + + // got a packet + struct media_player_cache_packet *pkt = entry->packets->pdata[read_idx]; + long long us_dur = pkt->duration; + + mp->cache_read_idx++; + + if (!finished) + mutex_unlock(&entry->lock); + + // make a copy to send out + size_t len = pkt->s.len + sizeof(struct rtp_header) + RTP_BUFFER_TAIL_ROOM; + char *buf = g_malloc(len); + memcpy(buf, pkt->buf, len); + + struct media_packet packet = { + .tv = rtpe_now, + .call = mp->call, + .media = mp->media, + .media_out = mp->media, + .rtp = (void *) buf, + .ssrc_out = mp->ssrc_out, + }; + + mp->last_frame_ts = pkt->pts; + + codec_output_rtp(&packet, &entry->csch, mp->coder.handler, buf, pkt->s.len, mp->buffer_ts, + read_idx == 0, mp->seq++, 0, -1, 0); + + mp->buffer_ts += pkt->duration_ts; + mp->sync_ts_tv = rtpe_now; + + media_packet_encrypt(mp->crypt_handler->out->rtp_crypt, mp->sink, &packet); + + mutex_lock(&mp->sink->out_lock); + if (media_socket_dequeue(&packet, mp->sink)) + ilog(LOG_ERR, "Error sending playback media to RTP sink"); + mutex_unlock(&mp->sink->out_lock); + + // schedule our next run + timeval_add_usec(&mp->next_run, us_dur); + timerthread_obj_schedule_abs(&mp->tt_obj, &mp->next_run); +} + +static void media_player_cached_reader_start(struct media_player *mp, const struct rtp_payload_type *dst_pt, + long long repeat) +{ + struct media_player_cache_entry *entry = mp->cache_entry; + + // create dummy codec handler and start timer + + mp->coder.handler = codec_handler_make_dummy(&entry->coder.handler->dest_pt, mp->media); + + mp->run_func = media_player_read_decoded_packet; + mp->next_run = rtpe_now; + mp->coder.duration = entry->coder.duration; + + // if we played anything before, scale our sync TS according to the time + // that has passed + if (mp->sync_ts_tv.tv_sec) { + long long ts_diff_us = timeval_diff(&rtpe_now, &mp->sync_ts_tv); + mp->buffer_ts += fraction_divl(ts_diff_us * dst_pt->clock_rate / 1000000, &dst_pt->codec_def->default_clockrate_fact); + } + + mp->sync_ts_tv = rtpe_now; + mp->repeat = repeat; + + media_player_read_decoded_packet(mp); +} + + +static void cache_packet_free(void *ptr) { + struct media_player_cache_packet *p = ptr; + g_free(p->buf); + g_slice_free1(sizeof(*p), p); +} + + +// returns: true = entry exists, decoding handled separately, use entry for playback +// false = no entry exists, OR entry is a new one, proceed to open decoder, then call _play_start +static bool media_player_cache_get_entry(struct media_player *mp, + const struct rtp_payload_type *dst_pt, long long repeat) +{ + if (!rtpe_config.player_cache) + return false; + if (mp->cache_index.type <= 0) + return false; + + struct media_player_cache_index lookup; + lookup.index = mp->cache_index; + lookup.dst_pt = *dst_pt; + + mutex_lock(&media_player_cache_lock); + struct media_player_cache_entry *entry = mp->cache_entry + = g_hash_table_lookup(media_player_cache, &lookup); + + bool ret = true; // entry exists, use cached data + if (entry) { + media_player_cached_reader_start(mp, dst_pt, repeat); + goto out; + } + + ret = false; // new entry, open decoder, then call media_player_play_start + + // initialise object + + struct media_player_cache_index *ins_key = g_slice_alloc(sizeof(*ins_key)); + *ins_key = lookup; + str_init_dup_str(&ins_key->index.file, &lookup.index.file); + codec_init_payload_type(&ins_key->dst_pt, MT_UNKNOWN); // duplicate contents + entry = mp->cache_entry = g_slice_alloc0(sizeof(*entry)); + mutex_init(&entry->lock); + cond_init(&entry->cond); + entry->packets = g_ptr_array_new_full(64, cache_packet_free); + + switch (lookup.index.type) { + case MP_DB: + entry->info_str = g_strdup_printf("DB media file #%llu", lookup.index.db_id); + break; + case MP_FILE: + entry->info_str = g_strdup_printf("media file '" STR_FORMAT "'", + STR_FMT(&lookup.index.file)); + break; + case MP_BLOB: + entry->info_str = g_strdup_printf("binary media blob"); + break; + default:; + } + + g_hash_table_insert(media_player_cache, ins_key, entry); + +out: + mutex_unlock(&media_player_cache_lock); + + return ret; +} + +static void media_player_cache_packet(struct media_player_cache_entry *entry, char *buf, size_t len, + long long us_dur, unsigned long long pts) +{ + // synthesise fake RTP header and media_packet context + + struct rtp_header rtp = { + .timestamp = pts, // taken verbatim by handler_func_playback w/o byte swap + }; + struct media_packet packet = { + .rtp = &rtp, + .cache_entry = entry, + }; + str_init_len(&packet.raw, buf, len); + packet.payload = packet.raw; + + entry->coder.handler->handler_func(entry->coder.handler, &packet); +} + +static void media_player_cache_entry_decoder_thread(void *p) { + struct media_player_cache_entry *entry = p; + + ilog(LOG_DEBUG, "Launching media decoder thread for %s", entry->info_str); + + while (true) { + // let us be cancelled + thread_cancel_enable(); + pthread_testcancel(); + thread_cancel_disable(); + + int ret = av_read_frame(entry->coder.fmtctx, entry->coder.pkt); + if (ret < 0) { + if (ret != AVERROR_EOF) + ilog(LOG_ERR, "Error while reading from media stream"); + break; + } + + media_player_coder_add_packet(&entry->coder, (void *) media_player_cache_packet, entry); + + av_packet_unref(entry->coder.pkt); + } + + mutex_lock(&entry->lock); + entry->finished = true; + cond_broadcast(&entry->cond); + mutex_unlock(&entry->lock); + + ilog(LOG_DEBUG, "Decoder thread for %s finished", entry->info_str); +} + +static void packet_encoded_cache(encoder_t *enc, struct codec_ssrc_handler *ch, struct media_packet *mp, + str *s, char *buf, unsigned int pkt_len) +{ + struct media_player_cache_entry *entry = mp->cache_entry; + + struct media_player_cache_packet *ep = g_slice_alloc0(sizeof(*ep)); + + *ep = (__typeof__(*ep)) { + .buf = buf, + .s = *s, + .pts = enc->avpkt->pts, + .duration_ts = enc->avpkt->duration, + .duration = (long long) enc->avpkt->duration * 1000000LL + / entry->coder.handler->dest_pt.clock_rate, + }; + + mutex_lock(&entry->lock); + g_ptr_array_add(entry->packets, ep); + + cond_broadcast(&entry->cond); + mutex_unlock(&entry->lock); +} + +static int media_player_packet_cache(encoder_t *enc, void *u1, void *u2) { + struct codec_ssrc_handler *ch = u1; + struct media_packet *mp = u2; + + packet_encoded_packetize(enc, ch, mp, packet_encoded_cache); + + return 0; +} + + +// called from media_player_play_start, which is called after media_player_cache_get_entry returned true. +// this can mean that either we don't have a cache entry and should continue normally, or if we +// do have a cache entry, initialise it, set up the thread, take over decoding, and then proceed as a +// media player consuming the data from the decoder thread. +// returns: false = continue normally decode in-thread, true = take data from other thread +static bool media_player_cache_entry_init(struct media_player *mp, const struct rtp_payload_type *dst_pt, + long long repeat) +{ + struct media_player_cache_entry *entry = mp->cache_entry; + if (!entry) + return false; + + // steal coder data + entry->coder = mp->coder; + ZERO(mp->coder); + mp->coder.duration = entry->coder.duration; // retain this for reporting + + entry->coder.handler->packet_encoded = media_player_packet_cache; + + // use low priority (10 nice) + thread_create_detach_prio(media_player_cache_entry_decoder_thread, entry, NULL, 10, "mp decoder"); + + media_player_cached_reader_start(mp, dst_pt, repeat); + + return true; +} + + + // find suitable output payload type static struct rtp_payload_type *media_player_get_dst_pt(struct media_player *mp) { struct rtp_payload_type *dst_pt = NULL; @@ -541,6 +863,9 @@ static void media_player_play_start(struct media_player *mp, const struct rtp_pa if (__ensure_codec_handler(mp, dst_pt)) return; + if (media_player_cache_entry_init(mp, dst_pt, repeat)) + return; + mp->next_run = rtpe_now; // give ourselves a bit of a head start with decoding timeval_add_usec(&mp->next_run, -50000); @@ -563,6 +888,12 @@ int media_player_play_file(struct media_player *mp, const str *file, long long r if (!dst_pt) return -1; + mp->cache_index.type = MP_FILE; + str_init_dup_str(&mp->cache_index.file, file); + + if (media_player_cache_get_entry(mp, dst_pt, repeat)) + return 0; + char file_s[PATH_MAX]; snprintf(file_s, sizeof(file_s), STR_FORMAT, STR_FMT(file)); @@ -574,7 +905,6 @@ int media_player_play_file(struct media_player *mp, const str *file, long long r media_player_play_start(mp, dst_pt, repeat, start_pos); - return 0; #else return -1; @@ -625,13 +955,14 @@ static int64_t __mp_avio_seek(void *opaque, int64_t offset, int whence) { return __mp_avio_seek_set(c, ((int64_t) c->blob->len) + offset); return AVERROR(EINVAL); } -#endif + // call->master_lock held in W -int media_player_play_blob(struct media_player *mp, const str *blob, long long repeat, long long start_pos) { -#ifdef WITH_TRANSCODING +static int media_player_play_blob_id(struct media_player *mp, const str *blob, long long repeat, + long long start_pos, long long db_id) +{ const char *err; int av_ret = 0; @@ -639,6 +970,21 @@ int media_player_play_blob(struct media_player *mp, const str *blob, long long r if (!dst_pt) return -1; + if (db_id >= 0) { + mp->cache_index.type = MP_DB; + mp->cache_index.db_id = db_id; + + if (media_player_cache_get_entry(mp, dst_pt, repeat)) + return 0; + } + else { + mp->cache_index.type = MP_BLOB; + str_init_dup_str(&mp->cache_index.file, blob); + + if (media_player_cache_get_entry(mp, dst_pt, repeat)) + return 0; + } + mp->coder.blob = str_dup(blob); err = "out of memory"; if (!mp->coder.blob) @@ -677,8 +1023,18 @@ err: ilog(LOG_ERR, "Failed to start media playback from memory: %s", err); if (av_ret) ilog(LOG_ERR, "Error returned from libav: %s", av_error(av_ret)); + return -1; +} #endif + + +// call->master_lock held in W +int media_player_play_blob(struct media_player *mp, const str *blob, long long repeat, long long start_pos) { +#ifdef WITH_TRANSCODING + return media_player_play_blob_id(mp, blob, repeat, start_pos, -1); +#else return -1; +#endif } @@ -753,7 +1109,7 @@ success:; str blob; str_init_len(&blob, row[0], lengths[0]); - int ret = media_player_play_blob(mp, &blob, repeat, start_pos); + int ret = media_player_play_blob_id(mp, &blob, repeat, start_pos, id); mysql_free_result(res); @@ -786,11 +1142,71 @@ static void media_player_run(void *ptr) { log_info_pop(); } + +static unsigned int media_player_cache_entry_hash(const void *p) { + const struct media_player_cache_index *i = p; + unsigned int ret; + switch (i->index.type) { + case MP_DB: + ret = i->index.db_id; + break; + case MP_FILE: + case MP_BLOB: + ret = str_hash(&i->index.file); + break; + default: + abort(); + } + ret ^= str_hash(&i->dst_pt.encoding_with_full_params); + ret ^= i->index.type; + return ret; +} +static gboolean media_player_cache_entry_eq(const void *A, const void *B) { + const struct media_player_cache_index *a = A, *b = B; + if (a->index.type != b->index.type) + return FALSE; + switch (a->index.type) { + case MP_DB: + if (a->index.db_id != b->index.db_id) + return FALSE; + break; + case MP_FILE: + case MP_BLOB: + if (!str_equal(&a->index.file, &b->index.file)) + return FALSE; + break; + default: + abort(); + } + return str_equal(&a->dst_pt.encoding_with_full_params, &b->dst_pt.encoding_with_full_params); +} +static void media_player_cache_index_free(void *p) { + struct media_player_cache_index *i = p; + g_free(i->index.file.s); + payload_type_clear(&i->dst_pt); + g_slice_free1(sizeof(*i), i); +} +static void media_player_cache_entry_free(void *p) { + struct media_player_cache_entry *e = p; + g_ptr_array_free(e->packets, TRUE); + mutex_destroy(&e->lock); + g_free(e->info_str); + media_player_coder_shutdown(&e->coder); + av_packet_free(&e->coder.pkt); + g_slice_free1(sizeof(*e), e); +} #endif void media_player_init(void) { #ifdef WITH_TRANSCODING + if (rtpe_config.player_cache) { + media_player_cache = g_hash_table_new_full(media_player_cache_entry_hash, + media_player_cache_entry_eq, media_player_cache_index_free, + media_player_cache_entry_free); + mutex_init(&media_player_cache_lock); + } + timerthread_init(&media_player_thread, media_player_run); #endif timerthread_init(&send_timer_thread, timerthread_queue_run); @@ -799,6 +1215,11 @@ void media_player_init(void) { void media_player_free(void) { #ifdef WITH_TRANSCODING timerthread_free(&media_player_thread); + + if (media_player_cache) { + mutex_destroy(&media_player_cache_lock); + g_hash_table_destroy(media_player_cache); + } #endif timerthread_free(&send_timer_thread); } diff --git a/daemon/rtpengine.pod b/daemon/rtpengine.pod index 0b7772d08..e95e0904a 100644 --- a/daemon/rtpengine.pod +++ b/daemon/rtpengine.pod @@ -930,6 +930,28 @@ directly as payload of B packets sent by B. The default values are 32 (-32 dBov) for the noise level and no spectral information. +=item B<--player-cache> + +Enable caching of encoded media packets for media player. This is applicable +for media playback initiated through the I command. When enabled +B will not simply decode given media files and then encode the media +to RTP on demand and on the fly, but will rather decode and encode each media +file in full the first time playback is requested, and then cache the resulting +RTP packets in memory. This is done once for each media file and for each +output RTP codec requested. + +Caching is done based on unique file name (with no consideration given to +different file names that may point to the same file), or integer index for +media files played from database. No verification of changing content of files +or database entries is done. Media files provided as binary I are also +cached, although in this case a hash over the entire media file must be +performed, therefore this usage is not recommended. + +It's not possible to choose a different I for playback with this +option enabled. + +RTP data is cached and retained in memory for the lifetime of the process. + =item B<--poller-per-thread> Enable 'poller per thread' functionality: for every worker thread (see the diff --git a/include/codec.h b/include/codec.h index 0d41e3845..415f4d5ed 100644 --- a/include/codec.h +++ b/include/codec.h @@ -99,6 +99,7 @@ struct codec_handler *codec_handler_get(struct call_media *, int payload_type, s void codec_handlers_free(struct call_media *); struct codec_handler *codec_handler_make_playback(const struct rtp_payload_type *src_pt, const struct rtp_payload_type *dst_pt, unsigned long ts, struct call_media *); +struct codec_handler *codec_handler_make_dummy(const struct rtp_payload_type *dst_pt, struct call_media *media); void codec_calc_jitter(struct ssrc_ctx *, unsigned long ts, unsigned int clockrate, const struct timeval *); void codec_update_all_handlers(struct call_monologue *ml); diff --git a/include/main.h b/include/main.h index f73e71b33..4af3710ee 100644 --- a/include/main.h +++ b/include/main.h @@ -131,6 +131,7 @@ struct rtpengine_config { double silence_detect_double; uint32_t silence_detect_int; str cn_payload; + int player_cache; char *software_id; int poller_per_thread; char *mqtt_host; diff --git a/include/media_player.h b/include/media_player.h index 8415db167..94bc444fe 100644 --- a/include/media_player.h +++ b/include/media_player.h @@ -25,6 +25,15 @@ struct rtp_payload_type; #include +struct media_player_cache_entry; + +struct media_player_content_index { + enum { MP_OTHER = 0, MP_FILE = 1, MP_DB, MP_BLOB } type; + long long db_id; + str file; // file name or binary blob +}; + + typedef void (*media_player_run_func)(struct media_player *); @@ -53,9 +62,13 @@ struct media_player { unsigned long repeat; struct media_player_coder coder; + struct media_player_content_index cache_index; + struct media_player_cache_entry *cache_entry; + unsigned int cache_read_idx; struct ssrc_ctx *ssrc_out; unsigned long seq; + unsigned long buffer_ts; unsigned long sync_ts; struct timeval sync_ts_tv; long long last_frame_ts; diff --git a/include/media_socket.h b/include/media_socket.h index 360b75c40..71ac9effb 100644 --- a/include/media_socket.h +++ b/include/media_socket.h @@ -23,6 +23,7 @@ struct rtpengine_srtp; struct jb_packet; struct stream_fd; struct poller; +struct media_player_cache_entry; typedef int rtcp_filter_func(struct media_packet *, GQueue *); typedef int (*rewrite_func)(str *, struct packet_stream *, struct stream_fd *, const endpoint_t *, @@ -188,6 +189,7 @@ struct media_packet { struct call_media *media; // stream->media struct call_media *media_out; // output media struct sink_handler sink; + struct media_player_cache_entry *cache_entry; struct rtp_header *rtp; struct rtcp_packet *rtcp; diff --git a/t/Makefile b/t/Makefile index 2bcd97794..eca0cc961 100644 --- a/t/Makefile +++ b/t/Makefile @@ -91,7 +91,7 @@ include ../lib/common.Makefile .PHONY: all-tests unit-tests daemon-tests daemon-tests \ daemon-tests-main daemon-tests-jb daemon-tests-dtx daemon-tests-dtx-cn daemon-tests-pubsub \ daemon-tests-intfs daemon-tests-stats daemon-tests-delay-buffer daemon-tests-delay-timing \ - daemon-tests-evs + daemon-tests-evs daemon-tests-player-cache TESTS= test-bitstr aes-crypt aead-aes-crypt test-const_str_hash.strhash ifeq ($(with_transcoding),yes) @@ -127,7 +127,7 @@ unit-tests: $(TESTS) daemon-tests: daemon-tests-main daemon-tests-jb daemon-tests-pubsub daemon-tests-websocket \ daemon-tests-evs \ - daemon-tests-intfs daemon-tests-stats # daemon-tests-delay-buffer daemon-tests-delay-timing + daemon-tests-intfs daemon-tests-stats daemon-tests-player-cache daemon-test-deps: tests-preload.so $(MAKE) -C ../daemon @@ -220,6 +220,14 @@ daemon-tests-evs: daemon-test-deps test "$$(ls fake-$@-sockets)" = "" rmdir fake-$@-sockets +daemon-tests-player-cache: daemon-test-deps + rm -rf fake-$@-sockets + mkdir fake-$@-sockets + LD_PRELOAD=../t/tests-preload.so RTPE_BIN=../daemon/rtpengine TEST_SOCKET_PATH=./fake-$@-sockets \ + perl -I../perl auto-daemon-tests-player-cache.pl + test "$$(ls fake-$@-sockets)" = "" + rmdir fake-$@-sockets + test-bitstr: test-bitstr.o spandsp_send_fax_pcm: spandsp_send_fax_pcm.o diff --git a/t/auto-daemon-tests-player-cache.pl b/t/auto-daemon-tests-player-cache.pl new file mode 100755 index 000000000..dd52d8b06 --- /dev/null +++ b/t/auto-daemon-tests-player-cache.pl @@ -0,0 +1,619 @@ +#!/usr/bin/perl + +use strict; +use warnings; +use NGCP::Rtpengine::Test; +use NGCP::Rtpclient::SRTP; +use NGCP::Rtpengine::AutoTest; +use Test::More; +use NGCP::Rtpclient::ICE; +use POSIX; + + +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 --player-cache)) + or die; + + + +# 100 ms sine wave + +my $wav_file = "\x52\x49\x46\x46\x64\x06\x00\x00\x57\x41\x56\x45\x66\x6d\x74\x20\x10\x00\x00\x00\x01\x00\x01\x00\x40\x1f\x00\x00\x80\x3e\x00\x00\x02\x00\x10\x00\x64\x61\x74\x61\x40\x06\x00\x00\x00\x00\xb0\x22\x45\x41\x25\x58\x95\x64\x24\x65\xbd\x59\xb6\x43\xb4\x25\x35\x03\x5e\xe0\x3b\xc1\x8c\xa9\x0f\x9c\x6a\x9a\xc2\xa4\xe7\xb9\x55\xd7\x92\xf9\x92\x1c\x30\x3c\xb2\x54\x2e\x63\xf3\x65\xa7\x5c\x68\x48\x9b\x2b\xa1\x09\x8a\xe6\x71\xc6\x28\xad\xab\x9d\xcc\x99\x06\xa2\x5c\xb5\x81\xd1\x2d\xf3\x53\x16\xe1\x36\xe8\x50\x64\x61\x59\x66\x36\x5f\xcf\x4c\x56\x31\x04\x10\xd0\xec\xe0\xcb\x19\xb1\xa9\x9f\x98\x99\xa8\x9f\x1a\xb1\xdf\xcb\xd1\xec\x04\x10\x54\x31\xd2\x4c\x33\x5f\x5c\x66\x61\x61\xeb\x50\xde\x36\x56\x16\x2b\xf3\x83\xd1\x59\xb5\x08\xa2\xcb\x99\xac\x9d\x28\xad\x70\xc6\x8a\xe6\xa3\x09\x98\x2b\x6a\x48\xa6\x5c\xf4\x65\x2d\x63\xb3\x54\x2e\x3c\x93\x1c\x93\xf9\x53\xd7\xe9\xb9\xc1\xa4\x69\x9a\x11\x9c\x8b\xa9\x3b\xc1\x5e\xe0\x36\x03\xb2\x25\xba\x43\xb7\x59\x2a\x65\x90\x64\x29\x58\x42\x41\xb2\x22\xff\xff\x50\xdd\xbb\xbe\xdb\xa7\x6b\x9b\xdd\x9a\x42\xa6\x4b\xbc\x4b\xda\xca\xfc\xa5\x1f\xc2\x3e\x77\x56\xed\x63\x9a\x65\x3b\x5b\x1b\x46\xa9\x28\x70\x06\x6c\xe3\xd2\xc3\x4d\xab\xd1\x9c\x10\x9a\x56\xa3\x99\xb7\x67\xd4\x5b\xf6\x79\x19\x8e\x39\xd7\x52\x58\x62\x30\x66\xfd\x5d\xa2\x4a\x81\x2e\xd1\x0c\xae\xe9\x1f\xc9\x17\xaf\x9e\x9e\xa4\x99\xce\xa0\x2c\xb3\xaf\xce\xf8\xef\x33\x13\x1e\x34\xe8\x4e\x57\x60\x68\x66\x57\x60\xe9\x4e\x1c\x34\x35\x13\xf6\xef\xb0\xce\x2d\xb3\xcc\xa0\xa6\x99\x9c\x9e\x17\xaf\x22\xc9\xa9\xe9\xd6\x0c\x7c\x2e\xa7\x4a\xf8\x5d\x36\x66\x52\x62\xdb\x52\x8c\x39\x79\x19\x5c\xf6\x67\xd4\x97\xb7\x59\xa3\x0e\x9a\xd1\x9c\x4e\xab\xd0\xc3\x6e\xe3\x6e\x06\xac\x28\x18\x46\x3d\x5b\x98\x65\xef\x63\x76\x56\xc3\x3e\xa4\x1f\xc9\xfc\x4e\xda\x49\xbc\x43\xa6\xdd\x9a\x69\x9b\xdd\xa7\xbb\xbe\x4f\xdd\x01\x00\xaf\x22\x47\x41\x23\x58\x96\x64\x24\x65\xbb\x59\xba\x43\xb0\x25\x39\x03\x59\xe0\x40\xc1\x87\xa9\x15\x9c\x65\x9a\xc4\xa4\xe7\xb9\x56\xd7\x90\xf9\x94\x1c\x2e\x3c\xb3\x54\x2f\x63\xf1\x65\xa8\x5c\x68\x48\x9a\x2b\xa2\x09\x8a\xe6\x71\xc6\x27\xad\xac\x9d\xcb\x99\x08\xa2\x59\xb5\x84\xd1\x2a\xf3\x56\x16\xe0\x36\xe7\x50\x65\x61\x59\x66\x35\x5f\xd1\x4c\x54\x31\x04\x10\xd2\xec\xdd\xcb\x1c\xb1\xa5\x9f\x9b\x99\xa8\x9f\x18\xb1\xe2\xcb\xcd\xec\x07\x10\x54\x31\xd1\x4c\x33\x5f\x5d\x66\x60\x61\xec\x50\xdd\x36\x57\x16\x29\xf3\x86\xd1\x57\xb5\x09\xa2\xcb\x99\xab\x9d\x29\xad\x70\xc6\x8a\xe6\xa2\x09\x9a\x2b\x69\x48\xa7\x5c\xf2\x65\x2e\x63\xb2\x54\x31\x3c\x91\x1c\x93\xf9\x53\xd7\xe9\xb9\xc1\xa4\x6a\x9a\x10\x9c\x8a\xa9\x3f\xc1\x59\xe0\x3a\x03\xb0\x25\xb8\x43\xbd\x59\x24\x65\x95\x64\x24\x58\x46\x41\xaf\x22\x02\x00\x4e\xdd\xbb\xbe\xdd\xa7\x68\x9b\xdf\x9a\x42\xa6\x48\xbc\x50\xda\xc6\xfc\xa7\x1f\xc2\x3e\x75\x56\xef\x63\x99\x65\x3c\x5b\x1a\x46\xaa\x28\x6e\x06\x6e\xe3\xd1\xc3\x4e\xab\xd1\x9c\x0e\x9a\x57\xa3\x9a\xb7\x64\xd4\x60\xf6\x75\x19\x90\x39\xd7\x52\x55\x62\x34\x66\xf9\x5d\xa8\x4a\x7a\x2e\xd8\x0c\xa7\xe9\x23\xc9\x16\xaf\x9d\x9e\xa6\x99\xcb\xa0\x2f\xb3\xad\xce\xfa\xef\x30\x13\x21\x34\xe6\x4e\x59\x60\x66\x66\x5a\x60\xe4\x4e\x23\x34\x2e\x13\xfc\xef\xab\xce\x30\xb3\xcb\xa0\xa5\x99\x9f\x9e\x14\xaf\x24\xc9\xa7\xe9\xd8\x0c\x7b\x2e\xa8\x4a\xf7\x5d\x36\x66\x53\x62\xda\x52\x8d\x39\x78\x19\x5d\xf6\x67\xd4\x97\xb7\x59\xa3\x0d\x9a\xd2\x9c\x4e\xab\xd1\xc3\x6d\xe3\x6f\x06\xaa\x28\x19\x46\x3f\x5b\x95\x65\xf2\x63\x74\x56\xc2\x3e\xa8\x1f\xc4\xfc\x52\xda\x45\xbc\x46\xa6\xdc\x9a\x6a\x9b\xdc\xa7\xba\xbe\x51\xdd\xff\xff\xb1\x22\x45\x41\x24\x58\x97\x64\x22\x65\xbd\x59\xb7\x43\xb3\x25\x37\x03\x5b\xe0\x3e\xc1\x89\xa9\x11\x9c\x6a\x9a\xc0\xa4\xeb\xb9\x51\xd7\x94\xf9\x91\x1c\x31\x3c\xb1\x54\x2f\x63\xf3\x65\xa5\x5c\x6c\x48\x95\x2b\xa7\x09\x86\xe6\x73\xc6\x28\xad\xa9\x9d\xcf\x99\x04\xa2\x5b\xb5\x84\xd1\x29\xf3\x57\x16\xde\x36\xe9\x50\x65\x61\x57\x66\x38\x5f\xcd\x4c\x57\x31\x04\x10\xd0\xec\xe1\xcb\x17\xb1\xaa\x9f\x97\x99\xaa\x9f\x18\xb1\xe1\xcb\xce\xec\x07\x10\x53\x31\xd0\x4c\x38\x5f\x55\x66\x68\x61\xe6\x50\xe0\x36\x56\x16\x2b\xf3\x81\xd1\x5d\xb5\x04\xa2\xce\x99\xaa\x9d\x29\xad\x70\xc6\x8a\xe6\xa2\x09\x9b\x2b\x67\x48\xa9\x5c\xf1\x65\x2e\x63\xb4\x54\x2e\x3c\x93\x1c\x92\xf9\x54\xd7\xe8\xb9\xc2\xa4\x69\x9a\x10\x9c\x8c\xa9\x3c\xc1\x5c\xe0\x37\x03\xb2\x25\xb8\x43\xbc\x59\x24\x65\x95\x64\x26\x58\x43\x41\xb2\x22\xff\xff\x50\xdd\xba\xbe\xde\xa7\x68\x9b\xdd\x9a\x45\xa6\x45\xbc\x52\xda\xc5\xfc\xa8\x1f\xbf\x3e\x79\x56\xec\x63\x9b\x65\x3b\x5b\x1a\x46\xaa\x28\x6f\x06\x6e\xe3\xd0\xc3\x4f\xab\xd0\x9c\x0f\x9a\x58\xa3\x97\xb7\x68\xd4\x5c\xf6\x78\x19\x8f\x39\xd6\x52\x57\x62\x32\x66\xfb\x5d\xa6\x4a\x7b\x2e\xd8\x0c\xa6\xe9\x25\xc9\x15\xaf\x9c\x9e\xa9\x99\xc7\xa0\x33\xb3\xa9\xce\xfd\xef\x2f\x13\x21\x34\xe6\x4e\x58\x60\x67\x66\x59\x60\xe5\x4e\x23\x34\x2c\x13\x00\xf0\xa6\xce\x35\xb3\xc7\xa0\xa8\x99\x9d\x9e\x15\xaf\x24\xc9\xa8\xe9\xd5\x0c\x7e\x2e\xa5\x4a\xfa\x5d\x35\x66\x52\x62\xdb\x52\x8d\x39\x77\x19\x5e\xf6\x66\xd4\x98\xb7\x59\xa3\x0c\x9a\xd3\x9c\x4d\xab\xd1\xc3\x6e\xe3\x6e\x06\xaa\x28\x1b\x46\x3b\x5b\x9a\x65\xed\x63\x76\x56\xc4\x3e\xa3\x1f\xcb\xfc\x4b\xda\x4a\xbc\x43\xa6\xdd\x9a\x6a\x9b\xdc\xa7\xba\xbe\x51\xdd\xff\xff\xb1\x22\x44\x41\x25\x58\x96\x64\x23\x65\xbd\x59\xb6\x43\xb4\x25\x36\x03\x5c\xe0\x3d\xc1\x8a\xa9\x12\x9c\x67\x9a\xc4\xa4\xe6\xb9\x55\xd7\x93\xf9\x91\x1c\x31\x3c\xb0\x54\x31\x63\xef\x65\xab\x5c\x66\x48\x9a\x2b\xa4\x09\x87\xe6\x73\xc6\x26\xad\xad\x9d\xcb\x99\x07\xa2\x5b\xb5\x81\xd1\x2c\xf3\x56\x16\xde\x36\xeb\x50\x62\x61\x59\x66\x38\x5f\xcc\x4c\x59\x31\x01\x10\xd3\xec\xdd\xcb\x1b\xb1\xa8\x9f\x98\x99\xa9\x9f\x18\xb1\xe0\xcb\xd1\xec\x03\x10\x57\x31\xce\x4c\x37\x5f\x58\x66\x63\x61\xec\x50\xdb\x36\x5a\x16\x27\xf3\x85\xd1\x5a\xb5\x05\xa2\xce\x99\xaa\x9d\x29\xad\x70\xc6\x8a\xe6\xa2\x09\x9a\x2b\x69\x48\xa6\x5c\xf4\x65\x2e\x63\xb1\x54\x32\x3c\x8e\x1c\x96\xf9\x52\xd7\xea\xb9\xc1\xa4\x67\x9a\x13\x9c\x8a\xa9\x3c\xc1\x5e\xe0\x33\x03\xb7\x25\xb4\x43\xbf\x59\x21\x65\x99\x64\x21\x58\x48\x41\xad\x22\x03\x00\x4f\xdd\xbb\xbe\xdb\xa7\x6a\x9b\xdd\x9a\x43\xa6\x4b\xbc\x4a\xda\xcb\xfc\xa4\x1f\xc3\x3e\x76\x56\xef\x63\x96\x65\x40\x5b\x17\x46\xac\x28\x6e\x06\x6d\xe3\xd2\xc3\x4d\xab\xd2\x9c\x0d\x9a\x59\xa3\x97\xb7\x68\xd4\x5c\xf6\x77\x19\x8f\x39\xd8\x52\x55\x62\x33\x66\xfb\x5d\xa4\x4a\x7f\x2e\xd4\x0c\xab\xe9\x20\xc9\x17\xaf\x9d\x9e\xa7\x99\xc9\xa0\x32\xb3\xa9\xce\xfd\xef\x2f\x13\x20\x34\xe8\x4e\x56\x60\x6a\x66\x55\x60\xe9\x4e\x1f\x34\x31\x13\xfa\xef\xad\xce\x2e\xb3\xcc\xa0\xa7\x99\x9b\x9e\x18\xaf\x20\xc9\xac\xe9\xd2\x0c\x81\x2e\xa1\x4a\xff\x5d\x30\x66\x56\x62\xd7\x52\x90\x39\x77\x19\x5d\xf6\x67\xd4\x96\xb7\x5a\xa3\x0e\x9a\xd0\x9c\x50\xab\xcf\xc3\x6e\xe3\x6f\x06\xaa\x28\x1a\x46\x3d\x5b\x98\x65\xee\x63\x77\x56\xc1\x3e\xa7\x1f\xc8\xfc\x4c\xda\x4b\xbc\x41\xa6\xdf\x9a\x68\x9b\xdd\xa7\xba\xbe\x51\xdd"; +is length($wav_file), 1644, 'embedded binary wav file'; + +my $pcma_1 = "\xd5\xb4\xa5\xa3\xac\xac\xa3\xa5\xb7\xfc\x0a\x3a\x20\x2d\x2c\x23\x24\x31\x6c\x89\xbb\xa0\xad\xac\xa2\xa7\xb0\x96\x0c\x39\x21\x2d\x2c\x22\x27\x32\x1c\x83\xbe\xa1\xad\xac\xa2\xa6\xbd\x9a\x06\x3f\x26\x2d\x2c\x2d\x26\x3f\x06\x9a\xbd\xa6\xa2\xac\xad\xa1\xbe\x83\x1c\x32\x27\x22\x2c\x2d\x21\x39\x0c\x96\xb0\xa7\xa2\xac\xad\xa0\xbb\x89\x6c\x31\x24\x23\x2c\x2d\x20\x3a\x0a\xfc\xb7\xa5\xa3\xac\xac\xa3\xa5\xb4\x55\x34\x25\x23\x2c\x2c\x23\x25\x37\x7c\x8a\xba\xa0\xad\xac\xa3\xa4\xb1\xec\x09\x3b\x20\x2d\x2c\x22\x27\x30\x16\x8c\xb9\xa1\xad\xac\xa2\xa7\xb2\x9c\x03\x3e\x21\x2d\x2c\x22\x26\x3d\x1a\x86\xbf\xa6\xad\xac\xad\xa6\xbf\x86\x1a\x3d\x26\x22\x2c"; +my $pcma_2 = "\x2d\x21\x3e\x03\x9c\xb2\xa7\xa2\xac\xad\xa1\xb9\x8c\x16\x30\x27\x22\x2c\x2d\x20\x3b\x09\xec\xb1\xa4\xa3\xac\xad\xa0\xba\x8a\x7c\x37\x25\x23\x2c\x2c\x23\x25\x34\xd5\xb4\xa5\xa3\xac\xac\xa3\xa5\xb7\xfc\x0a\x3a\x20\x2d\x2c\x23\x24\x31\x6c\x89\xbb\xa0\xad\xac\xa2\xa7\xb0\x96\x0c\x39\x21\x2d\x2c\x22\x27\x32\x1c\x83\xbe\xa1\xad\xac\xa2\xa6\xbd\x9a\x06\x3f\x26\x2d\x2c\x2d\x26\x3f\x06\x9a\xbd\xa6\xa2\xac\xad\xa1\xbe\x83\x1c\x32\x27\x22\x2c\x2d\x21\x39\x0c\x96\xb0\xa7\xa2\xac\xad\xa0\xbb\x89\x6c\x31\x24\x23\x2c\x2d\x20\x3a\x0a\xfc\xb7\xa5\xa3\xac\xac\xa3\xa5\xb4\xd5\x34\x25\x23\x2c\x2c\x23\x25\x37\x7c\x8a\xba\xa0\xad\xac\xa3\xa4\xb1\xec\x09"; +my $pcma_3 = "\x3b\x20\x2d\x2c\x22\x27\x30\x16\x8c\xb9\xa1\xad\xac\xa2\xa7\xb2\x9c\x03\x3e\x21\x2d\x2c\x22\x26\x3d\x1a\x86\xbf\xa6\xad\xac\xad\xa6\xbf\x86\x1a\x3d\x26\x22\x2c\x2d\x21\x3e\x03\x9c\xb2\xa7\xa2\xac\xad\xa1\xb9\x8c\x16\x30\x27\x22\x2c\x2d\x20\x3b\x09\xec\xb1\xa4\xa3\xac\xad\xa0\xba\x8a\x7c\x37\x25\x23\x2c\x2c\x23\x25\x34\x55\xb4\xa5\xa3\xac\xac\xa3\xa5\xb7\xfc\x0a\x3a\x20\x2d\x2c\x23\x24\x31\x6c\x89\xbb\xa0\xad\xac\xa2\xa7\xb0\x96\x0c\x39\x21\x2d\x2c\x22\x27\x32\x1c\x83\xbe\xa1\xad\xac\xa2\xa6\xbd\x9a\x06\x3f\x26\x2d\x2c\x2d\x26\x3f\x06\x9a\xbd\xa6\xa2\xac\xad\xa1\xbe\x83\x1c\x32\x27\x22\x2c\x2d\x21\x39\x0c\x96\xb0\xa7\xa2\xac\xad\xa0"; +my $pcma_4 = "\xbb\x89\x6c\x31\x24\x23\x2c\x2d\x20\x3a\x0a\xfc\xb7\xa5\xa3\xac\xac\xa3\xa5\xb4\x55\x34\x25\x23\x2c\x2c\x23\x25\x37\x7c\x8a\xba\xa0\xad\xac\xa3\xa4\xb1\xec\x09\x3b\x20\x2d\x2c\x22\x27\x30\x16\x8c\xb9\xa1\xad\xac\xa2\xa7\xb2\x9c\x03\x3e\x21\x2d\x2c\x22\x26\x3d\x1a\x86\xbf\xa6\xad\xac\xad\xa6\xbf\x86\x1a\x3d\x26\x22\x2c\x2d\x21\x3e\x03\x9c\xb2\xa7\xa2\xac\xad\xa1\xb9\x8c\x16\x30\x27\x22\x2c\x2d\x20\x3b\x09\xec\xb1\xa4\xa3\xac\xad\xa0\xba\x8a\x7c\x37\x25\x23\x2c\x2c\x23\x25\x34\x55\xb4\xa5\xa3\xac\xac\xa3\xa5\xb7\xfc\x0a\x3a\x20\x2d\x2c\x23\x24\x31\x6c\x89\xbb\xa0\xad\xac\xa2\xa7\xb0\x96\x0c\x39\x21\x2d\x2c\x22\x27\x32\x1c\x83\xbe\xa1"; +my $pcma_5 = "\xad\xac\xa2\xa6\xbd\x9a\x06\x3f\x26\x2d\x2c\x2d\x26\x3f\x06\x9a\xbd\xa6\xa2\xac\xad\xa1\xbe\x83\x1c\x32\x27\x22\x2c\x2d\x21\x39\x0c\x96\xb0\xa7\xa2\xac\xad\xa0\xbb\x89\x6c\x31\x24\x23\x2c\x2d\x20\x3a\x0a\xfc\xb7\xa5\xa3\xac\xac\xa3\xa5\xb4\xd5\x34\x25\x23\x2c\x2c\x23\x25\x37\x7c\x8a\xba\xa0\xad\xac\xa3\xa4\xb1\xec\x09\x3b\x20\x2d\x2c\x22\x27\x30\x16\x8c\xb9\xa1\xad\xac\xa2\xa7\xb2\x9c\x03\x3e\x21\x2d\x2c\x22\x26\x3d\x1a\x86\xbf\xa6\xad\xac\xad\xa6\xbf\x86\x1a\x3d\x26\x22\x2c\x2d\x21\x3e\x03\x9c\xb2\xa7\xa2\xac\xad\xa1\xb9\x8c\x16\x30\x27\x22\x2c\x2d\x20\x3b\x09\xec\xb1\xa4\xa3\xac\xad\xa0\xba\x8a\x7c\x37\x25\x23\x2c\x2c\x23\x25\x34"; + + + +my ($sock_a, $sock_b, $sock_c, $sock_d, $port_a, $port_b, $ssrc, $ssrc_b, $resp, + $sock_ax, $sock_bx, $port_ax, $port_bx, + $srtp_ctx_a, $srtp_ctx_b, $srtp_ctx_a_rev, $srtp_ctx_b_rev, $ufrag_a, $ufrag_b, + @ret1, @ret2, @ret3, @ret4, $srtp_key_a, $srtp_key_b, $ts, $seq, $has_recv); + + + +# media playback + +($sock_a) = new_call([qw(198.51.100.1 2020)]); + +offer('media playback, offer only', { ICE => 'remove', replace => ['origin'] }, < ft(), blob => $wav_file }); +is $resp->{duration}, 100, 'media duration'; + +(undef, $seq, $ts, $ssrc) = rcv($sock_a, -1, rtpm(8 | 0x80, -1, -1, -1, $pcma_1)); +rcv($sock_a, -1, rtpm(8, $seq + 1, $ts + 160 * 1, $ssrc, $pcma_2)); +rcv($sock_a, -1, rtpm(8, $seq + 2, $ts + 160 * 2, $ssrc, $pcma_3)); +rcv($sock_a, -1, rtpm(8, $seq + 3, $ts + 160 * 3, $ssrc, $pcma_4)); +rcv($sock_a, -1, rtpm(8, $seq + 4, $ts + 160 * 4, $ssrc, $pcma_5)); + + + + +($sock_a, $sock_b) = new_call([qw(198.51.100.1 2020)], [qw(198.51.100.3 2022)]); + +offer('media playback, side A', { ICE => 'remove', replace => ['origin'] }, < ['origin'] }, < ft(), blob => $wav_file }); +is $resp->{duration}, 100, 'media duration'; + +(undef, $seq, $ts, $ssrc) = rcv($sock_a, -1, rtpm(8 | 0x80, -1, -1, -1, $pcma_1)); +rcv($sock_a, -1, rtpm(8, $seq + 1, $ts + 160 * 1, $ssrc, $pcma_2)); +rcv($sock_a, -1, rtpm(8, $seq + 2, $ts + 160 * 2, $ssrc, $pcma_3)); +rcv($sock_a, -1, rtpm(8, $seq + 3, $ts + 160 * 3, $ssrc, $pcma_4)); +rcv($sock_a, -1, rtpm(8, $seq + 4, $ts + 160 * 4, $ssrc, $pcma_5)); + + + + +($sock_a, $sock_b) = new_call([qw(198.51.100.1 2100)], [qw(198.51.100.3 2102)]); + +offer('media playback, side A, repeat', { ICE => 'remove', replace => ['origin'] }, < ['origin'] }, < ft(), blob => $wav_file, 'repeat-times' => 2 }); +is $resp->{duration}, 100, 'media duration'; + +(undef, $seq, $ts, $ssrc) = rcv($sock_a, -1, rtpm(8 | 0x80, -1, -1, -1, $pcma_1)); +rcv($sock_a, -1, rtpm(8, $seq + 1, $ts + 160 * 1, $ssrc, $pcma_2)); +rcv($sock_a, -1, rtpm(8, $seq + 2, $ts + 160 * 2, $ssrc, $pcma_3)); +rcv($sock_a, -1, rtpm(8, $seq + 3, $ts + 160 * 3, $ssrc, $pcma_4)); +rcv($sock_a, -1, rtpm(8, $seq + 4, $ts + 160 * 4, $ssrc, $pcma_5)); +rcv($sock_a, -1, rtpm(8 | 0x80, $seq + 5, $ts + 160 * 5, $ssrc, $pcma_1)); +rcv($sock_a, -1, rtpm(8, $seq + 6, $ts + 160 * 6, $ssrc, $pcma_2)); +rcv($sock_a, -1, rtpm(8, $seq + 7, $ts + 160 * 7, $ssrc, $pcma_3)); +rcv($sock_a, -1, rtpm(8, $seq + 8, $ts + 160 * 8, $ssrc, $pcma_4)); +rcv($sock_a, -1, rtpm(8, $seq + 9, $ts + 160 * 9, $ssrc, $pcma_5)); + + + + + +($sock_a, $sock_b) = new_call([qw(198.51.100.1 2030)], [qw(198.51.100.3 2032)]); + +offer('media playback, side B', { ICE => 'remove', replace => ['origin'] }, < ['origin'] }, < tt(), blob => $wav_file }); +is $resp->{duration}, 100, 'media duration'; + +(undef, $seq, $ts, $ssrc) = rcv($sock_b, -1, rtpm(8 | 0x80, -1, -1, -1, $pcma_1)); +rcv($sock_b, -1, rtpm(8, $seq + 1, $ts + 160 * 1, $ssrc, $pcma_2)); +rcv($sock_b, -1, rtpm(8, $seq + 2, $ts + 160 * 2, $ssrc, $pcma_3)); +rcv($sock_b, -1, rtpm(8, $seq + 3, $ts + 160 * 3, $ssrc, $pcma_4)); +rcv($sock_b, -1, rtpm(8, $seq + 4, $ts + 160 * 4, $ssrc, $pcma_5)); + +$resp = rtpe_req('play media', 'restart media playback', { 'from-tag' => tt(), blob => $wav_file }); +is $resp->{duration}, 100, 'media duration'; + +$ts += 160 * 5; +my $old_ts = $ts; +(undef, $ts) = rcv($sock_b, -1, rtpm(8 | 0x80, $seq + 5, -1, $ssrc, $pcma_1)); +print("ts $ts old $old_ts\n"); +SKIP: { + skip 'random timestamp too close to margin', 2 if $old_ts < 500 or $old_ts > 4294966795; + cmp_ok($ts, '<', $old_ts + 500, 'ts within < range'); + cmp_ok($ts, '>', $old_ts - 500, 'ts within > range'); +} +rcv($sock_b, -1, rtpm(8, $seq + 6, $ts + 160 * 1, $ssrc, $pcma_2)); +rcv($sock_b, -1, rtpm(8, $seq + 7, $ts + 160 * 2, $ssrc, $pcma_3)); +rcv($sock_b, -1, rtpm(8, $seq + 8, $ts + 160 * 3, $ssrc, $pcma_4)); +rcv($sock_b, -1, rtpm(8, $seq + 9, $ts + 160 * 4, $ssrc, $pcma_5)); + + + + +($sock_a, $sock_b) = new_call([qw(198.51.100.9 2020)], [qw(198.51.100.9 2022)]); + +offer('media playback, side A, select by label', { ICE => 'remove', replace => ['origin'], + label => 'foobar' }, < ['origin'], label => 'blah' }, < 'foobar', + blob => $wav_file }); +is $resp->{duration}, 100, 'media duration'; + +(undef, $seq, $ts, $ssrc) = rcv($sock_a, -1, rtpm(8 | 0x80, -1, -1, -1, $pcma_1)); +rcv($sock_a, -1, rtpm(8, $seq + 1, $ts + 160 * 1, $ssrc, $pcma_2)); +rcv($sock_a, -1, rtpm(8, $seq + 2, $ts + 160 * 2, $ssrc, $pcma_3)); +rcv($sock_a, -1, rtpm(8, $seq + 3, $ts + 160 * 3, $ssrc, $pcma_4)); +rcv($sock_a, -1, rtpm(8, $seq + 4, $ts + 160 * 4, $ssrc, $pcma_5)); + + + + +($sock_a, $sock_b) = new_call([qw(198.51.100.9 2030)], [qw(198.51.100.9 2032)]); + +offer('media playback, side B, select by label', { ICE => 'remove', replace => ['origin'], + label => 'quux' }, < ['origin'], label => 'meh' }, < 'meh', blob => $wav_file }); +is $resp->{duration}, 100, 'media duration'; + +(undef, $seq, $ts, $ssrc) = rcv($sock_b, -1, rtpm(8 | 0x80, -1, -1, -1, $pcma_1)); +rcv($sock_b, -1, rtpm(8, $seq + 1, $ts + 160 * 1, $ssrc, $pcma_2)); +rcv($sock_b, -1, rtpm(8, $seq + 2, $ts + 160 * 2, $ssrc, $pcma_3)); +rcv($sock_b, -1, rtpm(8, $seq + 3, $ts + 160 * 3, $ssrc, $pcma_4)); +rcv($sock_b, -1, rtpm(8, $seq + 4, $ts + 160 * 4, $ssrc, $pcma_5)); + + + + + +($sock_a, $sock_b) = new_call([qw(198.51.100.1 2050)], [qw(198.51.100.3 2052)]); + +offer('media playback, SRTP', { ICE => 'remove', replace => ['origin'], DTLS => 'off' }, < ['origin'] }, < ft(), blob => $wav_file }); +is $resp->{duration}, 100, 'media duration'; + +my $srtp_ctx = { + cs => $NGCP::Rtpclient::SRTP::crypto_suites{AES_CM_128_HMAC_SHA1_80}, + key => 'DVM+BTeYX2UI1LaA9bgXrcBEDBxoItA9/39fSoRF', +}; +(undef, $seq, $ts, $ssrc) = srtp_rcv($sock_a, -1, rtpm(8 | 0x80, -1, -1, -1, $pcma_1), $srtp_ctx); +srtp_rcv($sock_a, -1, rtpm(8, $seq + 1, $ts + 160 * 1, $ssrc, $pcma_2), $srtp_ctx); +srtp_rcv($sock_a, -1, rtpm(8, $seq + 2, $ts + 160 * 2, $ssrc, $pcma_3), $srtp_ctx); +srtp_rcv($sock_a, -1, rtpm(8, $seq + 3, $ts + 160 * 3, $ssrc, $pcma_4), $srtp_ctx); +srtp_rcv($sock_a, -1, rtpm(8, $seq + 4, $ts + 160 * 4, $ssrc, $pcma_5), $srtp_ctx); + + + + + + + +# media playback after a delete + +($sock_a, $sock_b) = new_call([qw(198.51.100.1 3020)], [qw(198.51.100.3 3022)]); + +offer('media playback after delete', { ICE => 'remove', replace => ['origin'], + 'rtcp-mux' => ['demux'], 'via-branch' => 'xxxx', flags => ['strict-source', 'record-call'], + 'transport-protocol' => 'RTP/AVP' }, < ['origin'], 'transport-protocol' => 'RTP/AVP', + 'rtcp-mux' => ['demux'], 'via-branch' => 'xxxx' }, < ft() }); + +# new to-tag +new_tt(); + +offer('media playback after delete', { ICE => 'remove', replace => ['origin'], + 'transport-protocol' => 'transparent', flags => ['strict-source', 'record-call'], + 'rtcp-mux' => ['demux'], 'via-branch' => 'xxxx' }, < ['origin'], 'transport-protocol' => 'RTP/AVP', + 'rtcp-mux' => ['demux'], 'via-branch' => 'xxxx' }, < tt(), 'to-tag' => tt(), + blob => $wav_file }); +is $resp->{duration}, 100, 'media duration'; + +(undef, $seq, $ts, $ssrc) = rcv($sock_b, -1, rtpm(8 | 0x80, -1, -1, -1, $pcma_1)); +rcv($sock_b, -1, rtpm(8, $seq + 1, $ts + 160 * 1, $ssrc, $pcma_2)); +rcv($sock_b, -1, rtpm(8, $seq + 2, $ts + 160 * 2, $ssrc, $pcma_3)); +rcv($sock_b, -1, rtpm(8, $seq + 3, $ts + 160 * 3, $ssrc, $pcma_4)); +rcv($sock_b, -1, rtpm(8, $seq + 4, $ts + 160 * 4, $ssrc, $pcma_5)); + + + + +#done_testing;NGCP::Rtpengine::AutoTest::terminate('f00');exit; +done_testing();