From bdc4858bccdc9ecd30c48e0df13bc4fcfa9f05e3 Mon Sep 17 00:00:00 2001 From: Dmytro Bogovych Date: Tue, 24 Feb 2026 09:50:44 +0300 Subject: [PATCH] - initial work to fix RTPdump decoder + more fixes --- src/CMakeLists.txt | 2 + src/engine/agent/Agent_Impl.cpp | 40 +- src/engine/audio/Audio_DataWindow.cpp | 10 + src/engine/helper/HL_NetworkSocket.cpp | 15 +- src/engine/helper/HL_Rtp.cpp | 426 +++++++++++++--- src/engine/helper/HL_Rtp.h | 101 +++- src/engine/helper/HL_Sync.cpp | 3 +- src/engine/media/MT_Codec.h | 7 +- src/engine/media/MT_Dtmf.cpp | 582 +++++++++++----------- src/engine/media/MT_SingleAudioStream.cpp | 10 +- src/engine/media/MT_SingleAudioStream.h | 14 +- test/rtp_decode/CMakeLists.txt | 6 + test/rtp_decode/main.cpp | 284 +++++++++++ 13 files changed, 1080 insertions(+), 420 deletions(-) create mode 100644 test/rtp_decode/CMakeLists.txt create mode 100644 test/rtp_decode/main.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b1ff40c0..a40c1463 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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}) diff --git a/src/engine/agent/Agent_Impl.cpp b/src/engine/agent/Agent_Impl.cpp index 939fbf8d..0d185785 100644 --- a/src/engine/agent/Agent_Impl.cpp +++ b/src/engine/agent/Agent_Impl.cpp @@ -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; } @@ -431,28 +432,35 @@ void AgentImpl::processAcceptSession(JsonCpp::Value& request, JsonCpp::Value& an auto sessionIter = mSessionMap.find(request["session_id"].asInt()); if (sessionIter != mSessionMap.end()) { - // Ensure audio manager is here - mAudioManager->start(mUseNativeAudio ? AudioManager::atReceiver : AudioManager::atNull); + if (!mAudioManager) + { + 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 - PSession session = sessionIter->second; + // Accept session on SIP level + PSession session = sessionIter->second; - // Get user headers - Session::UserHeaders info; - JsonCpp::Value& arg = request["userinfo"]; - std::vector keys = arg.getMemberNames(); - for (const std::string& k: keys) - info[k] = arg[k].asString(); - session->setUserHeaders(info); + // Get user headers + Session::UserHeaders info; + JsonCpp::Value& arg = request["userinfo"]; + std::vector keys = arg.getMemberNames(); + for (const std::string& k: keys) + info[k] = arg[k].asString(); + session->setUserHeaders(info); - // Accept finally - session->accept(); + // Accept finally + session->accept(); - answer["status"] = Status_Ok; + answer["status"] = Status_Ok; + } } else answer["status"] = Status_SessionNotFound; - } void AgentImpl::processDestroySession(JsonCpp::Value& request, JsonCpp::Value& answer) diff --git a/src/engine/audio/Audio_DataWindow.cpp b/src/engine/audio/Audio_DataWindow.cpp index c33f5040..8a16c404 100644 --- a/src/engine/audio/Audio_DataWindow.cpp +++ b/src/engine/audio/Audio_DataWindow.cpp @@ -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; diff --git a/src/engine/helper/HL_NetworkSocket.cpp b/src/engine/helper/HL_NetworkSocket.cpp index 05dc7423..2d0802ce 100644 --- a/src/engine/helper/HL_NetworkSocket.cpp +++ b/src/engine/helper/HL_NetworkSocket.cpp @@ -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 @@ -19,11 +20,11 @@ #endif #include +#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; diff --git a/src/engine/helper/HL_Rtp.cpp b/src/engine/helper/HL_Rtp.cpp index 728d1ae0..d244bba6 100644 --- a/src/engine/helper/HL_Rtp.cpp +++ b/src/engine/helper/HL_Rtp.cpp @@ -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,70 +8,95 @@ # include #endif -#if defined(TARGET_LINUX) || defined(TARGET_ANDROID) +#if defined(TARGET_LINUX) || defined(TARGET_ANDROID) || defined(TARGET_OSX) # include #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 -#endif +#include +#include +#include +#include +#include -#include -#include +#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 */ unsigned char x:1; /* header extension flag */ unsigned char p:1; /* padding flag */ - unsigned char version:2; /* protocol version */ - unsigned char pt:7; /* payload type */ - unsigned char m:1; /* marker bit */ - unsigned short seq; /* sequence number */ - unsigned int ts; /* timestamp */ - unsigned int ssrc; /* synchronization source */ + unsigned char version:2; /* protocol version */ + unsigned char pt:7; /* payload type */ + unsigned char m:1; /* marker bit */ + unsigned short seq; /* sequence number */ + unsigned int ts; /* timestamp */ + unsigned int ssrc; /* synchronization source */ }; struct RtcpHeader { - unsigned char rc:5; /* reception report count */ + 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 */ + 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(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(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(buffer)->ssrc); - else + else if (isRtpOrRtcp(buffer, length)) return ntohl(reinterpret_cast(buffer)->ssrc); + return 0; } void RtpHelper::setSsrc(void* buffer, size_t length, uint32_t ssrc) { if (isRtp(buffer, length)) reinterpret_cast(buffer)->ssrc = htonl(ssrc); - else + else if (isRtpOrRtcp(buffer, length)) reinterpret_cast(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(buffer); + const uint8_t* p = static_cast(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(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(payloadLen); } -#if defined(USE_RTPDUMP) -RtpDump::RtpDump(const char *filename) - :mFilename(filename) -{} +// --- RtpDump implementation --- -RtpDump::~RtpDump() +std::shared_ptr 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(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 /\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(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(&buf32), 4); + mStartSec = ntohl(buf32); + + input.read(reinterpret_cast(&buf32), 4); + mStartUsec = ntohl(buf32); + + input.read(reinterpret_cast(&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(&buf16), 2); + mSourcePort = ntohs(buf16); + + input.read(reinterpret_cast(&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(&recLength), 2); + if (input.gcount() != 2) break; + recLength = ntohs(recLength); + + input.read(reinterpret_cast(&plen), 2); + if (input.gcount() != 2) break; + plen = ntohs(plen); + + input.read(reinterpret_cast(&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 body(plen); + input.read(reinterpret_cast(body.data()), plen); + if (static_cast(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(recLength) - 8 - plen; + if (pad > 0) + input.seekg(static_cast(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& 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(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(epoch); + auto usec = std::chrono::duration_cast(epoch - sec); + mStartSec = static_cast(sec.count()); + mStartUsec = static_cast(usec.count()); + } else { + auto elapsed = std::chrono::duration_cast(now - mRecordStart); + offsetMs = static_cast(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(buffer), + static_cast(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(textLine.size())); + + // --- 2. Binary file header (16 bytes) --- + uint32_t buf32; + uint16_t buf16; + + buf32 = htonl(mStartSec); + output.write(reinterpret_cast(&buf32), 4); + + buf32 = htonl(mStartUsec); + output.write(reinterpret_cast(&buf32), 4); + + buf32 = htonl(mSourceIp); + output.write(reinterpret_cast(&buf32), 4); + + buf16 = htons(mSourcePort); + output.write(reinterpret_cast(&buf16), 2); + + buf16 = 0; // padding + output.write(reinterpret_cast(&buf16), 2); + + // --- 3. Packet records --- + size_t written = 0; + + for (const auto& pkt : mPacketList) { + if (pkt.mRawData.empty()) + continue; + + uint16_t plen = static_cast(pkt.mRawData.size()); + uint16_t recLength = static_cast(plen + 8); + + buf16 = htons(recLength); + output.write(reinterpret_cast(&buf16), 2); + + buf16 = htons(plen); + output.write(reinterpret_cast(&buf16), 2); + + buf32 = htonl(pkt.mOffsetMs); + output.write(reinterpret_cast(&buf32), 4); + + output.write(reinterpret_cast(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; +} diff --git a/src/engine/helper/HL_Rtp.h b/src/engine/helper/HL_Rtp.h index cbcc4bef..a2c8798f 100644 --- a/src/engine/helper/HL_Rtp.h +++ b/src/engine/helper/HL_Rtp.h @@ -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 -#include +#include +#include +#include +#include +#include // Class to carry rtp/rtcp socket pair template @@ -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 /\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 mPacket; + std::vector mRawData; + uint32_t mOffsetMs = 0; }; typedef std::vector 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 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& 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 diff --git a/src/engine/helper/HL_Sync.cpp b/src/engine/helper/HL_Sync.cpp index 5a6f4592..517667a9 100644 --- a/src/engine/helper/HL_Sync.cpp +++ b/src/engine/helper/HL_Sync.cpp @@ -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 lk(m_mtx); diff --git a/src/engine/media/MT_Codec.h b/src/engine/media/MT_Codec.h index f0da8002..6972c9f5 100644 --- a/src/engine/media/MT_Codec.h +++ b/src/engine/media/MT_Codec.h @@ -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 +#include + #include "resiprocate/resip/stack/SdpContents.hxx" #include "../helper/HL_Types.h" -#include -#include "../helper/HL_Pointer.h" #include "../audio/Audio_Interface.h" namespace MT diff --git a/src/engine/media/MT_Dtmf.cpp b/src/engine/media/MT_Dtmf.cpp index 05d9be5a..5ba3838e 100644 --- a/src/engine/media/MT_Dtmf.cpp +++ b/src/engine/media/MT_Dtmf.cpp @@ -18,34 +18,34 @@ using namespace MT; void DtmfBuilder::buildRfc2833(int tone, int duration, int volume, bool endOfEvent, void* output) { - assert(duration); - assert(output); - assert(tone); + assert(duration); + assert(output); + assert(tone); - unsigned char toneValue = 0; - if (tone >= '0' && tone <='9') - toneValue = tone - '0'; - else - if (tone >= 'A' && tone <='D' ) - toneValue = tone - 'A' + 12; - else - if (tone == '*') - toneValue = 10; - else - if (tone == '#') - toneValue = 11; + unsigned char toneValue = 0; + if (tone >= '0' && tone <='9') + toneValue = tone - '0'; + else + if (tone >= 'A' && tone <='D' ) + toneValue = tone - 'A' + 12; + else + if (tone == '*') + toneValue = 10; + else + if (tone == '#') + toneValue = 11; - char* packet = (char*)output; + char* packet = (char*)output; - packet[0] = toneValue; - packet[1] = 1 | (volume << 2); - if (endOfEvent) - packet[1] |= 128; - else - packet[1] &= 127; + packet[0] = toneValue; + packet[1] = 1 | (volume << 2); + if (endOfEvent) + packet[1] |= 128; + else + packet[1] &= 127; - unsigned short durationValue = htons(duration); - memcpy(packet + 2, &durationValue, 2); + unsigned short durationValue = htons(duration); + memcpy(packet + 2, &durationValue, 2); } #pragma region Inband DTMF support @@ -62,7 +62,7 @@ static bool sineTabInit = false; static double sinetab[1 << 11]; static inline double sine(unsigned int ptr) { - return sinetab[ptr >> (32-11)]; + return sinetab[ptr >> (32-11)]; } #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 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 static inline int phinc(double f) { - return ifix(TWO32 * f / (double) AUDIO_SAMPLERATE); + return ifix(TWO32 * f / (double) AUDIO_SAMPLERATE); } static char dtmfSymbols[16] = { @@ -105,32 +105,32 @@ static char dtmfSymbols[16] = { char PDTMFEncoder_DtmfChar(int i) { - - if (i < 16) - return dtmfSymbols[i]; - else - return 0; + + if (i < 16) + return dtmfSymbols[i]; + else + return 0; } // DTMF frequencies as per http://www.commlinx.com.au/DTMF_frequencies.htm static double dtmfFreqs[16][2] = { - { 941.0, 1336.0 }, // 0 - { 697.0, 1209.0 }, // 1 - { 697.0, 1336.0 }, // 2 - { 697.0, 1477.0 }, // 3 - { 770.0, 1209.0 }, // 4 - { 770.0, 1336.0 }, // 5 - { 770.0, 1477.0 }, // 6 - { 852.0, 1209.0 }, // 7 - { 852.0, 1336.0 }, // 8 - { 852.0, 1477.0 }, // 9 - { 697.0, 1633.0 }, // A - { 770.0, 1633.0 }, // B - { 852.0, 1633.0 }, // C - { 941.0, 1633.0 }, // D - { 941.0, 1209.0 }, // * - { 941.0, 1477.0 } // # + { 941.0, 1336.0 }, // 0 + { 697.0, 1209.0 }, // 1 + { 697.0, 1336.0 }, // 2 + { 697.0, 1477.0 }, // 3 + { 770.0, 1209.0 }, // 4 + { 770.0, 1336.0 }, // 5 + { 770.0, 1477.0 }, // 6 + { 852.0, 1209.0 }, // 7 + { 852.0, 1336.0 }, // 8 + { 852.0, 1477.0 }, // 9 + { 697.0, 1633.0 }, // A + { 770.0, 1633.0 }, // B + { 852.0, 1633.0 }, // C + { 941.0, 1633.0 }, // D + { 941.0, 1209.0 }, // * + { 941.0, 1477.0 } // # }; @@ -138,81 +138,81 @@ static Mutex LocalDtmfMutex; void PDTMFEncoder_MakeSineTable() { - Lock lock(LocalDtmfMutex); - - if (!sineTabInit) { - for (int k = 0; k < SINELEN; k++) { - double th = TWOPI * (double) k / (double) SINELEN; - double v = sin(th); - sinetab[k] = v; + Lock lock(LocalDtmfMutex); + + if (!sineTabInit) { + for (int k = 0; k < SINELEN; k++) { + double th = TWOPI * (double) k / (double) SINELEN; + double v = sin(th); + sinetab[k] = v; + } + sineTabInit = true; } - sineTabInit = true; - } } 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]; - int phinc1 = phinc(f1), phinc2 = phinc(f2); - int ns1 = ms1 * (rate/1000); - int ns2 = ms2 * (rate/1000); - unsigned int ptr1 = 0, ptr2 = 0; - ptr1 += phinc1 * ns1; - ptr2 += phinc2 * ns1; + double amp = amptab[ak]; + int phinc1 = phinc(f1), phinc2 = phinc(f2); + int ns1 = ms1 * (rate/1000); + int ns2 = ms2 * (rate/1000); + unsigned int ptr1 = 0, ptr2 = 0; + ptr1 += phinc1 * 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)); - int ival = ifix(val); - if (ival < -32768) - ival = -32768; - else if (val > 32767) - ival = 32767; + double val = amp * (sine(ptr1) + sine(ptr2)); + int ival = ifix(val); + if (ival < -32768) + ival = -32768; + else if (val > 32767) + ival = 32767; - result[dataPtr++] = ival / 2; + result[dataPtr++] = ival / 2; - ptr1 += phinc1; - ptr2 += phinc2; - } + ptr1 += phinc1; + ptr2 += phinc2; + } } void PDTMFEncoder_AddTone(char _digit, unsigned startTime, unsigned finishTime, unsigned rate, short* result) { - char digit = (char)toupper(_digit); - if ('0' <= digit && digit <= '9') - digit = digit - '0'; + char digit = (char)toupper(_digit); + if ('0' <= digit && digit <= '9') + digit = digit - '0'; - else if ('A' <= digit && digit <= 'D') - digit = digit + 10 - 'A'; + else if ('A' <= digit && digit <= 'D') + digit = digit + 10 - 'A'; - else if (digit == '*') - digit = 14; + else if (digit == '*') + digit = 14; - else if (digit == '#') - digit = 15; + else if (digit == '#') + digit = 15; - else - return ; + else + 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 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 DtmfContext::DtmfContext() -:mType(Dtmf_Rfc2833) + :mType(Dtmf_Rfc2833) { } @@ -222,112 +222,112 @@ DtmfContext::~DtmfContext() void DtmfContext::setType(Type t) { - mType = t; + mType = t; } DtmfContext::Type DtmfContext::type() { - return mType; + return mType; } void DtmfContext::startTone(int tone, int volume) { - Lock l(mGuard); - - // Stop current tone if needed - if (mQueue.size()) - stopTone(); - mQueue.push_back(Dtmf(tone, volume, 0)); + Lock l(mGuard); + + // Stop current tone if needed + if (mQueue.size()) + stopTone(); + mQueue.push_back(Dtmf(tone, volume, 0)); } void DtmfContext::stopTone() { - Lock l(mGuard); - - // Switch to "emit 3 terminating packets" mode - if (mQueue.size()) - { - switch (mType) - { - case Dtmf_Rfc2833: - mQueue.front().mStopped = true; - mQueue.erase(mQueue.begin()); - break; + Lock l(mGuard); - case Dtmf_Inband: - if (!mQueue.front().mFinishCount) - mQueue.front().mFinishCount = MT_DTMF_END_PACKETS; - break; + // Switch to "emit 3 terminating packets" mode + if (mQueue.size()) + { + switch (mType) + { + case Dtmf_Rfc2833: + mQueue.front().mStopped = true; + mQueue.erase(mQueue.begin()); + break; + + case Dtmf_Inband: + if (!mQueue.front().mFinishCount) + mQueue.front().mFinishCount = MT_DTMF_END_PACKETS; + break; + } } - } } void DtmfContext::queueTone(int tone, int volume, int duration) { - Lock l(mGuard); - mQueue.push_back(Dtmf(tone, volume, duration)); + Lock l(mGuard); + mQueue.push_back(Dtmf(tone, volume, duration)); } void DtmfContext::clearAllTones() { - Lock l(mGuard); - mQueue.clear(); + Lock l(mGuard); + mQueue.clear(); } bool DtmfContext::getInband(int milliseconds, int rate, ByteBuffer& output) { - Lock l(mGuard); + Lock l(mGuard); - if (!mQueue.size() || mType != Dtmf_Inband) - return false; + if (!mQueue.size() || mType != Dtmf_Inband) + return false; - // - Dtmf& d = mQueue.front(); - - output.resize(milliseconds * rate / 1000 * 2); - DtmfBuilder::buildInband(d.mTone, d.mCurrentTime, d.mCurrentTime + milliseconds, rate, (short*)output.mutableData()); - d.mCurrentTime += milliseconds; - return true; + // + Dtmf& d = mQueue.front(); + + 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; } bool DtmfContext::getRfc2833(int milliseconds, ByteBuffer& output, ByteBuffer& stopPacket) { - Lock l(mGuard); - if (!mQueue.size() || mType != Dtmf_Rfc2833) - return false; + Lock l(mGuard); + if (!mQueue.size() || mType != Dtmf_Rfc2833) + return false; - Dtmf& d = mQueue.front(); - // See if tone has enough duration to produce another packet - if (d.mDuration > 0) - { - // Emit rfc2833 packet - output.resize(4); - DtmfBuilder::buildRfc2833(d.mTone, milliseconds, d.mVolume, false, output.mutableData()); - d.mDuration -= milliseconds; - if(d.mDuration <= 0) - d.mStopped = true; - } - else - if (!d.mStopped) - { - output.resize(4); - DtmfBuilder::buildRfc2833(d.mTone, milliseconds, d.mVolume, false, output.mutableData()); - } - else - output.clear(); - - if (d.mStopped) - { - stopPacket.resize(4); - DtmfBuilder::buildRfc2833(d.mTone, milliseconds, d.mVolume, true, stopPacket.mutableData()); - } - else - stopPacket.clear(); - - if (d.mStopped) - mQueue.erase(mQueue.begin()); + Dtmf& d = mQueue.front(); + // See if tone has enough duration to produce another packet + if (d.mDuration > 0) + { + // Emit rfc2833 packet + output.resize(4); + DtmfBuilder::buildRfc2833(d.mTone, milliseconds, d.mVolume, false, output.mutableData()); + d.mDuration -= milliseconds; + if(d.mDuration <= 0) + d.mStopped = true; + } + else + if (!d.mStopped) + { + output.resize(4); + DtmfBuilder::buildRfc2833(d.mTone, milliseconds, d.mVolume, false, output.mutableData()); + } + else + output.clear(); - return true; + if (d.mStopped) + { + stopPacket.resize(4); + DtmfBuilder::buildRfc2833(d.mTone, milliseconds, d.mVolume, true, stopPacket.mutableData()); + } + else + stopPacket.clear(); + + if (d.mStopped) + mQueue.erase(mQueue.begin()); + + return true; } 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); 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)); - zap_dtmf_detect_init((dtmf_detect_state_t*)mState); + memset(mState, 0, sizeof(dtmf_detect_state_t)); + zap_dtmf_detect_init((dtmf_detect_state_t*)mState); } DTMFDetector::~DTMFDetector() { - if (mState) - free(mState); + if (mState) + free(mState); } std::string DTMFDetector::streamPut(unsigned char* samples, unsigned int size) { - char buf[16]; buf[0] = 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); - return buf; + char buf[16]; buf[0] = 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); + return buf; } void DTMFDetector::resetState() { - zap_dtmf_detect_init((dtmf_detect_state_t*)mState); + zap_dtmf_detect_init((dtmf_detect_state_t*)mState); } #ifndef TRUE @@ -448,12 +448,12 @@ 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 + { + 697.0, 770.0, 852.0, 941.0 }; 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; @@ -461,10 +461,10 @@ static float fax_freq = 1100.0; static char dtmf_positions[] = "123A" "456B" "789C" "*0#D"; static void goertzel_init(goertzel_state_t *s, - tone_detection_descriptor_t *t) + tone_detection_descriptor_t *t) { s->v2 = - s->v3 = 0.0; + s->v3 = 0.0; s->fac = t->fac; } /*- 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]; __asm__ __volatile__ ( - " femms;\n" + " femms;\n" - " movq 16(%%edx),%%mm2;\n" - " movq 24(%%edx),%%mm3;\n" - " movq 32(%%edx),%%mm4;\n" - " movq 40(%%edx),%%mm5;\n" - " movq 48(%%edx),%%mm6;\n" - " movq 56(%%edx),%%mm7;\n" + " movq 16(%%edx),%%mm2;\n" + " movq 24(%%edx),%%mm3;\n" + " movq 32(%%edx),%%mm4;\n" + " movq 40(%%edx),%%mm5;\n" + " movq 48(%%edx),%%mm6;\n" + " movq 56(%%edx),%%mm7;\n" - " jmp 1f;\n" - " .align 32;\n" + " jmp 1f;\n" + " .align 32;\n" - " 1: ;\n" - " prefetch (%%eax);\n" - " movq %%mm3,%%mm1;\n" - " movq %%mm2,%%mm0;\n" - " movq %%mm5,%%mm3;\n" - " movq %%mm4,%%mm2;\n" + " 1: ;\n" + " prefetch (%%eax);\n" + " movq %%mm3,%%mm1;\n" + " movq %%mm2,%%mm0;\n" + " movq %%mm5,%%mm3;\n" + " movq %%mm4,%%mm2;\n" - " pfmul %%mm7,%%mm5;\n" - " pfmul %%mm6,%%mm4;\n" - " pfsub %%mm1,%%mm5;\n" - " pfsub %%mm0,%%mm4;\n" + " pfmul %%mm7,%%mm5;\n" + " pfmul %%mm6,%%mm4;\n" + " pfsub %%mm1,%%mm5;\n" + " pfsub %%mm0,%%mm4;\n" - " movq (%%eax),%%mm0;\n" - " movq %%mm0,%%mm1;\n" - " punpckldq %%mm0,%%mm1;\n" - " add $4,%%eax;\n" - " pfadd %%mm1,%%mm5;\n" - " pfadd %%mm1,%%mm4;\n" + " movq (%%eax),%%mm0;\n" + " movq %%mm0,%%mm1;\n" + " punpckldq %%mm0,%%mm1;\n" + " add $4,%%eax;\n" + " pfadd %%mm1,%%mm5;\n" + " pfadd %%mm1,%%mm4;\n" - " dec %%ecx;\n" + " dec %%ecx;\n" - " jnz 1b;\n" + " jnz 1b;\n" - " movq %%mm2,16(%%edx);\n" - " movq %%mm3,24(%%edx);\n" - " movq %%mm4,32(%%edx);\n" - " movq %%mm5,40(%%edx);\n" + " movq %%mm2,16(%%edx);\n" + " movq %%mm3,24(%%edx);\n" + " movq %%mm4,32(%%edx);\n" + " movq %%mm5,40(%%edx);\n" - " femms;\n" - : - : "c" (samples), "a" (x), "d" (vv) - : "memory", "eax", "ecx"); + " femms;\n" + : + : "c" (samples), "a" (x), "d" (vv) + : "memory", "eax", "ecx"); s[0].v2 = vv[4]; s[1].v2 = vv[5]; @@ -555,8 +555,8 @@ static inline void _dtmf_goertzel_update(goertzel_state_t *s, /*- End of function --------------------------------------------------------*/ void zap_goertzel_update(goertzel_state_t *s, - int16_t x[], - int samples) + int16_t x[], + int samples) { int i; float v1; @@ -582,7 +582,7 @@ void zap_dtmf_detect_init (dtmf_detect_state_t *s) float theta; s->hit1 = - s->hit2 = 0; + s->hit2 = 0; for (i = 0; i < 4; i++) { @@ -591,19 +591,19 @@ void zap_dtmf_detect_init (dtmf_detect_state_t *s) theta = float(2.0*M_PI*(dtmf_col[i]/SAMPLE_RATE)); dtmf_detect_col[i].fac = float(2.0*cos(theta)); - + theta = float(2.0*M_PI*(dtmf_row[i]*2.0/SAMPLE_RATE)); dtmf_detect_row_2nd[i].fac = float(2.0*cos(theta)); theta = float(2.0*M_PI*(dtmf_col[i]*2.0/SAMPLE_RATE)); dtmf_detect_col_2nd[i].fac = float(2.0*cos(theta)); - - goertzel_init (&s->row_out[i], &dtmf_detect_row[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->col_out2nd[i], &dtmf_detect_col_2nd[i]); -s->energy = 0.0; + goertzel_init (&s->row_out[i], &dtmf_detect_row[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->col_out2nd[i], &dtmf_detect_col_2nd[i]); + + s->energy = 0.0; } /* Same for the fax dector */ @@ -625,9 +625,9 @@ s->energy = 0.0; /*- End of function --------------------------------------------------------*/ int zap_dtmf_detect (dtmf_detect_state_t *s, - int16_t amp[], - int samples, - int isradio) + int16_t amp[], + int samples, + int isradio) { float row_energy[4]; @@ -665,35 +665,35 @@ int zap_dtmf_detect (dtmf_detect_state_t *s, for (j = sample; j < limit; 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% (rough estimate) as long as a neat little 0-3 loop */ v1 = s->row_out[0].v2; s->row_out[0].v2 = s->row_out[0].v3; s->row_out[0].v3 = s->row_out[0].fac*s->row_out[0].v2 - v1 + famp; - + v1 = s->col_out[0].v2; s->col_out[0].v2 = s->col_out[0].v3; s->col_out[0].v3 = s->col_out[0].fac*s->col_out[0].v2 - v1 + famp; - + v1 = s->row_out[1].v2; s->row_out[1].v2 = s->row_out[1].v3; s->row_out[1].v3 = s->row_out[1].fac*s->row_out[1].v2 - v1 + famp; - + v1 = s->col_out[1].v2; s->col_out[1].v2 = s->col_out[1].v3; s->col_out[1].v3 = s->col_out[1].fac*s->col_out[1].v2 - v1 + famp; - + v1 = s->row_out[2].v2; s->row_out[2].v2 = s->row_out[2].v3; s->row_out[2].v3 = s->row_out[2].fac*s->row_out[2].v2 - v1 + famp; - + v1 = s->col_out[2].v2; s->col_out[2].v2 = s->col_out[2].v3; s->col_out[2].v3 = s->col_out[2].fac*s->col_out[2].v2 - v1 + famp; - + v1 = s->row_out[3].v2; s->row_out[3].v2 = s->row_out[3].v3; s->row_out[3].v3 = s->row_out[3].fac*s->row_out[3].v2 - v1 + famp; @@ -705,36 +705,36 @@ int zap_dtmf_detect (dtmf_detect_state_t *s, v1 = s->col_out2nd[0].v2; s->col_out2nd[0].v2 = s->col_out2nd[0].v3; s->col_out2nd[0].v3 = s->col_out2nd[0].fac*s->col_out2nd[0].v2 - v1 + famp; - + v1 = s->row_out2nd[0].v2; s->row_out2nd[0].v2 = s->row_out2nd[0].v3; s->row_out2nd[0].v3 = s->row_out2nd[0].fac*s->row_out2nd[0].v2 - v1 + famp; - + v1 = s->col_out2nd[1].v2; s->col_out2nd[1].v2 = s->col_out2nd[1].v3; s->col_out2nd[1].v3 = s->col_out2nd[1].fac*s->col_out2nd[1].v2 - v1 + famp; - + v1 = s->row_out2nd[1].v2; s->row_out2nd[1].v2 = s->row_out2nd[1].v3; s->row_out2nd[1].v3 = s->row_out2nd[1].fac*s->row_out2nd[1].v2 - v1 + famp; - + v1 = s->col_out2nd[2].v2; s->col_out2nd[2].v2 = s->col_out2nd[2].v3; s->col_out2nd[2].v3 = s->col_out2nd[2].fac*s->col_out2nd[2].v2 - v1 + famp; - + v1 = s->row_out2nd[2].v2; s->row_out2nd[2].v2 = s->row_out2nd[2].v3; s->row_out2nd[2].v3 = s->row_out2nd[2].fac*s->row_out2nd[2].v2 - v1 + famp; - + v1 = s->col_out2nd[3].v2; s->col_out2nd[3].v2 = s->col_out2nd[3].v3; s->col_out2nd[3].v3 = s->col_out2nd[3].fac*s->col_out2nd[3].v2 - v1 + famp; - + v1 = s->row_out2nd[3].v2; 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,28 +748,28 @@ 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++) -{ - row_energy[i] = zap_goertzel_result (&s->row_out[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; - 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]) best_col = i; - } + } hit = 0; /* Basic signal level test and the twist test */ 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 && @@ -787,8 +787,8 @@ 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] && @@ -804,7 +804,7 @@ for (best_row = best_col = 0, i = 1; i < 4; i++) to a digit. */ if (hit == s->hit3 && s->hit3 != s->hit2) { - s->mhit = hit; + s->mhit = hit; s->digit_hits[(best_row << 2) + best_col]++; s->detected_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)) { -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) { - s->mhit = 'f'; - s->detected_digits++; - if (s->current_digits < MAX_DTMF_DIGITS) - { - s->digits[s->current_digits++] = hit; - s->digits[s->current_digits] = '\0'; - } - else - { - s->lost_digits++; - } -} -s->fax_hits = 0; -} + /* 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) + { + s->digits[s->current_digits++] = hit; + s->digits[s->current_digits] = '\0'; + } + else + { + s->lost_digits++; + } + } + s->fax_hits = 0; + } s->hit1 = s->hit2; s->hit2 = s->hit3; s->hit3 = hit; /* Reinitialise the detector for the next block */ 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->row_out2nd[i], &dtmf_detect_row_2nd[i]); - goertzel_init (&s->col_out2nd[i], &dtmf_detect_col_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->fax_tone, &fax_detect); - goertzel_init (&s->fax_tone2nd, &fax_detect_2nd); -s->energy = 0.0; + goertzel_init (&s->fax_tone, &fax_detect); + goertzel_init (&s->fax_tone2nd, &fax_detect_2nd); + 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); } /*- End of function --------------------------------------------------------*/ int zap_dtmf_get (dtmf_detect_state_t *s, - char *buf, - int max) + char *buf, + int max) { if (max > s->current_digits) max = s->current_digits; diff --git a/src/engine/media/MT_SingleAudioStream.cpp b/src/engine/media/MT_SingleAudioStream.cpp index 0baa426e..3aa5702d 100644 --- a/src/engine/media/MT_SingleAudioStream.cpp +++ b/src/engine/media/MT_SingleAudioStream.cpp @@ -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& packet) { diff --git a/src/engine/media/MT_SingleAudioStream.h b/src/engine/media/MT_SingleAudioStream.h index c62822b9..7c468819 100644 --- a/src/engine/media/MT_SingleAudioStream.h +++ b/src/engine/media/MT_SingleAudioStream.h @@ -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& packet); void copyPcmTo(Audio::DataWindow& output, int needed); - protected: +protected: DtmfReceiver mDtmfReceiver; AudioReceiver mReceiver; - }; - - typedef std::map AudioStreamMap; +}; + +typedef std::map AudioStreamMap; } #endif diff --git a/test/rtp_decode/CMakeLists.txt b/test/rtp_decode/CMakeLists.txt new file mode 100644 index 00000000..4258de1e --- /dev/null +++ b/test/rtp_decode/CMakeLists.txt @@ -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) diff --git a/test/rtp_decode/main.cpp b/test/rtp_decode/main.cpp new file mode 100644 index 00000000..220bda0e --- /dev/null +++ b/test/rtp_decode/main.cpp @@ -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 --codec [--pt ] [--rate ] [--channels ] + +#include "helper/HL_Rtp.h" +#include "media/MT_CodecList.h" +#include "media/MT_Codec.h" +#include "audio/Audio_WavFile.h" + +#include +#include +#include +#include +#include +#include + +// --------------------------------------------------------------------------- +// CLI helpers +// --------------------------------------------------------------------------- + +static void usage(const char* progname) +{ + fprintf(stderr, + "Usage: %s --codec [--pt ] [--rate ] [--channels ]\n" + "\n" + "Codecs: pcmu pcma g722 g729 opus gsm gsmhr gsmefr amrnb amrwb evs ilbc20 ilbc30 isac16 isac32\n" + "\n" + "Options:\n" + " --codec Codec name (required)\n" + " --pt Override RTP payload type\n" + " --rate Sample rate hint for Opus (default 48000)\n" + " --channels 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 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 input(payloadData, payloadLen); + std::span 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(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; +}