[Asterisk-code-review] Adding tests that test forward error correction in codec opus (testsuite[master])

Anonymous Coward asteriskteam at digium.com
Sun Jan 29 10:11:58 CST 2017


Anonymous Coward #1000019 has submitted this change and it was merged. ( https://gerrit.asterisk.org/4729 )

Change subject: Adding tests that test forward error correction in codec_opus
......................................................................


Adding tests that test forward error correction in codec_opus

When FEC is enabled and negotiated in codec_opus dropped frames can be built
from a subsequently received frame. This series of tests establish a call
between two endpoints. Once the call is 'up' RTP packets are 'played' back
from a pcap file that contains roughly a minute of opus audio. Several packets
have been deleted in order to simulate dropped packets. The other end of the
call is monitored to make sure packets are received in the correct order and
with the appropriate timestamp/interval. If FEC is working properly there should
be no gaps in the sequence numbers of the received packets. Also the difference
between the timestamps should not vary.

These tests also make sure FEC works with and/or without jitter buffers enabled.

Change-Id: Ia01a0432cc23071836d594c7c3dc46709c9880bf
---
M lib/python/asterisk/pcap.py
A tests/codecs/opus/fec/16_bit_48khz_60_fec_dropped.pcapng
A tests/codecs/opus/fec/16_bit_48khz_60_fec_rtp.pcapng
A tests/codecs/opus/fec/jitterbuffer/adaptive/configs/ast1/codecs.conf
A tests/codecs/opus/fec/jitterbuffer/adaptive/configs/ast1/extensions.conf
A tests/codecs/opus/fec/jitterbuffer/adaptive/configs/ast1/pjsip.conf
A tests/codecs/opus/fec/jitterbuffer/adaptive/sipp/invite.xml
A tests/codecs/opus/fec/jitterbuffer/adaptive/sipp/invite_recv.xml
A tests/codecs/opus/fec/jitterbuffer/adaptive/test-config.yaml
A tests/codecs/opus/fec/jitterbuffer/fixed/configs/ast1/codecs.conf
A tests/codecs/opus/fec/jitterbuffer/fixed/configs/ast1/extensions.conf
A tests/codecs/opus/fec/jitterbuffer/fixed/configs/ast1/pjsip.conf
A tests/codecs/opus/fec/jitterbuffer/fixed/sipp/invite.xml
A tests/codecs/opus/fec/jitterbuffer/fixed/sipp/invite_recv.xml
A tests/codecs/opus/fec/jitterbuffer/fixed/test-config.yaml
A tests/codecs/opus/fec/jitterbuffer/tests.yaml
A tests/codecs/opus/fec/no_jitterbuffer/configs/ast1/codecs.conf
A tests/codecs/opus/fec/no_jitterbuffer/configs/ast1/extensions.conf
A tests/codecs/opus/fec/no_jitterbuffer/configs/ast1/pjsip.conf
A tests/codecs/opus/fec/no_jitterbuffer/sipp/invite.xml
A tests/codecs/opus/fec/no_jitterbuffer/sipp/invite_recv.xml
A tests/codecs/opus/fec/no_jitterbuffer/test-config.yaml
A tests/codecs/opus/fec/tests.yaml
M tests/codecs/opus/tests.yaml
A tests/codecs/rtp_analyzer.py
25 files changed, 890 insertions(+), 0 deletions(-)

Approvals:
  Mark Michelson: Looks good to me, but someone else must approve
  Anonymous Coward #1000019: Verified
  Joshua Colp: Looks good to me, approved



diff --git a/lib/python/asterisk/pcap.py b/lib/python/asterisk/pcap.py
index 91031f5..ee1020d 100644
--- a/lib/python/asterisk/pcap.py
+++ b/lib/python/asterisk/pcap.py
@@ -116,6 +116,11 @@
             self.ip_layer = self.eth_layer.next
             self.transport_layer = self.ip_layer.next
 
+    @property
+    def dst_port(self):
+        """Retrieve the destination port"""
+        return self.transport_layer.header.destination
+
 
 class RTCPPacket(Packet):
     """An RTCP Packet
@@ -196,6 +201,22 @@
     """An RTP Packet
     """
 
+    rtp_header = Struct(
+        "rtp_header",
+        EmbeddedBitStruct(
+            BitField("version", 2),
+            Bit("padding"),
+            Bit("extension"),
+            Nibble("csrc_count"),
+            Bit("marker"),
+            BitField("payload_type", 7),
+        ),
+        UBInt16("sequence_number"),
+        UBInt32("timestamp"),
+        UBInt32("ssrc"),
+        # 'csrc' can be added later when needed
+    )
+
     def __init__(self, raw_packet, factory_manager):
         """Constructor
 
@@ -204,6 +225,9 @@
         factory_manager The packet manager that created this packet
         """
         Packet.__init__(self, packet_type='RTP', raw_packet=raw_packet)
+
+        self._rtp_header = self.rtp_header.parse(self.transport_layer.next)
+
         ports = factory_manager.get_global_data(self.ip_layer.header.source)
         if ports is None:
             raise Exception()
@@ -211,6 +235,15 @@
             raise Exception()
 
 
+    @property
+    def seqno(self):
+        return self._rtp_header.sequence_number
+
+    @property
+    def timestamp(self):
+        return self._rtp_header.timestamp
+
+
 class SDPPacket(Packet):
     """An SDP packet.
 
diff --git a/tests/codecs/opus/fec/16_bit_48khz_60_fec_dropped.pcapng b/tests/codecs/opus/fec/16_bit_48khz_60_fec_dropped.pcapng
new file mode 100644
index 0000000..e1adf5c
--- /dev/null
+++ b/tests/codecs/opus/fec/16_bit_48khz_60_fec_dropped.pcapng
Binary files differ
diff --git a/tests/codecs/opus/fec/16_bit_48khz_60_fec_rtp.pcapng b/tests/codecs/opus/fec/16_bit_48khz_60_fec_rtp.pcapng
new file mode 100644
index 0000000..5d44fa3
--- /dev/null
+++ b/tests/codecs/opus/fec/16_bit_48khz_60_fec_rtp.pcapng
Binary files differ
diff --git a/tests/codecs/opus/fec/jitterbuffer/adaptive/configs/ast1/codecs.conf b/tests/codecs/opus/fec/jitterbuffer/adaptive/configs/ast1/codecs.conf
new file mode 100644
index 0000000..70fbab4
--- /dev/null
+++ b/tests/codecs/opus/fec/jitterbuffer/adaptive/configs/ast1/codecs.conf
@@ -0,0 +1,3 @@
+[opus]
+type=opus
+fec=1
diff --git a/tests/codecs/opus/fec/jitterbuffer/adaptive/configs/ast1/extensions.conf b/tests/codecs/opus/fec/jitterbuffer/adaptive/configs/ast1/extensions.conf
new file mode 100644
index 0000000..45f3e95
--- /dev/null
+++ b/tests/codecs/opus/fec/jitterbuffer/adaptive/configs/ast1/extensions.conf
@@ -0,0 +1,8 @@
+[default]
+
+exten => outbound_jitterbuf,1,NoOp()
+      same => n,Set(JITTERBUFFER(adaptive)=default) ;200,,200)
+      same => n,Return()
+
+exten => bob,1,Dial(PJSIP/${EXTEN},,b(default^outbound_jitterbuf^1))
+    same => n,Hangup()
diff --git a/tests/codecs/opus/fec/jitterbuffer/adaptive/configs/ast1/pjsip.conf b/tests/codecs/opus/fec/jitterbuffer/adaptive/configs/ast1/pjsip.conf
new file mode 100644
index 0000000..5aca992
--- /dev/null
+++ b/tests/codecs/opus/fec/jitterbuffer/adaptive/configs/ast1/pjsip.conf
@@ -0,0 +1,31 @@
+[global]
+type=global
+debug=yes
+
+[transport]
+type=transport
+protocol=udp
+bind=127.0.0.1:5060
+
+[endpoint_t](!)
+type=endpoint
+context=default
+direct_media=no
+allow=!all,ulaw
+
+[aor_t](!)
+type=aor
+max_contacts=1
+
+[alice](aor_t)
+contact=sip:alice at 127.0.0.1:5061
+
+[alice](endpoint_t)
+aors=alice
+
+[bob](aor_t)
+contact=sip:bob at 127.0.0.1:5062
+
+[bob](endpoint_t)
+aors=bob
+allow=!all,opus
diff --git a/tests/codecs/opus/fec/jitterbuffer/adaptive/sipp/invite.xml b/tests/codecs/opus/fec/jitterbuffer/adaptive/sipp/invite.xml
new file mode 100644
index 0000000..ccf7b60
--- /dev/null
+++ b/tests/codecs/opus/fec/jitterbuffer/adaptive/sipp/invite.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!DOCTYPE scenario SYSTEM "sipp.dtd">
+
+<scenario name="Invite with variables">
+  <send retrans="500">
+    <![CDATA[
+      INVITE sip:bob@[remote_ip]:[remote_port] SIP/2.0
+      Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
+      From: "alice" <sip:alice@[local_ip]:[local_port]>;tag=[call_number]
+      To: "bob" <sip:bob@[remote_ip]:[remote_port]>
+      Call-ID: [call_id]
+      CSeq: [cseq] INVITE
+      Contact: sip:alice@[local_ip]:[local_port]
+      Max-Forwards: 70
+      Content-Type: application/sdp
+      Content-Length: [len]
+
+      v=0
+      o=- 1324901698 1324901698 IN IP4 [local_ip]
+      s=-
+      c=IN IP[media_ip_type] [media_ip]
+      t=0 0
+      m=audio [media_port] RTP/AVP 0 101
+      a=sendrecv
+      a=rtpmap:0 PCMU/8000
+      a=rtpmap:101 telephone-event/8000
+    ]]>
+  </send>
+
+  <recv response="100" optional="true" />
+
+  <recv response="180" optional="true" />
+
+  <recv response="200" />
+
+  <send>
+    <![CDATA[
+      ACK sip:bob@[remote_ip]:[remote_port] SIP/2.0
+      Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
+      From: <sip:alice@[local_ip]>;tag=[call_number]
+      To: <sip:bob@[remote_ip]:[remote_port]>[peer_tag_param]
+      CSeq: [cseq] ACK
+      Call-ID: [call_id]
+      Contact: <sip:alice@[local_ip]>
+      Allow: INVITE, ACK, MESSAGE, BYE
+      Max-Forwards: 70
+      Content-Length: 0
+
+    ]]>
+  </send>
+
+  <recv request="BYE" crlf="true" />
+
+  <send retrans="500">
+    <![CDATA[
+      SIP/2.0 200 OK
+      [last_Via:]
+      [last_From:]
+      [last_To:];tag=[call_number]
+      [last_Call-ID:]
+      [last_CSeq:]
+      Contact: <sip:alice@[local_ip]:[local_port];transport=[transport]>
+      Allow: INVITE, ACK, MESSAGE, BYE
+      Content-Type: application/sdp
+      Content-Length: 0
+
+    ]]>
+  </send>
+
+</scenario>
diff --git a/tests/codecs/opus/fec/jitterbuffer/adaptive/sipp/invite_recv.xml b/tests/codecs/opus/fec/jitterbuffer/adaptive/sipp/invite_recv.xml
new file mode 100644
index 0000000..fecf311
--- /dev/null
+++ b/tests/codecs/opus/fec/jitterbuffer/adaptive/sipp/invite_recv.xml
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!DOCTYPE scenario SYSTEM "sipp.dtd">
+
+<scenario name="Invite receive">
+  <Global variables="remote_tag" />
+  <recv request="INVITE" crlf="true">
+      <!-- Save the from tag. We'll need it when we send our BYE -->
+      <action>
+          <ereg regexp="\"alice\"\\s+<sip:alice.*(;tag=.*)"
+              search_in="hdr"
+              header="From:"
+              check_it="true"
+              assign_to="1,remote_tag"/>
+      </action>
+  </recv>
+
+  <Reference variables="1" />
+
+  <send>
+    <![CDATA[
+
+      SIP/2.0 180 Ringing
+      [last_Via:]
+      [last_From:]
+      [last_To:];tag=[pid]SIPpTag01[call_number]
+      [last_Call-ID:]
+      [last_CSeq:]
+      Contact: <sip:[local_ip]:[local_port];transport=[transport]>
+      Content-Length: 0
+
+    ]]>
+  </send>
+
+  <send retrans="500">
+    <![CDATA[
+
+      SIP/2.0 200 OK
+      [last_Via:]
+      [last_From:]
+      [last_To:];tag=[pid]SIPpTag01[call_number]
+      [last_Call-ID:]
+      [last_CSeq:]
+      Contact: <sip:[local_ip]:[local_port];transport=[transport]>
+      Content-Type: application/sdp
+      Content-Length: [len]
+
+      v=0
+      o=- 1324901698 1324901698 IN IP[media_ip_type] [local_ip]
+      s=-
+      c=IN IP[media_ip_type] [media_ip]
+      t=0 0
+      m=audio [media_port] RTP/AVP 0 107
+      a=sendrecv
+      a=rtpmap:107 opus/48000/2
+      a=fmtp:107 useinbandfec=1
+      a=rtpmap:101 telephone-event/8000
+    ]]>
+  </send>
+
+  <recv request="ACK" rtd="true" crlf="true" />
+
+  <nop>
+    <action>
+      <exec play_pcap_audio="tests/codecs/opus/fec/16_bit_48khz_60_fec_dropped.pcapng"/>
+    </action>
+  </nop>
+
+  <!-- Pause while the pcap file is played -->
+  <pause milliseconds="65000"/>
+
+  <send retrans="500">
+    <![CDATA[
+
+      BYE sip:alice@[remote_ip]:[remote_port] SIP/2.0
+      Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
+      From: <sip:bob@[local_ip]:[local_port]>;tag=[pid]SIPpTag01[call_number]
+      To: <sip:alice@[remote_ip]:[remote_port]>[$remote_tag]
+      [last_Call-ID:]
+      CSeq: [cseq] BYE
+      Contact: sip:bob@[local_ip]:[local_port]
+      Max-Forwards: 70
+      Subject: Test
+      Content-Length: 0
+
+    ]]>
+  </send>
+
+  <recv response="200" />
+</scenario>
diff --git a/tests/codecs/opus/fec/jitterbuffer/adaptive/test-config.yaml b/tests/codecs/opus/fec/jitterbuffer/adaptive/test-config.yaml
new file mode 100644
index 0000000..37563c6
--- /dev/null
+++ b/tests/codecs/opus/fec/jitterbuffer/adaptive/test-config.yaml
@@ -0,0 +1,43 @@
+testinfo:
+    summary: 'Test opus FEC handling with an adaptive jitter buffer'
+    description: |
+        'Establishes a call between two endpoints alice and bob. Once the call
+         is up start RTP playback from a pcap file that has missing packets in
+         it. If codec_opus handles the FEC correctly alice should receive RTP
+         with no dropped packets, in the correct order and correct timestamps.'
+
+test-modules:
+    add-to-search-path:
+        - 'tests/codecs'
+    test-object:
+        config-section: test-object-config
+        typename: 'sipp.SIPpTestCase'
+    modules:
+        -   config-section: rtp-analyzer-config
+            typename: 'rtp_analyzer.Analyzer'
+
+test-object-config:
+    reactor-timeout: 90
+    test-iterations:
+        -
+            scenarios:
+                - { 'key-args': { 'scenario':'invite_recv.xml', '-p':'5062', '-mp':'16002' } }
+                  # Specify the RTP port so the rtp analyzer knows where to
+                  # listen for packets. The analyzer defaults to 6001.
+                - { 'key-args': { 'scenario':'invite.xml', '-p':'5061', '-mp':'16001' } }
+
+rtp-analyzer-config:
+    # Expected time difference for ulaw
+    time_diff: 160
+
+properties:
+    minversion: ['13.14.0', '14.3.0']
+    dependencies:
+        - python: 'twisted'
+        - python: 'yappcap'
+        - asterisk : 'app_dial'
+        - asterisk : 'res_pjsip'
+        - asterisk : 'codec_opus'
+    tags:
+        - pjsip
+        - codec
diff --git a/tests/codecs/opus/fec/jitterbuffer/fixed/configs/ast1/codecs.conf b/tests/codecs/opus/fec/jitterbuffer/fixed/configs/ast1/codecs.conf
new file mode 100644
index 0000000..70fbab4
--- /dev/null
+++ b/tests/codecs/opus/fec/jitterbuffer/fixed/configs/ast1/codecs.conf
@@ -0,0 +1,3 @@
+[opus]
+type=opus
+fec=1
diff --git a/tests/codecs/opus/fec/jitterbuffer/fixed/configs/ast1/extensions.conf b/tests/codecs/opus/fec/jitterbuffer/fixed/configs/ast1/extensions.conf
new file mode 100644
index 0000000..1ad67f7
--- /dev/null
+++ b/tests/codecs/opus/fec/jitterbuffer/fixed/configs/ast1/extensions.conf
@@ -0,0 +1,8 @@
+[default]
+
+exten => outbound_jitterbuf,1,NoOp()
+      same => n,Set(JITTERBUFFER(fixed)=default)
+      same => n,Return()
+
+exten => bob,1,Dial(PJSIP/${EXTEN},,b(default^outbound_jitterbuf^1))
+    same => n,Hangup()
diff --git a/tests/codecs/opus/fec/jitterbuffer/fixed/configs/ast1/pjsip.conf b/tests/codecs/opus/fec/jitterbuffer/fixed/configs/ast1/pjsip.conf
new file mode 100644
index 0000000..5aca992
--- /dev/null
+++ b/tests/codecs/opus/fec/jitterbuffer/fixed/configs/ast1/pjsip.conf
@@ -0,0 +1,31 @@
+[global]
+type=global
+debug=yes
+
+[transport]
+type=transport
+protocol=udp
+bind=127.0.0.1:5060
+
+[endpoint_t](!)
+type=endpoint
+context=default
+direct_media=no
+allow=!all,ulaw
+
+[aor_t](!)
+type=aor
+max_contacts=1
+
+[alice](aor_t)
+contact=sip:alice at 127.0.0.1:5061
+
+[alice](endpoint_t)
+aors=alice
+
+[bob](aor_t)
+contact=sip:bob at 127.0.0.1:5062
+
+[bob](endpoint_t)
+aors=bob
+allow=!all,opus
diff --git a/tests/codecs/opus/fec/jitterbuffer/fixed/sipp/invite.xml b/tests/codecs/opus/fec/jitterbuffer/fixed/sipp/invite.xml
new file mode 100644
index 0000000..ccf7b60
--- /dev/null
+++ b/tests/codecs/opus/fec/jitterbuffer/fixed/sipp/invite.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!DOCTYPE scenario SYSTEM "sipp.dtd">
+
+<scenario name="Invite with variables">
+  <send retrans="500">
+    <![CDATA[
+      INVITE sip:bob@[remote_ip]:[remote_port] SIP/2.0
+      Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
+      From: "alice" <sip:alice@[local_ip]:[local_port]>;tag=[call_number]
+      To: "bob" <sip:bob@[remote_ip]:[remote_port]>
+      Call-ID: [call_id]
+      CSeq: [cseq] INVITE
+      Contact: sip:alice@[local_ip]:[local_port]
+      Max-Forwards: 70
+      Content-Type: application/sdp
+      Content-Length: [len]
+
+      v=0
+      o=- 1324901698 1324901698 IN IP4 [local_ip]
+      s=-
+      c=IN IP[media_ip_type] [media_ip]
+      t=0 0
+      m=audio [media_port] RTP/AVP 0 101
+      a=sendrecv
+      a=rtpmap:0 PCMU/8000
+      a=rtpmap:101 telephone-event/8000
+    ]]>
+  </send>
+
+  <recv response="100" optional="true" />
+
+  <recv response="180" optional="true" />
+
+  <recv response="200" />
+
+  <send>
+    <![CDATA[
+      ACK sip:bob@[remote_ip]:[remote_port] SIP/2.0
+      Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
+      From: <sip:alice@[local_ip]>;tag=[call_number]
+      To: <sip:bob@[remote_ip]:[remote_port]>[peer_tag_param]
+      CSeq: [cseq] ACK
+      Call-ID: [call_id]
+      Contact: <sip:alice@[local_ip]>
+      Allow: INVITE, ACK, MESSAGE, BYE
+      Max-Forwards: 70
+      Content-Length: 0
+
+    ]]>
+  </send>
+
+  <recv request="BYE" crlf="true" />
+
+  <send retrans="500">
+    <![CDATA[
+      SIP/2.0 200 OK
+      [last_Via:]
+      [last_From:]
+      [last_To:];tag=[call_number]
+      [last_Call-ID:]
+      [last_CSeq:]
+      Contact: <sip:alice@[local_ip]:[local_port];transport=[transport]>
+      Allow: INVITE, ACK, MESSAGE, BYE
+      Content-Type: application/sdp
+      Content-Length: 0
+
+    ]]>
+  </send>
+
+</scenario>
diff --git a/tests/codecs/opus/fec/jitterbuffer/fixed/sipp/invite_recv.xml b/tests/codecs/opus/fec/jitterbuffer/fixed/sipp/invite_recv.xml
new file mode 100644
index 0000000..fecf311
--- /dev/null
+++ b/tests/codecs/opus/fec/jitterbuffer/fixed/sipp/invite_recv.xml
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!DOCTYPE scenario SYSTEM "sipp.dtd">
+
+<scenario name="Invite receive">
+  <Global variables="remote_tag" />
+  <recv request="INVITE" crlf="true">
+      <!-- Save the from tag. We'll need it when we send our BYE -->
+      <action>
+          <ereg regexp="\"alice\"\\s+<sip:alice.*(;tag=.*)"
+              search_in="hdr"
+              header="From:"
+              check_it="true"
+              assign_to="1,remote_tag"/>
+      </action>
+  </recv>
+
+  <Reference variables="1" />
+
+  <send>
+    <![CDATA[
+
+      SIP/2.0 180 Ringing
+      [last_Via:]
+      [last_From:]
+      [last_To:];tag=[pid]SIPpTag01[call_number]
+      [last_Call-ID:]
+      [last_CSeq:]
+      Contact: <sip:[local_ip]:[local_port];transport=[transport]>
+      Content-Length: 0
+
+    ]]>
+  </send>
+
+  <send retrans="500">
+    <![CDATA[
+
+      SIP/2.0 200 OK
+      [last_Via:]
+      [last_From:]
+      [last_To:];tag=[pid]SIPpTag01[call_number]
+      [last_Call-ID:]
+      [last_CSeq:]
+      Contact: <sip:[local_ip]:[local_port];transport=[transport]>
+      Content-Type: application/sdp
+      Content-Length: [len]
+
+      v=0
+      o=- 1324901698 1324901698 IN IP[media_ip_type] [local_ip]
+      s=-
+      c=IN IP[media_ip_type] [media_ip]
+      t=0 0
+      m=audio [media_port] RTP/AVP 0 107
+      a=sendrecv
+      a=rtpmap:107 opus/48000/2
+      a=fmtp:107 useinbandfec=1
+      a=rtpmap:101 telephone-event/8000
+    ]]>
+  </send>
+
+  <recv request="ACK" rtd="true" crlf="true" />
+
+  <nop>
+    <action>
+      <exec play_pcap_audio="tests/codecs/opus/fec/16_bit_48khz_60_fec_dropped.pcapng"/>
+    </action>
+  </nop>
+
+  <!-- Pause while the pcap file is played -->
+  <pause milliseconds="65000"/>
+
+  <send retrans="500">
+    <![CDATA[
+
+      BYE sip:alice@[remote_ip]:[remote_port] SIP/2.0
+      Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
+      From: <sip:bob@[local_ip]:[local_port]>;tag=[pid]SIPpTag01[call_number]
+      To: <sip:alice@[remote_ip]:[remote_port]>[$remote_tag]
+      [last_Call-ID:]
+      CSeq: [cseq] BYE
+      Contact: sip:bob@[local_ip]:[local_port]
+      Max-Forwards: 70
+      Subject: Test
+      Content-Length: 0
+
+    ]]>
+  </send>
+
+  <recv response="200" />
+</scenario>
diff --git a/tests/codecs/opus/fec/jitterbuffer/fixed/test-config.yaml b/tests/codecs/opus/fec/jitterbuffer/fixed/test-config.yaml
new file mode 100644
index 0000000..5fa2f8f
--- /dev/null
+++ b/tests/codecs/opus/fec/jitterbuffer/fixed/test-config.yaml
@@ -0,0 +1,43 @@
+testinfo:
+    summary: 'Test opus FEC handling with a fixed jitter buffer'
+    description: |
+        'Establishes a call between two endpoints alice and bob. Once the call
+         is up start RTP playback from a pcap file that has missing packets in
+         it. If codec_opus handles the FEC correctly alice should receive RTP
+         with no dropped packets, in the correct order and correct timestamps.'
+
+test-modules:
+    add-to-search-path:
+        - 'tests/codecs'
+    test-object:
+        config-section: test-object-config
+        typename: 'sipp.SIPpTestCase'
+    modules:
+        -   config-section: rtp-analyzer-config
+            typename: 'rtp_analyzer.Analyzer'
+
+test-object-config:
+    reactor-timeout: 90
+    test-iterations:
+        -
+            scenarios:
+                - { 'key-args': { 'scenario':'invite_recv.xml', '-p':'5062', '-mp':'16002' } }
+                  # Specify the RTP port so the rtp analyzer knows where to
+                  # listen for packets. The analyzer defaults to 6001.
+                - { 'key-args': { 'scenario':'invite.xml', '-p':'5061', '-mp':'16001' } }
+
+rtp-analyzer-config:
+    # Expected time difference for ulaw
+    time_diff: 160
+
+properties:
+    minversion: ['13.14.0', '14.3.0']
+    dependencies:
+        - python: 'twisted'
+        - python: 'yappcap'
+        - asterisk : 'app_dial'
+        - asterisk : 'res_pjsip'
+        - asterisk : 'codec_opus'
+    tags:
+        - pjsip
+        - codec
diff --git a/tests/codecs/opus/fec/jitterbuffer/tests.yaml b/tests/codecs/opus/fec/jitterbuffer/tests.yaml
new file mode 100644
index 0000000..5074146
--- /dev/null
+++ b/tests/codecs/opus/fec/jitterbuffer/tests.yaml
@@ -0,0 +1,4 @@
+# Enter tests here in the order they should be considered for execution:
+tests:
+    - test: 'adaptive'
+    - test: 'fixed'
diff --git a/tests/codecs/opus/fec/no_jitterbuffer/configs/ast1/codecs.conf b/tests/codecs/opus/fec/no_jitterbuffer/configs/ast1/codecs.conf
new file mode 100644
index 0000000..70fbab4
--- /dev/null
+++ b/tests/codecs/opus/fec/no_jitterbuffer/configs/ast1/codecs.conf
@@ -0,0 +1,3 @@
+[opus]
+type=opus
+fec=1
diff --git a/tests/codecs/opus/fec/no_jitterbuffer/configs/ast1/extensions.conf b/tests/codecs/opus/fec/no_jitterbuffer/configs/ast1/extensions.conf
new file mode 100644
index 0000000..8ee5ba9
--- /dev/null
+++ b/tests/codecs/opus/fec/no_jitterbuffer/configs/ast1/extensions.conf
@@ -0,0 +1,4 @@
+[default]
+
+exten => bob,1,Dial(PJSIP/${EXTEN})
+    same => n,Hangup()
diff --git a/tests/codecs/opus/fec/no_jitterbuffer/configs/ast1/pjsip.conf b/tests/codecs/opus/fec/no_jitterbuffer/configs/ast1/pjsip.conf
new file mode 100644
index 0000000..5aca992
--- /dev/null
+++ b/tests/codecs/opus/fec/no_jitterbuffer/configs/ast1/pjsip.conf
@@ -0,0 +1,31 @@
+[global]
+type=global
+debug=yes
+
+[transport]
+type=transport
+protocol=udp
+bind=127.0.0.1:5060
+
+[endpoint_t](!)
+type=endpoint
+context=default
+direct_media=no
+allow=!all,ulaw
+
+[aor_t](!)
+type=aor
+max_contacts=1
+
+[alice](aor_t)
+contact=sip:alice at 127.0.0.1:5061
+
+[alice](endpoint_t)
+aors=alice
+
+[bob](aor_t)
+contact=sip:bob at 127.0.0.1:5062
+
+[bob](endpoint_t)
+aors=bob
+allow=!all,opus
diff --git a/tests/codecs/opus/fec/no_jitterbuffer/sipp/invite.xml b/tests/codecs/opus/fec/no_jitterbuffer/sipp/invite.xml
new file mode 100644
index 0000000..ccf7b60
--- /dev/null
+++ b/tests/codecs/opus/fec/no_jitterbuffer/sipp/invite.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!DOCTYPE scenario SYSTEM "sipp.dtd">
+
+<scenario name="Invite with variables">
+  <send retrans="500">
+    <![CDATA[
+      INVITE sip:bob@[remote_ip]:[remote_port] SIP/2.0
+      Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
+      From: "alice" <sip:alice@[local_ip]:[local_port]>;tag=[call_number]
+      To: "bob" <sip:bob@[remote_ip]:[remote_port]>
+      Call-ID: [call_id]
+      CSeq: [cseq] INVITE
+      Contact: sip:alice@[local_ip]:[local_port]
+      Max-Forwards: 70
+      Content-Type: application/sdp
+      Content-Length: [len]
+
+      v=0
+      o=- 1324901698 1324901698 IN IP4 [local_ip]
+      s=-
+      c=IN IP[media_ip_type] [media_ip]
+      t=0 0
+      m=audio [media_port] RTP/AVP 0 101
+      a=sendrecv
+      a=rtpmap:0 PCMU/8000
+      a=rtpmap:101 telephone-event/8000
+    ]]>
+  </send>
+
+  <recv response="100" optional="true" />
+
+  <recv response="180" optional="true" />
+
+  <recv response="200" />
+
+  <send>
+    <![CDATA[
+      ACK sip:bob@[remote_ip]:[remote_port] SIP/2.0
+      Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
+      From: <sip:alice@[local_ip]>;tag=[call_number]
+      To: <sip:bob@[remote_ip]:[remote_port]>[peer_tag_param]
+      CSeq: [cseq] ACK
+      Call-ID: [call_id]
+      Contact: <sip:alice@[local_ip]>
+      Allow: INVITE, ACK, MESSAGE, BYE
+      Max-Forwards: 70
+      Content-Length: 0
+
+    ]]>
+  </send>
+
+  <recv request="BYE" crlf="true" />
+
+  <send retrans="500">
+    <![CDATA[
+      SIP/2.0 200 OK
+      [last_Via:]
+      [last_From:]
+      [last_To:];tag=[call_number]
+      [last_Call-ID:]
+      [last_CSeq:]
+      Contact: <sip:alice@[local_ip]:[local_port];transport=[transport]>
+      Allow: INVITE, ACK, MESSAGE, BYE
+      Content-Type: application/sdp
+      Content-Length: 0
+
+    ]]>
+  </send>
+
+</scenario>
diff --git a/tests/codecs/opus/fec/no_jitterbuffer/sipp/invite_recv.xml b/tests/codecs/opus/fec/no_jitterbuffer/sipp/invite_recv.xml
new file mode 100644
index 0000000..fecf311
--- /dev/null
+++ b/tests/codecs/opus/fec/no_jitterbuffer/sipp/invite_recv.xml
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!DOCTYPE scenario SYSTEM "sipp.dtd">
+
+<scenario name="Invite receive">
+  <Global variables="remote_tag" />
+  <recv request="INVITE" crlf="true">
+      <!-- Save the from tag. We'll need it when we send our BYE -->
+      <action>
+          <ereg regexp="\"alice\"\\s+<sip:alice.*(;tag=.*)"
+              search_in="hdr"
+              header="From:"
+              check_it="true"
+              assign_to="1,remote_tag"/>
+      </action>
+  </recv>
+
+  <Reference variables="1" />
+
+  <send>
+    <![CDATA[
+
+      SIP/2.0 180 Ringing
+      [last_Via:]
+      [last_From:]
+      [last_To:];tag=[pid]SIPpTag01[call_number]
+      [last_Call-ID:]
+      [last_CSeq:]
+      Contact: <sip:[local_ip]:[local_port];transport=[transport]>
+      Content-Length: 0
+
+    ]]>
+  </send>
+
+  <send retrans="500">
+    <![CDATA[
+
+      SIP/2.0 200 OK
+      [last_Via:]
+      [last_From:]
+      [last_To:];tag=[pid]SIPpTag01[call_number]
+      [last_Call-ID:]
+      [last_CSeq:]
+      Contact: <sip:[local_ip]:[local_port];transport=[transport]>
+      Content-Type: application/sdp
+      Content-Length: [len]
+
+      v=0
+      o=- 1324901698 1324901698 IN IP[media_ip_type] [local_ip]
+      s=-
+      c=IN IP[media_ip_type] [media_ip]
+      t=0 0
+      m=audio [media_port] RTP/AVP 0 107
+      a=sendrecv
+      a=rtpmap:107 opus/48000/2
+      a=fmtp:107 useinbandfec=1
+      a=rtpmap:101 telephone-event/8000
+    ]]>
+  </send>
+
+  <recv request="ACK" rtd="true" crlf="true" />
+
+  <nop>
+    <action>
+      <exec play_pcap_audio="tests/codecs/opus/fec/16_bit_48khz_60_fec_dropped.pcapng"/>
+    </action>
+  </nop>
+
+  <!-- Pause while the pcap file is played -->
+  <pause milliseconds="65000"/>
+
+  <send retrans="500">
+    <![CDATA[
+
+      BYE sip:alice@[remote_ip]:[remote_port] SIP/2.0
+      Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
+      From: <sip:bob@[local_ip]:[local_port]>;tag=[pid]SIPpTag01[call_number]
+      To: <sip:alice@[remote_ip]:[remote_port]>[$remote_tag]
+      [last_Call-ID:]
+      CSeq: [cseq] BYE
+      Contact: sip:bob@[local_ip]:[local_port]
+      Max-Forwards: 70
+      Subject: Test
+      Content-Length: 0
+
+    ]]>
+  </send>
+
+  <recv response="200" />
+</scenario>
diff --git a/tests/codecs/opus/fec/no_jitterbuffer/test-config.yaml b/tests/codecs/opus/fec/no_jitterbuffer/test-config.yaml
new file mode 100644
index 0000000..e747291
--- /dev/null
+++ b/tests/codecs/opus/fec/no_jitterbuffer/test-config.yaml
@@ -0,0 +1,43 @@
+testinfo:
+    summary: 'Test opus FEC handling'
+    description: |
+        'Establishes a call between two endpoints alice and bob. Once the call
+         is up start RTP playback from a pcap file that has missing packets in
+         it. If codec_opus handles the FEC correctly alice should receive RTP
+         with no dropped packets, in the correct order and correct timestamps.'
+
+test-modules:
+    add-to-search-path:
+        - 'tests/codecs'
+    test-object:
+        config-section: test-object-config
+        typename: 'sipp.SIPpTestCase'
+    modules:
+        -   config-section: rtp-analyzer-config
+            typename: 'rtp_analyzer.Analyzer'
+
+test-object-config:
+    reactor-timeout: 90
+    test-iterations:
+        -
+            scenarios:
+                - { 'key-args': { 'scenario':'invite_recv.xml', '-p':'5062', '-mp':'16002' } }
+                  # Specify the RTP port so the rtp analyzer knows where to
+                  # listen for packets. The analyzer defaults to 6001.
+                - { 'key-args': { 'scenario':'invite.xml', '-p':'5061', '-mp':'16001' } }
+
+rtp-analyzer-config:
+    # Expected time difference for ulaw
+    time_diff: 160
+
+properties:
+    minversion: ['13.14.0', '14.3.0']
+    dependencies:
+        - python: 'twisted'
+        - python: 'yappcap'
+        - asterisk : 'app_dial'
+        - asterisk : 'res_pjsip'
+        - asterisk : 'codec_opus'
+    tags:
+        - pjsip
+        - codec
diff --git a/tests/codecs/opus/fec/tests.yaml b/tests/codecs/opus/fec/tests.yaml
new file mode 100644
index 0000000..5c43d58
--- /dev/null
+++ b/tests/codecs/opus/fec/tests.yaml
@@ -0,0 +1,4 @@
+# Enter tests here in the order they should be considered for execution:
+tests:
+    - test: 'no_jitterbuffer'
+    - dir: 'jitterbuffer'
diff --git a/tests/codecs/opus/tests.yaml b/tests/codecs/opus/tests.yaml
index cf5152d..7f393a0 100644
--- a/tests/codecs/opus/tests.yaml
+++ b/tests/codecs/opus/tests.yaml
@@ -3,3 +3,4 @@
     - test: 'encode'
     - test: 'decode'
     - test: 'fmtp_with_spaces'
+    - dir: 'fec'
diff --git a/tests/codecs/rtp_analyzer.py b/tests/codecs/rtp_analyzer.py
new file mode 100644
index 0000000..e20cde3
--- /dev/null
+++ b/tests/codecs/rtp_analyzer.py
@@ -0,0 +1,120 @@
+#!/usr/bin/env python
+'''
+Copyright (C) 2016, Digium, Inc.
+Kevin Harwell <kharwell at digium.com>
+
+This program is free software, distributed under the terms of
+the GNU General Public License Version 2.
+'''
+
+import sys
+import logging
+from twisted.internet import reactor
+
+sys.path.append("lib/python")
+
+import pcap
+
+LOGGER = logging.getLogger(__name__)
+
+
+class Analyzer(object):
+    """Pluggable module that analyzes incoming RTP packets on a given port
+    and verifies the sequence and timestamp difference between each packet.
+
+    Configuration options:
+      port: the port RTP is expected to be received on
+      time_diff: expected difference between RTP timestamps
+      num_packets: number of expected packets
+      device: device to listen on
+      snaplen: snap length
+      bfp-filter: packet filter
+      buffer-size: size of the packet buffer
+      debug-packets: whether or not to output packets to the log
+    """
+
+    def __init__(self, module_config, test_object):
+        """Constructor
+
+        Keyword Arguments:
+        module_config The module configuration for this pluggable module
+        test_object   The object we will attach to
+        """
+
+        self._test_object = test_object
+
+        self._last_seqno = 0
+        self._last_timestamp = 0
+        self._num_packets = 0
+
+        if not module_config:
+            module_config = {}
+
+        self._port = int(module_config.get('port', 16001))
+        self._time_diff = int(module_config.get('time_diff', 160))
+        self._total_packets = int(module_config.get('total_packets', 0))
+
+        pcap_defaults = {
+            'device': 'lo',
+            'snaplen': 2000,
+            'bpf-filter': '',
+            'debug-packets': False,
+            'buffer-size': 4194304,
+            'register-observer': True
+        }
+
+        pcap_config = dict(pcap_defaults.items() + module_config.items())
+
+        self._sniffer = pcap.VOIPListener(pcap_config, test_object)
+        self._sniffer.add_callback('RTP', self._handle_rtp)
+
+        if self._total_packets:
+            self._packets_failure = self._test_object.create_fail_token(
+                "Did not receive expected number of packets {0}".format(
+                    self._total_packets))
+
+    def _handle_rtp(self, packet):
+        """Make sure the time differences between RTP packets is correct."""
+
+        def __handle_error(message):
+            """Handle errors"""
+
+            LOGGER.error(message)
+            self._test_object.set_passed(False)
+            self._test_object.stop_reactor()
+
+        if packet.dst_port != self._port:
+            return
+
+        LOGGER.debug("!#### num packets={0}, seqno={1}, last_seq={2}".format(self._num_packets, packet.seqno, self._last_seqno))
+
+        # Ignore the first few packets (There seems to be a bug in the packet
+        # capture code where it sometimes drops a few packets near start up).
+        if self._num_packets > 40:
+            # Fail if packets are out of order
+            if packet.seqno - self._last_seqno != 1:
+                __handle_error("Bad sequence current={0}, last={1}".format(
+                    packet.seqno, self._last_seqno))
+                return
+
+            # Fail if the time difference does not also match
+            if packet.timestamp - self._last_timestamp != self._time_diff:
+                __handle_error("Bad timestamp current={0}, last={1}".format(
+                    packet.timestamp, self._last_timestamp))
+                return
+
+        self._num_packets += (packet.seqno - self._last_seqno
+                              if self._last_seqno else 1)
+
+        self._last_seqno = packet.seqno
+        self._last_timestamp = packet.timestamp
+
+        if self._total_packets:
+            # Remove the fail token when all packets are received
+            if (self._num_packets == self._total_packets):
+                self._test_object.remove_fail_token(self._packets_failure)
+            # Fail if more packets are received than expected
+            elif self._num_packets > self._total_packets:
+                __handle_error("Expected {0} packets received {1}".format(
+                    self._total_packets, self._num_packets))
+

-- 
To view, visit https://gerrit.asterisk.org/4729
To unsubscribe, visit https://gerrit.asterisk.org/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: Ia01a0432cc23071836d594c7c3dc46709c9880bf
Gerrit-PatchSet: 2
Gerrit-Project: testsuite
Gerrit-Branch: master
Gerrit-Owner: Kevin Harwell <kharwell at digium.com>
Gerrit-Reviewer: Anonymous Coward #1000019
Gerrit-Reviewer: George Joseph <gjoseph at digium.com>
Gerrit-Reviewer: Joshua Colp <jcolp at digium.com>
Gerrit-Reviewer: Mark Michelson <mmichelson at digium.com>



More information about the asterisk-code-review mailing list