- 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)
#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.")
set (DEFINES ${DEFINES} -DUSE_AMR_CODEC)
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_NoMediaAction = "no valid media action";
const std::string Status_NoCommand = "no valid command";
const std::string Status_NoAudioManager = "no audio manager";
#define LOG_SUBSYSTEM "Agent"
@@ -336,7 +337,7 @@ void AgentImpl::processStartSession(JsonCpp::Value& request, JsonCpp::Value& ans
{
// Agent was not started
ICELogError(<< "No audio manager installed.");
answer["status"] = "Audio manager not started. Most probably agent is not started.";
answer["status"] = Status_NoAudioManager;
return;
}
@@ -430,6 +431,13 @@ void AgentImpl::processAcceptSession(JsonCpp::Value& request, JsonCpp::Value& an
std::unique_lock<std::recursive_mutex> l(mAgentMutex);
auto sessionIter = mSessionMap.find(request["session_id"].asInt());
if (sessionIter != mSessionMap.end())
{
if (!mAudioManager)
{
ICELogError(<< "No audio manager installed.");
answer["status"] = Status_NoAudioManager;
}
else
{
// Ensure audio manager is here
mAudioManager->start(mUseNativeAudio ? AudioManager::atReceiver : AudioManager::atNull);
@@ -450,9 +458,9 @@ void AgentImpl::processAcceptSession(JsonCpp::Value& request, JsonCpp::Value& an
answer["status"] = Status_Ok;
}
}
else
answer["status"] = Status_SessionNotFound;
}
void AgentImpl::processDestroySession(JsonCpp::Value& request, JsonCpp::Value& answer)

View File

@@ -18,14 +18,24 @@ DataWindow::DataWindow()
DataWindow::~DataWindow()
{
if (mData)
{
free(mData);
mData = nullptr;
}
}
void DataWindow::setCapacity(int capacity)
{
Lock l(mMutex);
int tail = capacity - mCapacity;
char* buffer = mData;
mData = (char*)realloc(mData, capacity);
if (!mData)
{
// Realloc failed
mData = buffer;
throw std::bad_alloc();
}
if (tail > 0)
memset(mData + mCapacity, 0, tail);
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
* 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/. */
@@ -9,6 +9,7 @@
#include "../engine_config.h"
#include "HL_NetworkSocket.h"
#include "HL_Log.h"
#if defined(TARGET_OSX) || defined(TARGET_LINUX)
# include <fcntl.h>
@@ -19,11 +20,11 @@
#endif
#include <assert.h>
#define LOG_SUBSYSTEM "network"
DatagramSocket::DatagramSocket()
:mFamily(AF_INET), mHandle(INVALID_SOCKET), mLocalPort(0)
{
}
{}
DatagramSocket::~DatagramSocket()
{
@@ -160,6 +161,12 @@ void DatagramAgreggator::addSocket(PDatagramSocket socket)
if (socket->mHandle == INVALID_SOCKET)
return;
if (mSocketVector.size() >= 62)
{
ICELogError(<< "fd_set overflow; too much sockets");
return;
}
FD_SET(socket->mHandle, &mReadSet);
if (socket->mHandle > mMaxHandle)
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
* 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/. */
@@ -8,26 +8,29 @@
# include <Windows.h>
#endif
#if defined(TARGET_LINUX) || defined(TARGET_ANDROID)
#if defined(TARGET_LINUX) || defined(TARGET_ANDROID) || defined(TARGET_OSX)
# include <arpa/inet.h>
#endif
#include "HL_Rtp.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/rtpipv4address.h"
#endif
#include "jrtplib/src/rtprawpacket.h"
#include "jrtplib/src/rtpipv4address.h"
#if !defined(TARGET_WIN)
# include <alloca.h>
#endif
#include <stdexcept>
#include <fstream>
#include <cstring>
#include <cstdio>
#include <chrono>
#include <sstream>
#include <tuple>
#define LOG_SUBSYSTEM "RtpDump"
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
{
unsigned char cc:4; /* CSRC count */
@@ -46,32 +49,54 @@ struct RtcpHeader
unsigned char rc:5; /* reception report count */
unsigned char p:1; /* padding flag */
unsigned char version:2; /* protocol version */
unsigned char pt:8; /* payload type */
unsigned char pt; /* payload type */
uint16_t len; /* length */
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)
{
if (length < 12)
return false;
const RtpHeader* h = reinterpret_cast<const RtpHeader*>(buffer);
if (h->version != 0b10)
if (h->version != 2)
return false;
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;
}
bool RtpHelper::isRtpOrRtcp(const void* buffer, size_t length)
{
if (length < 12)
return false;
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)
@@ -83,15 +108,16 @@ unsigned RtpHelper::findSsrc(const void* buffer, size_t length)
{
if (isRtp(buffer, length))
return ntohl(reinterpret_cast<const RtpHeader*>(buffer)->ssrc);
else
else if (isRtpOrRtcp(buffer, length))
return ntohl(reinterpret_cast<const RtcpHeader*>(buffer)->ssrc);
return 0;
}
void RtpHelper::setSsrc(void* buffer, size_t length, uint32_t ssrc)
{
if (isRtp(buffer, length))
reinterpret_cast<RtpHeader*>(buffer)->ssrc = htonl(ssrc);
else
else if (isRtpOrRtcp(buffer, length))
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)
{
if (isRtp(buffer, length))
{
return length - 12;
}
else
if (!isRtp(buffer, length))
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::RtpDump(const char *filename)
:mFilename(filename)
{}
// --- RtpDump implementation ---
RtpDump::~RtpDump()
std::shared_ptr<jrtplib::RTPPacket> RtpDump::parseRtpData(const uint8_t* data, size_t len)
{
flush();
for (PacketList::iterator packetIter=mPacketList.begin(); packetIter!=mPacketList.end(); ++packetIter)
{
//free(packetIter->mData);
delete packetIter->mPacket;
if (!data || len < 12 || !RtpHelper::isRtp(data, len))
return nullptr;
try {
// 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()
{
FILE* f = fopen(mFilename.c_str(), "rb");
if (!f)
throw Exception(ERR_WAVFILE_FAILED);
if (mFilename.empty())
throw std::runtime_error("No filename specified");
while (!feof(f))
{
RtpData data;
fread(&data.mLength, sizeof data.mLength, 1, f);
data.mData = new char[data.mLength];
fread(data.mData, 1, data.mLength, f);
jrtplib::RTPIPv4Address addr(jrtplib::RTPAddress::IPv4Address);
jrtplib::RTPTime t(0);
jrtplib::RTPRawPacket* raw = new jrtplib::RTPRawPacket((unsigned char*)data.mData, data.mLength, &addr, t, true);
data.mPacket = new jrtplib::RTPPacket(*raw);
mPacketList.push_back(data);
std::ifstream input(mFilename, std::ios::binary);
if (!input.is_open())
throw std::runtime_error("Failed to open RTP dump file: " + mFilename);
mPacketList.clear();
// --- 1. Text header: "#!rtpplay1.0 <ip>/<port>\n" ---
std::string textLine;
std::getline(input, textLine);
if (textLine.compare(0, sizeof(RTPDUMP_SHEBANG) - 1, RTPDUMP_SHEBANG) != 0)
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
@@ -163,39 +328,142 @@ size_t RtpDump::count() const
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;
}
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)
{
RtpData data;
data.mData = malloc(len);
memcpy(data.mData, buffer, len);
data.mLength = len;
if (!buffer || len == 0)
return;
jrtplib::RTPIPv4Address addr(jrtplib::RTPAddress::IPv4Address);
jrtplib::RTPTime t(0);
jrtplib::RTPRawPacket* raw = new jrtplib::RTPRawPacket((unsigned char*)const_cast<void*>(data.mData), data.mLength, &addr, t, true);
data.mPacket = new jrtplib::RTPPacket(*raw);
//delete raw;
mPacketList.push_back(data);
uint32_t offsetMs = 0;
auto now = std::chrono::steady_clock::now();
if (!mRecording) {
mRecording = true;
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()
{
FILE* f = fopen(mFilename.c_str(), "wb");
if (!f)
throw Exception(ERR_WAVFILE_FAILED);
if (mFilename.empty())
throw std::runtime_error("No filename specified");
PacketList::iterator packetIter = mPacketList.begin();
for (;packetIter != mPacketList.end(); ++packetIter)
{
RtpData& data = *packetIter;
// Disabled for debugging only
//fwrite(&data.mLength, sizeof data.mLength, 1, f);
fwrite(data.mData, data.mLength, 1, f);
std::ofstream output(mFilename, std::ios::binary);
if (!output.is_open())
throw std::runtime_error("Failed to open file for writing: " + mFilename);
// --- 1. Text header ---
std::string textLine = std::string(RTPDUMP_SHEBANG) + " " +
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
* 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/. */
@@ -6,12 +6,14 @@
#ifndef __HL_RTP_H
#define __HL_RTP_H
#if defined(USE_RTPDUMP)
# include "jrtplib/src/rtppacket.h"
#endif
#include "jrtplib/src/rtppacket.h"
#include <cstdint>
#include <stdlib.h>
#include <cstdlib>
#include <vector>
#include <string>
#include <memory>
#include <chrono>
// Class to carry rtp/rtcp socket pair
template<class T>
@@ -27,7 +29,7 @@ struct RtpPair
:mRtp(rtp), mRtcp(rtcp)
{}
bool multiplexed() { return mRtp == mRtcp; }
bool multiplexed() const { return mRtp == mRtcp; }
};
class RtpHelper
@@ -35,7 +37,7 @@ class RtpHelper
public:
static bool isRtp(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 isRtcp(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);
};
#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
{
protected:
struct RtpData
{
jrtplib::RTPPacket* mPacket;
void* mData;
size_t mLength;
std::shared_ptr<jrtplib::RTPPacket> mPacket;
std::vector<uint8_t> mRawData;
uint32_t mOffsetMs = 0;
};
typedef std::vector<RtpData> PacketList;
PacketList mPacketList;
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:
RtpDump(const char* filename);
explicit RtpDump(const char* filename);
~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();
bool isLoaded() const { return mLoaded; }
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);
/** @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);
/** @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 clear();
const std::string& filename() const { return mFilename; }
};
#endif
#endif

View File

@@ -280,7 +280,8 @@ size_t TimerQueue::cancel(uint64_t id) {
//! Cancels all timers
// \return
// The number of timers cancelled
size_t TimerQueue::cancelAll() {
size_t TimerQueue::cancelAll()
{
// Setting all "end" to 0 (for immediate execution) is ok,
// since it maintains the heap integrity
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
* 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/. */
@@ -6,10 +6,11 @@
#ifndef __MT_CODEC_H
#define __MT_CODEC_H
#include <map>
#include <span>
#include "resiprocate/resip/stack/SdpContents.hxx"
#include "../helper/HL_Types.h"
#include <map>
#include "../helper/HL_Pointer.h"
#include "../audio/Audio_Interface.h"
namespace MT

View File

@@ -212,7 +212,7 @@ void DtmfBuilder::buildInband(int tone, int startTime, int finishTime, int rate,
#pragma region DtmfContext
DtmfContext::DtmfContext()
:mType(Dtmf_Rfc2833)
:mType(Dtmf_Rfc2833)
{
}
@@ -284,7 +284,7 @@ bool DtmfContext::getInband(int milliseconds, int rate, ByteBuffer& output)
//
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());
d.mCurrentTime += milliseconds;
return true;
@@ -376,7 +376,7 @@ 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);
DTMFDetector::DTMFDetector()
:mState(NULL)
:mState(NULL)
{
mState = malloc(sizeof(dtmf_detect_state_t));
@@ -448,11 +448,11 @@ static tone_detection_descriptor_t fax_detect;
static tone_detection_descriptor_t fax_detect_2nd;
static float dtmf_row[] =
{
{
697.0, 770.0, 852.0, 941.0
};
static float dtmf_col[] =
{
{
1209.0, 1336.0, 1477.0, 1633.0
};
@@ -603,7 +603,7 @@ void zap_dtmf_detect_init (dtmf_detect_state_t *s)
goertzel_init (&s->row_out2nd[i], &dtmf_detect_row_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 */
@@ -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].v3 = s->row_out2nd[3].fac*s->row_out2nd[3].v2 - v1 + famp;
/* Update fax tone */
/* Update fax tone */
v1 = s->fax_tone.v2;
s->fax_tone.v2 = s->fax_tone.v3;
s->fax_tone.v3 = s->fax_tone.fac*s->fax_tone.v2 - v1 + famp;
@@ -748,16 +748,16 @@ int zap_dtmf_detect (dtmf_detect_state_t *s,
if (s->current_sample < 102)
continue;
/* Detect the fax energy, too */
fax_energy = zap_goertzel_result(&s->fax_tone);
/* Detect the fax energy, too */
fax_energy = zap_goertzel_result(&s->fax_tone);
/* We are at the end of a DTMF detection block */
/* Find the peak row and the peak column */
row_energy[0] = zap_goertzel_result (&s->row_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]);
if (row_energy[i] > row_energy[best_row])
best_row = i;
@@ -788,7 +788,7 @@ for (best_row = best_col = 0, i = 1; i < 4; i++)
/* ... and second harmonic test */
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]
&&
@@ -819,18 +819,18 @@ for (best_row = best_col = 0, i = 1; i < 4; i++)
}
}
}
if (!hit && (fax_energy >= FAX_THRESHOLD) && (fax_energy > s->energy * 21.0)) {
fax_energy_2nd = zap_goertzel_result(&s->fax_tone2nd);
if (fax_energy_2nd * FAX_2ND_HARMONIC < fax_energy) {
if (!hit && (fax_energy >= FAX_THRESHOLD) && (fax_energy > s->energy * 21.0)) {
fax_energy_2nd = zap_goertzel_result(&s->fax_tone2nd);
if (fax_energy_2nd * FAX_2ND_HARMONIC < fax_energy) {
#if 0
printf("Fax energy/Second Harmonic: %f/%f\n", fax_energy, fax_energy_2nd);
#endif
/* XXX Probably need better checking than just this the energy XXX */
hit = 'f';
s->fax_hits++;
} /* Don't reset fax hits counter */
} else {
if (s->fax_hits > 5) {
/* XXX Probably need better checking than just this the energy XXX */
hit = 'f';
s->fax_hits++;
} /* Don't reset fax hits counter */
} else {
if (s->fax_hits > 5) {
s->mhit = 'f';
s->detected_digits++;
if (s->current_digits < MAX_DTMF_DIGITS)
@@ -842,9 +842,9 @@ if (s->fax_hits > 5) {
{
s->lost_digits++;
}
}
s->fax_hits = 0;
}
}
s->fax_hits = 0;
}
s->hit1 = s->hit2;
s->hit2 = s->hit3;
s->hit3 = hit;
@@ -858,13 +858,13 @@ s->fax_hits = 0;
}
goertzel_init (&s->fax_tone, &fax_detect);
goertzel_init (&s->fax_tone2nd, &fax_detect_2nd);
s->energy = 0.0;
s->energy = 0.0;
s->current_sample = 0;
}
if ((!s->mhit) || (s->mhit != hit))
{
s->mhit = 0;
return(0);
s->mhit = 0;
return(0);
}
return (hit);
}

View File

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

View File

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