]> git.ipfire.org Git - thirdparty/freeswitch.git/commitdiff
[core] RTP: a media timeout fix + add pcap based-unit tests. 1585/head
authorDragos Oancea <dragos@signalwire.com>
Sun, 20 Mar 2022 14:25:37 +0000 (17:25 +0300)
committerAndrey Volk <andywolk@gmail.com>
Wed, 29 Jun 2022 15:20:27 +0000 (18:20 +0300)
configure.ac
src/switch_core_media.c
src/switch_rtp.c
tests/unit/Makefile.am
tests/unit/conf_rtp/freeswitch.xml [new file with mode: 0644]
tests/unit/pcap/milliwatt.long.pcmu.rtp.pcap [new file with mode: 0644]
tests/unit/pcap/milliwatt.pcmu.rtp.pcap [new file with mode: 0644]
tests/unit/switch_rtp_pcap.c [new file with mode: 0644]

index 4131e629a68f550785e54241d154ae942e11f476..3ee7c58f9d7f4237fba1cd1695a295b82812114a 100644 (file)
@@ -1970,6 +1970,22 @@ else
        AC_MSG_WARN([python3 support disabled, building mod_python3 will fail!])
 fi
 
+# pcap lib for unit-testing
+AC_MSG_CHECKING(libpcap)
+AC_CHECK_PROG(HAVE_PCAP_CONFIG,pcap-config,[true],[false])
+if test x"$HAVE_PCAP_CONFIG" = x"true"; then
+   AC_MSG_RESULT(yes)
+   PCAP_CONFIG=pcap-config
+   PCAP_LIBS="`$PCAP_CONFIG --libs`"
+   PCAP_CFLAGS="`$PCAP_CONFIG --cflags`"
+   AM_CONDITIONAL([HAVE_PCAP], [true])
+else
+   AC_MSG_RESULT(no)
+   AM_CONDITIONAL([HAVE_PCAP], [false])
+fi
+AC_SUBST([PCAP_CFLAGS])
+AC_SUBST([PCAP_LIBS])
+
 #
 # SNMP checks for mod_snmp
 #
index e27b81e166d7c67d7ef5e34c7c4bcaebb8ecbcb1..347735cf1b3c78a8fefbf67eadc36fb3a15d265f 100644 (file)
@@ -2882,8 +2882,18 @@ static void check_media_timeout_params(switch_core_session_t *session, switch_rt
 
        if (switch_rtp_ready(engine->rtp_session) && engine->media_timeout) {
                switch_rtp_set_media_timeout(engine->rtp_session, engine->media_timeout);
-       }
+               if (engine->type == SWITCH_MEDIA_TYPE_AUDIO) {
+                       /* the values are in milliseconds, not in seconds as the deprecated rtp_timeout_sec */
+                       engine->max_missed_packets = (engine->read_impl.samples_per_second * engine->media_timeout / 1000) / engine->read_impl.samples_per_packet;
+
+                       switch_rtp_set_max_missed_packets(engine->rtp_session, engine->max_missed_packets);
+                       if (!engine->media_hold_timeout) {
+                               engine->media_hold_timeout = engine->media_timeout * 10;
+                       }
 
+                       engine->max_missed_hold_packets = (engine->read_impl.samples_per_second * engine->media_hold_timeout / 1000) / engine->read_impl.samples_per_packet;
+               }
+       }
 }
 
 SWITCH_DECLARE(switch_status_t) switch_core_media_read_frame(switch_core_session_t *session, switch_frame_t **frame,
index a7bd2f8d834bba347b3c89c6c3f5ff5afa17965b..5f4ce0f738f29db15e1c015510140bc35b1a77ec 100644 (file)
@@ -7350,7 +7350,7 @@ static void check_timeout(switch_rtp_t *rtp_session)
        }
 
        switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(rtp_session->session), SWITCH_LOG_DEBUG10,
-                                         "%s MEDIA TIMEOUT %s %d/%d", switch_core_session_get_name(rtp_session->session), rtp_type(rtp_session),
+                                         "%s MEDIA TIMEOUT %s %d/%d\n", switch_core_session_get_name(rtp_session->session), rtp_type(rtp_session),
                                          elapsed, rtp_session->media_timeout);
 
        if (elapsed > rtp_session->media_timeout) {
index d24e30f62be6e0e8c3d892b81c9e6e3429d86f14..e2cec6908934d574b3337fc1cfa49ea96e7fe4df 100644 (file)
@@ -5,6 +5,12 @@ noinst_PROGRAMS = switch_event switch_hash switch_ivr_originate switch_utils swi
 noinst_PROGRAMS += switch_core_video switch_core_db switch_vad switch_packetizer switch_core_session test_sofia switch_ivr_async switch_core_asr switch_log
 
 noinst_PROGRAMS+= switch_hold switch_sip
+
+if HAVE_PCAP
+noinst_PROGRAMS += switch_rtp_pcap
+AM_LDFLAGS += $(PCAP_LIBS)
+endif 
+
 AM_LDFLAGS += -avoid-version -no-undefined $(SWITCH_AM_LDFLAGS) $(openssl_LIBS)
 AM_LDFLAGS += $(FREESWITCH_LIBS) $(switch_builddir)/libfreeswitch.la $(CORE_LIBS) $(APR_LIBS)
 
diff --git a/tests/unit/conf_rtp/freeswitch.xml b/tests/unit/conf_rtp/freeswitch.xml
new file mode 100644 (file)
index 0000000..69130f8
--- /dev/null
@@ -0,0 +1,73 @@
+<?xml version="1.0"?>
+<document type="freeswitch/xml">
+  <X-PRE-PROCESS cmd="exec-set" data="test=echo 1234"/>
+  <X-PRE-PROCESS cmd="set" data="default_password=$${test}"/>
+  <X-PRE-PROCESS cmd="set" data="core_video_blank_image=$${conf_dir}/freeswitch-logo.png"/>
+  <section name="configuration" description="Various Configuration">
+    <configuration name="modules.conf" description="Modules">
+      <modules>
+        <load module="mod_console"/>
+        <load module="mod_loopback"/>
+        <load module="mod_commands"/>
+        <load module="mod_sndfile"/>
+        <load module="mod_dptools"/>
+        <load module="mod_tone_stream"/>
+               <load module="mod_test"/>
+      </modules>
+    </configuration>
+
+    <configuration name="console.conf" description="Console Logger">
+      <mappings>
+        <map name="all" value="console,debug,info,notice,warning,err,crit,alert"/>
+      </mappings>
+      <settings>
+        <param name="colorize" value="true"/>
+        <param name="loglevel" value="debug"/>
+    
+      </settings>
+    </configuration>
+
+ <configuration name="switch.conf" description="Core Configuration">
+   <default-ptimes>
+   </default-ptimes>
+   <settings>
+     <param name="colorize-console" value="false"/>
+     <param name="dialplan-timestamps" value="false"/>
+     <param name="loglevel" value="debug"/>
+     <param name="rtp-start-port" value="1234"/>
+     <param name="rtp-end-port" value="1234"/>
+     <param name="rtp-enable-zrtp" value="false"/>
+   </settings>
+ </configuration>
+    <configuration name="timezones.conf" description="Timezones">
+      <timezones>
+          <zone name="GMT" value="GMT0" />
+      </timezones>
+    </configuration>
+
+  </section>
+
+  <section name="dialplan" description="Regex/XML Dialplan">
+  <context name="test">
+    <extension>
+      <condition field="${sip_h_X-COUNTDOWN}" expression="^0$" break="on-true">
+        <action application="answer"/>
+        <action application="playback" data="tone_stream://%(251,0,1004);loops=-1"/>
+      </condition>
+      <condition field="${sip_h_X-COUNTDOWN}" expression="^(\d+)$" break="never">
+        <action application="export" data="_nolocal_sip_h_X-COUNTDOWN=${expr($1 - 1)}"/>
+        <anti-action application="export" data="_nolocal_sip_h_X-COUNTDOWN=10"/>
+      </condition>
+      <condition>
+        <action application="bridge" data="sofia/test/1234@127.0.0.1"/>
+      </condition>
+    </extension>
+  </context>
+
+       </section>
+</document>
diff --git a/tests/unit/pcap/milliwatt.long.pcmu.rtp.pcap b/tests/unit/pcap/milliwatt.long.pcmu.rtp.pcap
new file mode 100644 (file)
index 0000000..36ed6a1
Binary files /dev/null and b/tests/unit/pcap/milliwatt.long.pcmu.rtp.pcap differ
diff --git a/tests/unit/pcap/milliwatt.pcmu.rtp.pcap b/tests/unit/pcap/milliwatt.pcmu.rtp.pcap
new file mode 100644 (file)
index 0000000..8f68eac
Binary files /dev/null and b/tests/unit/pcap/milliwatt.pcmu.rtp.pcap differ
diff --git a/tests/unit/switch_rtp_pcap.c b/tests/unit/switch_rtp_pcap.c
new file mode 100644 (file)
index 0000000..74b79f9
--- /dev/null
@@ -0,0 +1,580 @@
+/*
+* FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
+* Copyright (C) 2005-2021, Anthony Minessale II <anthm@freeswitch.org>
+*
+* Version: MPL 1.1
+*
+* The contents of this file are subject to the Mozilla Public License Version
+* 1.1 (the "License"); you may not use this file except in compliance with
+* the License. You may obtain a copy of the License at
+* http://www.mozilla.org/MPL/
+*
+* Software distributed under the License is distributed on an "AS IS" basis,
+* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+* for the specific language governing rights and limitations under the
+* License.
+*
+* The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
+*
+* The Initial Developer of the Original Code is
+* Anthony Minessale II <anthm@freeswitch.org>
+* Portions created by the Initial Developer are Copyright (C)
+* the Initial Developer. All Rights Reserved.
+*
+* Contributor(s):
+* Dragos Oancea <dragos@signalwire.com>
+*
+* switch_rtp_pcap.c -- tests RTP stack using PCAP.
+*/
+
+
+#include <switch.h>
+#include <test/switch_test.h>
+
+/* before adding a pcap file: tcprewrite --dstipmap=X.X.X.X/32:192.168.0.1/32 --srcipmap=X.X.X.X/32:192.168.0.2/32 -i in.pcap -o out.pcap */
+
+#include <pcap.h>
+
+#ifndef MSG_CONFIRM
+#define MSG_CONFIRM 0
+#endif
+
+static const char *rx_host = "127.0.0.1";
+static const char *tx_host = "127.0.0.1";
+static switch_rtp_t *rtp_session = NULL;
+const char *err = NULL;
+switch_rtp_packet_t rtp_packet;
+switch_frame_flag_t *frame_flags;
+switch_io_flag_t io_flags;
+switch_payload_t read_pt;
+static switch_port_t audio_rx_port = 1234;
+
+static int got_media_timeout = 0;
+
+//#define USE_RTCP_PCAP 
+
+#define NTP_TIME_OFFSET 2208988800UL
+
+/* https://www.tcpdump.org/pcap.html */
+/* IP header */
+struct sniff_ip {
+       u_char ip_vhl;          /* version << 4 | header length >> 2 */
+       u_char ip_tos;          /* type of service */
+       u_short ip_len;         /* total length */
+       u_short ip_id;          /* identification */
+       u_short ip_off;         /* fragment offset field */
+#define IP_RF 0x8000           /* reserved fragment flag */
+#define IP_DF 0x4000           /* dont fragment flag */
+#define IP_MF 0x2000           /* more fragments flag */
+#define IP_OFFMASK 0x1fff      /* mask for fragmenting bits */
+       u_char ip_ttl;          /* time to live */
+       u_char ip_p;            /* protocol */
+       u_short ip_sum;         /* checksum */
+       struct in_addr ip_src,ip_dst; /* source and dest address */
+};
+
+#define IP_HL(ip)              (((ip)->ip_vhl) & 0x0f)
+
+/* switch_rtp.c - calc_local_lsr_now()  */
+static inline uint32_t test_calc_local_lsr_now(switch_time_t now, uint32_t past /*milliseconds*/) 
+{
+//     switch_time_t now;
+       uint32_t ntp_sec, ntp_usec, lsr_now, sec;
+//     now = switch_micro_time_now() - (past * 1000);
+       now = now - (past * 1000);
+       sec = (uint32_t)(now/1000000);        /* convert to seconds     */
+       ntp_sec = sec+NTP_TIME_OFFSET;  /* convert to NTP seconds */
+       ntp_usec = (uint32_t)(now - ((switch_time_t) sec*1000000)); /* remove seconds to keep only the microseconds */
+        
+       lsr_now = (uint32_t)(ntp_usec*0.065536) | (ntp_sec&0x0000ffff)<<16; /* 0.065536 is used for convertion from useconds to fraction of     65536 (x65536/1000000) */
+       return lsr_now;
+}
+
+#if 0
+static void test_prepare_rtcp(void *rtcp_packet, float est_last, uint32_t rtt, uint8_t loss) 
+{
+       /* taken from switch_rtp.c, rtcp_generate_sender_info() */
+       /* === */
+       char *rtcp_sr_trigger = rtcp_packet;
+       switch_time_t now;
+       uint32_t sec, ntp_sec, ntp_usec;
+       uint32_t ntp_msw;
+       uint32_t ntp_lsw;
+       uint32_t *ptr_msw;
+       uint32_t *ptr_lsw;
+       uint32_t lsr;
+       uint32_t *ptr_lsr;
+       uint32_t dlsr = 0;
+       uint32_t *ptr_dlsr;
+       uint8_t *ptr_loss;
+
+       now = switch_micro_time_now();
+       sec = (uint32_t)(now/1000000);        /* convert to seconds     */
+       ntp_sec = sec+NTP_TIME_OFFSET;  /* convert to NTP seconds */
+       ntp_msw = htonl(ntp_sec);   /* store result in "most significant word" */
+       ntp_usec = (uint32_t)(now - (sec*1000000)); /* remove seconds to keep only the microseconds */
+       ntp_lsw = htonl((u_long)(ntp_usec*(double)(((uint64_t)1)<<32)*1.0e-6)); 
+
+       /* === */
+
+       /*patch the RTCP payload to set the RTT we want */
+
+       ptr_msw = (uint32_t *)rtcp_sr_trigger + 2;
+       *ptr_msw = ntp_msw;
+       
+       ptr_lsw = (uint32_t *)rtcp_sr_trigger + 3;
+       *ptr_lsw = ntp_lsw;
+
+       lsr = test_calc_local_lsr_now(now, est_last * 1000 + rtt /*ms*/);
+
+       ptr_lsr = (uint32_t *)rtcp_sr_trigger + 11;
+       *ptr_lsr  = htonl(lsr);
+
+       ptr_dlsr = (uint32_t *)rtcp_sr_trigger + 12;
+       *ptr_dlsr  = htonl(dlsr);
+
+       ptr_loss = (uint8_t *)rtcp_sr_trigger + 32;
+       *ptr_loss  = loss;
+}
+#endif
+
+static switch_status_t rtp_test_start_call(switch_core_session_t **psession)
+{
+       char *r_sdp;
+       uint8_t match = 0, p = 0;
+       switch_core_session_t *session; 
+       switch_channel_t *channel = NULL;
+       switch_status_t status;
+       switch_media_handle_t *media_handle;
+       switch_core_media_params_t *mparams;
+       switch_stream_handle_t stream = { 0 };
+       switch_call_cause_t cause;
+
+       /*tone stream extension*/
+       status = switch_ivr_originate(NULL, psession, &cause, "null/+1234", 2, NULL, NULL, NULL, NULL, NULL, SOF_NONE, NULL, NULL);
+       session = *psession;
+
+       if (!(session)) {
+               switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "no session\n");
+               return SWITCH_STATUS_FALSE;
+       }
+
+       if (status != SWITCH_STATUS_SUCCESS) {
+               switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "switch_ivr_originate() failed\n");
+               return SWITCH_STATUS_FALSE;
+       }
+
+       channel = switch_core_session_get_channel(session);
+       if (!channel) {
+               switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "switch_core_session_get_channel() failed\n");
+               return SWITCH_STATUS_FALSE;
+       }
+       mparams = switch_core_session_alloc(session, sizeof(switch_core_media_params_t));
+       mparams->inbound_codec_string = switch_core_session_strdup(session, "PCMU");
+       mparams->outbound_codec_string = switch_core_session_strdup(session, "PCMU");
+       mparams->rtpip = switch_core_session_strdup(session, (char *)rx_host);
+
+       status = switch_media_handle_create(&media_handle, session, mparams);
+       if (status != SWITCH_STATUS_SUCCESS) {
+               switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "switch_media_handle_create() failed\n");
+               return SWITCH_STATUS_FALSE;
+       }
+
+       switch_channel_set_variable(channel, "absolute_codec_string", "PCMU");
+       switch_channel_set_variable(channel, "send_silence_when_idle", "-1");
+       switch_channel_set_variable(channel, "rtp_timer_name", "soft");
+       switch_channel_set_variable(channel, "media_timeout", "1000");
+
+       switch_channel_set_variable(channel, SWITCH_LOCAL_MEDIA_IP_VARIABLE, rx_host);
+       switch_channel_set_variable_printf(channel, SWITCH_LOCAL_MEDIA_PORT_VARIABLE, "%d", audio_rx_port);
+
+       r_sdp = switch_core_session_sprintf(session,
+       "v=0\n"
+       "o=FreeSWITCH 1632033305 1632033306 IN IP4 %s\n"
+       "s=-\n"
+       "c=IN IP4 %s\n"
+       "t=0 0\n"
+       "m=audio 11114 RTP/AVP 0 101\n"
+       "a=rtpmap:0 PCMU/8000\n"
+       "a=rtpmap:101 telephone-event/8000\n"
+       "a=rtcp-mux\n",
+       tx_host, tx_host);
+        
+       status = switch_core_media_prepare_codecs(session, SWITCH_FALSE);
+       if (status != SWITCH_STATUS_SUCCESS) {
+               switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "switch_core_media_prepare_codecs() failed\n");
+               return SWITCH_STATUS_FALSE;
+       }
+          
+       match = switch_core_media_negotiate_sdp(session, r_sdp, &p, SDP_TYPE_REQUEST);
+       if (match != 1) {
+               switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "switch_core_media_negotiate_sdp() failed\n");
+               return SWITCH_STATUS_FALSE;
+       }
+
+       status = switch_core_media_choose_ports(session, SWITCH_TRUE, SWITCH_FALSE);
+       if (status != SWITCH_STATUS_SUCCESS) {
+               switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "switch_core_media_choose_ports() failed\n");
+               return SWITCH_STATUS_FALSE;
+       }
+
+       status = switch_core_media_activate_rtp(session);
+       if (status != SWITCH_STATUS_SUCCESS) {
+               switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "switch_core_media_activate_rtp() failed\n");
+               return SWITCH_STATUS_FALSE;
+       }
+
+       switch_core_media_set_rtp_flag(session, SWITCH_MEDIA_TYPE_AUDIO, SWITCH_RTP_FLAG_DEBUG_RTP_READ);
+       switch_core_media_set_rtp_flag(session, SWITCH_MEDIA_TYPE_AUDIO, SWITCH_RTP_FLAG_DEBUG_RTP_WRITE);
+
+       SWITCH_STANDARD_STREAM(stream);
+       switch_api_execute("fsctl", "debug_level 10", session, &stream);
+       switch_safe_free(stream.data);
+
+       return SWITCH_STATUS_SUCCESS;
+}
+
+static switch_status_t rtp_test_end_call(switch_core_session_t **psession) 
+{
+       switch_channel_t *channel = NULL;
+       switch_core_session_t *session = *psession; 
+
+       channel = switch_core_session_get_channel(session);
+       if (!channel) {
+               return SWITCH_STATUS_FALSE;
+       }
+       switch_channel_hangup(channel, SWITCH_CAUSE_NORMAL_CLEARING);
+       switch_media_handle_destroy(session);
+       switch_core_session_rwunlock(session);
+       
+       return SWITCH_STATUS_SUCCESS;
+}
+
+static void rtp_test_init_frame(switch_frame_t **pwrite_frame, switch_core_session_t **psession)
+{
+       const unsigned char hdr_packet[]="\x80\x00\xcd\x15\xfd\x86\x00\x00\x61\x5a\xe1\x37";
+
+       switch_frame_alloc(pwrite_frame, SWITCH_RECOMMENDED_BUFFER_SIZE);
+       (*pwrite_frame)->codec = switch_core_session_get_write_codec(*psession);
+
+       (*pwrite_frame)->datalen = SWITCH_RTP_HEADER_LEN; /*init with dummy RTP header*/
+       memcpy((*pwrite_frame)->data, &hdr_packet, SWITCH_RTP_HEADER_LEN);
+}
+
+static void show_event(switch_event_t *event) {
+       char *str;
+       /*print the event*/
+       switch_event_serialize_json(event, &str);
+       if (str) {
+               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "%s\n", str);
+               switch_safe_free(str);
+       }
+}
+
+static void event_handler(switch_event_t *event) 
+{
+       const char *new_ev = switch_event_get_header(event, "Event-Name");
+
+       if (new_ev && !strcmp(new_ev, "CHANNEL_HANGUP")) {
+               if (!strcmp(switch_event_get_header(event, "Hangup-Cause"), "MEDIA_TIMEOUT")) {
+                       got_media_timeout = 1;
+               }
+       }
+
+       show_event(event);
+}
+
+FST_CORE_DB_BEGIN("./conf_rtp")
+{
+FST_SUITE_BEGIN(switch_rtp_pcap)
+{
+
+FST_SETUP_BEGIN()
+{
+       fst_requires_module("mod_loopback");
+}
+FST_SETUP_END()
+
+FST_TEARDOWN_BEGIN()
+{
+}
+FST_TEARDOWN_END()
+#if 0
+       FST_TEST_BEGIN(test_rtp_stall_with_rtcp_muxed_with_timer)
+       {
+               switch_core_session_t *session = NULL;
+               switch_status_t status;
+               uint32_t plen = SWITCH_RTP_HEADER_LEN;
+               char rpacket[SWITCH_RECOMMENDED_BUFFER_SIZE];
+               switch_payload_t pt = { 0 };
+               switch_frame_flag_t frameflags = { 0 };
+               int x = 0;
+               switch_frame_t *write_frame;
+               pcap_t *pcap;
+               const unsigned char *packet;
+               char errbuf[PCAP_ERRBUF_SIZE];
+               struct pcap_pkthdr pcap_header;
+               char rtcp_sr_trigger[] = "\x81\xc8\x00\x0c\x78\x9d\xac\x45\xe2\x67\xa5\x74\x30\x60\x56\x81\x00\x19"
+                       "\xaa\x00\x00\x00\x06\xd7\x00\x01\x2c\x03\x5e\xbd\x2f\x0b\x00"
+                       "\x00\x00\x00\x00\x00\x57\xc4\x00\x00\x00\x39\xa5\x73\xfe\x90\x00\x00\x2c\x87"
+                       "\x81\xca\x00\x0c\x78\x9d\xac\x45\x01\x18\x73\x69\x70\x3a\x64\x72\x40\x31\x39\x32\x2e"
+                       "\x31\x36\x38\x2e\x30\x2e\x31\x33\x3a\x37\x30\x36\x30\x06\x0e\x4c\x69\x6e\x70\x68\x6f"
+                       "\x6e\x65\x2d\x33\x2e\x36\x2e\x31\x00\x00";
+               const struct sniff_ip *ip; /* The IP header */
+               int size_ip, jump_over;
+               struct timeval prev_ts = { 0 };
+               switch_time_t time_nowpacket = 0, time_prevpacket = 0;
+               switch_socket_t *sock_rtp = NULL;
+               switch_sockaddr_t *sock_addr = NULL;
+               const char *str_err;
+               switch_size_t rough_add = 0;
+
+               status = rtp_test_start_call(&session);
+               fst_requires(status == SWITCH_STATUS_SUCCESS);
+               fst_requires(session);
+
+               pcap = pcap_open_offline_with_tstamp_precision("pcap/milliwatt.long.pcmu.rtp.pcap", PCAP_TSTAMP_PRECISION_MICRO, errbuf);
+               fst_requires(pcap);
+
+               switch_core_media_set_rtp_flag(session, SWITCH_MEDIA_TYPE_AUDIO, SWITCH_RTP_FLAG_ENABLE_RTCP);
+
+               rtp_session = switch_core_media_get_rtp_session(session, SWITCH_MEDIA_TYPE_AUDIO);
+
+               rtp_test_init_frame(&write_frame, &session);
+
+               switch_rtp_clear_flag(rtp_session, SWITCH_RTP_FLAG_PAUSE);
+
+               if (switch_socket_create(&sock_rtp, AF_INET, SOCK_DGRAM, 0, switch_core_session_get_pool(session)) != SWITCH_STATUS_SUCCESS) {
+                       fst_requires(0); /*exit*/ 
+               }
+
+               switch_sockaddr_new(&sock_addr, rx_host, audio_rx_port, switch_core_session_get_pool(session));
+               fst_requires(sock_addr);
+
+               switch_rtp_set_remote_address(rtp_session, tx_host, switch_sockaddr_get_port(sock_addr), 0, SWITCH_FALSE, &str_err);
+               switch_rtp_reset(rtp_session);
+
+               while ((packet = pcap_next(pcap, &pcap_header))) {
+                       /*assume only UDP/RTP packets in the pcap*/
+                       uint32_t rcvd_datalen = pcap_header.caplen;
+                       size_t len;
+                       switch_size_t tmp_len;
+
+                       int diff_us = (pcap_header.ts.tv_sec-prev_ts.tv_sec)*1000000+(pcap_header.ts.tv_usec-prev_ts.tv_usec);
+
+                       if (diff_us > 0) {
+                               switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "SENT pkt diff: %d us\n", diff_us);
+                               usleep(diff_us);
+                       }
+
+                       prev_ts = pcap_header.ts;
+
+                       len = pcap_header.caplen;
+
+                       if (len <= 42) {
+                               continue;
+                       } 
+
+                       ip = (struct sniff_ip*)(packet + 14);
+                       size_ip = IP_HL(ip) * 4;
+
+                       jump_over = 14 /*SIZE_ETHERNET*/ + size_ip /*IP HDR size*/ + 8 /* UDP HDR SIZE */; /* jump 42 bytes over network layers/headers */
+                       packet += jump_over;
+                       x++;
+                       
+                       if (!(x%10)) { /* send a RTCP SR packet every 10th RTP packet */
+                               int add_rtt = 200;
+                               test_prepare_rtcp(&rtcp_sr_trigger, 2, add_rtt, 0xa0);
+                               tmp_len = sizeof(rtcp_sr_trigger);
+                               /*RTCP muxed*/
+                               if (switch_socket_sendto(sock_rtp, sock_addr, MSG_CONFIRM, (const char*)rtcp_sr_trigger, &tmp_len) != SWITCH_STATUS_SUCCESS) {
+                                       fst_requires(0);
+                               }
+
+                               plen = sizeof(rtcp_sr_trigger);
+                               switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "Sent RTCP. Packet size = [%u]\n", plen);
+                               status = switch_rtp_read(rtp_session, (void *)rpacket, &rcvd_datalen, &pt, &frameflags, io_flags);
+                               if (pt == SWITCH_RTP_CNG_PAYLOAD /*timeout*/) { 
+                                       switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "read CNG/RTCP, skip\n");
+                                       while (1) {
+                                               status = switch_rtp_read(rtp_session, (void *)&rpacket, &rcvd_datalen, &pt, &frameflags, io_flags);
+                                               if (frameflags || SFF_RTCP) break;
+                                       }
+                               }
+                               fst_requires(status == SWITCH_STATUS_SUCCESS);
+                       }
+
+                       if (packet[0] == 0x80 && packet[1] == 0 /*PCMU*/) {
+                               int16_t *seq = (int16_t *)packet + 1;
+                               plen = len - jump_over;
+                               tmp_len = plen;
+                               switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Sent RTP. Packet size = [%u] seq = [%d]\n", plen, htons(*seq));
+                               if (switch_socket_sendto(sock_rtp, sock_addr, MSG_CONFIRM, (const char*)packet, &tmp_len) != SWITCH_STATUS_SUCCESS) {
+                                       fst_requires(0);
+                               }
+                       }
+
+                       status = switch_rtp_read(rtp_session, (void *)&rpacket, &rcvd_datalen, &pt, &frameflags, io_flags);
+                       if (pt == SWITCH_RTP_CNG_PAYLOAD /*timeout*/) {
+                               switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "read CNG, skip\n");
+                               continue;
+                       }
+                       time_prevpacket = time_nowpacket;
+                       time_nowpacket = switch_time_now();
+                       if (time_prevpacket) { // skip init.
+                               switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "RECV pkt diff: %ld us\n", time_nowpacket - time_prevpacket);
+
+                               fst_requires((time_nowpacket - time_prevpacket) < 80000);
+                               rough_add += time_nowpacket - time_prevpacket; /* just add to var for visual comparison */
+                       }
+                       fst_requires(status == SWITCH_STATUS_SUCCESS);
+                       if (pt == SWITCH_RTP_CNG_PAYLOAD /*timeout*/) continue;
+                       fst_requires(rcvd_datalen == plen - SWITCH_RTP_HEADER_LEN);
+               }
+
+               switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "RECV total delay: %lu\n", rough_add); /*around 17092408 us*/
+               switch_yield(1000 * 1000);
+
+               if (write_frame) switch_frame_free(&write_frame);
+
+               switch_rtp_destroy(&rtp_session);
+
+               rtp_test_end_call(&session);
+
+               switch_socket_close(sock_rtp);
+
+               pcap_close(pcap);
+
+               switch_yield(1000 * 1000);
+       }
+       FST_TEST_END()
+#endif
+
+       FST_TEST_BEGIN(test_rtp_media_timeout)
+       {
+               switch_core_session_t *session = NULL;
+               switch_status_t status;
+               uint32_t plen = SWITCH_RTP_HEADER_LEN;
+               char rpacket[SWITCH_RECOMMENDED_BUFFER_SIZE];
+               switch_payload_t pt = { 0 };
+               switch_frame_flag_t frameflags = { 0 };
+               int x = 0;
+               switch_frame_t *write_frame;
+               pcap_t *pcap;
+               const unsigned char *packet;
+               char errbuf[PCAP_ERRBUF_SIZE];
+               struct pcap_pkthdr pcap_header;
+               const struct sniff_ip *ip; /* The IP header */
+               int size_ip, jump_over;
+               struct timeval prev_ts = { 0 };
+               switch_socket_t *sock_rtp = NULL;
+               switch_sockaddr_t *sock_addr = NULL;
+               const char *str_err;
+
+               status = rtp_test_start_call(&session);
+               fst_requires(status == SWITCH_STATUS_SUCCESS);
+               fst_requires(session);
+
+               switch_event_bind("", SWITCH_EVENT_ALL, SWITCH_EVENT_SUBCLASS_ANY, event_handler, NULL);
+
+               pcap = pcap_open_offline_with_tstamp_precision("pcap/milliwatt.pcmu.rtp.pcap", PCAP_TSTAMP_PRECISION_MICRO, errbuf);
+               fst_requires(pcap);
+
+               switch_core_media_set_rtp_flag(session, SWITCH_MEDIA_TYPE_AUDIO, SWITCH_RTP_FLAG_ENABLE_RTCP);
+
+               rtp_session = switch_core_media_get_rtp_session(session, SWITCH_MEDIA_TYPE_AUDIO);
+               fst_requires(rtp_session);
+
+               rtp_test_init_frame(&write_frame, &session);
+
+               switch_rtp_clear_flag(rtp_session, SWITCH_RTP_FLAG_PAUSE);
+
+               if (switch_socket_create(&sock_rtp, AF_INET, SOCK_DGRAM, 0, switch_core_session_get_pool(session)) != SWITCH_STATUS_SUCCESS) {
+                       fst_requires(0); /*exit*/ 
+               }
+
+               switch_sockaddr_new(&sock_addr, rx_host, audio_rx_port, switch_core_session_get_pool(session));
+               fst_requires(sock_addr);
+
+               switch_rtp_set_remote_address(rtp_session, tx_host, switch_sockaddr_get_port(sock_addr), 0, SWITCH_FALSE, &str_err);
+               switch_rtp_reset(rtp_session);
+
+               /* send 3 packets then wait and expect RTP timeout */
+               while ((packet = pcap_next(pcap, &pcap_header)) && x < 3) {
+                       /*assume only UDP/RTP packets in the pcap*/
+                       uint32_t rcvd_datalen = pcap_header.caplen;
+                       size_t len;
+                       switch_size_t tmp_len;
+
+                       int diff_us = (pcap_header.ts.tv_sec-prev_ts.tv_sec)*1000000+(pcap_header.ts.tv_usec-prev_ts.tv_usec);
+                       if (diff_us > 0) {
+                               switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "SENT pkt diff: %d us\n", diff_us);
+                               usleep(diff_us);
+                       }
+
+                       x++;
+
+                       prev_ts = pcap_header.ts;
+
+                       len = pcap_header.caplen;
+
+                       if (len <= 42) {
+                               continue;
+                       } 
+
+                       ip = (struct sniff_ip*)(packet + 14);
+                       size_ip = IP_HL(ip) * 4;
+
+                       jump_over = 14 /*SIZE_ETHERNET*/ + size_ip /*IP HDR size*/ + 8 /* UDP HDR SIZE */; /* jump 42 bytes over network layers/headers */
+                       packet += jump_over;
+                       
+                       if (packet[0] == 0x80 && packet[1] == 0 /*PCMU*/) {
+                               int16_t *seq = (int16_t *)packet + 1;
+                               plen = len - jump_over;
+                               tmp_len = plen;
+                               switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Sent RTP. Packet size = [%u] seq = [%d]\n", plen, htons(*seq));
+                               if (switch_socket_sendto(sock_rtp, sock_addr, MSG_CONFIRM, (const char*)packet, &tmp_len) != SWITCH_STATUS_SUCCESS) {
+                                       fst_requires(0);
+                               }
+                       }
+
+                       status = switch_rtp_read(rtp_session, (void *)&rpacket, &rcvd_datalen, &pt, &frameflags, io_flags);
+                       if (pt == SWITCH_RTP_CNG_PAYLOAD /*timeout*/) {
+                               switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "read CNG, skip\n");
+                               continue;
+                       }
+
+                       fst_requires(status == SWITCH_STATUS_SUCCESS);
+                       fst_requires(rcvd_datalen == plen - SWITCH_RTP_HEADER_LEN);
+               }
+
+               x = 150; /* 3 seconds max */
+               while (x || !got_media_timeout) {
+                       uint32_t rcvd_datalen; 
+                       status = switch_rtp_read(rtp_session, (void *)&rpacket, &rcvd_datalen, &pt, &frameflags, io_flags);
+                       if (pt == SWITCH_RTP_CNG_PAYLOAD /*timeout*/) {
+                               switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "read CNG, skip\n");
+                       }
+                       switch_yield(20 * 1000);
+                       fst_requires(status == SWITCH_STATUS_SUCCESS);
+                       x--;
+               }
+
+               if (write_frame) switch_frame_free(&write_frame);
+
+               switch_rtp_destroy(&rtp_session);
+
+               rtp_test_end_call(&session);
+
+               switch_socket_close(sock_rtp);
+
+               pcap_close(pcap);
+
+               fst_check(got_media_timeout);
+       }
+       FST_TEST_END()
+}
+FST_SUITE_END()
+}
+FST_CORE_END()
+