- initial work to fix RTPdump decoder + more fixes
This commit is contained in:
@@ -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})
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
6
test/rtp_decode/CMakeLists.txt
Normal file
6
test/rtp_decode/CMakeLists.txt
Normal 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
284
test/rtp_decode/main.cpp
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user