- initial work to fix RTPdump decoder + more fixes

This commit is contained in:
2026-02-24 09:50:44 +03:00
parent 783359c616
commit bdc4858bcc
13 changed files with 1080 additions and 420 deletions

View File

@@ -341,6 +341,8 @@ set (LIBS_STATIC ${LIBS_STATIC} jrtplib g729_codec gsm_codec opus
if (USE_AMR_CODEC) if (USE_AMR_CODEC)
#include (${LIB_PLATFORM}/platform_libs.cmake) #include (${LIB_PLATFORM}/platform_libs.cmake)
set (OPENCORE_AMRNB opencore-amrnb)
set (OPENCORE_AMRWB opencore-amrwb)
message("Media: AMR NB and WB codecs will be included.") message("Media: AMR NB and WB codecs will be included.")
set (DEFINES ${DEFINES} -DUSE_AMR_CODEC) set (DEFINES ${DEFINES} -DUSE_AMR_CODEC)
set (LIBS_STATIC ${LIBS_STATIC} ${OPENCORE_AMRNB} ${OPENCORE_AMRWB}) set (LIBS_STATIC ${LIBS_STATIC} ${OPENCORE_AMRNB} ${OPENCORE_AMRWB})

View File

@@ -16,6 +16,7 @@ const std::string Status_FailedToOpenFile = "failed to open file";
const std::string Status_NoActiveProvider = "no active provider"; const std::string Status_NoActiveProvider = "no active provider";
const std::string Status_NoMediaAction = "no valid media action"; const std::string Status_NoMediaAction = "no valid media action";
const std::string Status_NoCommand = "no valid command"; const std::string Status_NoCommand = "no valid command";
const std::string Status_NoAudioManager = "no audio manager";
#define LOG_SUBSYSTEM "Agent" #define LOG_SUBSYSTEM "Agent"
@@ -336,7 +337,7 @@ void AgentImpl::processStartSession(JsonCpp::Value& request, JsonCpp::Value& ans
{ {
// Agent was not started // Agent was not started
ICELogError(<< "No audio manager installed."); ICELogError(<< "No audio manager installed.");
answer["status"] = "Audio manager not started. Most probably agent is not started."; answer["status"] = Status_NoAudioManager;
return; return;
} }
@@ -431,28 +432,35 @@ void AgentImpl::processAcceptSession(JsonCpp::Value& request, JsonCpp::Value& an
auto sessionIter = mSessionMap.find(request["session_id"].asInt()); auto sessionIter = mSessionMap.find(request["session_id"].asInt());
if (sessionIter != mSessionMap.end()) if (sessionIter != mSessionMap.end())
{ {
// Ensure audio manager is here if (!mAudioManager)
mAudioManager->start(mUseNativeAudio ? AudioManager::atReceiver : AudioManager::atNull); {
ICELogError(<< "No audio manager installed.");
answer["status"] = Status_NoAudioManager;
}
else
{
// Ensure audio manager is here
mAudioManager->start(mUseNativeAudio ? AudioManager::atReceiver : AudioManager::atNull);
// Accept session on SIP level // Accept session on SIP level
PSession session = sessionIter->second; PSession session = sessionIter->second;
// Get user headers // Get user headers
Session::UserHeaders info; Session::UserHeaders info;
JsonCpp::Value& arg = request["userinfo"]; JsonCpp::Value& arg = request["userinfo"];
std::vector<std::string> keys = arg.getMemberNames(); std::vector<std::string> keys = arg.getMemberNames();
for (const std::string& k: keys) for (const std::string& k: keys)
info[k] = arg[k].asString(); info[k] = arg[k].asString();
session->setUserHeaders(info); session->setUserHeaders(info);
// Accept finally // Accept finally
session->accept(); session->accept();
answer["status"] = Status_Ok; answer["status"] = Status_Ok;
}
} }
else else
answer["status"] = Status_SessionNotFound; answer["status"] = Status_SessionNotFound;
} }
void AgentImpl::processDestroySession(JsonCpp::Value& request, JsonCpp::Value& answer) void AgentImpl::processDestroySession(JsonCpp::Value& request, JsonCpp::Value& answer)

View File

@@ -18,14 +18,24 @@ DataWindow::DataWindow()
DataWindow::~DataWindow() DataWindow::~DataWindow()
{ {
if (mData) if (mData)
{
free(mData); free(mData);
mData = nullptr;
}
} }
void DataWindow::setCapacity(int capacity) void DataWindow::setCapacity(int capacity)
{ {
Lock l(mMutex); Lock l(mMutex);
int tail = capacity - mCapacity; int tail = capacity - mCapacity;
char* buffer = mData;
mData = (char*)realloc(mData, capacity); mData = (char*)realloc(mData, capacity);
if (!mData)
{
// Realloc failed
mData = buffer;
throw std::bad_alloc();
}
if (tail > 0) if (tail > 0)
memset(mData + mCapacity, 0, tail); memset(mData + mCapacity, 0, tail);
mCapacity = capacity; mCapacity = capacity;

View File

@@ -1,4 +1,4 @@
/* Copyright(C) 2007-2017 VoIP objects (voipobjects.com) /* Copyright(C) 2007-2026 VoIP objects (voipobjects.com)
* This Source Code Form is subject to the terms of the Mozilla Public * This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
@@ -9,6 +9,7 @@
#include "../engine_config.h" #include "../engine_config.h"
#include "HL_NetworkSocket.h" #include "HL_NetworkSocket.h"
#include "HL_Log.h"
#if defined(TARGET_OSX) || defined(TARGET_LINUX) #if defined(TARGET_OSX) || defined(TARGET_LINUX)
# include <fcntl.h> # include <fcntl.h>
@@ -19,11 +20,11 @@
#endif #endif
#include <assert.h> #include <assert.h>
#define LOG_SUBSYSTEM "network"
DatagramSocket::DatagramSocket() DatagramSocket::DatagramSocket()
:mFamily(AF_INET), mHandle(INVALID_SOCKET), mLocalPort(0) :mFamily(AF_INET), mHandle(INVALID_SOCKET), mLocalPort(0)
{ {}
}
DatagramSocket::~DatagramSocket() DatagramSocket::~DatagramSocket()
{ {
@@ -160,6 +161,12 @@ void DatagramAgreggator::addSocket(PDatagramSocket socket)
if (socket->mHandle == INVALID_SOCKET) if (socket->mHandle == INVALID_SOCKET)
return; return;
if (mSocketVector.size() >= 62)
{
ICELogError(<< "fd_set overflow; too much sockets");
return;
}
FD_SET(socket->mHandle, &mReadSet); FD_SET(socket->mHandle, &mReadSet);
if (socket->mHandle > mMaxHandle) if (socket->mHandle > mMaxHandle)
mMaxHandle = socket->mHandle; mMaxHandle = socket->mHandle;

View File

@@ -1,4 +1,4 @@
/* Copyright(C) 2007-2017 VoIPobjects (voipobjects.com) /* Copyright(C) 2007-2026 VoIPobjects (voipobjects.com)
* This Source Code Form is subject to the terms of the Mozilla Public * This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
@@ -8,70 +8,95 @@
# include <Windows.h> # include <Windows.h>
#endif #endif
#if defined(TARGET_LINUX) || defined(TARGET_ANDROID) #if defined(TARGET_LINUX) || defined(TARGET_ANDROID) || defined(TARGET_OSX)
# include <arpa/inet.h> # include <arpa/inet.h>
#endif #endif
#include "HL_Rtp.h" #include "HL_Rtp.h"
#include "HL_Exception.h" #include "HL_Exception.h"
#include "HL_String.h" #include "HL_Log.h"
#if defined(USE_RTP_DUMP) #include "jrtplib/src/rtprawpacket.h"
# include "jrtplib/src/rtprawpacket.h" #include "jrtplib/src/rtpipv4address.h"
# include "jrtplib/src/rtpipv4address.h"
#endif
#if !defined(TARGET_WIN) #include <stdexcept>
# include <alloca.h> #include <fstream>
#endif #include <cstring>
#include <cstdio>
#include <chrono>
#include <sstream> #define LOG_SUBSYSTEM "RtpDump"
#include <tuple>
static constexpr size_t MAX_RTP_PACKET_SIZE = 65535;
static const char RTPDUMP_SHEBANG[] = "#!rtpplay1.0";
// RTP fixed header (little-endian bit-field layout)
struct RtpHeader struct RtpHeader
{ {
unsigned char cc:4; /* CSRC count */ unsigned char cc:4; /* CSRC count */
unsigned char x:1; /* header extension flag */ unsigned char x:1; /* header extension flag */
unsigned char p:1; /* padding flag */ unsigned char p:1; /* padding flag */
unsigned char version:2; /* protocol version */ unsigned char version:2; /* protocol version */
unsigned char pt:7; /* payload type */ unsigned char pt:7; /* payload type */
unsigned char m:1; /* marker bit */ unsigned char m:1; /* marker bit */
unsigned short seq; /* sequence number */ unsigned short seq; /* sequence number */
unsigned int ts; /* timestamp */ unsigned int ts; /* timestamp */
unsigned int ssrc; /* synchronization source */ unsigned int ssrc; /* synchronization source */
}; };
struct RtcpHeader struct RtcpHeader
{ {
unsigned char rc:5; /* reception report count */ unsigned char rc:5; /* reception report count */
unsigned char p:1; /* padding flag */ unsigned char p:1; /* padding flag */
unsigned char version:2; /* protocol version */ unsigned char version:2; /* protocol version */
unsigned char pt:8; /* payload type */ unsigned char pt; /* payload type */
uint16_t len; /* length */ uint16_t len; /* length */
uint32_t ssrc; /* synchronization source */ uint32_t ssrc; /* synchronization source */
}; };
// --- IPv4 address helpers ---
static std::string ipToString(uint32_t ip)
{
// ip in host byte order → dotted-decimal
return std::to_string((ip >> 24) & 0xFF) + "." +
std::to_string((ip >> 16) & 0xFF) + "." +
std::to_string((ip >> 8) & 0xFF) + "." +
std::to_string( ip & 0xFF);
}
static uint32_t stringToIp(const std::string& s)
{
unsigned a = 0, b = 0, c = 0, d = 0;
if (std::sscanf(s.c_str(), "%u.%u.%u.%u", &a, &b, &c, &d) != 4)
return 0;
if (a > 255 || b > 255 || c > 255 || d > 255)
return 0;
return (a << 24) | (b << 16) | (c << 8) | d;
}
// --- RtpHelper implementation ---
bool RtpHelper::isRtp(const void* buffer, size_t length) bool RtpHelper::isRtp(const void* buffer, size_t length)
{ {
if (length < 12) if (length < 12)
return false; return false;
const RtpHeader* h = reinterpret_cast<const RtpHeader*>(buffer); const RtpHeader* h = reinterpret_cast<const RtpHeader*>(buffer);
if (h->version != 0b10) if (h->version != 2)
return false; return false;
unsigned char pt = h->pt; unsigned char pt = h->pt;
bool rtp = ( (pt & 0x7F) >= 96 && (pt & 0x7F) <= 127) || ((pt & 0x7F) < 35); bool rtp = (pt >= 96 && pt <= 127) || (pt < 35);
return rtp; return rtp;
} }
bool RtpHelper::isRtpOrRtcp(const void* buffer, size_t length) bool RtpHelper::isRtpOrRtcp(const void* buffer, size_t length)
{ {
if (length < 12) if (length < 12)
return false; return false;
const RtcpHeader* h = reinterpret_cast<const RtcpHeader*>(buffer); const RtcpHeader* h = reinterpret_cast<const RtcpHeader*>(buffer);
return h->version == 0b10; return h->version == 2;
} }
bool RtpHelper::isRtcp(const void* buffer, size_t length) bool RtpHelper::isRtcp(const void* buffer, size_t length)
@@ -83,15 +108,16 @@ unsigned RtpHelper::findSsrc(const void* buffer, size_t length)
{ {
if (isRtp(buffer, length)) if (isRtp(buffer, length))
return ntohl(reinterpret_cast<const RtpHeader*>(buffer)->ssrc); return ntohl(reinterpret_cast<const RtpHeader*>(buffer)->ssrc);
else else if (isRtpOrRtcp(buffer, length))
return ntohl(reinterpret_cast<const RtcpHeader*>(buffer)->ssrc); return ntohl(reinterpret_cast<const RtcpHeader*>(buffer)->ssrc);
return 0;
} }
void RtpHelper::setSsrc(void* buffer, size_t length, uint32_t ssrc) void RtpHelper::setSsrc(void* buffer, size_t length, uint32_t ssrc)
{ {
if (isRtp(buffer, length)) if (isRtp(buffer, length))
reinterpret_cast<RtpHeader*>(buffer)->ssrc = htonl(ssrc); reinterpret_cast<RtpHeader*>(buffer)->ssrc = htonl(ssrc);
else else if (isRtpOrRtcp(buffer, length))
reinterpret_cast<RtcpHeader*>(buffer)->ssrc = htonl(ssrc); reinterpret_cast<RtcpHeader*>(buffer)->ssrc = htonl(ssrc);
} }
@@ -113,47 +139,186 @@ int RtpHelper::findPacketNo(const void *buffer, size_t length)
int RtpHelper::findPayloadLength(const void* buffer, size_t length) int RtpHelper::findPayloadLength(const void* buffer, size_t length)
{ {
if (isRtp(buffer, length)) if (!isRtp(buffer, length))
{
return length - 12;
}
else
return -1; return -1;
const RtpHeader* h = reinterpret_cast<const RtpHeader*>(buffer);
const uint8_t* p = static_cast<const uint8_t*>(buffer);
// Fixed header (12 bytes) + CSRC list (4 * CC bytes)
size_t offset = 12 + 4u * h->cc;
if (offset > length)
return -1;
// Header extension
if (h->x) {
if (offset + 4 > length)
return -1;
uint16_t extWords = (static_cast<uint16_t>(p[offset + 2]) << 8) | p[offset + 3];
offset += 4 + 4u * extWords;
if (offset > length)
return -1;
}
size_t payloadLen = length - offset;
// Padding
if (h->p && payloadLen > 0) {
uint8_t padBytes = p[length - 1];
if (padBytes > payloadLen)
return -1;
payloadLen -= padBytes;
}
return static_cast<int>(payloadLen);
} }
#if defined(USE_RTPDUMP) // --- RtpDump implementation ---
RtpDump::RtpDump(const char *filename)
:mFilename(filename)
{}
RtpDump::~RtpDump() std::shared_ptr<jrtplib::RTPPacket> RtpDump::parseRtpData(const uint8_t* data, size_t len)
{ {
flush(); if (!data || len < 12 || !RtpHelper::isRtp(data, len))
for (PacketList::iterator packetIter=mPacketList.begin(); packetIter!=mPacketList.end(); ++packetIter) return nullptr;
{
//free(packetIter->mData); try {
delete packetIter->mPacket; // Both are heap-allocated; RTPRawPacket takes ownership and deletes them
auto* addr = new jrtplib::RTPIPv4Address(uint32_t(0), uint16_t(0));
uint8_t* dataCopy = new uint8_t[len];
std::memcpy(dataCopy, data, len);
jrtplib::RTPRawPacket raw(dataCopy, len, addr, jrtplib::RTPTime(0), true);
auto packet = std::make_shared<jrtplib::RTPPacket>(raw);
if (packet->GetCreationError() != 0)
return nullptr;
return packet;
} catch (const std::exception& e) {
ICELogInfo(<< "Failed to parse RTP packet: " << e.what());
return nullptr;
} }
} }
RtpDump::RtpDump(const char* filename)
: mFilename(filename ? filename : "")
{
}
RtpDump::~RtpDump() = default;
void RtpDump::setSource(uint32_t ip, uint16_t port)
{
mSourceIp = ip;
mSourcePort = port;
}
void RtpDump::load() void RtpDump::load()
{ {
FILE* f = fopen(mFilename.c_str(), "rb"); if (mFilename.empty())
if (!f) throw std::runtime_error("No filename specified");
throw Exception(ERR_WAVFILE_FAILED);
while (!feof(f)) std::ifstream input(mFilename, std::ios::binary);
{ if (!input.is_open())
RtpData data; throw std::runtime_error("Failed to open RTP dump file: " + mFilename);
fread(&data.mLength, sizeof data.mLength, 1, f);
data.mData = new char[data.mLength]; mPacketList.clear();
fread(data.mData, 1, data.mLength, f);
jrtplib::RTPIPv4Address addr(jrtplib::RTPAddress::IPv4Address); // --- 1. Text header: "#!rtpplay1.0 <ip>/<port>\n" ---
jrtplib::RTPTime t(0); std::string textLine;
jrtplib::RTPRawPacket* raw = new jrtplib::RTPRawPacket((unsigned char*)data.mData, data.mLength, &addr, t, true); std::getline(input, textLine);
data.mPacket = new jrtplib::RTPPacket(*raw); if (textLine.compare(0, sizeof(RTPDUMP_SHEBANG) - 1, RTPDUMP_SHEBANG) != 0)
mPacketList.push_back(data); throw std::runtime_error("Invalid rtpdump header: expected " + std::string(RTPDUMP_SHEBANG));
// Parse source address from the text line
size_t spacePos = textLine.find(' ');
if (spacePos != std::string::npos) {
std::string addrPart = textLine.substr(spacePos + 1);
size_t slashPos = addrPart.find('/');
if (slashPos != std::string::npos) {
mSourceIp = stringToIp(addrPart.substr(0, slashPos));
try {
mSourcePort = static_cast<uint16_t>(std::stoi(addrPart.substr(slashPos + 1)));
} catch (...) {
mSourcePort = 0;
}
}
} }
// --- 2. Binary file header (RD_hdr_t, 16 bytes) ---
uint32_t buf32;
uint16_t buf16;
input.read(reinterpret_cast<char*>(&buf32), 4);
mStartSec = ntohl(buf32);
input.read(reinterpret_cast<char*>(&buf32), 4);
mStartUsec = ntohl(buf32);
input.read(reinterpret_cast<char*>(&buf32), 4); // source IP (already NBO in file)
// The binary header stores IP in network byte order; convert to host
mSourceIp = ntohl(buf32);
input.read(reinterpret_cast<char*>(&buf16), 2);
mSourcePort = ntohs(buf16);
input.read(reinterpret_cast<char*>(&buf16), 2); // padding — discard
if (!input.good())
throw std::runtime_error("Failed to read rtpdump binary header");
// --- 3. Packet records ---
size_t packetCount = 0;
while (input.good() && input.peek() != EOF) {
// Packet header: length(2) + plen(2) + offset(4) = 8 bytes
uint16_t recLength, plen;
uint32_t offsetMs;
input.read(reinterpret_cast<char*>(&recLength), 2);
if (input.gcount() != 2) break;
recLength = ntohs(recLength);
input.read(reinterpret_cast<char*>(&plen), 2);
if (input.gcount() != 2) break;
plen = ntohs(plen);
input.read(reinterpret_cast<char*>(&offsetMs), 4);
if (input.gcount() != 4) break;
offsetMs = ntohl(offsetMs);
// All-zeros record signals end of file in some implementations
if (recLength == 0 && plen == 0 && offsetMs == 0)
break;
if (plen == 0 || plen > MAX_RTP_PACKET_SIZE)
throw std::runtime_error("Invalid packet payload length: " + std::to_string(plen));
if (recLength < plen + 8)
throw std::runtime_error("Record length (" + std::to_string(recLength) +
") smaller than payload + header (" + std::to_string(plen + 8) + ")");
// Read body
std::vector<uint8_t> body(plen);
input.read(reinterpret_cast<char*>(body.data()), plen);
if (static_cast<size_t>(input.gcount()) != plen)
throw std::runtime_error("Incomplete packet data in rtpdump file");
// Skip any padding between plen and recLength-8
size_t pad = static_cast<size_t>(recLength) - 8 - plen;
if (pad > 0)
input.seekg(static_cast<std::streamoff>(pad), std::ios::cur);
RtpData entry;
entry.mRawData = std::move(body);
entry.mOffsetMs = offsetMs;
entry.mPacket = parseRtpData(entry.mRawData.data(), entry.mRawData.size());
mPacketList.push_back(std::move(entry));
packetCount++;
}
ICELogInfo(<< "Loaded " << packetCount << " packets from " << mFilename);
mLoaded = true;
} }
size_t RtpDump::count() const size_t RtpDump::count() const
@@ -163,39 +328,142 @@ size_t RtpDump::count() const
jrtplib::RTPPacket& RtpDump::packetAt(size_t index) jrtplib::RTPPacket& RtpDump::packetAt(size_t index)
{ {
if (index >= mPacketList.size())
throw std::out_of_range("Packet index out of range: " + std::to_string(index));
if (!mPacketList[index].mPacket)
throw std::runtime_error("No parsed RTP data at index " + std::to_string(index));
return *mPacketList[index].mPacket; return *mPacketList[index].mPacket;
} }
const std::vector<uint8_t>& RtpDump::rawDataAt(size_t index) const
{
if (index >= mPacketList.size())
throw std::out_of_range("Packet index out of range: " + std::to_string(index));
return mPacketList[index].mRawData;
}
uint32_t RtpDump::offsetAt(size_t index) const
{
if (index >= mPacketList.size())
throw std::out_of_range("Packet index out of range: " + std::to_string(index));
return mPacketList[index].mOffsetMs;
}
void RtpDump::add(const void* buffer, size_t len) void RtpDump::add(const void* buffer, size_t len)
{ {
RtpData data; if (!buffer || len == 0)
data.mData = malloc(len); return;
memcpy(data.mData, buffer, len);
data.mLength = len;
jrtplib::RTPIPv4Address addr(jrtplib::RTPAddress::IPv4Address); uint32_t offsetMs = 0;
jrtplib::RTPTime t(0); auto now = std::chrono::steady_clock::now();
jrtplib::RTPRawPacket* raw = new jrtplib::RTPRawPacket((unsigned char*)const_cast<void*>(data.mData), data.mLength, &addr, t, true);
data.mPacket = new jrtplib::RTPPacket(*raw); if (!mRecording) {
//delete raw; mRecording = true;
mPacketList.push_back(data); mRecordStart = now;
// Capture wall-clock start time
auto wallNow = std::chrono::system_clock::now();
auto epoch = wallNow.time_since_epoch();
auto sec = std::chrono::duration_cast<std::chrono::seconds>(epoch);
auto usec = std::chrono::duration_cast<std::chrono::microseconds>(epoch - sec);
mStartSec = static_cast<uint32_t>(sec.count());
mStartUsec = static_cast<uint32_t>(usec.count());
} else {
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - mRecordStart);
offsetMs = static_cast<uint32_t>(elapsed.count());
}
add(buffer, len, offsetMs);
}
void RtpDump::add(const void* buffer, size_t len, uint32_t offsetMs)
{
if (!buffer || len == 0)
return;
if (len > MAX_RTP_PACKET_SIZE)
throw std::runtime_error("Packet too large: " + std::to_string(len));
RtpData entry;
entry.mRawData.assign(static_cast<const uint8_t*>(buffer),
static_cast<const uint8_t*>(buffer) + len);
entry.mOffsetMs = offsetMs;
entry.mPacket = parseRtpData(entry.mRawData.data(), entry.mRawData.size());
mPacketList.push_back(std::move(entry));
} }
void RtpDump::flush() void RtpDump::flush()
{ {
FILE* f = fopen(mFilename.c_str(), "wb"); if (mFilename.empty())
if (!f) throw std::runtime_error("No filename specified");
throw Exception(ERR_WAVFILE_FAILED);
PacketList::iterator packetIter = mPacketList.begin(); std::ofstream output(mFilename, std::ios::binary);
for (;packetIter != mPacketList.end(); ++packetIter) if (!output.is_open())
{ throw std::runtime_error("Failed to open file for writing: " + mFilename);
RtpData& data = *packetIter;
// Disabled for debugging only // --- 1. Text header ---
//fwrite(&data.mLength, sizeof data.mLength, 1, f); std::string textLine = std::string(RTPDUMP_SHEBANG) + " " +
fwrite(data.mData, data.mLength, 1, f); ipToString(mSourceIp) + "/" +
std::to_string(mSourcePort) + "\n";
output.write(textLine.data(), static_cast<std::streamsize>(textLine.size()));
// --- 2. Binary file header (16 bytes) ---
uint32_t buf32;
uint16_t buf16;
buf32 = htonl(mStartSec);
output.write(reinterpret_cast<const char*>(&buf32), 4);
buf32 = htonl(mStartUsec);
output.write(reinterpret_cast<const char*>(&buf32), 4);
buf32 = htonl(mSourceIp);
output.write(reinterpret_cast<const char*>(&buf32), 4);
buf16 = htons(mSourcePort);
output.write(reinterpret_cast<const char*>(&buf16), 2);
buf16 = 0; // padding
output.write(reinterpret_cast<const char*>(&buf16), 2);
// --- 3. Packet records ---
size_t written = 0;
for (const auto& pkt : mPacketList) {
if (pkt.mRawData.empty())
continue;
uint16_t plen = static_cast<uint16_t>(pkt.mRawData.size());
uint16_t recLength = static_cast<uint16_t>(plen + 8);
buf16 = htons(recLength);
output.write(reinterpret_cast<const char*>(&buf16), 2);
buf16 = htons(plen);
output.write(reinterpret_cast<const char*>(&buf16), 2);
buf32 = htonl(pkt.mOffsetMs);
output.write(reinterpret_cast<const char*>(&buf32), 4);
output.write(reinterpret_cast<const char*>(pkt.mRawData.data()), plen);
written++;
} }
fclose(f);
}
#endif
if (!output.good())
throw std::runtime_error("Failed to write rtpdump file: " + mFilename);
ICELogInfo(<< "Wrote " << written << " packets to " << mFilename);
}
void RtpDump::clear()
{
mPacketList.clear();
mLoaded = false;
mRecording = false;
}

View File

@@ -1,4 +1,4 @@
/* Copyright(C) 2007-2025 VoIPobjects (voipobjects.com) /* Copyright(C) 2007-2026 VoIPobjects (voipobjects.com)
* This Source Code Form is subject to the terms of the Mozilla Public * This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
@@ -6,12 +6,14 @@
#ifndef __HL_RTP_H #ifndef __HL_RTP_H
#define __HL_RTP_H #define __HL_RTP_H
#if defined(USE_RTPDUMP) #include "jrtplib/src/rtppacket.h"
# include "jrtplib/src/rtppacket.h"
#endif
#include <cstdint> #include <cstdint>
#include <stdlib.h> #include <cstdlib>
#include <vector>
#include <string>
#include <memory>
#include <chrono>
// Class to carry rtp/rtcp socket pair // Class to carry rtp/rtcp socket pair
template<class T> template<class T>
@@ -27,7 +29,7 @@ struct RtpPair
:mRtp(rtp), mRtcp(rtcp) :mRtp(rtp), mRtcp(rtcp)
{} {}
bool multiplexed() { return mRtp == mRtcp; } bool multiplexed() const { return mRtp == mRtcp; }
}; };
class RtpHelper class RtpHelper
@@ -35,7 +37,7 @@ class RtpHelper
public: public:
static bool isRtp(const void* buffer, size_t length); static bool isRtp(const void* buffer, size_t length);
static int findPtype(const void* buffer, size_t length); static int findPtype(const void* buffer, size_t length);
static int findPacketNo(const void* buffer, size_t length); static int findPacketNo(const void *buffer, size_t length);
static bool isRtpOrRtcp(const void* buffer, size_t length); static bool isRtpOrRtcp(const void* buffer, size_t length);
static bool isRtcp(const void* buffer, size_t length); static bool isRtcp(const void* buffer, size_t length);
static unsigned findSsrc(const void* buffer, size_t length); static unsigned findSsrc(const void* buffer, size_t length);
@@ -43,31 +45,104 @@ public:
static int findPayloadLength(const void* buffer, size_t length); static int findPayloadLength(const void* buffer, size_t length);
}; };
#if defined(USE_RTPDUMP) /**
* @brief Standard rtpdump file format (rtptools / Wireshark compatible)
*
* Conforms to the rtpdump format defined by rtptools:
* https://formats.kaitai.io/rtpdump/
*
* File layout:
* 1. Text header line:
* "#!rtpplay1.0 <source_ip>/<source_port>\n"
*
* 2. Binary file header (RD_hdr_t, 16 bytes, all big-endian):
* uint32_t start_sec - recording start time, seconds since epoch
* uint32_t start_usec - recording start time, microseconds
* uint32_t source_ip - source IP address (network byte order)
* uint16_t source_port - source port
* uint16_t padding - always 0
*
* 3. Packet records (repeated until EOF):
* Per-packet header (RD_packet_t, 8 bytes, all big-endian):
* uint16_t length - total record length (this 8-byte header + plen)
* uint16_t plen - RTP/RTCP payload length in bytes
* uint32_t offset - milliseconds since recording start
* Followed by plen bytes of RTP/RTCP packet data.
*
* Maximum single packet payload: 65535 bytes (enforced for safety).
*/
class RtpDump class RtpDump
{ {
protected: protected:
struct RtpData struct RtpData
{ {
jrtplib::RTPPacket* mPacket; std::shared_ptr<jrtplib::RTPPacket> mPacket;
void* mData; std::vector<uint8_t> mRawData;
size_t mLength; uint32_t mOffsetMs = 0;
}; };
typedef std::vector<RtpData> PacketList; typedef std::vector<RtpData> PacketList;
PacketList mPacketList; PacketList mPacketList;
std::string mFilename; std::string mFilename;
bool mLoaded = false;
// File header fields
uint32_t mSourceIp = 0;
uint16_t mSourcePort = 0;
uint32_t mStartSec = 0;
uint32_t mStartUsec = 0;
// Auto-compute packet offsets during recording
bool mRecording = false;
std::chrono::steady_clock::time_point mRecordStart;
std::shared_ptr<jrtplib::RTPPacket> parseRtpData(const uint8_t* data, size_t len);
public: public:
RtpDump(const char* filename); explicit RtpDump(const char* filename);
~RtpDump(); ~RtpDump();
/** Set source address for the file header (host byte order). */
void setSource(uint32_t ip, uint16_t port);
uint32_t sourceIp() const { return mSourceIp; }
uint16_t sourcePort() const { return mSourcePort; }
/**
* @brief Load packets from an rtpdump file
* @throws std::runtime_error on file/format error
*/
void load(); void load();
bool isLoaded() const { return mLoaded; }
size_t count() const; size_t count() const;
/**
* @brief Get parsed RTP packet at index
* @throws std::out_of_range if index is invalid
* @throws std::runtime_error if packet could not be parsed as RTP
*/
jrtplib::RTPPacket& packetAt(size_t index); jrtplib::RTPPacket& packetAt(size_t index);
/** @brief Get raw packet bytes at index */
const std::vector<uint8_t>& rawDataAt(size_t index) const;
/** @brief Get packet time offset in milliseconds */
uint32_t offsetAt(size_t index) const;
/** @brief Add a packet; time offset is auto-computed from first add() call */
void add(const void* data, size_t len); void add(const void* data, size_t len);
/** @brief Add a packet with an explicit millisecond offset */
void add(const void* data, size_t len, uint32_t offsetMs);
/**
* @brief Write all packets to file in rtpdump format
* @throws std::runtime_error on file error
*/
void flush(); void flush();
void clear();
const std::string& filename() const { return mFilename; }
}; };
#endif
#endif #endif

View File

@@ -280,7 +280,8 @@ size_t TimerQueue::cancel(uint64_t id) {
//! Cancels all timers //! Cancels all timers
// \return // \return
// The number of timers cancelled // The number of timers cancelled
size_t TimerQueue::cancelAll() { size_t TimerQueue::cancelAll()
{
// Setting all "end" to 0 (for immediate execution) is ok, // Setting all "end" to 0 (for immediate execution) is ok,
// since it maintains the heap integrity // since it maintains the heap integrity
std::unique_lock<std::mutex> lk(m_mtx); std::unique_lock<std::mutex> lk(m_mtx);

View File

@@ -1,4 +1,4 @@
/* Copyright(C) 2007-2016 VoIP objects (voipobjects.com) /* Copyright(C) 2007-2026 VoIP objects (voipobjects.com)
* This Source Code Form is subject to the terms of the Mozilla Public * This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
@@ -6,10 +6,11 @@
#ifndef __MT_CODEC_H #ifndef __MT_CODEC_H
#define __MT_CODEC_H #define __MT_CODEC_H
#include <map>
#include <span>
#include "resiprocate/resip/stack/SdpContents.hxx" #include "resiprocate/resip/stack/SdpContents.hxx"
#include "../helper/HL_Types.h" #include "../helper/HL_Types.h"
#include <map>
#include "../helper/HL_Pointer.h"
#include "../audio/Audio_Interface.h" #include "../audio/Audio_Interface.h"
namespace MT namespace MT

View File

@@ -18,34 +18,34 @@ using namespace MT;
void DtmfBuilder::buildRfc2833(int tone, int duration, int volume, bool endOfEvent, void* output) void DtmfBuilder::buildRfc2833(int tone, int duration, int volume, bool endOfEvent, void* output)
{ {
assert(duration); assert(duration);
assert(output); assert(output);
assert(tone); assert(tone);
unsigned char toneValue = 0; unsigned char toneValue = 0;
if (tone >= '0' && tone <='9') if (tone >= '0' && tone <='9')
toneValue = tone - '0'; toneValue = tone - '0';
else else
if (tone >= 'A' && tone <='D' ) if (tone >= 'A' && tone <='D' )
toneValue = tone - 'A' + 12; toneValue = tone - 'A' + 12;
else else
if (tone == '*') if (tone == '*')
toneValue = 10; toneValue = 10;
else else
if (tone == '#') if (tone == '#')
toneValue = 11; toneValue = 11;
char* packet = (char*)output; char* packet = (char*)output;
packet[0] = toneValue; packet[0] = toneValue;
packet[1] = 1 | (volume << 2); packet[1] = 1 | (volume << 2);
if (endOfEvent) if (endOfEvent)
packet[1] |= 128; packet[1] |= 128;
else else
packet[1] &= 127; packet[1] &= 127;
unsigned short durationValue = htons(duration); unsigned short durationValue = htons(duration);
memcpy(packet + 2, &durationValue, 2); memcpy(packet + 2, &durationValue, 2);
} }
#pragma region Inband DTMF support #pragma region Inband DTMF support
@@ -62,7 +62,7 @@ static bool sineTabInit = false;
static double sinetab[1 << 11]; static double sinetab[1 << 11];
static inline double sine(unsigned int ptr) static inline double sine(unsigned int ptr)
{ {
return sinetab[ptr >> (32-11)]; return sinetab[ptr >> (32-11)];
} }
#define TWOPI (2.0 * 3.14159265358979323846) #define TWOPI (2.0 * 3.14159265358979323846)
@@ -75,13 +75,13 @@ static inline double sine(unsigned int ptr)
static double amptab[2] = { 8191.75, 16383.5 }; static double amptab[2] = { 8191.75, 16383.5 };
static inline int ifix(double x) static inline int ifix(double x)
{ {
return (x >= 0.0) ? (int) (x+0.5) : (int) (x-0.5); return (x >= 0.0) ? (int) (x+0.5) : (int) (x-0.5);
} }
// given frequency f, return corresponding phase increment // given frequency f, return corresponding phase increment
static inline int phinc(double f) static inline int phinc(double f)
{ {
return ifix(TWO32 * f / (double) AUDIO_SAMPLERATE); return ifix(TWO32 * f / (double) AUDIO_SAMPLERATE);
} }
static char dtmfSymbols[16] = { static char dtmfSymbols[16] = {
@@ -106,31 +106,31 @@ static char dtmfSymbols[16] = {
char PDTMFEncoder_DtmfChar(int i) char PDTMFEncoder_DtmfChar(int i)
{ {
if (i < 16) if (i < 16)
return dtmfSymbols[i]; return dtmfSymbols[i];
else else
return 0; return 0;
} }
// DTMF frequencies as per http://www.commlinx.com.au/DTMF_frequencies.htm // DTMF frequencies as per http://www.commlinx.com.au/DTMF_frequencies.htm
static double dtmfFreqs[16][2] = { static double dtmfFreqs[16][2] = {
{ 941.0, 1336.0 }, // 0 { 941.0, 1336.0 }, // 0
{ 697.0, 1209.0 }, // 1 { 697.0, 1209.0 }, // 1
{ 697.0, 1336.0 }, // 2 { 697.0, 1336.0 }, // 2
{ 697.0, 1477.0 }, // 3 { 697.0, 1477.0 }, // 3
{ 770.0, 1209.0 }, // 4 { 770.0, 1209.0 }, // 4
{ 770.0, 1336.0 }, // 5 { 770.0, 1336.0 }, // 5
{ 770.0, 1477.0 }, // 6 { 770.0, 1477.0 }, // 6
{ 852.0, 1209.0 }, // 7 { 852.0, 1209.0 }, // 7
{ 852.0, 1336.0 }, // 8 { 852.0, 1336.0 }, // 8
{ 852.0, 1477.0 }, // 9 { 852.0, 1477.0 }, // 9
{ 697.0, 1633.0 }, // A { 697.0, 1633.0 }, // A
{ 770.0, 1633.0 }, // B { 770.0, 1633.0 }, // B
{ 852.0, 1633.0 }, // C { 852.0, 1633.0 }, // C
{ 941.0, 1633.0 }, // D { 941.0, 1633.0 }, // D
{ 941.0, 1209.0 }, // * { 941.0, 1209.0 }, // *
{ 941.0, 1477.0 } // # { 941.0, 1477.0 } // #
}; };
@@ -138,81 +138,81 @@ static Mutex LocalDtmfMutex;
void PDTMFEncoder_MakeSineTable() void PDTMFEncoder_MakeSineTable()
{ {
Lock lock(LocalDtmfMutex); Lock lock(LocalDtmfMutex);
if (!sineTabInit) { if (!sineTabInit) {
for (int k = 0; k < SINELEN; k++) { for (int k = 0; k < SINELEN; k++) {
double th = TWOPI * (double) k / (double) SINELEN; double th = TWOPI * (double) k / (double) SINELEN;
double v = sin(th); double v = sin(th);
sinetab[k] = v; sinetab[k] = v;
}
sineTabInit = true;
} }
sineTabInit = true;
}
} }
void PDTMFEncoder_AddTone(double f1, double f2, unsigned ms1, unsigned ms2, unsigned rate, short* result) void PDTMFEncoder_AddTone(double f1, double f2, unsigned ms1, unsigned ms2, unsigned rate, short* result)
{ {
int ak = 0; int ak = 0;
PDTMFEncoder_MakeSineTable(); PDTMFEncoder_MakeSineTable();
int dataPtr = 0; int dataPtr = 0;
double amp = amptab[ak]; double amp = amptab[ak];
int phinc1 = phinc(f1), phinc2 = phinc(f2); int phinc1 = phinc(f1), phinc2 = phinc(f2);
int ns1 = ms1 * (rate/1000); int ns1 = ms1 * (rate/1000);
int ns2 = ms2 * (rate/1000); int ns2 = ms2 * (rate/1000);
unsigned int ptr1 = 0, ptr2 = 0; unsigned int ptr1 = 0, ptr2 = 0;
ptr1 += phinc1 * ns1; ptr1 += phinc1 * ns1;
ptr2 += phinc2 * ns1; ptr2 += phinc2 * ns1;
for (int n = ns1; n < ns2; n++) { for (int n = ns1; n < ns2; n++) {
double val = amp * (sine(ptr1) + sine(ptr2)); double val = amp * (sine(ptr1) + sine(ptr2));
int ival = ifix(val); int ival = ifix(val);
if (ival < -32768) if (ival < -32768)
ival = -32768; ival = -32768;
else if (val > 32767) else if (val > 32767)
ival = 32767; ival = 32767;
result[dataPtr++] = ival / 2; result[dataPtr++] = ival / 2;
ptr1 += phinc1; ptr1 += phinc1;
ptr2 += phinc2; ptr2 += phinc2;
} }
} }
void PDTMFEncoder_AddTone(char _digit, unsigned startTime, unsigned finishTime, unsigned rate, short* result) void PDTMFEncoder_AddTone(char _digit, unsigned startTime, unsigned finishTime, unsigned rate, short* result)
{ {
char digit = (char)toupper(_digit); char digit = (char)toupper(_digit);
if ('0' <= digit && digit <= '9') if ('0' <= digit && digit <= '9')
digit = digit - '0'; digit = digit - '0';
else if ('A' <= digit && digit <= 'D') else if ('A' <= digit && digit <= 'D')
digit = digit + 10 - 'A'; digit = digit + 10 - 'A';
else if (digit == '*') else if (digit == '*')
digit = 14; digit = 14;
else if (digit == '#') else if (digit == '#')
digit = 15; digit = 15;
else else
return ; return ;
PDTMFEncoder_AddTone(dtmfFreqs[(int)digit][0], dtmfFreqs[(int)digit][1], startTime, finishTime, rate, result); PDTMFEncoder_AddTone(dtmfFreqs[(int)digit][0], dtmfFreqs[(int)digit][1], startTime, finishTime, rate, result);
} }
#pragma endregion #pragma endregion
void DtmfBuilder::buildInband(int tone, int startTime, int finishTime, int rate, short* buf) void DtmfBuilder::buildInband(int tone, int startTime, int finishTime, int rate, short* buf)
{ {
PDTMFEncoder_AddTone(tone, startTime, finishTime, rate, buf); PDTMFEncoder_AddTone(tone, startTime, finishTime, rate, buf);
} }
#pragma region DtmfContext #pragma region DtmfContext
DtmfContext::DtmfContext() DtmfContext::DtmfContext()
:mType(Dtmf_Rfc2833) :mType(Dtmf_Rfc2833)
{ {
} }
@@ -222,112 +222,112 @@ DtmfContext::~DtmfContext()
void DtmfContext::setType(Type t) void DtmfContext::setType(Type t)
{ {
mType = t; mType = t;
} }
DtmfContext::Type DtmfContext::type() DtmfContext::Type DtmfContext::type()
{ {
return mType; return mType;
} }
void DtmfContext::startTone(int tone, int volume) void DtmfContext::startTone(int tone, int volume)
{ {
Lock l(mGuard); Lock l(mGuard);
// Stop current tone if needed // Stop current tone if needed
if (mQueue.size()) if (mQueue.size())
stopTone(); stopTone();
mQueue.push_back(Dtmf(tone, volume, 0)); mQueue.push_back(Dtmf(tone, volume, 0));
} }
void DtmfContext::stopTone() void DtmfContext::stopTone()
{ {
Lock l(mGuard); Lock l(mGuard);
// Switch to "emit 3 terminating packets" mode // Switch to "emit 3 terminating packets" mode
if (mQueue.size()) if (mQueue.size())
{
switch (mType)
{ {
case Dtmf_Rfc2833: switch (mType)
mQueue.front().mStopped = true; {
mQueue.erase(mQueue.begin()); case Dtmf_Rfc2833:
break; mQueue.front().mStopped = true;
mQueue.erase(mQueue.begin());
break;
case Dtmf_Inband: case Dtmf_Inband:
if (!mQueue.front().mFinishCount) if (!mQueue.front().mFinishCount)
mQueue.front().mFinishCount = MT_DTMF_END_PACKETS; mQueue.front().mFinishCount = MT_DTMF_END_PACKETS;
break; break;
}
} }
}
} }
void DtmfContext::queueTone(int tone, int volume, int duration) void DtmfContext::queueTone(int tone, int volume, int duration)
{ {
Lock l(mGuard); Lock l(mGuard);
mQueue.push_back(Dtmf(tone, volume, duration)); mQueue.push_back(Dtmf(tone, volume, duration));
} }
void DtmfContext::clearAllTones() void DtmfContext::clearAllTones()
{ {
Lock l(mGuard); Lock l(mGuard);
mQueue.clear(); mQueue.clear();
} }
bool DtmfContext::getInband(int milliseconds, int rate, ByteBuffer& output) bool DtmfContext::getInband(int milliseconds, int rate, ByteBuffer& output)
{ {
Lock l(mGuard); Lock l(mGuard);
if (!mQueue.size() || mType != Dtmf_Inband) if (!mQueue.size() || mType != Dtmf_Inband)
return false; return false;
// //
Dtmf& d = mQueue.front(); Dtmf& d = mQueue.front();
output.resize(milliseconds * rate / 1000 * 2); output.resize((uint64_t)milliseconds * rate / 1000 * 2);
DtmfBuilder::buildInband(d.mTone, d.mCurrentTime, d.mCurrentTime + milliseconds, rate, (short*)output.mutableData()); DtmfBuilder::buildInband(d.mTone, d.mCurrentTime, d.mCurrentTime + milliseconds, rate, (short*)output.mutableData());
d.mCurrentTime += milliseconds; d.mCurrentTime += milliseconds;
return true; return true;
} }
bool DtmfContext::getRfc2833(int milliseconds, ByteBuffer& output, ByteBuffer& stopPacket) bool DtmfContext::getRfc2833(int milliseconds, ByteBuffer& output, ByteBuffer& stopPacket)
{ {
Lock l(mGuard); Lock l(mGuard);
if (!mQueue.size() || mType != Dtmf_Rfc2833) if (!mQueue.size() || mType != Dtmf_Rfc2833)
return false; return false;
Dtmf& d = mQueue.front(); Dtmf& d = mQueue.front();
// See if tone has enough duration to produce another packet // See if tone has enough duration to produce another packet
if (d.mDuration > 0) if (d.mDuration > 0)
{ {
// Emit rfc2833 packet // Emit rfc2833 packet
output.resize(4); output.resize(4);
DtmfBuilder::buildRfc2833(d.mTone, milliseconds, d.mVolume, false, output.mutableData()); DtmfBuilder::buildRfc2833(d.mTone, milliseconds, d.mVolume, false, output.mutableData());
d.mDuration -= milliseconds; d.mDuration -= milliseconds;
if(d.mDuration <= 0) if(d.mDuration <= 0)
d.mStopped = true; d.mStopped = true;
} }
else else
if (!d.mStopped) if (!d.mStopped)
{ {
output.resize(4); output.resize(4);
DtmfBuilder::buildRfc2833(d.mTone, milliseconds, d.mVolume, false, output.mutableData()); DtmfBuilder::buildRfc2833(d.mTone, milliseconds, d.mVolume, false, output.mutableData());
} }
else else
output.clear(); output.clear();
if (d.mStopped) if (d.mStopped)
{ {
stopPacket.resize(4); stopPacket.resize(4);
DtmfBuilder::buildRfc2833(d.mTone, milliseconds, d.mVolume, true, stopPacket.mutableData()); DtmfBuilder::buildRfc2833(d.mTone, milliseconds, d.mVolume, true, stopPacket.mutableData());
} }
else else
stopPacket.clear(); stopPacket.clear();
if (d.mStopped) if (d.mStopped)
mQueue.erase(mQueue.begin()); mQueue.erase(mQueue.begin());
return true; return true;
} }
typedef struct typedef struct
@@ -376,31 +376,31 @@ int zap_dtmf_detect(dtmf_detect_state_t *s, int16_t amp[], int samples, int is
int zap_dtmf_get(dtmf_detect_state_t *s, char *buf, int max); int zap_dtmf_get(dtmf_detect_state_t *s, char *buf, int max);
DTMFDetector::DTMFDetector() DTMFDetector::DTMFDetector()
:mState(NULL) :mState(NULL)
{ {
mState = malloc(sizeof(dtmf_detect_state_t)); mState = malloc(sizeof(dtmf_detect_state_t));
memset(mState, 0, sizeof(dtmf_detect_state_t)); memset(mState, 0, sizeof(dtmf_detect_state_t));
zap_dtmf_detect_init((dtmf_detect_state_t*)mState); zap_dtmf_detect_init((dtmf_detect_state_t*)mState);
} }
DTMFDetector::~DTMFDetector() DTMFDetector::~DTMFDetector()
{ {
if (mState) if (mState)
free(mState); free(mState);
} }
std::string DTMFDetector::streamPut(unsigned char* samples, unsigned int size) std::string DTMFDetector::streamPut(unsigned char* samples, unsigned int size)
{ {
char buf[16]; buf[0] = 0; char buf[16]; buf[0] = 0;
if (zap_dtmf_detect((dtmf_detect_state_t*)mState, (int16_t*)samples, size/2, 0)) if (zap_dtmf_detect((dtmf_detect_state_t*)mState, (int16_t*)samples, size/2, 0))
zap_dtmf_get((dtmf_detect_state_t*)mState, buf, 15); zap_dtmf_get((dtmf_detect_state_t*)mState, buf, 15);
return buf; return buf;
} }
void DTMFDetector::resetState() void DTMFDetector::resetState()
{ {
zap_dtmf_detect_init((dtmf_detect_state_t*)mState); zap_dtmf_detect_init((dtmf_detect_state_t*)mState);
} }
#ifndef TRUE #ifndef TRUE
@@ -448,12 +448,12 @@ static tone_detection_descriptor_t fax_detect;
static tone_detection_descriptor_t fax_detect_2nd; static tone_detection_descriptor_t fax_detect_2nd;
static float dtmf_row[] = static float dtmf_row[] =
{ {
697.0, 770.0, 852.0, 941.0 697.0, 770.0, 852.0, 941.0
}; };
static float dtmf_col[] = static float dtmf_col[] =
{ {
1209.0, 1336.0, 1477.0, 1633.0 1209.0, 1336.0, 1477.0, 1633.0
}; };
static float fax_freq = 1100.0; static float fax_freq = 1100.0;
@@ -461,10 +461,10 @@ static float fax_freq = 1100.0;
static char dtmf_positions[] = "123A" "456B" "789C" "*0#D"; static char dtmf_positions[] = "123A" "456B" "789C" "*0#D";
static void goertzel_init(goertzel_state_t *s, static void goertzel_init(goertzel_state_t *s,
tone_detection_descriptor_t *t) tone_detection_descriptor_t *t)
{ {
s->v2 = s->v2 =
s->v3 = 0.0; s->v3 = 0.0;
s->fac = t->fac; s->fac = t->fac;
} }
/*- End of function --------------------------------------------------------*/ /*- End of function --------------------------------------------------------*/
@@ -497,50 +497,50 @@ static inline void _dtmf_goertzel_update(goertzel_state_t *s,
//s->v3 = s->fac*s->v2 - v1 + x[0]; //s->v3 = s->fac*s->v2 - v1 + x[0];
__asm__ __volatile__ ( __asm__ __volatile__ (
" femms;\n" " femms;\n"
" movq 16(%%edx),%%mm2;\n" " movq 16(%%edx),%%mm2;\n"
" movq 24(%%edx),%%mm3;\n" " movq 24(%%edx),%%mm3;\n"
" movq 32(%%edx),%%mm4;\n" " movq 32(%%edx),%%mm4;\n"
" movq 40(%%edx),%%mm5;\n" " movq 40(%%edx),%%mm5;\n"
" movq 48(%%edx),%%mm6;\n" " movq 48(%%edx),%%mm6;\n"
" movq 56(%%edx),%%mm7;\n" " movq 56(%%edx),%%mm7;\n"
" jmp 1f;\n" " jmp 1f;\n"
" .align 32;\n" " .align 32;\n"
" 1: ;\n" " 1: ;\n"
" prefetch (%%eax);\n" " prefetch (%%eax);\n"
" movq %%mm3,%%mm1;\n" " movq %%mm3,%%mm1;\n"
" movq %%mm2,%%mm0;\n" " movq %%mm2,%%mm0;\n"
" movq %%mm5,%%mm3;\n" " movq %%mm5,%%mm3;\n"
" movq %%mm4,%%mm2;\n" " movq %%mm4,%%mm2;\n"
" pfmul %%mm7,%%mm5;\n" " pfmul %%mm7,%%mm5;\n"
" pfmul %%mm6,%%mm4;\n" " pfmul %%mm6,%%mm4;\n"
" pfsub %%mm1,%%mm5;\n" " pfsub %%mm1,%%mm5;\n"
" pfsub %%mm0,%%mm4;\n" " pfsub %%mm0,%%mm4;\n"
" movq (%%eax),%%mm0;\n" " movq (%%eax),%%mm0;\n"
" movq %%mm0,%%mm1;\n" " movq %%mm0,%%mm1;\n"
" punpckldq %%mm0,%%mm1;\n" " punpckldq %%mm0,%%mm1;\n"
" add $4,%%eax;\n" " add $4,%%eax;\n"
" pfadd %%mm1,%%mm5;\n" " pfadd %%mm1,%%mm5;\n"
" pfadd %%mm1,%%mm4;\n" " pfadd %%mm1,%%mm4;\n"
" dec %%ecx;\n" " dec %%ecx;\n"
" jnz 1b;\n" " jnz 1b;\n"
" movq %%mm2,16(%%edx);\n" " movq %%mm2,16(%%edx);\n"
" movq %%mm3,24(%%edx);\n" " movq %%mm3,24(%%edx);\n"
" movq %%mm4,32(%%edx);\n" " movq %%mm4,32(%%edx);\n"
" movq %%mm5,40(%%edx);\n" " movq %%mm5,40(%%edx);\n"
" femms;\n" " femms;\n"
: :
: "c" (samples), "a" (x), "d" (vv) : "c" (samples), "a" (x), "d" (vv)
: "memory", "eax", "ecx"); : "memory", "eax", "ecx");
s[0].v2 = vv[4]; s[0].v2 = vv[4];
s[1].v2 = vv[5]; s[1].v2 = vv[5];
@@ -555,8 +555,8 @@ static inline void _dtmf_goertzel_update(goertzel_state_t *s,
/*- End of function --------------------------------------------------------*/ /*- End of function --------------------------------------------------------*/
void zap_goertzel_update(goertzel_state_t *s, void zap_goertzel_update(goertzel_state_t *s,
int16_t x[], int16_t x[],
int samples) int samples)
{ {
int i; int i;
float v1; float v1;
@@ -582,7 +582,7 @@ void zap_dtmf_detect_init (dtmf_detect_state_t *s)
float theta; float theta;
s->hit1 = s->hit1 =
s->hit2 = 0; s->hit2 = 0;
for (i = 0; i < 4; i++) for (i = 0; i < 4; i++)
{ {
@@ -598,12 +598,12 @@ void zap_dtmf_detect_init (dtmf_detect_state_t *s)
theta = float(2.0*M_PI*(dtmf_col[i]*2.0/SAMPLE_RATE)); theta = float(2.0*M_PI*(dtmf_col[i]*2.0/SAMPLE_RATE));
dtmf_detect_col_2nd[i].fac = float(2.0*cos(theta)); dtmf_detect_col_2nd[i].fac = float(2.0*cos(theta));
goertzel_init (&s->row_out[i], &dtmf_detect_row[i]); goertzel_init (&s->row_out[i], &dtmf_detect_row[i]);
goertzel_init (&s->col_out[i], &dtmf_detect_col[i]); goertzel_init (&s->col_out[i], &dtmf_detect_col[i]);
goertzel_init (&s->row_out2nd[i], &dtmf_detect_row_2nd[i]); goertzel_init (&s->row_out2nd[i], &dtmf_detect_row_2nd[i]);
goertzel_init (&s->col_out2nd[i], &dtmf_detect_col_2nd[i]); goertzel_init (&s->col_out2nd[i], &dtmf_detect_col_2nd[i]);
s->energy = 0.0; s->energy = 0.0;
} }
/* Same for the fax dector */ /* Same for the fax dector */
@@ -625,9 +625,9 @@ s->energy = 0.0;
/*- End of function --------------------------------------------------------*/ /*- End of function --------------------------------------------------------*/
int zap_dtmf_detect (dtmf_detect_state_t *s, int zap_dtmf_detect (dtmf_detect_state_t *s,
int16_t amp[], int16_t amp[],
int samples, int samples,
int isradio) int isradio)
{ {
float row_energy[4]; float row_energy[4];
@@ -666,7 +666,7 @@ int zap_dtmf_detect (dtmf_detect_state_t *s,
{ {
famp = amp[j]; famp = amp[j];
s->energy += famp*famp; s->energy += famp*famp;
/* With GCC 2.95, the following unrolled code seems to take about 35% /* With GCC 2.95, the following unrolled code seems to take about 35%
(rough estimate) as long as a neat little 0-3 loop */ (rough estimate) as long as a neat little 0-3 loop */
@@ -734,7 +734,7 @@ int zap_dtmf_detect (dtmf_detect_state_t *s,
s->row_out2nd[3].v2 = s->row_out2nd[3].v3; s->row_out2nd[3].v2 = s->row_out2nd[3].v3;
s->row_out2nd[3].v3 = s->row_out2nd[3].fac*s->row_out2nd[3].v2 - v1 + famp; s->row_out2nd[3].v3 = s->row_out2nd[3].fac*s->row_out2nd[3].v2 - v1 + famp;
/* Update fax tone */ /* Update fax tone */
v1 = s->fax_tone.v2; v1 = s->fax_tone.v2;
s->fax_tone.v2 = s->fax_tone.v3; s->fax_tone.v2 = s->fax_tone.v3;
s->fax_tone.v3 = s->fax_tone.fac*s->fax_tone.v2 - v1 + famp; s->fax_tone.v3 = s->fax_tone.fac*s->fax_tone.v2 - v1 + famp;
@@ -748,28 +748,28 @@ int zap_dtmf_detect (dtmf_detect_state_t *s,
if (s->current_sample < 102) if (s->current_sample < 102)
continue; continue;
/* Detect the fax energy, too */ /* Detect the fax energy, too */
fax_energy = zap_goertzel_result(&s->fax_tone); fax_energy = zap_goertzel_result(&s->fax_tone);
/* We are at the end of a DTMF detection block */ /* We are at the end of a DTMF detection block */
/* Find the peak row and the peak column */ /* Find the peak row and the peak column */
row_energy[0] = zap_goertzel_result (&s->row_out[0]); row_energy[0] = zap_goertzel_result (&s->row_out[0]);
col_energy[0] = zap_goertzel_result (&s->col_out[0]); col_energy[0] = zap_goertzel_result (&s->col_out[0]);
for (best_row = best_col = 0, i = 1; i < 4; i++) for (best_row = best_col = 0, i = 1; i < 4; i++)
{ {
row_energy[i] = zap_goertzel_result (&s->row_out[i]); row_energy[i] = zap_goertzel_result (&s->row_out[i]);
if (row_energy[i] > row_energy[best_row]) if (row_energy[i] > row_energy[best_row])
best_row = i; best_row = i;
col_energy[i] = zap_goertzel_result (&s->col_out[i]); col_energy[i] = zap_goertzel_result (&s->col_out[i]);
if (col_energy[i] > col_energy[best_col]) if (col_energy[i] > col_energy[best_col])
best_col = i; best_col = i;
} }
hit = 0; hit = 0;
/* Basic signal level test and the twist test */ /* Basic signal level test and the twist test */
if (row_energy[best_row] >= DTMF_THRESHOLD if (row_energy[best_row] >= DTMF_THRESHOLD
&& &&
col_energy[best_col] >= DTMF_THRESHOLD col_energy[best_col] >= DTMF_THRESHOLD
&& &&
col_energy[best_col] < row_energy[best_row]*DTMF_REVERSE_TWIST col_energy[best_col] < row_energy[best_row]*DTMF_REVERSE_TWIST
&& &&
@@ -787,8 +787,8 @@ for (best_row = best_col = 0, i = 1; i < 4; i++)
} }
/* ... and second harmonic test */ /* ... and second harmonic test */
if (i >= 4 if (i >= 4
&& &&
(row_energy[best_row] + col_energy[best_col]) > 42.0*s->energy (row_energy[best_row] + col_energy[best_col]) > 42.0*s->energy
&& &&
zap_goertzel_result (&s->col_out2nd[best_col])*DTMF_2ND_HARMONIC_COL < col_energy[best_col] zap_goertzel_result (&s->col_out2nd[best_col])*DTMF_2ND_HARMONIC_COL < col_energy[best_col]
&& &&
@@ -804,7 +804,7 @@ for (best_row = best_col = 0, i = 1; i < 4; i++)
to a digit. */ to a digit. */
if (hit == s->hit3 && s->hit3 != s->hit2) if (hit == s->hit3 && s->hit3 != s->hit2)
{ {
s->mhit = hit; s->mhit = hit;
s->digit_hits[(best_row << 2) + best_col]++; s->digit_hits[(best_row << 2) + best_col]++;
s->detected_digits++; s->detected_digits++;
if (s->current_digits < MAX_DTMF_DIGITS) if (s->current_digits < MAX_DTMF_DIGITS)
@@ -819,60 +819,60 @@ for (best_row = best_col = 0, i = 1; i < 4; i++)
} }
} }
} }
if (!hit && (fax_energy >= FAX_THRESHOLD) && (fax_energy > s->energy * 21.0)) { if (!hit && (fax_energy >= FAX_THRESHOLD) && (fax_energy > s->energy * 21.0)) {
fax_energy_2nd = zap_goertzel_result(&s->fax_tone2nd); fax_energy_2nd = zap_goertzel_result(&s->fax_tone2nd);
if (fax_energy_2nd * FAX_2ND_HARMONIC < fax_energy) { if (fax_energy_2nd * FAX_2ND_HARMONIC < fax_energy) {
#if 0 #if 0
printf("Fax energy/Second Harmonic: %f/%f\n", fax_energy, fax_energy_2nd); printf("Fax energy/Second Harmonic: %f/%f\n", fax_energy, fax_energy_2nd);
#endif #endif
/* XXX Probably need better checking than just this the energy XXX */ /* XXX Probably need better checking than just this the energy XXX */
hit = 'f'; hit = 'f';
s->fax_hits++; s->fax_hits++;
} /* Don't reset fax hits counter */ } /* Don't reset fax hits counter */
} else { } else {
if (s->fax_hits > 5) { if (s->fax_hits > 5) {
s->mhit = 'f'; s->mhit = 'f';
s->detected_digits++; s->detected_digits++;
if (s->current_digits < MAX_DTMF_DIGITS) if (s->current_digits < MAX_DTMF_DIGITS)
{ {
s->digits[s->current_digits++] = hit; s->digits[s->current_digits++] = hit;
s->digits[s->current_digits] = '\0'; s->digits[s->current_digits] = '\0';
} }
else else
{ {
s->lost_digits++; s->lost_digits++;
} }
} }
s->fax_hits = 0; s->fax_hits = 0;
} }
s->hit1 = s->hit2; s->hit1 = s->hit2;
s->hit2 = s->hit3; s->hit2 = s->hit3;
s->hit3 = hit; s->hit3 = hit;
/* Reinitialise the detector for the next block */ /* Reinitialise the detector for the next block */
for (i = 0; i < 4; i++) for (i = 0; i < 4; i++)
{ {
goertzel_init (&s->row_out[i], &dtmf_detect_row[i]); goertzel_init (&s->row_out[i], &dtmf_detect_row[i]);
goertzel_init (&s->col_out[i], &dtmf_detect_col[i]); goertzel_init (&s->col_out[i], &dtmf_detect_col[i]);
goertzel_init (&s->row_out2nd[i], &dtmf_detect_row_2nd[i]); goertzel_init (&s->row_out2nd[i], &dtmf_detect_row_2nd[i]);
goertzel_init (&s->col_out2nd[i], &dtmf_detect_col_2nd[i]); goertzel_init (&s->col_out2nd[i], &dtmf_detect_col_2nd[i]);
} }
goertzel_init (&s->fax_tone, &fax_detect); goertzel_init (&s->fax_tone, &fax_detect);
goertzel_init (&s->fax_tone2nd, &fax_detect_2nd); goertzel_init (&s->fax_tone2nd, &fax_detect_2nd);
s->energy = 0.0; s->energy = 0.0;
s->current_sample = 0; s->current_sample = 0;
} }
if ((!s->mhit) || (s->mhit != hit)) if ((!s->mhit) || (s->mhit != hit))
{ {
s->mhit = 0; s->mhit = 0;
return(0); return(0);
} }
return (hit); return (hit);
} }
/*- End of function --------------------------------------------------------*/ /*- End of function --------------------------------------------------------*/
int zap_dtmf_get (dtmf_detect_state_t *s, int zap_dtmf_get (dtmf_detect_state_t *s,
char *buf, char *buf,
int max) int max)
{ {
if (max > s->current_digits) if (max > s->current_digits)
max = s->current_digits; max = s->current_digits;

View File

@@ -13,14 +13,12 @@
using namespace MT; using namespace MT;
SingleAudioStream::SingleAudioStream(const CodecList::Settings& codecSettings, Statistics& stat) SingleAudioStream::SingleAudioStream(const CodecList::Settings& codecSettings, Statistics& stat)
:mReceiver(codecSettings, stat), mDtmfReceiver(stat) :mReceiver(codecSettings, stat),
{ mDtmfReceiver(stat)
} {}
SingleAudioStream::~SingleAudioStream() SingleAudioStream::~SingleAudioStream()
{ {}
}
void SingleAudioStream::process(const std::shared_ptr<jrtplib::RTPPacket>& packet) void SingleAudioStream::process(const std::shared_ptr<jrtplib::RTPPacket>& packet)
{ {

View File

@@ -13,19 +13,19 @@
#include "MT_AudioReceiver.h" #include "MT_AudioReceiver.h"
namespace MT namespace MT
{ {
class SingleAudioStream class SingleAudioStream
{ {
public: public:
SingleAudioStream(const CodecList::Settings& codecSettings, Statistics& stat); SingleAudioStream(const CodecList::Settings& codecSettings, Statistics& stat);
~SingleAudioStream(); ~SingleAudioStream();
void process(const std::shared_ptr<jrtplib::RTPPacket>& packet); void process(const std::shared_ptr<jrtplib::RTPPacket>& packet);
void copyPcmTo(Audio::DataWindow& output, int needed); void copyPcmTo(Audio::DataWindow& output, int needed);
protected: protected:
DtmfReceiver mDtmfReceiver; DtmfReceiver mDtmfReceiver;
AudioReceiver mReceiver; AudioReceiver mReceiver;
}; };
typedef std::map<unsigned, SingleAudioStream*> AudioStreamMap; typedef std::map<unsigned, SingleAudioStream*> AudioStreamMap;
} }
#endif #endif

View File

@@ -0,0 +1,6 @@
set (CMAKE_CXX_STANDARD 20)
set (CMAKE_CXX_STANDARD_REQUIRED ON)
add_executable(rtp_decode main.cpp)
add_subdirectory(../../src build_rtphone)
target_link_libraries(rtp_decode PRIVATE rtphone)

284
test/rtp_decode/main.cpp Normal file
View File

@@ -0,0 +1,284 @@
/* Copyright(C) 2007-2026 VoIPobjects (voipobjects.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// rtp_decode — read an rtpdump file, decode RTP with a given codec, write WAV.
//
// Usage:
// rtp_decode <input.rtp> <output.wav> --codec <name> [--pt <N>] [--rate <N>] [--channels <N>]
#include "helper/HL_Rtp.h"
#include "media/MT_CodecList.h"
#include "media/MT_Codec.h"
#include "audio/Audio_WavFile.h"
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <vector>
#include <stdexcept>
// ---------------------------------------------------------------------------
// CLI helpers
// ---------------------------------------------------------------------------
static void usage(const char* progname)
{
fprintf(stderr,
"Usage: %s <input.rtp> <output.wav> --codec <name> [--pt <N>] [--rate <N>] [--channels <N>]\n"
"\n"
"Codecs: pcmu pcma g722 g729 opus gsm gsmhr gsmefr amrnb amrwb evs ilbc20 ilbc30 isac16 isac32\n"
"\n"
"Options:\n"
" --codec <name> Codec name (required)\n"
" --pt <N> Override RTP payload type\n"
" --rate <N> Sample rate hint for Opus (default 48000)\n"
" --channels <N> Channel count hint for Opus (default 2)\n",
progname);
}
static const char* getOption(int argc, char* argv[], const char* name)
{
for (int i = 1; i < argc - 1; ++i) {
if (strcmp(argv[i], name) == 0)
return argv[i + 1];
}
return nullptr;
}
// ---------------------------------------------------------------------------
// Default payload types for codecs without a fixed standard PT
// ---------------------------------------------------------------------------
struct CodecDefaults
{
const char* name;
int defaultPt; // -1 = must be specified via --pt
bool needsPt; // true if --pt is required when no default exists
};
static const CodecDefaults kCodecTable[] = {
{ "pcmu", 0, false },
{ "pcma", 8, false },
{ "g722", 9, false },
{ "g729", 18, false },
{ "gsm", 3, false },
{ "opus", 106, false },
{ "amrnb", -1, true },
{ "amrwb", -1, true },
{ "gsmhr", -1, true },
{ "gsmefr", 126, false },
{ "evs", 127, false },
{ "ilbc20", -1, true },
{ "ilbc30", -1, true },
{ "isac16", -1, true },
{ "isac32", -1, true },
};
static const CodecDefaults* findCodecDefaults(const std::string& name)
{
for (auto& c : kCodecTable)
if (name == c.name)
return &c;
return nullptr;
}
// ---------------------------------------------------------------------------
// Build CodecList::Settings for the requested codec
// ---------------------------------------------------------------------------
static MT::CodecList::Settings buildSettings(const std::string& codecName, int pt,
int opusRate, int opusChannels)
{
MT::CodecList::Settings s;
if (codecName == "opus") {
s.mOpusSpec.push_back(MT::CodecList::Settings::OpusSpec(pt, opusRate, opusChannels));
} else if (codecName == "gsm") {
s.mGsmFrPayloadType = pt;
} else if (codecName == "gsmhr") {
s.mGsmHrPayloadType = pt;
} else if (codecName == "gsmefr") {
s.mGsmEfrPayloadType = pt;
} else if (codecName == "amrnb") {
s.mAmrNbOctetPayloadType.insert(pt);
} else if (codecName == "amrwb") {
s.mAmrWbOctetPayloadType.insert(pt);
} else if (codecName == "evs") {
MT::CodecList::Settings::EvsSpec ev;
ev.mPayloadType = pt;
s.mEvsSpec.push_back(ev);
} else if (codecName == "ilbc20") {
s.mIlbc20PayloadType = pt;
} else if (codecName == "ilbc30") {
s.mIlbc30PayloadType = pt;
} else if (codecName == "isac16") {
s.mIsac16KPayloadType = pt;
} else if (codecName == "isac32") {
s.mIsac32KPayloadType = pt;
}
// pcmu, pcma, g722, g729 — fixed PT, auto-registered by CodecList::init()
return s;
}
// ---------------------------------------------------------------------------
// main
// ---------------------------------------------------------------------------
int main(int argc, char* argv[])
{
if (argc < 4) {
usage(argv[0]);
return 1;
}
const char* inputPath = argv[1];
const char* outputPath = argv[2];
const char* codecArg = getOption(argc, argv, "--codec");
if (!codecArg) {
fprintf(stderr, "Error: --codec is required\n\n");
usage(argv[0]);
return 1;
}
std::string codecName = codecArg;
const auto* defaults = findCodecDefaults(codecName);
if (!defaults) {
fprintf(stderr, "Error: unknown codec '%s'\n\n", codecArg);
usage(argv[0]);
return 1;
}
// Resolve payload type
int pt = defaults->defaultPt;
const char* ptArg = getOption(argc, argv, "--pt");
if (ptArg) {
pt = atoi(ptArg);
} else if (defaults->needsPt) {
fprintf(stderr, "Error: --pt is required for codec '%s'\n\n", codecArg);
usage(argv[0]);
return 1;
}
int opusRate = 48000;
int opusChannels = 2;
const char* rateArg = getOption(argc, argv, "--rate");
if (rateArg)
opusRate = atoi(rateArg);
const char* chArg = getOption(argc, argv, "--channels");
if (chArg)
opusChannels = atoi(chArg);
// -----------------------------------------------------------------------
// 1. Load rtpdump
// -----------------------------------------------------------------------
RtpDump dump(inputPath);
try {
dump.load();
} catch (const std::exception& e) {
fprintf(stderr, "Error loading rtpdump '%s': %s\n", inputPath, e.what());
return 1;
}
if (dump.count() == 0) {
fprintf(stderr, "No packets in '%s'\n", inputPath);
return 1;
}
fprintf(stderr, "Loaded %zu packets from '%s'\n", dump.count(), inputPath);
// -----------------------------------------------------------------------
// 2. Create codec
// -----------------------------------------------------------------------
auto settings = buildSettings(codecName, pt, opusRate, opusChannels);
MT::CodecList codecList(settings);
MT::PCodec codec = codecList.createCodecByPayloadType(pt);
if (!codec) {
fprintf(stderr, "Error: could not create codec for payload type %d\n", pt);
return 1;
}
auto codecInfo = codec->info();
fprintf(stderr, "Codec: %s samplerate=%d channels=%d pcmLength=%d frameTime=%dms\n",
codecInfo.mName.c_str(), codecInfo.mSamplerate, codecInfo.mChannels,
codecInfo.mPcmLength, codecInfo.mFrameTime);
// -----------------------------------------------------------------------
// 3. Open WAV writer
// -----------------------------------------------------------------------
Audio::WavFileWriter writer;
if (!writer.open(outputPath, codecInfo.mSamplerate, codecInfo.mChannels)) {
fprintf(stderr, "Error: could not open WAV file '%s' for writing\n", outputPath);
return 1;
}
// -----------------------------------------------------------------------
// 4. Decode loop
// -----------------------------------------------------------------------
std::vector<uint8_t> pcmBuffer(65536);
size_t totalDecodedBytes = 0;
size_t packetsDecoded = 0;
size_t packetsSkipped = 0;
for (size_t i = 0; i < dump.count(); ++i) {
const auto& rawData = dump.rawDataAt(i);
// Verify it's actually RTP
if (!RtpHelper::isRtp(rawData.data(), rawData.size())) {
++packetsSkipped;
continue;
}
// Parse RTP to get payload
jrtplib::RTPPacket& rtpPacket = dump.packetAt(i);
// Check payload type matches what we expect
int pktPt = rtpPacket.GetPayloadType();
if (pktPt != pt) {
++packetsSkipped;
continue;
}
uint8_t* payloadData = rtpPacket.GetPayloadData();
size_t payloadLen = rtpPacket.GetPayloadLength();
if (!payloadData || payloadLen == 0) {
++packetsSkipped;
continue;
}
std::span<const uint8_t> input(payloadData, payloadLen);
std::span<uint8_t> output(pcmBuffer.data(), pcmBuffer.size());
try {
auto result = codec->decode(input, output);
if (result.mDecoded > 0) {
writer.write(pcmBuffer.data(), result.mDecoded);
totalDecodedBytes += result.mDecoded;
++packetsDecoded;
}
} catch (const std::exception& e) {
fprintf(stderr, "Warning: decode error at packet %zu: %s\n", i, e.what());
++packetsSkipped;
}
}
// -----------------------------------------------------------------------
// 5. Close WAV and print summary
// -----------------------------------------------------------------------
writer.close();
size_t totalSamples = totalDecodedBytes / (sizeof(int16_t) * codecInfo.mChannels);
double durationSec = (codecInfo.mSamplerate > 0)
? static_cast<double>(totalSamples) / codecInfo.mSamplerate
: 0.0;
fprintf(stderr, "\nDone.\n");
fprintf(stderr, " Packets decoded: %zu\n", packetsDecoded);
fprintf(stderr, " Packets skipped: %zu\n", packetsSkipped);
fprintf(stderr, " Decoded PCM: %zu bytes\n", totalDecodedBytes);
fprintf(stderr, " Duration: %.3f seconds\n", durationSec);
fprintf(stderr, " Output: %s\n", outputPath);
return 0;
}