{"id":9682,"date":"2026-05-11T16:45:00","date_gmt":"2026-05-11T08:45:00","guid":{"rendered":"http:\/\/www.freesip.org\/?p=9682"},"modified":"2026-05-11T16:45:14","modified_gmt":"2026-05-11T08:45:14","slug":"sipp-media-handling","status":"publish","type":"post","link":"https:\/\/www.freesip.org\/?p=9682","title":{"rendered":"SIPp Media Handling"},"content":{"rendered":"\n<h2 class=\"wp-block-heading\">SIPp Media Handling Workflow<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">SIPp&#8217;s four media handling modes: RTP Streaming [1a-1f] for file playback, PCAP Playback [2a-2f] for replaying captures, RTP Echo [3a-3e] for reflecting media, and DTMF Generation [4a-4e] for RFC 4733 tones. Key entry points are scenario parsing in scenario.cpp [1a,2a,4a], SDP parsing in call.cpp [2b], and the core engines in rtpstream.cpp [1c-1f,3a-3e], prepare_pcap.c [2c,4b-4d], and send_packets.c [2d-2f,4e].<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">1. RTP Streaming File Playback<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Triggers when scenario executes &#8211; plays audio files over RTP using threaded playback engine<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">1a. Parse rtp_stream XML action (<code>scenario.cpp:1686<\/code>)<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Scenario parser extracts rtp_stream action and sets action type based on command (play, pause, resume, pattern)<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>else if ((cptr = xp_get_value(\"rtp_stream\"))) {<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">1b. Cache audio file in memory (<code>actions.cpp:885<\/code>)<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Action execution loads file into global cache once, configures codec parameters based on payload type<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>if (rtpstream_cache_file(\n            M_rtpstream_actinfo.filename,\n            pattern_mode \/* 0: FILE - 1: PATTERN *\/,\n            M_rtpstream_actinfo.pattern_id,\n            M_rtpstream_actinfo.bytes_per_packet,\n            stream_type) &lt; 0) {<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">1c. Bind local RTP socket (<code>rtpstream.cpp:2414<\/code>)<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">rtpstream_play() allocates UDP port from min_rtp_port range, binds RTP\/RTCP sockets<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>rtpstream_get_local_audioport(callinfo);<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">1d. Signal playback start (<code>rtpstream.cpp:2436<\/code>)<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Sets TI_PLAYFILE flag to trigger playback thread to begin sending packets<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>taskinfo-&gt;flags |= TI_PLAYFILE;<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">1e. Create playback thread (<code>rtpstream.cpp:1456<\/code>)<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">rtpstream_start_task() spawns new pthread if no threads have capacity, adds task to thread pool<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>if (pthread_create(&amp;threadID, nullptr, rtpstream_playback_thread, threaddata)) {<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">1f. Send RTP packet (<code>rtpstream.cpp:728<\/code>)<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Playback thread builds RTP header with seq\/timestamp\/SSRC, copies payload, sends via UDP socket<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>rc = send(taskinfo-&gt;audio_rtp_socket, audio_out.data(), audio_out.size(), 0);<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">2. PCAP Playback<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Triggers when scenario executes &#8211; replays pre-recorded RTP using raw sockets<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">2a. Parse play_pcap_audio action (<code>scenario.cpp:1657<\/code>)<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Scenario parser sets E_AT_PLAY_PCAP_AUDIO action type with PCAP filename<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>} else if ((ptr = xp_get_keyword_value(\"play_pcap_audio\"))) {\n                tmpAction-&gt;setPcapArgs(ptr);\n                tmpAction-&gt;setActionType(CAction::E_AT_PLAY_PCAP_AUDIO);<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">2b. Extract remote media address (<code>call.cpp:198<\/code>)<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">get_remote_media_addr() parses SDP from 200 OK to extract destination IP and audio port<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>std::string port = find_in_sdp(\"m=audio \", msg);\n    if (!port.empty()) {\n        gai_getsockaddr(&amp;play_args_a.to, host.c_str(), port.c_str(),\n                        AI_NUMERICHOST | AI_NUMERICSERV, family);<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">2c. Parse PCAP file (<code>prepare_pcap.c:213<\/code>)<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">prepare_pkts() opens PCAP, extracts UDP\/RTP frames, pre-computes partial checksums<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>pcap = pcap_open_offline(file, errbuf);\n    if (!pcap)\n        ERROR(\"Can't open PCAP file '%s': %s\", file, errbuf);<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">2d. Create raw socket (<code>send_packets.c:177<\/code>)<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">send_packets() creates raw UDP socket (requires root privileges) for PCAP replay<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sock = socket(PF_INET, SOCK_RAW, IPPROTO_UDP);\n        if (sock &lt; 0) {\n            ERROR(\"Can't create raw IPv4 socket (need to run as root?): %s\", strerror(errno));<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">2e. Adjust UDP ports (<code>send_packets.c:234<\/code>)<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Modifies source\/destination ports relative to SDP-negotiated base port from original capture<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>udp-&gt;uh_sport = htons(port_diff + ntohs(*from_port));\n        udp-&gt;uh_dport = htons(port_diff + ntohs(*to_port));<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">2f. Send with original timing (<code>send_packets.c:256<\/code>)<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Sleeps to match inter-packet timing from capture, then sends packet via raw socket<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>do_sleep ((struct timeval *) &amp;pkt_index-&gt;ts, &amp;last, &amp;didsleep,\n                  &amp;start);\n        ret = sendto(sock, buffer, pkt_index-&gt;pktlen, MSG_DONTWAIT,<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">3. RTP Echo<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Triggers when -rtp_echo flag is set &#8211; reflects incoming RTP back to sender via dedicated echo threads<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">3a. Spawn echo thread (<code>rtpstream.cpp:3180<\/code>)<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">rtpstream_rtpecho_startaudio() creates pthread for audio echo, sets up SRTP contexts if USE_TLS<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>if (pthread_create(&amp;pthread_audioecho_id, nullptr, (void *(*) (void *)) rtpstream_audioecho_thread, p.p) == -1) {<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">3b. Receive RTP packet (<code>rtpstream.cpp:2695<\/code>)<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Echo thread loops on recvfrom() to receive incoming RTP packets on media port<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>nr = recvfrom(sock, audio_packet_in.data(), audio_packet_in.size(), MSG_DONTWAIT \/* NON-BLOCKING *\/, (sockaddr *) (void *) &amp;remote_rtp_addr, &amp;len);<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">3c. Decrypt SRTP (if enabled) (<code>rtpstream.cpp:2726<\/code>)<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">If SRTP is active, decrypts incoming packet using JLSRTP library<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>rc = g_rxUASAudio.processIncomingPacket(seq_num, audio_packet_in, rtp_header, payload_data);<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">3d. Encrypt for echo (if enabled) (<code>rtpstream.cpp:2768<\/code>)<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Re-encrypts packet for transmission back to sender using SRTP<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>rc = g_txUASAudio.processOutgoingPacket(seq_num, rtp_header, payload_data, audio_packet_out);<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">3e. Echo packet back (<code>rtpstream.cpp:2777<\/code>)<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Sends packet back to source address, completing echo loop<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ns = sendto(sock, audio_packet_out.data(), sizeof(rtp_header_t) + g_txUASAudio.getSrtpPayloadSize() + g_txUASAudio.getAuthenticationTagSize(), MSG_DONTWAIT, (sockaddr *) (void *) &amp;remote_rtp_addr, len);<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">4. DTMF Generation<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Triggers when scenario executes &#8211; generates RFC 4733 DTMF event packets<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">4a. Parse play_dtmf action (<code>scenario.cpp:1672<\/code>)<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Scenario parser sets E_AT_PLAY_DTMF action type with DTMF digit string<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>} else if ((cptr = xp_get_value(\"play_dtmf\"))) {\n                tmpAction-&gt;setMessage(cptr);\n                tmpAction-&gt;setActionType(CAction::E_AT_PLAY_DTMF);<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">4b. Generate DTMF start packets (<code>prepare_pcap.c:452<\/code>)<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">prepare_dtmf_digit_start() builds RFC 4733 event packets with marker bit set for each digit<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>fill_default_dtmf(dtmfpacket, !marked,\n                          *n_pkts + start_seq_no, n_digits * tone_len * 2 + timestamp_start,\n                          uc_digit, 0, cur_tone_len);<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">4c. Generate DTMF end packets (<code>prepare_pcap.c:492<\/code>)<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">prepare_dtmf_digit_end() creates 3 packets with end-of-event flag set per RFC 4733<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>fill_default_dtmf(dtmfpacket, 0,\n                          *n_pkts + start_seq_no, n_digits * tone_len * 2 + timestamp_start,\n                          uc_digit, 1, tone_len);<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">4d. Build packet list (<code>prepare_pcap.c:436<\/code>)<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Dynamically allocates packet list and stores generated DTMF packets with timestamps<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>pkts-&gt;pkts = realloc(pkts-&gt;pkts, sizeof(*pkts-&gt;pkts) * (*n_pkts + 1));\n        pkt_index-&gt;data = malloc(pktlen);<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">4e. Send DTMF packets (<code>send_packets.c:260<\/code>)<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">send_packets() sends DTMF packets using same raw socket path as PCAP playback with timing<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ret = sendto(sock, buffer, pkt_index-&gt;pktlen, MSG_DONTWAIT,<\/code><\/pre>\n","protected":false},"excerpt":{"rendered":"<p>SIPp Media Handling Workflow SIPp&#8217;s four media handling modes: RTP Streaming [1a-1f] for file playback, PCAP Playback [2a-2f] for replaying captures, RTP Echo [3a-3e] for reflecting media, and DTMF Generation [4a-4e] for RFC 4733 tones. Key entry points are scenario parsing in scenario.cpp [1a,2a,4a], SDP parsing in call.cpp [2b], and the core engines in rtpstream.cpp [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-9682","post","type-post","status-publish","format-standard","hentry","category-projects"],"_links":{"self":[{"href":"https:\/\/www.freesip.org\/index.php?rest_route=\/wp\/v2\/posts\/9682","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.freesip.org\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.freesip.org\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.freesip.org\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.freesip.org\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=9682"}],"version-history":[{"count":2,"href":"https:\/\/www.freesip.org\/index.php?rest_route=\/wp\/v2\/posts\/9682\/revisions"}],"predecessor-version":[{"id":9684,"href":"https:\/\/www.freesip.org\/index.php?rest_route=\/wp\/v2\/posts\/9682\/revisions\/9684"}],"wp:attachment":[{"href":"https:\/\/www.freesip.org\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=9682"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.freesip.org\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=9682"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.freesip.org\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=9682"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}