- 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)
|
if (USE_AMR_CODEC)
|
||||||
#include (${LIB_PLATFORM}/platform_libs.cmake)
|
#include (${LIB_PLATFORM}/platform_libs.cmake)
|
||||||
|
set (OPENCORE_AMRNB opencore-amrnb)
|
||||||
|
set (OPENCORE_AMRWB opencore-amrwb)
|
||||||
message("Media: AMR NB and WB codecs will be included.")
|
message("Media: AMR NB and WB codecs will be included.")
|
||||||
set (DEFINES ${DEFINES} -DUSE_AMR_CODEC)
|
set (DEFINES ${DEFINES} -DUSE_AMR_CODEC)
|
||||||
set (LIBS_STATIC ${LIBS_STATIC} ${OPENCORE_AMRNB} ${OPENCORE_AMRWB})
|
set (LIBS_STATIC ${LIBS_STATIC} ${OPENCORE_AMRNB} ${OPENCORE_AMRWB})
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ const std::string Status_FailedToOpenFile = "failed to open file";
|
|||||||
const std::string Status_NoActiveProvider = "no active provider";
|
const std::string Status_NoActiveProvider = "no active provider";
|
||||||
const std::string Status_NoMediaAction = "no valid media action";
|
const std::string Status_NoMediaAction = "no valid media action";
|
||||||
const std::string Status_NoCommand = "no valid command";
|
const std::string Status_NoCommand = "no valid command";
|
||||||
|
const std::string Status_NoAudioManager = "no audio manager";
|
||||||
|
|
||||||
#define LOG_SUBSYSTEM "Agent"
|
#define LOG_SUBSYSTEM "Agent"
|
||||||
|
|
||||||
@@ -336,7 +337,7 @@ void AgentImpl::processStartSession(JsonCpp::Value& request, JsonCpp::Value& ans
|
|||||||
{
|
{
|
||||||
// Agent was not started
|
// Agent was not started
|
||||||
ICELogError(<< "No audio manager installed.");
|
ICELogError(<< "No audio manager installed.");
|
||||||
answer["status"] = "Audio manager not started. Most probably agent is not started.";
|
answer["status"] = Status_NoAudioManager;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -431,28 +432,35 @@ void AgentImpl::processAcceptSession(JsonCpp::Value& request, JsonCpp::Value& an
|
|||||||
auto sessionIter = mSessionMap.find(request["session_id"].asInt());
|
auto sessionIter = mSessionMap.find(request["session_id"].asInt());
|
||||||
if (sessionIter != mSessionMap.end())
|
if (sessionIter != mSessionMap.end())
|
||||||
{
|
{
|
||||||
// Ensure audio manager is here
|
if (!mAudioManager)
|
||||||
mAudioManager->start(mUseNativeAudio ? AudioManager::atReceiver : AudioManager::atNull);
|
{
|
||||||
|
ICELogError(<< "No audio manager installed.");
|
||||||
|
answer["status"] = Status_NoAudioManager;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Ensure audio manager is here
|
||||||
|
mAudioManager->start(mUseNativeAudio ? AudioManager::atReceiver : AudioManager::atNull);
|
||||||
|
|
||||||
// Accept session on SIP level
|
// Accept session on SIP level
|
||||||
PSession session = sessionIter->second;
|
PSession session = sessionIter->second;
|
||||||
|
|
||||||
// Get user headers
|
// Get user headers
|
||||||
Session::UserHeaders info;
|
Session::UserHeaders info;
|
||||||
JsonCpp::Value& arg = request["userinfo"];
|
JsonCpp::Value& arg = request["userinfo"];
|
||||||
std::vector<std::string> keys = arg.getMemberNames();
|
std::vector<std::string> keys = arg.getMemberNames();
|
||||||
for (const std::string& k: keys)
|
for (const std::string& k: keys)
|
||||||
info[k] = arg[k].asString();
|
info[k] = arg[k].asString();
|
||||||
session->setUserHeaders(info);
|
session->setUserHeaders(info);
|
||||||
|
|
||||||
// Accept finally
|
// Accept finally
|
||||||
session->accept();
|
session->accept();
|
||||||
|
|
||||||
answer["status"] = Status_Ok;
|
answer["status"] = Status_Ok;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
answer["status"] = Status_SessionNotFound;
|
answer["status"] = Status_SessionNotFound;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AgentImpl::processDestroySession(JsonCpp::Value& request, JsonCpp::Value& answer)
|
void AgentImpl::processDestroySession(JsonCpp::Value& request, JsonCpp::Value& answer)
|
||||||
|
|||||||
@@ -18,14 +18,24 @@ DataWindow::DataWindow()
|
|||||||
DataWindow::~DataWindow()
|
DataWindow::~DataWindow()
|
||||||
{
|
{
|
||||||
if (mData)
|
if (mData)
|
||||||
|
{
|
||||||
free(mData);
|
free(mData);
|
||||||
|
mData = nullptr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DataWindow::setCapacity(int capacity)
|
void DataWindow::setCapacity(int capacity)
|
||||||
{
|
{
|
||||||
Lock l(mMutex);
|
Lock l(mMutex);
|
||||||
int tail = capacity - mCapacity;
|
int tail = capacity - mCapacity;
|
||||||
|
char* buffer = mData;
|
||||||
mData = (char*)realloc(mData, capacity);
|
mData = (char*)realloc(mData, capacity);
|
||||||
|
if (!mData)
|
||||||
|
{
|
||||||
|
// Realloc failed
|
||||||
|
mData = buffer;
|
||||||
|
throw std::bad_alloc();
|
||||||
|
}
|
||||||
if (tail > 0)
|
if (tail > 0)
|
||||||
memset(mData + mCapacity, 0, tail);
|
memset(mData + mCapacity, 0, tail);
|
||||||
mCapacity = capacity;
|
mCapacity = capacity;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright(C) 2007-2017 VoIP objects (voipobjects.com)
|
/* Copyright(C) 2007-2026 VoIP objects (voipobjects.com)
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
#include "../engine_config.h"
|
#include "../engine_config.h"
|
||||||
#include "HL_NetworkSocket.h"
|
#include "HL_NetworkSocket.h"
|
||||||
|
#include "HL_Log.h"
|
||||||
|
|
||||||
#if defined(TARGET_OSX) || defined(TARGET_LINUX)
|
#if defined(TARGET_OSX) || defined(TARGET_LINUX)
|
||||||
# include <fcntl.h>
|
# include <fcntl.h>
|
||||||
@@ -19,11 +20,11 @@
|
|||||||
#endif
|
#endif
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
|
#define LOG_SUBSYSTEM "network"
|
||||||
|
|
||||||
DatagramSocket::DatagramSocket()
|
DatagramSocket::DatagramSocket()
|
||||||
:mFamily(AF_INET), mHandle(INVALID_SOCKET), mLocalPort(0)
|
:mFamily(AF_INET), mHandle(INVALID_SOCKET), mLocalPort(0)
|
||||||
{
|
{}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
DatagramSocket::~DatagramSocket()
|
DatagramSocket::~DatagramSocket()
|
||||||
{
|
{
|
||||||
@@ -160,6 +161,12 @@ void DatagramAgreggator::addSocket(PDatagramSocket socket)
|
|||||||
if (socket->mHandle == INVALID_SOCKET)
|
if (socket->mHandle == INVALID_SOCKET)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (mSocketVector.size() >= 62)
|
||||||
|
{
|
||||||
|
ICELogError(<< "fd_set overflow; too much sockets");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
FD_SET(socket->mHandle, &mReadSet);
|
FD_SET(socket->mHandle, &mReadSet);
|
||||||
if (socket->mHandle > mMaxHandle)
|
if (socket->mHandle > mMaxHandle)
|
||||||
mMaxHandle = socket->mHandle;
|
mMaxHandle = socket->mHandle;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright(C) 2007-2017 VoIPobjects (voipobjects.com)
|
/* Copyright(C) 2007-2026 VoIPobjects (voipobjects.com)
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
@@ -8,70 +8,95 @@
|
|||||||
# include <Windows.h>
|
# include <Windows.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(TARGET_LINUX) || defined(TARGET_ANDROID)
|
#if defined(TARGET_LINUX) || defined(TARGET_ANDROID) || defined(TARGET_OSX)
|
||||||
# include <arpa/inet.h>
|
# include <arpa/inet.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "HL_Rtp.h"
|
#include "HL_Rtp.h"
|
||||||
#include "HL_Exception.h"
|
#include "HL_Exception.h"
|
||||||
#include "HL_String.h"
|
#include "HL_Log.h"
|
||||||
|
|
||||||
#if defined(USE_RTP_DUMP)
|
#include "jrtplib/src/rtprawpacket.h"
|
||||||
# include "jrtplib/src/rtprawpacket.h"
|
#include "jrtplib/src/rtpipv4address.h"
|
||||||
# include "jrtplib/src/rtpipv4address.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if !defined(TARGET_WIN)
|
#include <stdexcept>
|
||||||
# include <alloca.h>
|
#include <fstream>
|
||||||
#endif
|
#include <cstring>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
#include <sstream>
|
#define LOG_SUBSYSTEM "RtpDump"
|
||||||
#include <tuple>
|
|
||||||
|
|
||||||
|
static constexpr size_t MAX_RTP_PACKET_SIZE = 65535;
|
||||||
|
static const char RTPDUMP_SHEBANG[] = "#!rtpplay1.0";
|
||||||
|
|
||||||
|
// RTP fixed header (little-endian bit-field layout)
|
||||||
struct RtpHeader
|
struct RtpHeader
|
||||||
{
|
{
|
||||||
unsigned char cc:4; /* CSRC count */
|
unsigned char cc:4; /* CSRC count */
|
||||||
unsigned char x:1; /* header extension flag */
|
unsigned char x:1; /* header extension flag */
|
||||||
unsigned char p:1; /* padding flag */
|
unsigned char p:1; /* padding flag */
|
||||||
unsigned char version:2; /* protocol version */
|
unsigned char version:2; /* protocol version */
|
||||||
unsigned char pt:7; /* payload type */
|
unsigned char pt:7; /* payload type */
|
||||||
unsigned char m:1; /* marker bit */
|
unsigned char m:1; /* marker bit */
|
||||||
unsigned short seq; /* sequence number */
|
unsigned short seq; /* sequence number */
|
||||||
unsigned int ts; /* timestamp */
|
unsigned int ts; /* timestamp */
|
||||||
unsigned int ssrc; /* synchronization source */
|
unsigned int ssrc; /* synchronization source */
|
||||||
};
|
};
|
||||||
|
|
||||||
struct RtcpHeader
|
struct RtcpHeader
|
||||||
{
|
{
|
||||||
unsigned char rc:5; /* reception report count */
|
unsigned char rc:5; /* reception report count */
|
||||||
unsigned char p:1; /* padding flag */
|
unsigned char p:1; /* padding flag */
|
||||||
unsigned char version:2; /* protocol version */
|
unsigned char version:2; /* protocol version */
|
||||||
unsigned char pt:8; /* payload type */
|
unsigned char pt; /* payload type */
|
||||||
uint16_t len; /* length */
|
uint16_t len; /* length */
|
||||||
uint32_t ssrc; /* synchronization source */
|
uint32_t ssrc; /* synchronization source */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// --- IPv4 address helpers ---
|
||||||
|
|
||||||
|
static std::string ipToString(uint32_t ip)
|
||||||
|
{
|
||||||
|
// ip in host byte order → dotted-decimal
|
||||||
|
return std::to_string((ip >> 24) & 0xFF) + "." +
|
||||||
|
std::to_string((ip >> 16) & 0xFF) + "." +
|
||||||
|
std::to_string((ip >> 8) & 0xFF) + "." +
|
||||||
|
std::to_string( ip & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t stringToIp(const std::string& s)
|
||||||
|
{
|
||||||
|
unsigned a = 0, b = 0, c = 0, d = 0;
|
||||||
|
if (std::sscanf(s.c_str(), "%u.%u.%u.%u", &a, &b, &c, &d) != 4)
|
||||||
|
return 0;
|
||||||
|
if (a > 255 || b > 255 || c > 255 || d > 255)
|
||||||
|
return 0;
|
||||||
|
return (a << 24) | (b << 16) | (c << 8) | d;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- RtpHelper implementation ---
|
||||||
|
|
||||||
bool RtpHelper::isRtp(const void* buffer, size_t length)
|
bool RtpHelper::isRtp(const void* buffer, size_t length)
|
||||||
{
|
{
|
||||||
if (length < 12)
|
if (length < 12)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
const RtpHeader* h = reinterpret_cast<const RtpHeader*>(buffer);
|
const RtpHeader* h = reinterpret_cast<const RtpHeader*>(buffer);
|
||||||
if (h->version != 0b10)
|
if (h->version != 2)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
unsigned char pt = h->pt;
|
unsigned char pt = h->pt;
|
||||||
bool rtp = ( (pt & 0x7F) >= 96 && (pt & 0x7F) <= 127) || ((pt & 0x7F) < 35);
|
bool rtp = (pt >= 96 && pt <= 127) || (pt < 35);
|
||||||
return rtp;
|
return rtp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool RtpHelper::isRtpOrRtcp(const void* buffer, size_t length)
|
bool RtpHelper::isRtpOrRtcp(const void* buffer, size_t length)
|
||||||
{
|
{
|
||||||
if (length < 12)
|
if (length < 12)
|
||||||
return false;
|
return false;
|
||||||
const RtcpHeader* h = reinterpret_cast<const RtcpHeader*>(buffer);
|
const RtcpHeader* h = reinterpret_cast<const RtcpHeader*>(buffer);
|
||||||
return h->version == 0b10;
|
return h->version == 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RtpHelper::isRtcp(const void* buffer, size_t length)
|
bool RtpHelper::isRtcp(const void* buffer, size_t length)
|
||||||
@@ -83,15 +108,16 @@ unsigned RtpHelper::findSsrc(const void* buffer, size_t length)
|
|||||||
{
|
{
|
||||||
if (isRtp(buffer, length))
|
if (isRtp(buffer, length))
|
||||||
return ntohl(reinterpret_cast<const RtpHeader*>(buffer)->ssrc);
|
return ntohl(reinterpret_cast<const RtpHeader*>(buffer)->ssrc);
|
||||||
else
|
else if (isRtpOrRtcp(buffer, length))
|
||||||
return ntohl(reinterpret_cast<const RtcpHeader*>(buffer)->ssrc);
|
return ntohl(reinterpret_cast<const RtcpHeader*>(buffer)->ssrc);
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RtpHelper::setSsrc(void* buffer, size_t length, uint32_t ssrc)
|
void RtpHelper::setSsrc(void* buffer, size_t length, uint32_t ssrc)
|
||||||
{
|
{
|
||||||
if (isRtp(buffer, length))
|
if (isRtp(buffer, length))
|
||||||
reinterpret_cast<RtpHeader*>(buffer)->ssrc = htonl(ssrc);
|
reinterpret_cast<RtpHeader*>(buffer)->ssrc = htonl(ssrc);
|
||||||
else
|
else if (isRtpOrRtcp(buffer, length))
|
||||||
reinterpret_cast<RtcpHeader*>(buffer)->ssrc = htonl(ssrc);
|
reinterpret_cast<RtcpHeader*>(buffer)->ssrc = htonl(ssrc);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,47 +139,186 @@ int RtpHelper::findPacketNo(const void *buffer, size_t length)
|
|||||||
|
|
||||||
int RtpHelper::findPayloadLength(const void* buffer, size_t length)
|
int RtpHelper::findPayloadLength(const void* buffer, size_t length)
|
||||||
{
|
{
|
||||||
if (isRtp(buffer, length))
|
if (!isRtp(buffer, length))
|
||||||
{
|
|
||||||
return length - 12;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
|
const RtpHeader* h = reinterpret_cast<const RtpHeader*>(buffer);
|
||||||
|
const uint8_t* p = static_cast<const uint8_t*>(buffer);
|
||||||
|
|
||||||
|
// Fixed header (12 bytes) + CSRC list (4 * CC bytes)
|
||||||
|
size_t offset = 12 + 4u * h->cc;
|
||||||
|
if (offset > length)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
// Header extension
|
||||||
|
if (h->x) {
|
||||||
|
if (offset + 4 > length)
|
||||||
|
return -1;
|
||||||
|
uint16_t extWords = (static_cast<uint16_t>(p[offset + 2]) << 8) | p[offset + 3];
|
||||||
|
offset += 4 + 4u * extWords;
|
||||||
|
if (offset > length)
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t payloadLen = length - offset;
|
||||||
|
|
||||||
|
// Padding
|
||||||
|
if (h->p && payloadLen > 0) {
|
||||||
|
uint8_t padBytes = p[length - 1];
|
||||||
|
if (padBytes > payloadLen)
|
||||||
|
return -1;
|
||||||
|
payloadLen -= padBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
return static_cast<int>(payloadLen);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(USE_RTPDUMP)
|
// --- RtpDump implementation ---
|
||||||
RtpDump::RtpDump(const char *filename)
|
|
||||||
:mFilename(filename)
|
|
||||||
{}
|
|
||||||
|
|
||||||
RtpDump::~RtpDump()
|
std::shared_ptr<jrtplib::RTPPacket> RtpDump::parseRtpData(const uint8_t* data, size_t len)
|
||||||
{
|
{
|
||||||
flush();
|
if (!data || len < 12 || !RtpHelper::isRtp(data, len))
|
||||||
for (PacketList::iterator packetIter=mPacketList.begin(); packetIter!=mPacketList.end(); ++packetIter)
|
return nullptr;
|
||||||
{
|
|
||||||
//free(packetIter->mData);
|
try {
|
||||||
delete packetIter->mPacket;
|
// Both are heap-allocated; RTPRawPacket takes ownership and deletes them
|
||||||
|
auto* addr = new jrtplib::RTPIPv4Address(uint32_t(0), uint16_t(0));
|
||||||
|
uint8_t* dataCopy = new uint8_t[len];
|
||||||
|
std::memcpy(dataCopy, data, len);
|
||||||
|
|
||||||
|
jrtplib::RTPRawPacket raw(dataCopy, len, addr, jrtplib::RTPTime(0), true);
|
||||||
|
auto packet = std::make_shared<jrtplib::RTPPacket>(raw);
|
||||||
|
|
||||||
|
if (packet->GetCreationError() != 0)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
return packet;
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
ICELogInfo(<< "Failed to parse RTP packet: " << e.what());
|
||||||
|
return nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RtpDump::RtpDump(const char* filename)
|
||||||
|
: mFilename(filename ? filename : "")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
RtpDump::~RtpDump() = default;
|
||||||
|
|
||||||
|
void RtpDump::setSource(uint32_t ip, uint16_t port)
|
||||||
|
{
|
||||||
|
mSourceIp = ip;
|
||||||
|
mSourcePort = port;
|
||||||
|
}
|
||||||
|
|
||||||
void RtpDump::load()
|
void RtpDump::load()
|
||||||
{
|
{
|
||||||
FILE* f = fopen(mFilename.c_str(), "rb");
|
if (mFilename.empty())
|
||||||
if (!f)
|
throw std::runtime_error("No filename specified");
|
||||||
throw Exception(ERR_WAVFILE_FAILED);
|
|
||||||
|
|
||||||
while (!feof(f))
|
std::ifstream input(mFilename, std::ios::binary);
|
||||||
{
|
if (!input.is_open())
|
||||||
RtpData data;
|
throw std::runtime_error("Failed to open RTP dump file: " + mFilename);
|
||||||
fread(&data.mLength, sizeof data.mLength, 1, f);
|
|
||||||
data.mData = new char[data.mLength];
|
mPacketList.clear();
|
||||||
fread(data.mData, 1, data.mLength, f);
|
|
||||||
jrtplib::RTPIPv4Address addr(jrtplib::RTPAddress::IPv4Address);
|
// --- 1. Text header: "#!rtpplay1.0 <ip>/<port>\n" ---
|
||||||
jrtplib::RTPTime t(0);
|
std::string textLine;
|
||||||
jrtplib::RTPRawPacket* raw = new jrtplib::RTPRawPacket((unsigned char*)data.mData, data.mLength, &addr, t, true);
|
std::getline(input, textLine);
|
||||||
data.mPacket = new jrtplib::RTPPacket(*raw);
|
if (textLine.compare(0, sizeof(RTPDUMP_SHEBANG) - 1, RTPDUMP_SHEBANG) != 0)
|
||||||
mPacketList.push_back(data);
|
throw std::runtime_error("Invalid rtpdump header: expected " + std::string(RTPDUMP_SHEBANG));
|
||||||
|
|
||||||
|
// Parse source address from the text line
|
||||||
|
size_t spacePos = textLine.find(' ');
|
||||||
|
if (spacePos != std::string::npos) {
|
||||||
|
std::string addrPart = textLine.substr(spacePos + 1);
|
||||||
|
size_t slashPos = addrPart.find('/');
|
||||||
|
if (slashPos != std::string::npos) {
|
||||||
|
mSourceIp = stringToIp(addrPart.substr(0, slashPos));
|
||||||
|
try {
|
||||||
|
mSourcePort = static_cast<uint16_t>(std::stoi(addrPart.substr(slashPos + 1)));
|
||||||
|
} catch (...) {
|
||||||
|
mSourcePort = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- 2. Binary file header (RD_hdr_t, 16 bytes) ---
|
||||||
|
uint32_t buf32;
|
||||||
|
uint16_t buf16;
|
||||||
|
|
||||||
|
input.read(reinterpret_cast<char*>(&buf32), 4);
|
||||||
|
mStartSec = ntohl(buf32);
|
||||||
|
|
||||||
|
input.read(reinterpret_cast<char*>(&buf32), 4);
|
||||||
|
mStartUsec = ntohl(buf32);
|
||||||
|
|
||||||
|
input.read(reinterpret_cast<char*>(&buf32), 4); // source IP (already NBO in file)
|
||||||
|
// The binary header stores IP in network byte order; convert to host
|
||||||
|
mSourceIp = ntohl(buf32);
|
||||||
|
|
||||||
|
input.read(reinterpret_cast<char*>(&buf16), 2);
|
||||||
|
mSourcePort = ntohs(buf16);
|
||||||
|
|
||||||
|
input.read(reinterpret_cast<char*>(&buf16), 2); // padding — discard
|
||||||
|
|
||||||
|
if (!input.good())
|
||||||
|
throw std::runtime_error("Failed to read rtpdump binary header");
|
||||||
|
|
||||||
|
// --- 3. Packet records ---
|
||||||
|
size_t packetCount = 0;
|
||||||
|
|
||||||
|
while (input.good() && input.peek() != EOF) {
|
||||||
|
// Packet header: length(2) + plen(2) + offset(4) = 8 bytes
|
||||||
|
uint16_t recLength, plen;
|
||||||
|
uint32_t offsetMs;
|
||||||
|
|
||||||
|
input.read(reinterpret_cast<char*>(&recLength), 2);
|
||||||
|
if (input.gcount() != 2) break;
|
||||||
|
recLength = ntohs(recLength);
|
||||||
|
|
||||||
|
input.read(reinterpret_cast<char*>(&plen), 2);
|
||||||
|
if (input.gcount() != 2) break;
|
||||||
|
plen = ntohs(plen);
|
||||||
|
|
||||||
|
input.read(reinterpret_cast<char*>(&offsetMs), 4);
|
||||||
|
if (input.gcount() != 4) break;
|
||||||
|
offsetMs = ntohl(offsetMs);
|
||||||
|
|
||||||
|
// All-zeros record signals end of file in some implementations
|
||||||
|
if (recLength == 0 && plen == 0 && offsetMs == 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (plen == 0 || plen > MAX_RTP_PACKET_SIZE)
|
||||||
|
throw std::runtime_error("Invalid packet payload length: " + std::to_string(plen));
|
||||||
|
|
||||||
|
if (recLength < plen + 8)
|
||||||
|
throw std::runtime_error("Record length (" + std::to_string(recLength) +
|
||||||
|
") smaller than payload + header (" + std::to_string(plen + 8) + ")");
|
||||||
|
|
||||||
|
// Read body
|
||||||
|
std::vector<uint8_t> body(plen);
|
||||||
|
input.read(reinterpret_cast<char*>(body.data()), plen);
|
||||||
|
if (static_cast<size_t>(input.gcount()) != plen)
|
||||||
|
throw std::runtime_error("Incomplete packet data in rtpdump file");
|
||||||
|
|
||||||
|
// Skip any padding between plen and recLength-8
|
||||||
|
size_t pad = static_cast<size_t>(recLength) - 8 - plen;
|
||||||
|
if (pad > 0)
|
||||||
|
input.seekg(static_cast<std::streamoff>(pad), std::ios::cur);
|
||||||
|
|
||||||
|
RtpData entry;
|
||||||
|
entry.mRawData = std::move(body);
|
||||||
|
entry.mOffsetMs = offsetMs;
|
||||||
|
entry.mPacket = parseRtpData(entry.mRawData.data(), entry.mRawData.size());
|
||||||
|
|
||||||
|
mPacketList.push_back(std::move(entry));
|
||||||
|
packetCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
ICELogInfo(<< "Loaded " << packetCount << " packets from " << mFilename);
|
||||||
|
mLoaded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t RtpDump::count() const
|
size_t RtpDump::count() const
|
||||||
@@ -163,39 +328,142 @@ size_t RtpDump::count() const
|
|||||||
|
|
||||||
jrtplib::RTPPacket& RtpDump::packetAt(size_t index)
|
jrtplib::RTPPacket& RtpDump::packetAt(size_t index)
|
||||||
{
|
{
|
||||||
|
if (index >= mPacketList.size())
|
||||||
|
throw std::out_of_range("Packet index out of range: " + std::to_string(index));
|
||||||
|
|
||||||
|
if (!mPacketList[index].mPacket)
|
||||||
|
throw std::runtime_error("No parsed RTP data at index " + std::to_string(index));
|
||||||
|
|
||||||
return *mPacketList[index].mPacket;
|
return *mPacketList[index].mPacket;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const std::vector<uint8_t>& RtpDump::rawDataAt(size_t index) const
|
||||||
|
{
|
||||||
|
if (index >= mPacketList.size())
|
||||||
|
throw std::out_of_range("Packet index out of range: " + std::to_string(index));
|
||||||
|
|
||||||
|
return mPacketList[index].mRawData;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t RtpDump::offsetAt(size_t index) const
|
||||||
|
{
|
||||||
|
if (index >= mPacketList.size())
|
||||||
|
throw std::out_of_range("Packet index out of range: " + std::to_string(index));
|
||||||
|
|
||||||
|
return mPacketList[index].mOffsetMs;
|
||||||
|
}
|
||||||
|
|
||||||
void RtpDump::add(const void* buffer, size_t len)
|
void RtpDump::add(const void* buffer, size_t len)
|
||||||
{
|
{
|
||||||
RtpData data;
|
if (!buffer || len == 0)
|
||||||
data.mData = malloc(len);
|
return;
|
||||||
memcpy(data.mData, buffer, len);
|
|
||||||
data.mLength = len;
|
|
||||||
|
|
||||||
jrtplib::RTPIPv4Address addr(jrtplib::RTPAddress::IPv4Address);
|
uint32_t offsetMs = 0;
|
||||||
jrtplib::RTPTime t(0);
|
auto now = std::chrono::steady_clock::now();
|
||||||
jrtplib::RTPRawPacket* raw = new jrtplib::RTPRawPacket((unsigned char*)const_cast<void*>(data.mData), data.mLength, &addr, t, true);
|
|
||||||
data.mPacket = new jrtplib::RTPPacket(*raw);
|
if (!mRecording) {
|
||||||
//delete raw;
|
mRecording = true;
|
||||||
mPacketList.push_back(data);
|
mRecordStart = now;
|
||||||
|
|
||||||
|
// Capture wall-clock start time
|
||||||
|
auto wallNow = std::chrono::system_clock::now();
|
||||||
|
auto epoch = wallNow.time_since_epoch();
|
||||||
|
auto sec = std::chrono::duration_cast<std::chrono::seconds>(epoch);
|
||||||
|
auto usec = std::chrono::duration_cast<std::chrono::microseconds>(epoch - sec);
|
||||||
|
mStartSec = static_cast<uint32_t>(sec.count());
|
||||||
|
mStartUsec = static_cast<uint32_t>(usec.count());
|
||||||
|
} else {
|
||||||
|
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - mRecordStart);
|
||||||
|
offsetMs = static_cast<uint32_t>(elapsed.count());
|
||||||
|
}
|
||||||
|
|
||||||
|
add(buffer, len, offsetMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RtpDump::add(const void* buffer, size_t len, uint32_t offsetMs)
|
||||||
|
{
|
||||||
|
if (!buffer || len == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (len > MAX_RTP_PACKET_SIZE)
|
||||||
|
throw std::runtime_error("Packet too large: " + std::to_string(len));
|
||||||
|
|
||||||
|
RtpData entry;
|
||||||
|
entry.mRawData.assign(static_cast<const uint8_t*>(buffer),
|
||||||
|
static_cast<const uint8_t*>(buffer) + len);
|
||||||
|
entry.mOffsetMs = offsetMs;
|
||||||
|
entry.mPacket = parseRtpData(entry.mRawData.data(), entry.mRawData.size());
|
||||||
|
|
||||||
|
mPacketList.push_back(std::move(entry));
|
||||||
}
|
}
|
||||||
|
|
||||||
void RtpDump::flush()
|
void RtpDump::flush()
|
||||||
{
|
{
|
||||||
FILE* f = fopen(mFilename.c_str(), "wb");
|
if (mFilename.empty())
|
||||||
if (!f)
|
throw std::runtime_error("No filename specified");
|
||||||
throw Exception(ERR_WAVFILE_FAILED);
|
|
||||||
|
|
||||||
PacketList::iterator packetIter = mPacketList.begin();
|
std::ofstream output(mFilename, std::ios::binary);
|
||||||
for (;packetIter != mPacketList.end(); ++packetIter)
|
if (!output.is_open())
|
||||||
{
|
throw std::runtime_error("Failed to open file for writing: " + mFilename);
|
||||||
RtpData& data = *packetIter;
|
|
||||||
// Disabled for debugging only
|
// --- 1. Text header ---
|
||||||
//fwrite(&data.mLength, sizeof data.mLength, 1, f);
|
std::string textLine = std::string(RTPDUMP_SHEBANG) + " " +
|
||||||
fwrite(data.mData, data.mLength, 1, f);
|
ipToString(mSourceIp) + "/" +
|
||||||
|
std::to_string(mSourcePort) + "\n";
|
||||||
|
output.write(textLine.data(), static_cast<std::streamsize>(textLine.size()));
|
||||||
|
|
||||||
|
// --- 2. Binary file header (16 bytes) ---
|
||||||
|
uint32_t buf32;
|
||||||
|
uint16_t buf16;
|
||||||
|
|
||||||
|
buf32 = htonl(mStartSec);
|
||||||
|
output.write(reinterpret_cast<const char*>(&buf32), 4);
|
||||||
|
|
||||||
|
buf32 = htonl(mStartUsec);
|
||||||
|
output.write(reinterpret_cast<const char*>(&buf32), 4);
|
||||||
|
|
||||||
|
buf32 = htonl(mSourceIp);
|
||||||
|
output.write(reinterpret_cast<const char*>(&buf32), 4);
|
||||||
|
|
||||||
|
buf16 = htons(mSourcePort);
|
||||||
|
output.write(reinterpret_cast<const char*>(&buf16), 2);
|
||||||
|
|
||||||
|
buf16 = 0; // padding
|
||||||
|
output.write(reinterpret_cast<const char*>(&buf16), 2);
|
||||||
|
|
||||||
|
// --- 3. Packet records ---
|
||||||
|
size_t written = 0;
|
||||||
|
|
||||||
|
for (const auto& pkt : mPacketList) {
|
||||||
|
if (pkt.mRawData.empty())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
uint16_t plen = static_cast<uint16_t>(pkt.mRawData.size());
|
||||||
|
uint16_t recLength = static_cast<uint16_t>(plen + 8);
|
||||||
|
|
||||||
|
buf16 = htons(recLength);
|
||||||
|
output.write(reinterpret_cast<const char*>(&buf16), 2);
|
||||||
|
|
||||||
|
buf16 = htons(plen);
|
||||||
|
output.write(reinterpret_cast<const char*>(&buf16), 2);
|
||||||
|
|
||||||
|
buf32 = htonl(pkt.mOffsetMs);
|
||||||
|
output.write(reinterpret_cast<const char*>(&buf32), 4);
|
||||||
|
|
||||||
|
output.write(reinterpret_cast<const char*>(pkt.mRawData.data()), plen);
|
||||||
|
|
||||||
|
written++;
|
||||||
}
|
}
|
||||||
fclose(f);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
if (!output.good())
|
||||||
|
throw std::runtime_error("Failed to write rtpdump file: " + mFilename);
|
||||||
|
|
||||||
|
ICELogInfo(<< "Wrote " << written << " packets to " << mFilename);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RtpDump::clear()
|
||||||
|
{
|
||||||
|
mPacketList.clear();
|
||||||
|
mLoaded = false;
|
||||||
|
mRecording = false;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright(C) 2007-2025 VoIPobjects (voipobjects.com)
|
/* Copyright(C) 2007-2026 VoIPobjects (voipobjects.com)
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
@@ -6,12 +6,14 @@
|
|||||||
#ifndef __HL_RTP_H
|
#ifndef __HL_RTP_H
|
||||||
#define __HL_RTP_H
|
#define __HL_RTP_H
|
||||||
|
|
||||||
#if defined(USE_RTPDUMP)
|
#include "jrtplib/src/rtppacket.h"
|
||||||
# include "jrtplib/src/rtppacket.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <stdlib.h>
|
#include <cstdlib>
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include <memory>
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
// Class to carry rtp/rtcp socket pair
|
// Class to carry rtp/rtcp socket pair
|
||||||
template<class T>
|
template<class T>
|
||||||
@@ -27,7 +29,7 @@ struct RtpPair
|
|||||||
:mRtp(rtp), mRtcp(rtcp)
|
:mRtp(rtp), mRtcp(rtcp)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
bool multiplexed() { return mRtp == mRtcp; }
|
bool multiplexed() const { return mRtp == mRtcp; }
|
||||||
};
|
};
|
||||||
|
|
||||||
class RtpHelper
|
class RtpHelper
|
||||||
@@ -35,7 +37,7 @@ class RtpHelper
|
|||||||
public:
|
public:
|
||||||
static bool isRtp(const void* buffer, size_t length);
|
static bool isRtp(const void* buffer, size_t length);
|
||||||
static int findPtype(const void* buffer, size_t length);
|
static int findPtype(const void* buffer, size_t length);
|
||||||
static int findPacketNo(const void* buffer, size_t length);
|
static int findPacketNo(const void *buffer, size_t length);
|
||||||
static bool isRtpOrRtcp(const void* buffer, size_t length);
|
static bool isRtpOrRtcp(const void* buffer, size_t length);
|
||||||
static bool isRtcp(const void* buffer, size_t length);
|
static bool isRtcp(const void* buffer, size_t length);
|
||||||
static unsigned findSsrc(const void* buffer, size_t length);
|
static unsigned findSsrc(const void* buffer, size_t length);
|
||||||
@@ -43,31 +45,104 @@ public:
|
|||||||
static int findPayloadLength(const void* buffer, size_t length);
|
static int findPayloadLength(const void* buffer, size_t length);
|
||||||
};
|
};
|
||||||
|
|
||||||
#if defined(USE_RTPDUMP)
|
/**
|
||||||
|
* @brief Standard rtpdump file format (rtptools / Wireshark compatible)
|
||||||
|
*
|
||||||
|
* Conforms to the rtpdump format defined by rtptools:
|
||||||
|
* https://formats.kaitai.io/rtpdump/
|
||||||
|
*
|
||||||
|
* File layout:
|
||||||
|
* 1. Text header line:
|
||||||
|
* "#!rtpplay1.0 <source_ip>/<source_port>\n"
|
||||||
|
*
|
||||||
|
* 2. Binary file header (RD_hdr_t, 16 bytes, all big-endian):
|
||||||
|
* uint32_t start_sec - recording start time, seconds since epoch
|
||||||
|
* uint32_t start_usec - recording start time, microseconds
|
||||||
|
* uint32_t source_ip - source IP address (network byte order)
|
||||||
|
* uint16_t source_port - source port
|
||||||
|
* uint16_t padding - always 0
|
||||||
|
*
|
||||||
|
* 3. Packet records (repeated until EOF):
|
||||||
|
* Per-packet header (RD_packet_t, 8 bytes, all big-endian):
|
||||||
|
* uint16_t length - total record length (this 8-byte header + plen)
|
||||||
|
* uint16_t plen - RTP/RTCP payload length in bytes
|
||||||
|
* uint32_t offset - milliseconds since recording start
|
||||||
|
* Followed by plen bytes of RTP/RTCP packet data.
|
||||||
|
*
|
||||||
|
* Maximum single packet payload: 65535 bytes (enforced for safety).
|
||||||
|
*/
|
||||||
class RtpDump
|
class RtpDump
|
||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
struct RtpData
|
struct RtpData
|
||||||
{
|
{
|
||||||
jrtplib::RTPPacket* mPacket;
|
std::shared_ptr<jrtplib::RTPPacket> mPacket;
|
||||||
void* mData;
|
std::vector<uint8_t> mRawData;
|
||||||
size_t mLength;
|
uint32_t mOffsetMs = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef std::vector<RtpData> PacketList;
|
typedef std::vector<RtpData> PacketList;
|
||||||
PacketList mPacketList;
|
PacketList mPacketList;
|
||||||
std::string mFilename;
|
std::string mFilename;
|
||||||
|
bool mLoaded = false;
|
||||||
|
|
||||||
|
// File header fields
|
||||||
|
uint32_t mSourceIp = 0;
|
||||||
|
uint16_t mSourcePort = 0;
|
||||||
|
uint32_t mStartSec = 0;
|
||||||
|
uint32_t mStartUsec = 0;
|
||||||
|
|
||||||
|
// Auto-compute packet offsets during recording
|
||||||
|
bool mRecording = false;
|
||||||
|
std::chrono::steady_clock::time_point mRecordStart;
|
||||||
|
|
||||||
|
std::shared_ptr<jrtplib::RTPPacket> parseRtpData(const uint8_t* data, size_t len);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
RtpDump(const char* filename);
|
explicit RtpDump(const char* filename);
|
||||||
~RtpDump();
|
~RtpDump();
|
||||||
|
|
||||||
|
/** Set source address for the file header (host byte order). */
|
||||||
|
void setSource(uint32_t ip, uint16_t port);
|
||||||
|
uint32_t sourceIp() const { return mSourceIp; }
|
||||||
|
uint16_t sourcePort() const { return mSourcePort; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Load packets from an rtpdump file
|
||||||
|
* @throws std::runtime_error on file/format error
|
||||||
|
*/
|
||||||
void load();
|
void load();
|
||||||
|
bool isLoaded() const { return mLoaded; }
|
||||||
|
|
||||||
size_t count() const;
|
size_t count() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get parsed RTP packet at index
|
||||||
|
* @throws std::out_of_range if index is invalid
|
||||||
|
* @throws std::runtime_error if packet could not be parsed as RTP
|
||||||
|
*/
|
||||||
jrtplib::RTPPacket& packetAt(size_t index);
|
jrtplib::RTPPacket& packetAt(size_t index);
|
||||||
|
|
||||||
|
/** @brief Get raw packet bytes at index */
|
||||||
|
const std::vector<uint8_t>& rawDataAt(size_t index) const;
|
||||||
|
|
||||||
|
/** @brief Get packet time offset in milliseconds */
|
||||||
|
uint32_t offsetAt(size_t index) const;
|
||||||
|
|
||||||
|
/** @brief Add a packet; time offset is auto-computed from first add() call */
|
||||||
void add(const void* data, size_t len);
|
void add(const void* data, size_t len);
|
||||||
|
|
||||||
|
/** @brief Add a packet with an explicit millisecond offset */
|
||||||
|
void add(const void* data, size_t len, uint32_t offsetMs);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Write all packets to file in rtpdump format
|
||||||
|
* @throws std::runtime_error on file error
|
||||||
|
*/
|
||||||
void flush();
|
void flush();
|
||||||
|
|
||||||
|
void clear();
|
||||||
|
const std::string& filename() const { return mFilename; }
|
||||||
};
|
};
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -280,7 +280,8 @@ size_t TimerQueue::cancel(uint64_t id) {
|
|||||||
//! Cancels all timers
|
//! Cancels all timers
|
||||||
// \return
|
// \return
|
||||||
// The number of timers cancelled
|
// The number of timers cancelled
|
||||||
size_t TimerQueue::cancelAll() {
|
size_t TimerQueue::cancelAll()
|
||||||
|
{
|
||||||
// Setting all "end" to 0 (for immediate execution) is ok,
|
// Setting all "end" to 0 (for immediate execution) is ok,
|
||||||
// since it maintains the heap integrity
|
// since it maintains the heap integrity
|
||||||
std::unique_lock<std::mutex> lk(m_mtx);
|
std::unique_lock<std::mutex> lk(m_mtx);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright(C) 2007-2016 VoIP objects (voipobjects.com)
|
/* Copyright(C) 2007-2026 VoIP objects (voipobjects.com)
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
@@ -6,10 +6,11 @@
|
|||||||
#ifndef __MT_CODEC_H
|
#ifndef __MT_CODEC_H
|
||||||
#define __MT_CODEC_H
|
#define __MT_CODEC_H
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <span>
|
||||||
|
|
||||||
#include "resiprocate/resip/stack/SdpContents.hxx"
|
#include "resiprocate/resip/stack/SdpContents.hxx"
|
||||||
#include "../helper/HL_Types.h"
|
#include "../helper/HL_Types.h"
|
||||||
#include <map>
|
|
||||||
#include "../helper/HL_Pointer.h"
|
|
||||||
#include "../audio/Audio_Interface.h"
|
#include "../audio/Audio_Interface.h"
|
||||||
|
|
||||||
namespace MT
|
namespace MT
|
||||||
|
|||||||
@@ -18,34 +18,34 @@ using namespace MT;
|
|||||||
|
|
||||||
void DtmfBuilder::buildRfc2833(int tone, int duration, int volume, bool endOfEvent, void* output)
|
void DtmfBuilder::buildRfc2833(int tone, int duration, int volume, bool endOfEvent, void* output)
|
||||||
{
|
{
|
||||||
assert(duration);
|
assert(duration);
|
||||||
assert(output);
|
assert(output);
|
||||||
assert(tone);
|
assert(tone);
|
||||||
|
|
||||||
unsigned char toneValue = 0;
|
unsigned char toneValue = 0;
|
||||||
if (tone >= '0' && tone <='9')
|
if (tone >= '0' && tone <='9')
|
||||||
toneValue = tone - '0';
|
toneValue = tone - '0';
|
||||||
else
|
else
|
||||||
if (tone >= 'A' && tone <='D' )
|
if (tone >= 'A' && tone <='D' )
|
||||||
toneValue = tone - 'A' + 12;
|
toneValue = tone - 'A' + 12;
|
||||||
else
|
else
|
||||||
if (tone == '*')
|
if (tone == '*')
|
||||||
toneValue = 10;
|
toneValue = 10;
|
||||||
else
|
else
|
||||||
if (tone == '#')
|
if (tone == '#')
|
||||||
toneValue = 11;
|
toneValue = 11;
|
||||||
|
|
||||||
char* packet = (char*)output;
|
char* packet = (char*)output;
|
||||||
|
|
||||||
packet[0] = toneValue;
|
packet[0] = toneValue;
|
||||||
packet[1] = 1 | (volume << 2);
|
packet[1] = 1 | (volume << 2);
|
||||||
if (endOfEvent)
|
if (endOfEvent)
|
||||||
packet[1] |= 128;
|
packet[1] |= 128;
|
||||||
else
|
else
|
||||||
packet[1] &= 127;
|
packet[1] &= 127;
|
||||||
|
|
||||||
unsigned short durationValue = htons(duration);
|
unsigned short durationValue = htons(duration);
|
||||||
memcpy(packet + 2, &durationValue, 2);
|
memcpy(packet + 2, &durationValue, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma region Inband DTMF support
|
#pragma region Inband DTMF support
|
||||||
@@ -62,7 +62,7 @@ static bool sineTabInit = false;
|
|||||||
static double sinetab[1 << 11];
|
static double sinetab[1 << 11];
|
||||||
static inline double sine(unsigned int ptr)
|
static inline double sine(unsigned int ptr)
|
||||||
{
|
{
|
||||||
return sinetab[ptr >> (32-11)];
|
return sinetab[ptr >> (32-11)];
|
||||||
}
|
}
|
||||||
|
|
||||||
#define TWOPI (2.0 * 3.14159265358979323846)
|
#define TWOPI (2.0 * 3.14159265358979323846)
|
||||||
@@ -75,13 +75,13 @@ static inline double sine(unsigned int ptr)
|
|||||||
static double amptab[2] = { 8191.75, 16383.5 };
|
static double amptab[2] = { 8191.75, 16383.5 };
|
||||||
static inline int ifix(double x)
|
static inline int ifix(double x)
|
||||||
{
|
{
|
||||||
return (x >= 0.0) ? (int) (x+0.5) : (int) (x-0.5);
|
return (x >= 0.0) ? (int) (x+0.5) : (int) (x-0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
// given frequency f, return corresponding phase increment
|
// given frequency f, return corresponding phase increment
|
||||||
static inline int phinc(double f)
|
static inline int phinc(double f)
|
||||||
{
|
{
|
||||||
return ifix(TWO32 * f / (double) AUDIO_SAMPLERATE);
|
return ifix(TWO32 * f / (double) AUDIO_SAMPLERATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
static char dtmfSymbols[16] = {
|
static char dtmfSymbols[16] = {
|
||||||
@@ -106,31 +106,31 @@ static char dtmfSymbols[16] = {
|
|||||||
char PDTMFEncoder_DtmfChar(int i)
|
char PDTMFEncoder_DtmfChar(int i)
|
||||||
{
|
{
|
||||||
|
|
||||||
if (i < 16)
|
if (i < 16)
|
||||||
return dtmfSymbols[i];
|
return dtmfSymbols[i];
|
||||||
else
|
else
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// DTMF frequencies as per http://www.commlinx.com.au/DTMF_frequencies.htm
|
// DTMF frequencies as per http://www.commlinx.com.au/DTMF_frequencies.htm
|
||||||
|
|
||||||
static double dtmfFreqs[16][2] = {
|
static double dtmfFreqs[16][2] = {
|
||||||
{ 941.0, 1336.0 }, // 0
|
{ 941.0, 1336.0 }, // 0
|
||||||
{ 697.0, 1209.0 }, // 1
|
{ 697.0, 1209.0 }, // 1
|
||||||
{ 697.0, 1336.0 }, // 2
|
{ 697.0, 1336.0 }, // 2
|
||||||
{ 697.0, 1477.0 }, // 3
|
{ 697.0, 1477.0 }, // 3
|
||||||
{ 770.0, 1209.0 }, // 4
|
{ 770.0, 1209.0 }, // 4
|
||||||
{ 770.0, 1336.0 }, // 5
|
{ 770.0, 1336.0 }, // 5
|
||||||
{ 770.0, 1477.0 }, // 6
|
{ 770.0, 1477.0 }, // 6
|
||||||
{ 852.0, 1209.0 }, // 7
|
{ 852.0, 1209.0 }, // 7
|
||||||
{ 852.0, 1336.0 }, // 8
|
{ 852.0, 1336.0 }, // 8
|
||||||
{ 852.0, 1477.0 }, // 9
|
{ 852.0, 1477.0 }, // 9
|
||||||
{ 697.0, 1633.0 }, // A
|
{ 697.0, 1633.0 }, // A
|
||||||
{ 770.0, 1633.0 }, // B
|
{ 770.0, 1633.0 }, // B
|
||||||
{ 852.0, 1633.0 }, // C
|
{ 852.0, 1633.0 }, // C
|
||||||
{ 941.0, 1633.0 }, // D
|
{ 941.0, 1633.0 }, // D
|
||||||
{ 941.0, 1209.0 }, // *
|
{ 941.0, 1209.0 }, // *
|
||||||
{ 941.0, 1477.0 } // #
|
{ 941.0, 1477.0 } // #
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -138,81 +138,81 @@ static Mutex LocalDtmfMutex;
|
|||||||
|
|
||||||
void PDTMFEncoder_MakeSineTable()
|
void PDTMFEncoder_MakeSineTable()
|
||||||
{
|
{
|
||||||
Lock lock(LocalDtmfMutex);
|
Lock lock(LocalDtmfMutex);
|
||||||
|
|
||||||
if (!sineTabInit) {
|
if (!sineTabInit) {
|
||||||
for (int k = 0; k < SINELEN; k++) {
|
for (int k = 0; k < SINELEN; k++) {
|
||||||
double th = TWOPI * (double) k / (double) SINELEN;
|
double th = TWOPI * (double) k / (double) SINELEN;
|
||||||
double v = sin(th);
|
double v = sin(th);
|
||||||
sinetab[k] = v;
|
sinetab[k] = v;
|
||||||
|
}
|
||||||
|
sineTabInit = true;
|
||||||
}
|
}
|
||||||
sineTabInit = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PDTMFEncoder_AddTone(double f1, double f2, unsigned ms1, unsigned ms2, unsigned rate, short* result)
|
void PDTMFEncoder_AddTone(double f1, double f2, unsigned ms1, unsigned ms2, unsigned rate, short* result)
|
||||||
{
|
{
|
||||||
int ak = 0;
|
int ak = 0;
|
||||||
|
|
||||||
PDTMFEncoder_MakeSineTable();
|
PDTMFEncoder_MakeSineTable();
|
||||||
|
|
||||||
int dataPtr = 0;
|
int dataPtr = 0;
|
||||||
|
|
||||||
double amp = amptab[ak];
|
double amp = amptab[ak];
|
||||||
int phinc1 = phinc(f1), phinc2 = phinc(f2);
|
int phinc1 = phinc(f1), phinc2 = phinc(f2);
|
||||||
int ns1 = ms1 * (rate/1000);
|
int ns1 = ms1 * (rate/1000);
|
||||||
int ns2 = ms2 * (rate/1000);
|
int ns2 = ms2 * (rate/1000);
|
||||||
unsigned int ptr1 = 0, ptr2 = 0;
|
unsigned int ptr1 = 0, ptr2 = 0;
|
||||||
ptr1 += phinc1 * ns1;
|
ptr1 += phinc1 * ns1;
|
||||||
ptr2 += phinc2 * ns1;
|
ptr2 += phinc2 * ns1;
|
||||||
|
|
||||||
for (int n = ns1; n < ns2; n++) {
|
for (int n = ns1; n < ns2; n++) {
|
||||||
|
|
||||||
double val = amp * (sine(ptr1) + sine(ptr2));
|
double val = amp * (sine(ptr1) + sine(ptr2));
|
||||||
int ival = ifix(val);
|
int ival = ifix(val);
|
||||||
if (ival < -32768)
|
if (ival < -32768)
|
||||||
ival = -32768;
|
ival = -32768;
|
||||||
else if (val > 32767)
|
else if (val > 32767)
|
||||||
ival = 32767;
|
ival = 32767;
|
||||||
|
|
||||||
result[dataPtr++] = ival / 2;
|
result[dataPtr++] = ival / 2;
|
||||||
|
|
||||||
ptr1 += phinc1;
|
ptr1 += phinc1;
|
||||||
ptr2 += phinc2;
|
ptr2 += phinc2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PDTMFEncoder_AddTone(char _digit, unsigned startTime, unsigned finishTime, unsigned rate, short* result)
|
void PDTMFEncoder_AddTone(char _digit, unsigned startTime, unsigned finishTime, unsigned rate, short* result)
|
||||||
{
|
{
|
||||||
char digit = (char)toupper(_digit);
|
char digit = (char)toupper(_digit);
|
||||||
if ('0' <= digit && digit <= '9')
|
if ('0' <= digit && digit <= '9')
|
||||||
digit = digit - '0';
|
digit = digit - '0';
|
||||||
|
|
||||||
else if ('A' <= digit && digit <= 'D')
|
else if ('A' <= digit && digit <= 'D')
|
||||||
digit = digit + 10 - 'A';
|
digit = digit + 10 - 'A';
|
||||||
|
|
||||||
else if (digit == '*')
|
else if (digit == '*')
|
||||||
digit = 14;
|
digit = 14;
|
||||||
|
|
||||||
else if (digit == '#')
|
else if (digit == '#')
|
||||||
digit = 15;
|
digit = 15;
|
||||||
|
|
||||||
else
|
else
|
||||||
return ;
|
return ;
|
||||||
|
|
||||||
PDTMFEncoder_AddTone(dtmfFreqs[(int)digit][0], dtmfFreqs[(int)digit][1], startTime, finishTime, rate, result);
|
PDTMFEncoder_AddTone(dtmfFreqs[(int)digit][0], dtmfFreqs[(int)digit][1], startTime, finishTime, rate, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma endregion
|
#pragma endregion
|
||||||
|
|
||||||
void DtmfBuilder::buildInband(int tone, int startTime, int finishTime, int rate, short* buf)
|
void DtmfBuilder::buildInband(int tone, int startTime, int finishTime, int rate, short* buf)
|
||||||
{
|
{
|
||||||
PDTMFEncoder_AddTone(tone, startTime, finishTime, rate, buf);
|
PDTMFEncoder_AddTone(tone, startTime, finishTime, rate, buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma region DtmfContext
|
#pragma region DtmfContext
|
||||||
DtmfContext::DtmfContext()
|
DtmfContext::DtmfContext()
|
||||||
:mType(Dtmf_Rfc2833)
|
:mType(Dtmf_Rfc2833)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,112 +222,112 @@ DtmfContext::~DtmfContext()
|
|||||||
|
|
||||||
void DtmfContext::setType(Type t)
|
void DtmfContext::setType(Type t)
|
||||||
{
|
{
|
||||||
mType = t;
|
mType = t;
|
||||||
}
|
}
|
||||||
|
|
||||||
DtmfContext::Type DtmfContext::type()
|
DtmfContext::Type DtmfContext::type()
|
||||||
{
|
{
|
||||||
return mType;
|
return mType;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DtmfContext::startTone(int tone, int volume)
|
void DtmfContext::startTone(int tone, int volume)
|
||||||
{
|
{
|
||||||
Lock l(mGuard);
|
Lock l(mGuard);
|
||||||
|
|
||||||
// Stop current tone if needed
|
// Stop current tone if needed
|
||||||
if (mQueue.size())
|
if (mQueue.size())
|
||||||
stopTone();
|
stopTone();
|
||||||
mQueue.push_back(Dtmf(tone, volume, 0));
|
mQueue.push_back(Dtmf(tone, volume, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
void DtmfContext::stopTone()
|
void DtmfContext::stopTone()
|
||||||
{
|
{
|
||||||
Lock l(mGuard);
|
Lock l(mGuard);
|
||||||
|
|
||||||
// Switch to "emit 3 terminating packets" mode
|
// Switch to "emit 3 terminating packets" mode
|
||||||
if (mQueue.size())
|
if (mQueue.size())
|
||||||
{
|
|
||||||
switch (mType)
|
|
||||||
{
|
{
|
||||||
case Dtmf_Rfc2833:
|
switch (mType)
|
||||||
mQueue.front().mStopped = true;
|
{
|
||||||
mQueue.erase(mQueue.begin());
|
case Dtmf_Rfc2833:
|
||||||
break;
|
mQueue.front().mStopped = true;
|
||||||
|
mQueue.erase(mQueue.begin());
|
||||||
|
break;
|
||||||
|
|
||||||
case Dtmf_Inband:
|
case Dtmf_Inband:
|
||||||
if (!mQueue.front().mFinishCount)
|
if (!mQueue.front().mFinishCount)
|
||||||
mQueue.front().mFinishCount = MT_DTMF_END_PACKETS;
|
mQueue.front().mFinishCount = MT_DTMF_END_PACKETS;
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DtmfContext::queueTone(int tone, int volume, int duration)
|
void DtmfContext::queueTone(int tone, int volume, int duration)
|
||||||
{
|
{
|
||||||
Lock l(mGuard);
|
Lock l(mGuard);
|
||||||
mQueue.push_back(Dtmf(tone, volume, duration));
|
mQueue.push_back(Dtmf(tone, volume, duration));
|
||||||
}
|
}
|
||||||
|
|
||||||
void DtmfContext::clearAllTones()
|
void DtmfContext::clearAllTones()
|
||||||
{
|
{
|
||||||
Lock l(mGuard);
|
Lock l(mGuard);
|
||||||
mQueue.clear();
|
mQueue.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DtmfContext::getInband(int milliseconds, int rate, ByteBuffer& output)
|
bool DtmfContext::getInband(int milliseconds, int rate, ByteBuffer& output)
|
||||||
{
|
{
|
||||||
Lock l(mGuard);
|
Lock l(mGuard);
|
||||||
|
|
||||||
if (!mQueue.size() || mType != Dtmf_Inband)
|
if (!mQueue.size() || mType != Dtmf_Inband)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
//
|
//
|
||||||
Dtmf& d = mQueue.front();
|
Dtmf& d = mQueue.front();
|
||||||
|
|
||||||
output.resize(milliseconds * rate / 1000 * 2);
|
output.resize((uint64_t)milliseconds * rate / 1000 * 2);
|
||||||
DtmfBuilder::buildInband(d.mTone, d.mCurrentTime, d.mCurrentTime + milliseconds, rate, (short*)output.mutableData());
|
DtmfBuilder::buildInband(d.mTone, d.mCurrentTime, d.mCurrentTime + milliseconds, rate, (short*)output.mutableData());
|
||||||
d.mCurrentTime += milliseconds;
|
d.mCurrentTime += milliseconds;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DtmfContext::getRfc2833(int milliseconds, ByteBuffer& output, ByteBuffer& stopPacket)
|
bool DtmfContext::getRfc2833(int milliseconds, ByteBuffer& output, ByteBuffer& stopPacket)
|
||||||
{
|
{
|
||||||
Lock l(mGuard);
|
Lock l(mGuard);
|
||||||
if (!mQueue.size() || mType != Dtmf_Rfc2833)
|
if (!mQueue.size() || mType != Dtmf_Rfc2833)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
Dtmf& d = mQueue.front();
|
Dtmf& d = mQueue.front();
|
||||||
// See if tone has enough duration to produce another packet
|
// See if tone has enough duration to produce another packet
|
||||||
if (d.mDuration > 0)
|
if (d.mDuration > 0)
|
||||||
{
|
{
|
||||||
// Emit rfc2833 packet
|
// Emit rfc2833 packet
|
||||||
output.resize(4);
|
output.resize(4);
|
||||||
DtmfBuilder::buildRfc2833(d.mTone, milliseconds, d.mVolume, false, output.mutableData());
|
DtmfBuilder::buildRfc2833(d.mTone, milliseconds, d.mVolume, false, output.mutableData());
|
||||||
d.mDuration -= milliseconds;
|
d.mDuration -= milliseconds;
|
||||||
if(d.mDuration <= 0)
|
if(d.mDuration <= 0)
|
||||||
d.mStopped = true;
|
d.mStopped = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
if (!d.mStopped)
|
if (!d.mStopped)
|
||||||
{
|
{
|
||||||
output.resize(4);
|
output.resize(4);
|
||||||
DtmfBuilder::buildRfc2833(d.mTone, milliseconds, d.mVolume, false, output.mutableData());
|
DtmfBuilder::buildRfc2833(d.mTone, milliseconds, d.mVolume, false, output.mutableData());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
output.clear();
|
output.clear();
|
||||||
|
|
||||||
if (d.mStopped)
|
if (d.mStopped)
|
||||||
{
|
{
|
||||||
stopPacket.resize(4);
|
stopPacket.resize(4);
|
||||||
DtmfBuilder::buildRfc2833(d.mTone, milliseconds, d.mVolume, true, stopPacket.mutableData());
|
DtmfBuilder::buildRfc2833(d.mTone, milliseconds, d.mVolume, true, stopPacket.mutableData());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
stopPacket.clear();
|
stopPacket.clear();
|
||||||
|
|
||||||
if (d.mStopped)
|
if (d.mStopped)
|
||||||
mQueue.erase(mQueue.begin());
|
mQueue.erase(mQueue.begin());
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
@@ -376,31 +376,31 @@ int zap_dtmf_detect(dtmf_detect_state_t *s, int16_t amp[], int samples, int is
|
|||||||
int zap_dtmf_get(dtmf_detect_state_t *s, char *buf, int max);
|
int zap_dtmf_get(dtmf_detect_state_t *s, char *buf, int max);
|
||||||
|
|
||||||
DTMFDetector::DTMFDetector()
|
DTMFDetector::DTMFDetector()
|
||||||
:mState(NULL)
|
:mState(NULL)
|
||||||
{
|
{
|
||||||
mState = malloc(sizeof(dtmf_detect_state_t));
|
mState = malloc(sizeof(dtmf_detect_state_t));
|
||||||
|
|
||||||
memset(mState, 0, sizeof(dtmf_detect_state_t));
|
memset(mState, 0, sizeof(dtmf_detect_state_t));
|
||||||
zap_dtmf_detect_init((dtmf_detect_state_t*)mState);
|
zap_dtmf_detect_init((dtmf_detect_state_t*)mState);
|
||||||
}
|
}
|
||||||
|
|
||||||
DTMFDetector::~DTMFDetector()
|
DTMFDetector::~DTMFDetector()
|
||||||
{
|
{
|
||||||
if (mState)
|
if (mState)
|
||||||
free(mState);
|
free(mState);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string DTMFDetector::streamPut(unsigned char* samples, unsigned int size)
|
std::string DTMFDetector::streamPut(unsigned char* samples, unsigned int size)
|
||||||
{
|
{
|
||||||
char buf[16]; buf[0] = 0;
|
char buf[16]; buf[0] = 0;
|
||||||
if (zap_dtmf_detect((dtmf_detect_state_t*)mState, (int16_t*)samples, size/2, 0))
|
if (zap_dtmf_detect((dtmf_detect_state_t*)mState, (int16_t*)samples, size/2, 0))
|
||||||
zap_dtmf_get((dtmf_detect_state_t*)mState, buf, 15);
|
zap_dtmf_get((dtmf_detect_state_t*)mState, buf, 15);
|
||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DTMFDetector::resetState()
|
void DTMFDetector::resetState()
|
||||||
{
|
{
|
||||||
zap_dtmf_detect_init((dtmf_detect_state_t*)mState);
|
zap_dtmf_detect_init((dtmf_detect_state_t*)mState);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef TRUE
|
#ifndef TRUE
|
||||||
@@ -448,12 +448,12 @@ static tone_detection_descriptor_t fax_detect;
|
|||||||
static tone_detection_descriptor_t fax_detect_2nd;
|
static tone_detection_descriptor_t fax_detect_2nd;
|
||||||
|
|
||||||
static float dtmf_row[] =
|
static float dtmf_row[] =
|
||||||
{
|
{
|
||||||
697.0, 770.0, 852.0, 941.0
|
697.0, 770.0, 852.0, 941.0
|
||||||
};
|
};
|
||||||
static float dtmf_col[] =
|
static float dtmf_col[] =
|
||||||
{
|
{
|
||||||
1209.0, 1336.0, 1477.0, 1633.0
|
1209.0, 1336.0, 1477.0, 1633.0
|
||||||
};
|
};
|
||||||
|
|
||||||
static float fax_freq = 1100.0;
|
static float fax_freq = 1100.0;
|
||||||
@@ -461,10 +461,10 @@ static float fax_freq = 1100.0;
|
|||||||
static char dtmf_positions[] = "123A" "456B" "789C" "*0#D";
|
static char dtmf_positions[] = "123A" "456B" "789C" "*0#D";
|
||||||
|
|
||||||
static void goertzel_init(goertzel_state_t *s,
|
static void goertzel_init(goertzel_state_t *s,
|
||||||
tone_detection_descriptor_t *t)
|
tone_detection_descriptor_t *t)
|
||||||
{
|
{
|
||||||
s->v2 =
|
s->v2 =
|
||||||
s->v3 = 0.0;
|
s->v3 = 0.0;
|
||||||
s->fac = t->fac;
|
s->fac = t->fac;
|
||||||
}
|
}
|
||||||
/*- End of function --------------------------------------------------------*/
|
/*- End of function --------------------------------------------------------*/
|
||||||
@@ -497,50 +497,50 @@ static inline void _dtmf_goertzel_update(goertzel_state_t *s,
|
|||||||
//s->v3 = s->fac*s->v2 - v1 + x[0];
|
//s->v3 = s->fac*s->v2 - v1 + x[0];
|
||||||
|
|
||||||
__asm__ __volatile__ (
|
__asm__ __volatile__ (
|
||||||
" femms;\n"
|
" femms;\n"
|
||||||
|
|
||||||
" movq 16(%%edx),%%mm2;\n"
|
" movq 16(%%edx),%%mm2;\n"
|
||||||
" movq 24(%%edx),%%mm3;\n"
|
" movq 24(%%edx),%%mm3;\n"
|
||||||
" movq 32(%%edx),%%mm4;\n"
|
" movq 32(%%edx),%%mm4;\n"
|
||||||
" movq 40(%%edx),%%mm5;\n"
|
" movq 40(%%edx),%%mm5;\n"
|
||||||
" movq 48(%%edx),%%mm6;\n"
|
" movq 48(%%edx),%%mm6;\n"
|
||||||
" movq 56(%%edx),%%mm7;\n"
|
" movq 56(%%edx),%%mm7;\n"
|
||||||
|
|
||||||
" jmp 1f;\n"
|
" jmp 1f;\n"
|
||||||
" .align 32;\n"
|
" .align 32;\n"
|
||||||
|
|
||||||
" 1: ;\n"
|
" 1: ;\n"
|
||||||
" prefetch (%%eax);\n"
|
" prefetch (%%eax);\n"
|
||||||
" movq %%mm3,%%mm1;\n"
|
" movq %%mm3,%%mm1;\n"
|
||||||
" movq %%mm2,%%mm0;\n"
|
" movq %%mm2,%%mm0;\n"
|
||||||
" movq %%mm5,%%mm3;\n"
|
" movq %%mm5,%%mm3;\n"
|
||||||
" movq %%mm4,%%mm2;\n"
|
" movq %%mm4,%%mm2;\n"
|
||||||
|
|
||||||
" pfmul %%mm7,%%mm5;\n"
|
" pfmul %%mm7,%%mm5;\n"
|
||||||
" pfmul %%mm6,%%mm4;\n"
|
" pfmul %%mm6,%%mm4;\n"
|
||||||
" pfsub %%mm1,%%mm5;\n"
|
" pfsub %%mm1,%%mm5;\n"
|
||||||
" pfsub %%mm0,%%mm4;\n"
|
" pfsub %%mm0,%%mm4;\n"
|
||||||
|
|
||||||
" movq (%%eax),%%mm0;\n"
|
" movq (%%eax),%%mm0;\n"
|
||||||
" movq %%mm0,%%mm1;\n"
|
" movq %%mm0,%%mm1;\n"
|
||||||
" punpckldq %%mm0,%%mm1;\n"
|
" punpckldq %%mm0,%%mm1;\n"
|
||||||
" add $4,%%eax;\n"
|
" add $4,%%eax;\n"
|
||||||
" pfadd %%mm1,%%mm5;\n"
|
" pfadd %%mm1,%%mm5;\n"
|
||||||
" pfadd %%mm1,%%mm4;\n"
|
" pfadd %%mm1,%%mm4;\n"
|
||||||
|
|
||||||
" dec %%ecx;\n"
|
" dec %%ecx;\n"
|
||||||
|
|
||||||
" jnz 1b;\n"
|
" jnz 1b;\n"
|
||||||
|
|
||||||
" movq %%mm2,16(%%edx);\n"
|
" movq %%mm2,16(%%edx);\n"
|
||||||
" movq %%mm3,24(%%edx);\n"
|
" movq %%mm3,24(%%edx);\n"
|
||||||
" movq %%mm4,32(%%edx);\n"
|
" movq %%mm4,32(%%edx);\n"
|
||||||
" movq %%mm5,40(%%edx);\n"
|
" movq %%mm5,40(%%edx);\n"
|
||||||
|
|
||||||
" femms;\n"
|
" femms;\n"
|
||||||
:
|
:
|
||||||
: "c" (samples), "a" (x), "d" (vv)
|
: "c" (samples), "a" (x), "d" (vv)
|
||||||
: "memory", "eax", "ecx");
|
: "memory", "eax", "ecx");
|
||||||
|
|
||||||
s[0].v2 = vv[4];
|
s[0].v2 = vv[4];
|
||||||
s[1].v2 = vv[5];
|
s[1].v2 = vv[5];
|
||||||
@@ -555,8 +555,8 @@ static inline void _dtmf_goertzel_update(goertzel_state_t *s,
|
|||||||
/*- End of function --------------------------------------------------------*/
|
/*- End of function --------------------------------------------------------*/
|
||||||
|
|
||||||
void zap_goertzel_update(goertzel_state_t *s,
|
void zap_goertzel_update(goertzel_state_t *s,
|
||||||
int16_t x[],
|
int16_t x[],
|
||||||
int samples)
|
int samples)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
float v1;
|
float v1;
|
||||||
@@ -582,7 +582,7 @@ void zap_dtmf_detect_init (dtmf_detect_state_t *s)
|
|||||||
float theta;
|
float theta;
|
||||||
|
|
||||||
s->hit1 =
|
s->hit1 =
|
||||||
s->hit2 = 0;
|
s->hit2 = 0;
|
||||||
|
|
||||||
for (i = 0; i < 4; i++)
|
for (i = 0; i < 4; i++)
|
||||||
{
|
{
|
||||||
@@ -598,12 +598,12 @@ void zap_dtmf_detect_init (dtmf_detect_state_t *s)
|
|||||||
theta = float(2.0*M_PI*(dtmf_col[i]*2.0/SAMPLE_RATE));
|
theta = float(2.0*M_PI*(dtmf_col[i]*2.0/SAMPLE_RATE));
|
||||||
dtmf_detect_col_2nd[i].fac = float(2.0*cos(theta));
|
dtmf_detect_col_2nd[i].fac = float(2.0*cos(theta));
|
||||||
|
|
||||||
goertzel_init (&s->row_out[i], &dtmf_detect_row[i]);
|
goertzel_init (&s->row_out[i], &dtmf_detect_row[i]);
|
||||||
goertzel_init (&s->col_out[i], &dtmf_detect_col[i]);
|
goertzel_init (&s->col_out[i], &dtmf_detect_col[i]);
|
||||||
goertzel_init (&s->row_out2nd[i], &dtmf_detect_row_2nd[i]);
|
goertzel_init (&s->row_out2nd[i], &dtmf_detect_row_2nd[i]);
|
||||||
goertzel_init (&s->col_out2nd[i], &dtmf_detect_col_2nd[i]);
|
goertzel_init (&s->col_out2nd[i], &dtmf_detect_col_2nd[i]);
|
||||||
|
|
||||||
s->energy = 0.0;
|
s->energy = 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Same for the fax dector */
|
/* Same for the fax dector */
|
||||||
@@ -625,9 +625,9 @@ s->energy = 0.0;
|
|||||||
/*- End of function --------------------------------------------------------*/
|
/*- End of function --------------------------------------------------------*/
|
||||||
|
|
||||||
int zap_dtmf_detect (dtmf_detect_state_t *s,
|
int zap_dtmf_detect (dtmf_detect_state_t *s,
|
||||||
int16_t amp[],
|
int16_t amp[],
|
||||||
int samples,
|
int samples,
|
||||||
int isradio)
|
int isradio)
|
||||||
{
|
{
|
||||||
|
|
||||||
float row_energy[4];
|
float row_energy[4];
|
||||||
@@ -666,7 +666,7 @@ int zap_dtmf_detect (dtmf_detect_state_t *s,
|
|||||||
{
|
{
|
||||||
famp = amp[j];
|
famp = amp[j];
|
||||||
|
|
||||||
s->energy += famp*famp;
|
s->energy += famp*famp;
|
||||||
|
|
||||||
/* With GCC 2.95, the following unrolled code seems to take about 35%
|
/* With GCC 2.95, the following unrolled code seems to take about 35%
|
||||||
(rough estimate) as long as a neat little 0-3 loop */
|
(rough estimate) as long as a neat little 0-3 loop */
|
||||||
@@ -734,7 +734,7 @@ int zap_dtmf_detect (dtmf_detect_state_t *s,
|
|||||||
s->row_out2nd[3].v2 = s->row_out2nd[3].v3;
|
s->row_out2nd[3].v2 = s->row_out2nd[3].v3;
|
||||||
s->row_out2nd[3].v3 = s->row_out2nd[3].fac*s->row_out2nd[3].v2 - v1 + famp;
|
s->row_out2nd[3].v3 = s->row_out2nd[3].fac*s->row_out2nd[3].v2 - v1 + famp;
|
||||||
|
|
||||||
/* Update fax tone */
|
/* Update fax tone */
|
||||||
v1 = s->fax_tone.v2;
|
v1 = s->fax_tone.v2;
|
||||||
s->fax_tone.v2 = s->fax_tone.v3;
|
s->fax_tone.v2 = s->fax_tone.v3;
|
||||||
s->fax_tone.v3 = s->fax_tone.fac*s->fax_tone.v2 - v1 + famp;
|
s->fax_tone.v3 = s->fax_tone.fac*s->fax_tone.v2 - v1 + famp;
|
||||||
@@ -748,28 +748,28 @@ int zap_dtmf_detect (dtmf_detect_state_t *s,
|
|||||||
if (s->current_sample < 102)
|
if (s->current_sample < 102)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
/* Detect the fax energy, too */
|
/* Detect the fax energy, too */
|
||||||
fax_energy = zap_goertzel_result(&s->fax_tone);
|
fax_energy = zap_goertzel_result(&s->fax_tone);
|
||||||
|
|
||||||
/* We are at the end of a DTMF detection block */
|
/* We are at the end of a DTMF detection block */
|
||||||
/* Find the peak row and the peak column */
|
/* Find the peak row and the peak column */
|
||||||
row_energy[0] = zap_goertzel_result (&s->row_out[0]);
|
row_energy[0] = zap_goertzel_result (&s->row_out[0]);
|
||||||
col_energy[0] = zap_goertzel_result (&s->col_out[0]);
|
col_energy[0] = zap_goertzel_result (&s->col_out[0]);
|
||||||
|
|
||||||
for (best_row = best_col = 0, i = 1; i < 4; i++)
|
for (best_row = best_col = 0, i = 1; i < 4; i++)
|
||||||
{
|
{
|
||||||
row_energy[i] = zap_goertzel_result (&s->row_out[i]);
|
row_energy[i] = zap_goertzel_result (&s->row_out[i]);
|
||||||
if (row_energy[i] > row_energy[best_row])
|
if (row_energy[i] > row_energy[best_row])
|
||||||
best_row = i;
|
best_row = i;
|
||||||
col_energy[i] = zap_goertzel_result (&s->col_out[i]);
|
col_energy[i] = zap_goertzel_result (&s->col_out[i]);
|
||||||
if (col_energy[i] > col_energy[best_col])
|
if (col_energy[i] > col_energy[best_col])
|
||||||
best_col = i;
|
best_col = i;
|
||||||
}
|
}
|
||||||
hit = 0;
|
hit = 0;
|
||||||
/* Basic signal level test and the twist test */
|
/* Basic signal level test and the twist test */
|
||||||
if (row_energy[best_row] >= DTMF_THRESHOLD
|
if (row_energy[best_row] >= DTMF_THRESHOLD
|
||||||
&&
|
&&
|
||||||
col_energy[best_col] >= DTMF_THRESHOLD
|
col_energy[best_col] >= DTMF_THRESHOLD
|
||||||
&&
|
&&
|
||||||
col_energy[best_col] < row_energy[best_row]*DTMF_REVERSE_TWIST
|
col_energy[best_col] < row_energy[best_row]*DTMF_REVERSE_TWIST
|
||||||
&&
|
&&
|
||||||
@@ -787,8 +787,8 @@ for (best_row = best_col = 0, i = 1; i < 4; i++)
|
|||||||
}
|
}
|
||||||
/* ... and second harmonic test */
|
/* ... and second harmonic test */
|
||||||
if (i >= 4
|
if (i >= 4
|
||||||
&&
|
&&
|
||||||
(row_energy[best_row] + col_energy[best_col]) > 42.0*s->energy
|
(row_energy[best_row] + col_energy[best_col]) > 42.0*s->energy
|
||||||
&&
|
&&
|
||||||
zap_goertzel_result (&s->col_out2nd[best_col])*DTMF_2ND_HARMONIC_COL < col_energy[best_col]
|
zap_goertzel_result (&s->col_out2nd[best_col])*DTMF_2ND_HARMONIC_COL < col_energy[best_col]
|
||||||
&&
|
&&
|
||||||
@@ -804,7 +804,7 @@ for (best_row = best_col = 0, i = 1; i < 4; i++)
|
|||||||
to a digit. */
|
to a digit. */
|
||||||
if (hit == s->hit3 && s->hit3 != s->hit2)
|
if (hit == s->hit3 && s->hit3 != s->hit2)
|
||||||
{
|
{
|
||||||
s->mhit = hit;
|
s->mhit = hit;
|
||||||
s->digit_hits[(best_row << 2) + best_col]++;
|
s->digit_hits[(best_row << 2) + best_col]++;
|
||||||
s->detected_digits++;
|
s->detected_digits++;
|
||||||
if (s->current_digits < MAX_DTMF_DIGITS)
|
if (s->current_digits < MAX_DTMF_DIGITS)
|
||||||
@@ -819,60 +819,60 @@ for (best_row = best_col = 0, i = 1; i < 4; i++)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!hit && (fax_energy >= FAX_THRESHOLD) && (fax_energy > s->energy * 21.0)) {
|
if (!hit && (fax_energy >= FAX_THRESHOLD) && (fax_energy > s->energy * 21.0)) {
|
||||||
fax_energy_2nd = zap_goertzel_result(&s->fax_tone2nd);
|
fax_energy_2nd = zap_goertzel_result(&s->fax_tone2nd);
|
||||||
if (fax_energy_2nd * FAX_2ND_HARMONIC < fax_energy) {
|
if (fax_energy_2nd * FAX_2ND_HARMONIC < fax_energy) {
|
||||||
#if 0
|
#if 0
|
||||||
printf("Fax energy/Second Harmonic: %f/%f\n", fax_energy, fax_energy_2nd);
|
printf("Fax energy/Second Harmonic: %f/%f\n", fax_energy, fax_energy_2nd);
|
||||||
#endif
|
#endif
|
||||||
/* XXX Probably need better checking than just this the energy XXX */
|
/* XXX Probably need better checking than just this the energy XXX */
|
||||||
hit = 'f';
|
hit = 'f';
|
||||||
s->fax_hits++;
|
s->fax_hits++;
|
||||||
} /* Don't reset fax hits counter */
|
} /* Don't reset fax hits counter */
|
||||||
} else {
|
} else {
|
||||||
if (s->fax_hits > 5) {
|
if (s->fax_hits > 5) {
|
||||||
s->mhit = 'f';
|
s->mhit = 'f';
|
||||||
s->detected_digits++;
|
s->detected_digits++;
|
||||||
if (s->current_digits < MAX_DTMF_DIGITS)
|
if (s->current_digits < MAX_DTMF_DIGITS)
|
||||||
{
|
{
|
||||||
s->digits[s->current_digits++] = hit;
|
s->digits[s->current_digits++] = hit;
|
||||||
s->digits[s->current_digits] = '\0';
|
s->digits[s->current_digits] = '\0';
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
s->lost_digits++;
|
s->lost_digits++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s->fax_hits = 0;
|
s->fax_hits = 0;
|
||||||
}
|
}
|
||||||
s->hit1 = s->hit2;
|
s->hit1 = s->hit2;
|
||||||
s->hit2 = s->hit3;
|
s->hit2 = s->hit3;
|
||||||
s->hit3 = hit;
|
s->hit3 = hit;
|
||||||
/* Reinitialise the detector for the next block */
|
/* Reinitialise the detector for the next block */
|
||||||
for (i = 0; i < 4; i++)
|
for (i = 0; i < 4; i++)
|
||||||
{
|
{
|
||||||
goertzel_init (&s->row_out[i], &dtmf_detect_row[i]);
|
goertzel_init (&s->row_out[i], &dtmf_detect_row[i]);
|
||||||
goertzel_init (&s->col_out[i], &dtmf_detect_col[i]);
|
goertzel_init (&s->col_out[i], &dtmf_detect_col[i]);
|
||||||
goertzel_init (&s->row_out2nd[i], &dtmf_detect_row_2nd[i]);
|
goertzel_init (&s->row_out2nd[i], &dtmf_detect_row_2nd[i]);
|
||||||
goertzel_init (&s->col_out2nd[i], &dtmf_detect_col_2nd[i]);
|
goertzel_init (&s->col_out2nd[i], &dtmf_detect_col_2nd[i]);
|
||||||
}
|
}
|
||||||
goertzel_init (&s->fax_tone, &fax_detect);
|
goertzel_init (&s->fax_tone, &fax_detect);
|
||||||
goertzel_init (&s->fax_tone2nd, &fax_detect_2nd);
|
goertzel_init (&s->fax_tone2nd, &fax_detect_2nd);
|
||||||
s->energy = 0.0;
|
s->energy = 0.0;
|
||||||
s->current_sample = 0;
|
s->current_sample = 0;
|
||||||
}
|
}
|
||||||
if ((!s->mhit) || (s->mhit != hit))
|
if ((!s->mhit) || (s->mhit != hit))
|
||||||
{
|
{
|
||||||
s->mhit = 0;
|
s->mhit = 0;
|
||||||
return(0);
|
return(0);
|
||||||
}
|
}
|
||||||
return (hit);
|
return (hit);
|
||||||
}
|
}
|
||||||
/*- End of function --------------------------------------------------------*/
|
/*- End of function --------------------------------------------------------*/
|
||||||
|
|
||||||
int zap_dtmf_get (dtmf_detect_state_t *s,
|
int zap_dtmf_get (dtmf_detect_state_t *s,
|
||||||
char *buf,
|
char *buf,
|
||||||
int max)
|
int max)
|
||||||
{
|
{
|
||||||
if (max > s->current_digits)
|
if (max > s->current_digits)
|
||||||
max = s->current_digits;
|
max = s->current_digits;
|
||||||
|
|||||||
@@ -13,14 +13,12 @@
|
|||||||
using namespace MT;
|
using namespace MT;
|
||||||
|
|
||||||
SingleAudioStream::SingleAudioStream(const CodecList::Settings& codecSettings, Statistics& stat)
|
SingleAudioStream::SingleAudioStream(const CodecList::Settings& codecSettings, Statistics& stat)
|
||||||
:mReceiver(codecSettings, stat), mDtmfReceiver(stat)
|
:mReceiver(codecSettings, stat),
|
||||||
{
|
mDtmfReceiver(stat)
|
||||||
}
|
{}
|
||||||
|
|
||||||
SingleAudioStream::~SingleAudioStream()
|
SingleAudioStream::~SingleAudioStream()
|
||||||
{
|
{}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void SingleAudioStream::process(const std::shared_ptr<jrtplib::RTPPacket>& packet)
|
void SingleAudioStream::process(const std::shared_ptr<jrtplib::RTPPacket>& packet)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -13,19 +13,19 @@
|
|||||||
#include "MT_AudioReceiver.h"
|
#include "MT_AudioReceiver.h"
|
||||||
namespace MT
|
namespace MT
|
||||||
{
|
{
|
||||||
class SingleAudioStream
|
class SingleAudioStream
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
SingleAudioStream(const CodecList::Settings& codecSettings, Statistics& stat);
|
SingleAudioStream(const CodecList::Settings& codecSettings, Statistics& stat);
|
||||||
~SingleAudioStream();
|
~SingleAudioStream();
|
||||||
void process(const std::shared_ptr<jrtplib::RTPPacket>& packet);
|
void process(const std::shared_ptr<jrtplib::RTPPacket>& packet);
|
||||||
void copyPcmTo(Audio::DataWindow& output, int needed);
|
void copyPcmTo(Audio::DataWindow& output, int needed);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
DtmfReceiver mDtmfReceiver;
|
DtmfReceiver mDtmfReceiver;
|
||||||
AudioReceiver mReceiver;
|
AudioReceiver mReceiver;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef std::map<unsigned, SingleAudioStream*> AudioStreamMap;
|
typedef std::map<unsigned, SingleAudioStream*> AudioStreamMap;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
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