title = “RTP Stream Matching” weight = 4 +++

Introduction

The fetch_and_match method from TestRtpLuaAgent Lua API is used to validate the RTP packets which have been received from the remote endpoint.

The method will:

Matching API

.fetch_and_match [Asynchronous]

The fetch_and_match method requests the TestRtpApp to send us all of the RTP packets which where received by our local endpoint since the most recent previous call to fetch_and_match or (more typically) since the initial registration of our local UDP port.

The agent will wait (asynchronous) for the stashed RTP packets to be supplied to us, and then will iterate and perform the testing logic according to the provided list of expected content.

The match will nearly always involve “fuzzy” timing boundaries, since there is inevitably a variable timing latency associated with the SIP signalling to set up the RTP stream. Hence the “expected” matching rules have a minimum/maximum timing window for each element, as described here.

The arguments to the method are:

Argument Type Description
expected Object [Required] A container with subfields describing the expected RTP packets.
encoding 0 - 255 [Required] The event type number, indicating which key was pressed/released, typically as per RFC 4733.
encoding PCMU / PCMA / AMR / AMR-WB [Required] The encoding of the RTP audio stream.
This must be one of the supported encoding constants, being PCMU (ITU-T REC G.711 µ-Law), PCMA (ITU-T REC G.711 a-Law), AMR (3GPP TS 26.073), AMR-WB (ITU-T REC G.722.2)
fragments Array of Object [Required] Array of "expected" fragments in sequence, each one being either a files fragment, or a "silence" fragment with silence_min and silence_max attributes.
files Array of String An array of filenames which are appended together to make the expected source for this fragment.
Each filename may include a relative filepath within the audio_dir parameter of TestRtpLuaAgent.
Each filename must include the file suffix.
(Default = This fragment is not a "files" fragment).
min Float This value applies only when files is present for this fragment.
The minimum audio duration (in seconds) of stream data expected to match from these appended files.
(Default = The entire file content should be present in the stream).
max Float This value applies only when files is present for this fragment.
The maximum audio duration (in seconds) of stream data expected to match from these appended files.
(Default = Up to the the entire file content may be present in the stream).
silence_min Float This value applies only when files is not present for this fragment.
The minimum audio duration (in seconds) of silence expected to receive for this fragment.
(Default = The silence may be as short as zero seconds).
silence_max Float [Required] This value is mandatory when files is not present for this fragment.
The maximum audio duration (in seconds) of silence expected to receive for this fragment.
amr Object Container for additional AMR mode changes that require validation.
The expected AMR mode changes are tested against the entire stream, independent of the audio fragment validation.
(Default = do not perform any validation of AMR mode changes).
modes Array of Object Array of object each of which describe an AMR mode change period to validate.
(Default = do not perform any validation of AMR mode changes).
mode 0 - 8 [Required] The AMR mode number which applies for this period.
min Float The minimum duration (in seconds) that we expect this AMR period to apply within the stream.
(Default = the AMR mode has no mimimum duration).
max Float The maximum duration (in seconds) that we expect this AMR period to apply within the stream.
(Default = the AMR mode has no maximum duration).

Example negotiating AMR-WB with mode changes, and validating those changes:

-- Attempt to establish a RTP listener that we can direct RTP packets at and store for comparison.
local register_result = truo.register ()

-- Static for our call.
local endpoints = tsuo.default_endpoints ({ local_rtp_port = register_result.local_port })
local calling_party = '665566'
local called_party = '4000'
local encoding = 'AMR/8000'

-- Get a SIP outcall context from our helper library.
local context = tsuo.invite_context (endpoints, calling_party, called_party)

-- Construct the SDP.
local sdp_session_id = os.time () % 10000
local sdp_session_version = math.random (1000) - 1
local sdp_offer =
"v=0\
o=" .. calling_party .. " " .. sdp_session_id .. " " .. sdp_session_version .. " IN IP4 " .. endpoints.local_rtp_ip .. "\
s=-\
c=IN IP4 " .. endpoints.local_rtp_ip .. "\
t=0 0\
m=audio " .. endpoints.local_rtp_port .. " RTP/AVP 96 102\
a=rtpmap:96 AMR/8000\
a=fmtp:96 mode-set=2,4,7;mode-change-neighbor=1;mode-change-period=2;octet-align=1\
a=rtpmap:102 telephone-event/8000"

-- Construct and Send INVITE Request.
tsuo.invite_send_request (context, sdp_offer)

-- Expect Trying & Ringing.
tsuo.invite_expect_response (context, 100, "Trying", { ['User-Agent'] = "N-Squared LHO" })
tsuo.invite_expect_response (context, 180, "Ringing")
tv = match.elapsed ("Ring Notification (immediate)", tv, 0.0)

-- Expect INVITE Answer Response (200 OK) after 2 seconds.
local invite_response = tsuo.invite_expect_response (context, 200, "OK", nil, { sdp_media = tsuo.SDP_MEDIA_N2_AMRNB_OA })
tv = match.elapsed ("Call Answered (2.0s)", tv, 1.99)

-- Request our RTP tester to connect to the far-end IP/port.  Now we can send RTP as well as receive it.
truo.connect (invite_response.sdp.connection.ip4.address, invite_response.sdp.media.audio.port)

-- Send an AMR CMR before we ACK to ensure streaming uses the desired mode.
truo.send_amr_cmr (96, 2)

-- Try to ENSURE the CMR goes on the wire before the ACK. Without this,
-- sometimes the ACK wins.
n2svcd.wait (0.02)

-- ACK the 200.
tsuo.invite_send_2xx_ack (context)

-- Voice call in progress.
-- The "Goodbye" announcement is 0.65 seconds and it is played twice before hangup.

-- Request to go to mode 7. The mode change restrictions will cause us to
-- send two mode 4 frames before our requested change will be applied.
n2svcd.wait (0.2)
truo.send_amr_cmr (96, 7)

-- ...and back to mode 2.
n2svcd.wait (0.2)
truo.send_amr_cmr (96, 2)

n2svcd.wait (0.1)

-- Send BYE from our side.
tsuo.bye_send_request (context)
tv = match.elapsed ("Send BYE (0.5s)", tv, 0.5)

-- Expect immediate acknowledgement.
tsuo.bye_expect_response (context, 200, "OK")
tv = match.elapsed ("BYE Confirmed (immediate)", tv, 0.0)

-- Trigger a collection of RTP packets and match against the expected configuration.
truo.fetch_and_match ({
    encoding = encoding, 
    fragments = {
        { files = { 'English/announcements/Goodbye.amr' }, min = 0.5, max = 0.65 }, 
        { silence_min = 0, silence_max = 0.1 }

    }, amr = {
        modes = {
            { mode = 2, min = 0.15, max = 0.25 },
            { mode = 4, min = 0.04, max = 0.04 },
            { mode = 7, min = 0.15, max = 0.25 },
            { mode = 4, min = 0.04, max = 0.04 },
            { mode = 2 }
        }
    }
})