Compare commits

..

10 Commits

39 changed files with 3483 additions and 2753 deletions

View File

@@ -19,7 +19,8 @@ def make_build() -> Path:
os.mkdir(DIR_BUILD)
os.chdir(DIR_BUILD)
cmd = f'cmake ../src -G Ninja'
# OPUS_X86_MAY_HAVE_SSE4_1 is for clang builds
cmd = f'cmake ../src -G Ninja -D OPUS_X86_MAY_HAVE_SSE4_1=ON'
retcode = os.system(cmd)
if retcode != 0:
raise RuntimeError('Problem when configuring the project')

View File

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

View File

@@ -16,6 +16,7 @@ const std::string Status_FailedToOpenFile = "failed to open file";
const std::string Status_NoActiveProvider = "no active provider";
const std::string Status_NoMediaAction = "no valid media action";
const std::string Status_NoCommand = "no valid command";
const std::string Status_NoAudioManager = "no audio manager";
#define LOG_SUBSYSTEM "Agent"
@@ -336,7 +337,7 @@ void AgentImpl::processStartSession(JsonCpp::Value& request, JsonCpp::Value& ans
{
// Agent was not started
ICELogError(<< "No audio manager installed.");
answer["status"] = "Audio manager not started. Most probably agent is not started.";
answer["status"] = Status_NoAudioManager;
return;
}
@@ -431,28 +432,35 @@ void AgentImpl::processAcceptSession(JsonCpp::Value& request, JsonCpp::Value& an
auto sessionIter = mSessionMap.find(request["session_id"].asInt());
if (sessionIter != mSessionMap.end())
{
// Ensure audio manager is here
mAudioManager->start(mUseNativeAudio ? AudioManager::atReceiver : AudioManager::atNull);
if (!mAudioManager)
{
ICELogError(<< "No audio manager installed.");
answer["status"] = Status_NoAudioManager;
}
else
{
// Ensure audio manager is here
mAudioManager->start(mUseNativeAudio ? AudioManager::atReceiver : AudioManager::atNull);
// Accept session on SIP level
PSession session = sessionIter->second;
// Accept session on SIP level
PSession session = sessionIter->second;
// Get user headers
Session::UserHeaders info;
JsonCpp::Value& arg = request["userinfo"];
std::vector<std::string> keys = arg.getMemberNames();
for (const std::string& k: keys)
info[k] = arg[k].asString();
session->setUserHeaders(info);
// Get user headers
Session::UserHeaders info;
JsonCpp::Value& arg = request["userinfo"];
std::vector<std::string> keys = arg.getMemberNames();
for (const std::string& k: keys)
info[k] = arg[k].asString();
session->setUserHeaders(info);
// Accept finally
session->accept();
// Accept finally
session->accept();
answer["status"] = Status_Ok;
answer["status"] = Status_Ok;
}
}
else
answer["status"] = Status_SessionNotFound;
}
void AgentImpl::processDestroySession(JsonCpp::Value& request, JsonCpp::Value& answer)
@@ -526,6 +534,8 @@ void AgentImpl::processGetMediaStats(JsonCpp::Value& request, JsonCpp::Value& an
answer["rtt"] = result[SessionInfo_Rtt].asFloat();
if (result.exists(SessionInfo_BitrateSwitchCounter))
answer["bitrate_switch_counter"] = result[SessionInfo_BitrateSwitchCounter].asInt();
if (result.exists(SessionInfo_CngCounter))
answer["cng_counter"] = result[SessionInfo_CngCounter].asInt();
if (result.exists(SessionInfo_SSRC))
answer["rtp_ssrc"] = result[SessionInfo_SSRC].asInt();
if (result.exists(SessionInfo_RemotePeer))
@@ -685,7 +695,7 @@ void AgentImpl::processUseStreamForSession(JsonCpp::Value& request, JsonCpp::Val
answer["status"] = Status_Ok;
}
else
answer["status"] = Status_AccountNotFound;
answer["status"] = Status_NoCommand;
}
else
answer["status"] = Status_NoMediaAction;
@@ -707,11 +717,13 @@ void AgentImpl::onMedia(const void* data, int length, MT::Stream::MediaDirection
PDataProvider AgentImpl::onProviderNeeded(const std::string& name)
{
assert(mTerminal);
EVENT_WITH_NAME("provider_needed");
v["provider_name"] = name;
addEvent(v);
return PDataProvider(new AudioProvider(*this, *mTerminal));
return std::make_shared<AudioProvider>(*this, *mTerminal);
}
// Called on new session offer

View File

@@ -11,21 +11,31 @@ using namespace Audio;
DataWindow::DataWindow()
{
mFilled = 0;
mData = NULL;
mData = nullptr;
mCapacity = 0;
}
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;
@@ -166,6 +176,25 @@ void DataWindow::zero(int length)
memset(mData, 0, mFilled);
}
size_t DataWindow::moveTo(DataWindow& dst, size_t size)
{
Lock l(mMutex);
size_t avail = std::min(size, (size_t)filled());
if (avail != 0)
{
dst.add(mData, avail);
erase(avail);
}
return avail;
}
std::chrono::milliseconds DataWindow::getTimeLength(int samplerate, int channels) const
{
Lock l(mMutex);
return std::chrono::milliseconds(mFilled / sizeof(short) / channels / (samplerate / 1000));
}
void DataWindow::makeStereoFromMono(DataWindow& dst, DataWindow& src)
{
Lock lockDst(dst.mMutex), lockSrc(src.mMutex);

View File

@@ -11,37 +11,40 @@
namespace Audio
{
class DataWindow
{
public:
class DataWindow
{
public:
DataWindow();
~DataWindow();
void setCapacity(int capacity);
int capacity() const;
void setCapacity(int capacity);
int capacity() const;
void addZero(int length);
void add(const void* data, int length);
void add(short sample);
int read(void* buffer, int length);
void erase(int length = -1);
void addZero(int length);
void add(const void* data, int length);
void add(short sample);
int read(void* buffer, int length);
void erase(int length = -1);
const char* data() const;
char* mutableData();
int filled() const;
void setFilled(int filled);
void clear();
char* mutableData();
int filled() const;
void setFilled(int filled);
void clear();
short shortAt(int index) const;
void setShortAt(short value, int index);
void zero(int length);
short shortAt(int index) const;
void setShortAt(short value, int index);
void zero(int length);
size_t moveTo(DataWindow& dst, size_t size);
std::chrono::milliseconds getTimeLength(int samplerate, int channels) const;
static void makeStereoFromMono(DataWindow& dst, DataWindow& src);
protected:
protected:
mutable Mutex mMutex;
char* mData;
int mFilled;
int mCapacity;
};
};
}
#endif

View File

@@ -51,6 +51,11 @@ struct Format
return float((milliseconds * mRate) / 500.0 * mChannels);
}
size_t sizeFromTime(std::chrono::milliseconds ms) const
{
return sizeFromTime(ms.count());
}
std::string toString()
{
char buffer[64];

View File

@@ -483,6 +483,7 @@ void Session::getSessionInfo(Session::InfoOptions options, VariantMap& info)
info[SessionInfo_Rtt] = static_cast<float>(stat.mRttDelay * 1000);
#if defined(USE_AMR_CODEC)
info[SessionInfo_BitrateSwitchCounter] = stat.mBitrateSwitchCounter;
info[SessionInfo_CngCounter] = stat.mCng;
#endif
info[SessionInfo_SSRC] = stat.mSsrc;
info[SessionInfo_RemotePeer] = stat.mRemotePeer.toStdString();
@@ -882,7 +883,7 @@ void Session::refreshMediaPath()
// Bring new socket to provider and stream
RtpPair<PDatagramSocket> s4 = SocketHeap::instance().allocSocketPair(AF_INET, this, IS_MULTIPLEX() ),
s6 = SocketHeap::instance().allocSocketPair(AF_INET, this, IS_MULTIPLEX());
s6 = SocketHeap::instance().allocSocketPair(AF_INET6, this, IS_MULTIPLEX());
p->setSocket(s4, s6);
s.setSocket4(s4);

View File

@@ -72,6 +72,7 @@ enum SessionInfo
SessionInfo_BitrateSwitchCounter, // It is for AMR codecs only
SessionInfo_RemotePeer,
SessionInfo_SSRC,
SessionInfo_CngCounter // For AMR codecs only
};

View File

@@ -38,11 +38,11 @@
//#define AUDIO_DUMPOUTPUT
#define UA_REGISTRATION_TIME 3600
#define UA_MEDIA_PORT_START 20000
#define UA_MEDIA_PORT_FINISH 30000
#define UA_MAX_UDP_PACKET_SIZE 576
#define UA_PUBLICATION_ID "314"
#define UA_REGISTRATION_TIME 3600
#define UA_MEDIA_PORT_START 20000
#define UA_MEDIA_PORT_FINISH 30000
#define UA_MAX_UDP_PACKET_SIZE 576
#define UA_PUBLICATION_ID "314"
#define MT_SAMPLERATE AUDIO_SAMPLERATE
@@ -50,13 +50,11 @@
#define MT_MAXRTPPACKET 1500
#define MT_DTMF_END_PACKETS 3
#define RTP_BUFFER_HIGH 0
#define RTP_BUFFER_LOW 0
#define RTP_BUFFER_PREBUFFER 0
// Milliseconds before
#define RTP_BUFFER_HIGH (2000)
#define RTP_BUFFER_LOW (0)
#define RTP_BUFFER_PREBUFFER (100)
// #define RTP_BUFFER_HIGH 160
// #define RTP_BUFFER_LOW 10
// #define RTP_BUFFER_PREBUFFER 160
#define RTP_DECODED_CAPACITY 2048
#define DEFAULT_SUBSCRIPTION_TIME 1200

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -1,4 +1,4 @@
/* Copyright(C) 2007-2017 VoIPobjects (voipobjects.com)
/* Copyright(C) 2007-2026 VoIPobjects (voipobjects.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
@@ -8,70 +8,95 @@
# 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 */
unsigned char x:1; /* header extension flag */
unsigned char p:1; /* padding flag */
unsigned char version:2; /* protocol version */
unsigned char pt:7; /* payload type */
unsigned char m:1; /* marker bit */
unsigned short seq; /* sequence number */
unsigned int ts; /* timestamp */
unsigned int ssrc; /* synchronization source */
unsigned char version:2; /* protocol version */
unsigned char pt:7; /* payload type */
unsigned char m:1; /* marker bit */
unsigned short seq; /* sequence number */
unsigned int ts; /* timestamp */
unsigned int ssrc; /* synchronization source */
};
struct RtcpHeader
{
unsigned char rc:5; /* reception report count */
unsigned char rc:5; /* reception report count */
unsigned char p:1; /* padding flag */
unsigned char version:2; /* protocol version */
unsigned char pt:8; /* payload type */
unsigned char pt; /* payload type */
uint16_t len; /* length */
uint32_t ssrc; /* synchronization source */
uint32_t ssrc; /* synchronization source */
};
// --- IPv4 address helpers ---
static std::string ipToString(uint32_t ip)
{
// ip in host byte order → dotted-decimal
return std::to_string((ip >> 24) & 0xFF) + "." +
std::to_string((ip >> 16) & 0xFF) + "." +
std::to_string((ip >> 8) & 0xFF) + "." +
std::to_string( ip & 0xFF);
}
static uint32_t stringToIp(const std::string& s)
{
unsigned a = 0, b = 0, c = 0, d = 0;
if (std::sscanf(s.c_str(), "%u.%u.%u.%u", &a, &b, &c, &d) != 4)
return 0;
if (a > 255 || b > 255 || c > 255 || d > 255)
return 0;
return (a << 24) | (b << 16) | (c << 8) | d;
}
// --- RtpHelper implementation ---
bool RtpHelper::isRtp(const void* buffer, size_t length)
{
if (length < 12)
return false;
const RtpHeader* h = reinterpret_cast<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,191 @@ 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::~RtpDump()
std::chrono::microseconds RtpHelper::toMicroseconds(const jrtplib::RTPTime& t)
{
flush();
for (PacketList::iterator packetIter=mPacketList.begin(); packetIter!=mPacketList.end(); ++packetIter)
{
//free(packetIter->mData);
delete packetIter->mPacket;
return std::chrono::microseconds(uint64_t(t.GetDouble() * 1000000));
}
// --- RtpDump implementation ---
std::shared_ptr<jrtplib::RTPPacket> RtpDump::parseRtpData(const uint8_t* data, size_t len)
{
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 +333,142 @@ size_t RtpDump::count() const
jrtplib::RTPPacket& RtpDump::packetAt(size_t index)
{
if (index >= mPacketList.size())
throw std::out_of_range("Packet index out of range: " + std::to_string(index));
if (!mPacketList[index].mPacket)
throw std::runtime_error("No parsed RTP data at index " + std::to_string(index));
return *mPacketList[index].mPacket;
}
const std::vector<uint8_t>& RtpDump::rawDataAt(size_t index) const
{
if (index >= mPacketList.size())
throw std::out_of_range("Packet index out of range: " + std::to_string(index));
return mPacketList[index].mRawData;
}
uint32_t RtpDump::offsetAt(size_t index) const
{
if (index >= mPacketList.size())
throw std::out_of_range("Packet index out of range: " + std::to_string(index));
return mPacketList[index].mOffsetMs;
}
void RtpDump::add(const void* buffer, size_t len)
{
RtpData data;
data.mData = malloc(len);
memcpy(data.mData, buffer, len);
data.mLength = len;
if (!buffer || len == 0)
return;
jrtplib::RTPIPv4Address addr(jrtplib::RTPAddress::IPv4Address);
jrtplib::RTPTime t(0);
jrtplib::RTPRawPacket* raw = new jrtplib::RTPRawPacket((unsigned char*)const_cast<void*>(data.mData), data.mLength, &addr, t, true);
data.mPacket = new jrtplib::RTPPacket(*raw);
//delete raw;
mPacketList.push_back(data);
uint32_t offsetMs = 0;
auto now = std::chrono::steady_clock::now();
if (!mRecording) {
mRecording = true;
mRecordStart = now;
// Capture wall-clock start time
auto wallNow = std::chrono::system_clock::now();
auto epoch = wallNow.time_since_epoch();
auto sec = std::chrono::duration_cast<std::chrono::seconds>(epoch);
auto usec = std::chrono::duration_cast<std::chrono::microseconds>(epoch - sec);
mStartSec = static_cast<uint32_t>(sec.count());
mStartUsec = static_cast<uint32_t>(usec.count());
} else {
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - mRecordStart);
offsetMs = static_cast<uint32_t>(elapsed.count());
}
add(buffer, len, offsetMs);
}
void RtpDump::add(const void* buffer, size_t len, uint32_t offsetMs)
{
if (!buffer || len == 0)
return;
if (len > MAX_RTP_PACKET_SIZE)
throw std::runtime_error("Packet too large: " + std::to_string(len));
RtpData entry;
entry.mRawData.assign(static_cast<const uint8_t*>(buffer),
static_cast<const uint8_t*>(buffer) + len);
entry.mOffsetMs = offsetMs;
entry.mPacket = parseRtpData(entry.mRawData.data(), entry.mRawData.size());
mPacketList.push_back(std::move(entry));
}
void RtpDump::flush()
{
FILE* f = fopen(mFilename.c_str(), "wb");
if (!f)
throw Exception(ERR_WAVFILE_FAILED);
if (mFilename.empty())
throw std::runtime_error("No filename specified");
PacketList::iterator packetIter = mPacketList.begin();
for (;packetIter != mPacketList.end(); ++packetIter)
{
RtpData& data = *packetIter;
// Disabled for debugging only
//fwrite(&data.mLength, sizeof data.mLength, 1, f);
fwrite(data.mData, data.mLength, 1, f);
std::ofstream output(mFilename, std::ios::binary);
if (!output.is_open())
throw std::runtime_error("Failed to open file for writing: " + mFilename);
// --- 1. Text header ---
std::string textLine = std::string(RTPDUMP_SHEBANG) + " " +
ipToString(mSourceIp) + "/" +
std::to_string(mSourcePort) + "\n";
output.write(textLine.data(), static_cast<std::streamsize>(textLine.size()));
// --- 2. Binary file header (16 bytes) ---
uint32_t buf32;
uint16_t buf16;
buf32 = htonl(mStartSec);
output.write(reinterpret_cast<const char*>(&buf32), 4);
buf32 = htonl(mStartUsec);
output.write(reinterpret_cast<const char*>(&buf32), 4);
buf32 = htonl(mSourceIp);
output.write(reinterpret_cast<const char*>(&buf32), 4);
buf16 = htons(mSourcePort);
output.write(reinterpret_cast<const char*>(&buf16), 2);
buf16 = 0; // padding
output.write(reinterpret_cast<const char*>(&buf16), 2);
// --- 3. Packet records ---
size_t written = 0;
for (const auto& pkt : mPacketList) {
if (pkt.mRawData.empty())
continue;
uint16_t plen = static_cast<uint16_t>(pkt.mRawData.size());
uint16_t recLength = static_cast<uint16_t>(plen + 8);
buf16 = htons(recLength);
output.write(reinterpret_cast<const char*>(&buf16), 2);
buf16 = htons(plen);
output.write(reinterpret_cast<const char*>(&buf16), 2);
buf32 = htonl(pkt.mOffsetMs);
output.write(reinterpret_cast<const char*>(&buf32), 4);
output.write(reinterpret_cast<const char*>(pkt.mRawData.data()), plen);
written++;
}
fclose(f);
}
#endif
if (!output.good())
throw std::runtime_error("Failed to write rtpdump file: " + mFilename);
ICELogInfo(<< "Wrote " << written << " packets to " << mFilename);
}
void RtpDump::clear()
{
mPacketList.clear();
mLoaded = false;
mRecording = false;
}

View File

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

View File

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

View File

@@ -65,6 +65,7 @@ void thread_pool::run_worker()
tasks.pop();
}
}
t(); // function<void()> type
if (t)
t(); // function<void()> type
}
}

View File

@@ -240,7 +240,7 @@ int64_t Variant::asInt64() const
if (mType != VTYPE_INT64)
throw Exception(ERR_BAD_VARIANT_TYPE);
return mInt;
return mInt64;
}
bool Variant::asBool() const

View File

@@ -3,11 +3,8 @@
#include "MT_AmrCodec.h"
#include "../helper/HL_ByteBuffer.h"
#include "../helper/HL_Log.h"
#include "../helper/HL_IuUP.h"
#include "../helper/HL_Exception.h"
#include <iostream>
#include "../helper/HL_Log.h"
#define LOG_SUBSYSTEM "AmrCodec"
using namespace MT;
@@ -31,37 +28,37 @@ const uint16_t amrwb_framelenbits[10] =
struct AmrPayloadInfo
{
const uint8_t* mPayload;
int mPayloadLength;
bool mOctetAligned;
bool mInterleaving;
bool mWideband;
uint64_t mCurrentTimestamp;
const uint8_t* mPayload = nullptr;
int mPayloadLength = 0;
bool mOctetAligned = false;
bool mInterleaving = false;
bool mWideband = false;
uint64_t mCurrentTimestamp = 0;
};
struct AmrFrame
{
uint8_t mFrameType;
uint8_t mMode;
bool mGoodQuality;
uint64_t mTimestamp;
uint8_t mFrameType = 0;
uint8_t mMode = 0;
bool mGoodQuality = false;
uint64_t mTimestamp = 0;
std::shared_ptr<ByteBuffer> mData;
uint8_t mSTI;
uint8_t mSTI = 0;
};
struct AmrPayload
{
uint8_t mCodeModeRequest;
uint8_t mCodeModeRequest = 0;
std::vector<AmrFrame> mFrames;
bool mDiscardPacket;
bool mDiscardPacket = false;
};
// ARM RTP payload has next structure
// Header
// Table of Contents
// Frames
static AmrPayload parseAmrPayload(AmrPayloadInfo& input)
static AmrPayload parseAmrPayload(AmrPayloadInfo& input, size_t& cngCounter)
{
AmrPayload result;
@@ -128,6 +125,8 @@ static AmrPayload parseAmrPayload(AmrPayloadInfo& input)
frame.mTimestamp = input.mCurrentTimestamp;
result.mFrames.push_back(frame);
input.mCurrentTimestamp += input.mWideband ? 320 : 160;
if (FT == SID_FT)
cngCounter++;
}
while (F != 0);
@@ -140,13 +139,17 @@ static AmrPayload parseAmrPayload(AmrPayloadInfo& input)
// avoid the loss of data synchronization in the depacketization
// process, which can result in a huge degradation in speech quality.
bool discard = input.mWideband ? (f.mFrameType >= 10 && f.mFrameType <= 13) : (f.mFrameType >= 9 && f.mFrameType <= 14);
// discard |= input.mWideband ? f.mFrameType >= 14 : f.mFrameType >= 15;
if (discard)
{
result.mDiscardPacket = true;
continue;
}
// if (input.mWideband && f.mMode == 0xFF /* CNG */)
// {
// int a = 1;
// }`
if (input.mWideband && f.mFrameType == 15)
{
// DTX, no sense to decode the data
@@ -165,8 +168,8 @@ static AmrPayload parseAmrPayload(AmrPayloadInfo& input)
continue;
}
size_t bitsLength = input.mWideband ? amrwb_framelenbits[f.mFrameType] : amrnb_framelenbits[f.mFrameType];
size_t byteLength = input.mWideband ? amrwb_framelen[f.mFrameType] : amrnb_framelen[f.mFrameType];
size_t bitsLength = input.mWideband ? amrwb_framelenbits[f.mFrameType] : amrnb_framelenbits[f.mFrameType];
size_t byteLength = input.mWideband ? amrwb_framelen[f.mFrameType] : amrnb_framelen[f.mFrameType];
if (bitsLength > 0)
{
@@ -260,8 +263,7 @@ PCodec AmrNbCodec::CodecFactory::create()
AmrNbCodec::AmrNbCodec(const AmrCodecConfig& config)
:mEncoderCtx(nullptr), mDecoderCtx(nullptr), mConfig(config), mCurrentDecoderTimestamp(0),
mSwitchCounter(0), mPreviousPacketLength(0)
:mConfig(config)
{
mEncoderCtx = Encoder_Interface_init(1);
mDecoderCtx = Decoder_Interface_init();
@@ -282,44 +284,31 @@ AmrNbCodec::~AmrNbCodec()
}
}
const char* AmrNbCodec::name()
Codec::Info AmrNbCodec::info()
{
return MT_AMRNB_CODECNAME;
return {
.mName = MT_AMRNB_CODECNAME,
.mSamplerate = 8000,
.mChannels = 1,
.mPcmLength = 20 * 16,
.mFrameTime = 20,
.mRtpLength = 0
};
}
int AmrNbCodec::pcmLength()
Codec::EncodeResult AmrNbCodec::encode(std::span<const uint8_t> input, std::span<uint8_t> output)
{
return 20 * 16;
}
int AmrNbCodec::rtpLength()
{
return 0;
}
int AmrNbCodec::frameTime()
{
return 20;
}
int AmrNbCodec::samplerate()
{
return 8000;
}
int AmrNbCodec::encode(const void* input, int inputBytes, void* output, int outputCapacity)
{
if (inputBytes % pcmLength())
return 0;
if (input.size_bytes() % pcmLength())
return {.mEncoded = 0};
// Declare the data input pointer
auto *dataIn = (const short *)input;
auto *dataIn = (const short *)input.data();
// Declare the data output pointer
auto *dataOut = (unsigned char *)output;
auto *dataOut = (unsigned char *)output.data();
// Find how much RTP frames will be generated
unsigned int frames = inputBytes / pcmLength();
unsigned int frames = input.size_bytes() / pcmLength();
// Generate frames
for (unsigned int i = 0; i < frames; i++)
@@ -328,28 +317,28 @@ int AmrNbCodec::encode(const void* input, int inputBytes, void* output, int outp
dataIn += pcmLength() / 2;
}
return dataOut - (unsigned char*)output;
return {.mEncoded = (size_t)(dataOut - (unsigned char*)output.data())};
}
#define L_FRAME 160
#define AMR_BITRATE_DTX 15
int AmrNbCodec::decode(const void* input, int inputBytes, void* output, int outputCapacity)
Codec::DecodeResult AmrNbCodec::decode(std::span<const uint8_t> input, std::span<uint8_t> output)
{
if (mConfig.mOctetAligned)
return 0;
return {.mDecoded = 0};
if (mConfig.mIuUP)
{
// Try to parse IuUP frame
IuUP::Frame frame;
if (!IuUP::parse2((const uint8_t*)input, inputBytes, frame))
return 0;
if (!IuUP::parse2((const uint8_t*)input.data(), input.size_bytes(), frame))
return {0};
// Check if CRC failed - it is check from IuUP data
if (!frame.mHeaderCrcOk || !frame.mPayloadCrcOk)
{
ICELogInfo(<< "CRC check failed.");
return 0;
return {0};
}
// Build NB frame to decode
@@ -366,61 +355,61 @@ int AmrNbCodec::decode(const void* input, int inputBytes, void* output, int outp
// Check if frameType comparing is correct
if (frameType == 0xFF)
return 0;
return {0};
dataToDecode.mutableData()[0] = (frameType << 3) | (1 << 2);
Decoder_Interface_Decode(mDecoderCtx, (const unsigned char*)dataToDecode.data(), (short*)output, 0);
return pcmLength();
Decoder_Interface_Decode(mDecoderCtx, (const unsigned char*)dataToDecode.data(), (short*)output.data(), 0);
return {.mDecoded = (size_t)pcmLength()};
}
else
{
if (outputCapacity < pcmLength())
return 0;
if (output.size_bytes() < pcmLength())
return {.mDecoded = 0};
if (inputBytes == 0)
if (input.size_bytes() == 0)
{ // PLC part
unsigned char buffer[32];
buffer[0] = (AMR_BITRATE_DTX << 3)|4;
Decoder_Interface_Decode(mDecoderCtx, buffer, (short*)output, 0); // Handle missing data
return pcmLength();
Decoder_Interface_Decode(mDecoderCtx, buffer, (short*)output.data(), 0); // Handle missing data
return {.mDecoded = (size_t)pcmLength()};
}
AmrPayloadInfo info;
info.mCurrentTimestamp = mCurrentDecoderTimestamp;
info.mOctetAligned = mConfig.mOctetAligned;
info.mPayload = (const uint8_t*)input;
info.mPayloadLength = inputBytes;
info.mPayload = input.data();
info.mPayloadLength = input.size_bytes();
info.mWideband = false;
info.mInterleaving = false;
AmrPayload ap;
try
{
ap = parseAmrPayload(info);
ap = parseAmrPayload(info, mCngCounter);
}
catch(...)
{
ICELogDebug(<< "Failed to decode AMR payload.");
return 0;
return {.mDecoded = 0};
}
// Save current timestamp
mCurrentDecoderTimestamp = info.mCurrentTimestamp;
// Check if packet is corrupted
if (ap.mDiscardPacket)
return 0;
return {.mDecoded = 0};
// Check for output buffer capacity
if (outputCapacity < (int)ap.mFrames.size() * pcmLength())
return 0;
if (output.size_bytes() < (int)ap.mFrames.size() * pcmLength())
return {.mDecoded = 0};
if (ap.mFrames.empty())
{
ICELogError(<< "No AMR frames");
}
short* dataOut = (short*)output;
short* dataOut = (short*)output.data();
for (AmrFrame& frame: ap.mFrames)
{
if (frame.mData)
@@ -430,18 +419,18 @@ int AmrNbCodec::decode(const void* input, int inputBytes, void* output, int outp
dataOut += pcmLength() / 2;
}
}
return pcmLength() * ap.mFrames.size();
return {.mDecoded = pcmLength() * ap.mFrames.size()};
}
return pcmLength();
return {.mDecoded = (size_t)pcmLength()};
}
int AmrNbCodec::plc(int lostFrames, void* output, int outputCapacity)
size_t AmrNbCodec::plc(int lostFrames, std::span<uint8_t> output)
{
if (outputCapacity < lostFrames * pcmLength())
if (output.size_bytes() < lostFrames * pcmLength())
return 0;
short* dataOut = (short*)output;
short* dataOut = (short*)output.data();
for (int i=0; i < lostFrames; i++)
{
@@ -459,6 +448,11 @@ int AmrNbCodec::getSwitchCounter() const
return mSwitchCounter;
}
int AmrNbCodec::getCngCounter() const
{
return mCngCounter;
}
// -------- AMR WB codec
AmrWbCodec::CodecFactory::CodecFactory(const AmrCodecConfig& config)
:mConfig(config)
@@ -500,11 +494,9 @@ PCodec AmrWbCodec::CodecFactory::create()
AmrWbStatistics MT::GAmrWbStatistics;
AmrWbCodec::AmrWbCodec(const AmrCodecConfig& config)
:mEncoderCtx(nullptr), mDecoderCtx(nullptr), mConfig(config),
mSwitchCounter(0), mPreviousPacketLength(0)
:mConfig(config)
{
mDecoderCtx = D_IF_init();
mCurrentDecoderTimestamp = 0;
}
AmrWbCodec::~AmrWbCodec()
@@ -522,49 +514,37 @@ AmrWbCodec::~AmrWbCodec()
}
}
const char* AmrWbCodec::name()
{
return MT_AMRWB_CODECNAME;
Codec::Info AmrWbCodec::info() {
return {
.mName = MT_AMRWB_CODECNAME,
.mSamplerate = 16000,
.mChannels = 1,
.mPcmLength = 20 * 16 * 2,
.mFrameTime = 20,
.mRtpLength = 0 /* There is complex structure inside AMR packet which may include multilple frames with various length. */
};
}
int AmrWbCodec::pcmLength()
{
return 20 * 16 * 2;
}
int AmrWbCodec::rtpLength()
Codec::EncodeResult AmrWbCodec::encode(std::span<const uint8_t> input, std::span<uint8_t> output)
{
return 0; // VBR
}
int AmrWbCodec::frameTime()
{
return 20;
}
int AmrWbCodec::samplerate()
{
return 16000;
}
int AmrWbCodec::encode(const void* input, int inputBytes, void* output, int outputCapacity)
{
throw Exception(ERR_NOT_IMPLEMENTED);
// Still no support for encoding - emit silence instead
return {.mEncoded = 0};
}
#define L_FRAME 160
#define AMR_BITRATE_DTX 15
int AmrWbCodec::decodeIuup(std::span<const uint8_t> input, std::span<uint8_t> output)
Codec::DecodeResult AmrWbCodec::decodeIuup(std::span<const uint8_t> input, std::span<uint8_t> output)
{
IuUP::Frame frame;
if (!IuUP::parse2(input.data(), input.size(), frame))
return 0;
return {.mDecoded = 0};
if (!frame.mHeaderCrcOk || !frame.mPayloadCrcOk)
{
ICELogInfo(<< "CRC check failed.");
return 0;
return {.mDecoded = 0};
}
// Reserve space
@@ -579,34 +559,34 @@ int AmrWbCodec::decodeIuup(std::span<const uint8_t> input, std::span<uint8_t> ou
frameType = ftIndex;
if (frameType == 0xFF)
return 0;
return {.mDecoded = 0, .mIsCng = true};
dataToDecode.mutableData()[0] = (frameType << 3) | (1 << 2);
D_IF_decode(mDecoderCtx, (const unsigned char*)dataToDecode.data(), (short*)output.data(), 0);
return pcmLength();
return {.mDecoded = (size_t)pcmLength()};
}
int AmrWbCodec::decodePlain(std::span<const uint8_t> input, std::span<uint8_t> output)
Codec::DecodeResult AmrWbCodec::decodePlain(std::span<const uint8_t> input, std::span<uint8_t> output)
{
AmrPayloadInfo info;
info.mCurrentTimestamp = mCurrentDecoderTimestamp;
info.mOctetAligned = mConfig.mOctetAligned;
info.mPayload = input.data();
info.mPayloadLength = input.size();
info.mWideband = true;
info.mInterleaving = false;
info.mCurrentTimestamp = mCurrentDecoderTimestamp;
info.mOctetAligned = mConfig.mOctetAligned;
info.mPayload = input.data();
info.mPayloadLength = input.size();
info.mWideband = true;
info.mInterleaving = false;
AmrPayload ap;
try
{
ap = parseAmrPayload(info);
ap = parseAmrPayload(info, mCngCounter);
}
catch(...)
{
GAmrWbStatistics.mNonParsed++;
ICELogDebug(<< "Failed to decode AMR payload");
return 0;
return {.mDecoded = 0};
}
// Save current timestamp
mCurrentDecoderTimestamp = info.mCurrentTimestamp;
@@ -615,58 +595,67 @@ int AmrWbCodec::decodePlain(std::span<const uint8_t> input, std::span<uint8_t> o
if (ap.mDiscardPacket)
{
GAmrWbStatistics.mDiscarded++;
return 0;
return {.mDecoded = 0};
}
// Check for output buffer capacity
if (output.size() < (int)ap.mFrames.size() * pcmLength())
return 0;
// Find the required output capacity
size_t capacity = 0;
for (AmrFrame& frame: ap.mFrames)
capacity += frame.mMode == 0xFF /* CNG */ ? pcmLength() : pcmLength();
if (output.size() < capacity)
return {.mDecoded = 0};
short* dataOut = (short*)output.data();
size_t dataOutSizeInBytes = 0;
for (AmrFrame& frame: ap.mFrames)
{
memset(dataOut, 0, static_cast<size_t>(output.size()));
size_t frameOutputSize = frame.mMode == 0xFF ? pcmLength() : pcmLength();
memset(dataOut, 0, frameOutputSize);
if (frame.mData)
{
if (frame.mMode == 0xFF)
{
// int bp = 1;
}
D_IF_decode(mDecoderCtx, (const unsigned char*)frame.mData->data(), (short*)dataOut, 0);
dataOut += pcmLength() / 2;
dataOutSizeInBytes += pcmLength();
dataOut += frameOutputSize / 2;
dataOutSizeInBytes += frameOutputSize;
}
}
return dataOutSizeInBytes;
return {.mDecoded = dataOutSizeInBytes,
.mIsCng = ap.mFrames.size() == 1 ? (ap.mFrames.front().mMode == 0xFF) : false};
}
int AmrWbCodec::decode(const void* input, int inputBytes, void* output, int outputCapacity)
Codec::DecodeResult AmrWbCodec::decode(std::span<const uint8_t> input, std::span<uint8_t> output)
{
auto inputBuffer = std::span<const uint8_t>((uint8_t*)input, (size_t)inputBytes);
auto outputBuffer = std::span<uint8_t>((uint8_t*)output, (size_t)outputCapacity);
if (mConfig.mIuUP)
return decodeIuup(inputBuffer, outputBuffer);
return decodeIuup(input, output);
else
return decodePlain(inputBuffer, outputBuffer);
return 0;
return decodePlain(input, output);
}
int AmrWbCodec::plc(int lostFrames, void* output, int outputCapacity)
size_t AmrWbCodec::plc(int lostFrames, std::span<uint8_t> output)
{
/* if (outputCapacity < lostFrames * pcmLength())
return 0;
short* dataOut = (short*)output;
for (int i=0; i < lostFrames; i++)
{
unsigned char buffer[32];
buffer[0] = (AMR_BITRATE_DTX << 3)|4;
Decoder_Interface_Decode(mDecoderCtx, buffer, dataOut, 0); // Handle missing data
dataOut += L_FRAME;
}
*/
// ToDo: Check again if PLC works for AMR-WB
// For now return the silence
memset(output.data(), 0, output.size_bytes());
return lostFrames * pcmLength();
/*
if (outputCapacity < lostFrames * pcmLength())
return 0;
short* dataOut = (short*)output;
for (int i=0; i < lostFrames; i++)
{
unsigned char buffer[32];
buffer[0] = (AMR_BITRATE_DTX << 3)|4;
Decoder_Interface_Decode(mDecoderCtx, buffer, dataOut, 0); // Handle missing data
dataOut += L_FRAME;
}
*/
}
int AmrWbCodec::getSwitchCounter() const
@@ -674,14 +663,16 @@ int AmrWbCodec::getSwitchCounter() const
return mSwitchCounter;
}
int AmrWbCodec::getCngCounter() const
{
return mCngCounter;
}
// ------------- GSM EFR -----------------
GsmEfrCodec::GsmEfrFactory::GsmEfrFactory(bool iuup, int ptype)
:mIuUP(iuup), mPayloadType(ptype)
{
}
{}
const char* GsmEfrCodec::GsmEfrFactory::name()
{
@@ -699,9 +690,7 @@ int GsmEfrCodec::GsmEfrFactory::payloadType()
}
void GsmEfrCodec::GsmEfrFactory::updateSdp(resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction)
{
}
{}
int GsmEfrCodec::GsmEfrFactory::processSdp(const resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction)
{
@@ -719,7 +708,7 @@ PCodec GsmEfrCodec::GsmEfrFactory::create()
}
GsmEfrCodec::GsmEfrCodec(bool iuup)
:mEncoderCtx(nullptr), mDecoderCtx(nullptr), mIuUP(iuup)
:mIuUP(iuup)
{
mEncoderCtx = Encoder_Interface_init(1);
mDecoderCtx = Decoder_Interface_init();
@@ -740,44 +729,31 @@ GsmEfrCodec::~GsmEfrCodec()
}
}
const char* GsmEfrCodec::name()
Codec::Info GsmEfrCodec::info()
{
return MT_GSMEFR_CODECNAME;
return {
.mName = MT_GSMEFR_CODECNAME,
.mSamplerate = 8000,
.mChannels = 1,
.mPcmLength = 20 * 16,
.mFrameTime = 20,
.mRtpLength = 0
};
}
int GsmEfrCodec::pcmLength()
Codec::EncodeResult GsmEfrCodec::encode(std::span<const uint8_t> input, std::span<uint8_t> output)
{
return 20 * 16;
}
int GsmEfrCodec::rtpLength()
{
return 0;
}
int GsmEfrCodec::frameTime()
{
return 20;
}
int GsmEfrCodec::samplerate()
{
return 8000;
}
int GsmEfrCodec::encode(const void* input, int inputBytes, void* output, int outputCapacity)
{
if (inputBytes % pcmLength())
return 0;
if (input.size_bytes() % pcmLength())
return {.mEncoded = 0};
// Declare the data input pointer
const short *dataIn = (const short *)input;
const short *dataIn = (const short *)input.data();
// Declare the data output pointer
unsigned char *dataOut = (unsigned char *)output;
unsigned char *dataOut = (unsigned char *)output.data();
// Find how much RTP frames will be generated
unsigned int frames = inputBytes / pcmLength();
unsigned int frames = input.size_bytes() / pcmLength();
// Generate frames
for (unsigned int i = 0; i < frames; i++)
@@ -786,7 +762,7 @@ int GsmEfrCodec::encode(const void* input, int inputBytes, void* output, int out
dataIn += pcmLength() / 2;
}
return frames * rtpLength();
return {.mEncoded = frames * rtpLength()};
}
#define L_FRAME 160
@@ -843,100 +819,61 @@ const uint16_t gsm690_12_2_bitorder[244] = {
237, 236, 96, 199,
};
int GsmEfrCodec::decode(const void* input, int inputBytes, void* output, int outputCapacity)
Codec::DecodeResult GsmEfrCodec::decode(std::span<const uint8_t> input, std::span<uint8_t> output)
{
/* if (mIuUP)
{
// Try to parse IuUP frame
IuUP::Frame frame;
if (!IuUP::parse2((const uint8_t*)input, inputBytes, frame))
return 0;
if (output.size_bytes() < pcmLength())
return {.mDecoded = 0};
// Check if CRC failed - it is check from IuUP data
if (!frame.mHeaderCrcOk || !frame.mPayloadCrcOk)
{
ICELogInfo(<< "CRC check failed.");
return 0;
if (input.size_bytes() == 0)
{ // PLC part
unsigned char buffer[32];
buffer[0] = (AMR_BITRATE_DTX << 3)|4;
Decoder_Interface_Decode(mDecoderCtx, buffer, (short*)output.data(), 0); // Handle missing data
}
// Build NB frame to decode
ByteBuffer dataToDecode;
dataToDecode.resize(1 + frame.mPayloadSize); // Reserve place
// Copy AMR data
memmove(dataToDecode.mutableData() + 1, frame.mPayload, frame.mPayloadSize);
uint8_t frameType = 0xFF;
for (uint8_t ftIndex = 0; ftIndex <= 9 && frameType == 0xFF; ftIndex++)
if (amrnb_framelen[ftIndex] == frame.mPayloadSize)
frameType = ftIndex;
// Check if frameType comparing is correct
if (frameType == 0xFF)
return 0;
dataToDecode.mutableData()[0] = (frameType << 3) | (1 << 2);
Decoder_Interface_Decode(mDecoderCtx, (const unsigned char*)dataToDecode.data(), (short*)output, 0);
return pcmLength();
}
else */
else
{
if (outputCapacity < pcmLength())
return 0;
// Reorder bytes from input to dst
uint8_t dst[GSM_EFR_FRAME_LEN];
const uint8_t* src = input.data();
for (int i=0; i<(GSM_EFR_FRAME_LEN-1); i++)
dst[i] = (src[i] << 4) | (src[i+1] >> 4);
dst[GSM_EFR_FRAME_LEN-1] = src[GSM_EFR_FRAME_LEN-1] << 4;
if (inputBytes == 0)
{ // PLC part
unsigned char buffer[32];
buffer[0] = (AMR_BITRATE_DTX << 3)|4;
Decoder_Interface_Decode(mDecoderCtx, buffer, (short*)output, 0); // Handle missing data
}
else
unsigned char in[GSM_EFR_FRAME_LEN + 1];
// Reorder bits
in[0] = 0x3c; /* AMR mode 7 = GSM-EFR, Quality bit is set */
in[GSM_EFR_FRAME_LEN] = 0x0;
for (int i=0; i<244; i++)
{
// Reorder bytes from input to dst
uint8_t dst[GSM_EFR_FRAME_LEN];
const uint8_t* src = (const uint8_t*)input;
for (int i=0; i<(GSM_EFR_FRAME_LEN-1); i++)
dst[i] = (src[i] << 4) | (src[i+1] >> 4);
dst[GSM_EFR_FRAME_LEN-1] = src[GSM_EFR_FRAME_LEN-1] << 4;
int si = gsm690_12_2_bitorder[i];
int di = i;
msb_put_bit(in + 1, di, msb_get_bit(dst, si));
}
unsigned char in[GSM_EFR_FRAME_LEN + 1];
// Decode
memset(output.data(), 0, pcmLength());
Decoder_Interface_Decode(mDecoderCtx, in, (short*)output.data(), 0);
// Reorder bits
in[0] = 0x3c; /* AMR mode 7 = GSM-EFR, Quality bit is set */
in[GSM_EFR_FRAME_LEN] = 0x0;
for (int i=0; i<244; i++)
{
int si = gsm690_12_2_bitorder[i];
int di = i;
msb_put_bit(in + 1, di, msb_get_bit(dst, si));
}
// Decode
memset(output, 0, pcmLength());
Decoder_Interface_Decode(mDecoderCtx, in, (short*)output, 0);
uint8_t* pcm = (uint8_t*)output;
for (int i=0; i<160; i++)
{
uint16_t w = ((uint16_t*)output)[i];
pcm[(i<<1) ] = w & 0xff;
pcm[(i<<1)+1] = (w >> 8) & 0xff;
}
uint8_t* pcm = (uint8_t*)output.data();
for (int i=0; i<160; i++)
{
uint16_t w = ((uint16_t*)output.data())[i];
pcm[(i<<1) ] = w & 0xff;
pcm[(i<<1)+1] = (w >> 8) & 0xff;
}
}
return pcmLength();
return {.mDecoded = (size_t)pcmLength()};
}
int GsmEfrCodec::plc(int lostFrames, void* output, int outputCapacity)
size_t GsmEfrCodec::plc(int lostFrames, std::span<uint8_t> output)
{
if (outputCapacity < lostFrames * pcmLength())
if (output.size_bytes() < lostFrames * pcmLength())
return 0;
short* dataOut = (short*)output;
short* dataOut = (short*)output.data();
for (int i=0; i < lostFrames; i++)
{

View File

@@ -26,13 +26,13 @@ struct AmrCodecConfig
class AmrNbCodec : public Codec
{
protected:
void* mEncoderCtx;
void* mDecoderCtx;
void* mEncoderCtx = nullptr;
void* mDecoderCtx = nullptr;
AmrCodecConfig mConfig;
unsigned mCurrentDecoderTimestamp;
int mSwitchCounter;
int mPreviousPacketLength;
unsigned mCurrentDecoderTimestamp = 0;
int mPreviousPacketLength = 0;
size_t mCngCounter = 0;
size_t mSwitchCounter = 0;
public:
class CodecFactory: public Factory
{
@@ -54,17 +54,16 @@ public:
};
AmrNbCodec(const AmrCodecConfig& config);
~AmrNbCodec();
Info info() override;
EncodeResult encode(std::span<const uint8_t> input, std::span<uint8_t> output) override;
DecodeResult decode(std::span<const uint8_t> input, std::span<uint8_t> output) override;
size_t plc(int lostFrames, std::span<uint8_t> output) override;
virtual ~AmrNbCodec();
const char* name() override;
int pcmLength() override;
int rtpLength() override;
int frameTime() override;
int samplerate() override;
int encode(const void* input, int inputBytes, void* output, int outputCapacity) override;
int decode(const void* input, int inputBytes, void* output, int outputCapacity) override;
int plc(int lostFrames, void* output, int outputCapacity) override;
int getSwitchCounter() const;
int getCngCounter() const;
};
struct AmrWbStatistics
@@ -77,15 +76,17 @@ extern AmrWbStatistics GAmrWbStatistics;
class AmrWbCodec : public Codec
{
protected:
void* mEncoderCtx;
void* mDecoderCtx;
void* mEncoderCtx = nullptr;
void* mDecoderCtx = nullptr;
AmrCodecConfig mConfig;
uint64_t mCurrentDecoderTimestamp;
int mSwitchCounter;
uint64_t mCurrentDecoderTimestamp = 0;
size_t mSwitchCounter = 0;
size_t mCngCounter = 0;
int mPreviousPacketLength;
int decodeIuup(std::span<const uint8_t> input, std::span<uint8_t> output);
int decodePlain(std::span<const uint8_t> input, std::span<uint8_t> output);
DecodeResult decodeIuup(std::span<const uint8_t> input, std::span<uint8_t> output);
DecodeResult decodePlain(std::span<const uint8_t> input, std::span<uint8_t> output);
public:
class CodecFactory: public Factory
@@ -110,23 +111,21 @@ public:
AmrWbCodec(const AmrCodecConfig& config);
virtual ~AmrWbCodec();
const char* name() override;
int pcmLength() override;
int rtpLength() override;
int frameTime() override;
int samplerate() override;
int encode(const void* input, int inputBytes, void* output, int outputCapacity) override;
int decode(const void* input, int inputBytes, void* output, int outputCapacity) override;
int plc(int lostFrames, void* output, int outputCapacity) override;
Info info() override;
EncodeResult encode(std::span<const uint8_t> input, std::span<uint8_t> output) override;
DecodeResult decode(std::span<const uint8_t> input, std::span<uint8_t> output) override;
size_t plc(int lostFrames, std::span<uint8_t> output) override;
int getSwitchCounter() const;
int getCngCounter() const;
};
class GsmEfrCodec : public Codec
{
protected:
void* mEncoderCtx;
void* mDecoderCtx;
bool mIuUP;
void* mEncoderCtx = nullptr;
void* mDecoderCtx = nullptr;
bool mIuUP = false;
public:
class GsmEfrFactory: public Factory
@@ -143,23 +142,19 @@ public:
void create(CodecMap& codecs) override;
PCodec create() override;
protected:
bool mIuUP;
int mPayloadType;
};
GsmEfrCodec(bool iuup = false);
~GsmEfrCodec();
virtual ~GsmEfrCodec();
const char* name() override;
int pcmLength() override;
int rtpLength() override;
int frameTime() override;
int samplerate() override;
int encode(const void* input, int inputBytes, void* output, int outputCapacity) override;
int decode(const void* input, int inputBytes, void* output, int outputCapacity) override;
int plc(int lostFrames, void* output, int outputCapacity) override;
Info info() override;
EncodeResult encode(std::span<const uint8_t> input, std::span<uint8_t> output) override;
DecodeResult decode(std::span<const uint8_t> input, std::span<uint8_t> output) override;
size_t plc(int lostFrames, std::span<uint8_t> output) override;
};
} // End of MT namespace

View File

@@ -91,38 +91,20 @@ G729Codec::~G729Codec()
}
}
const char* G729Codec::name()
Codec::Info G729Codec::info()
{
return "G729";
}
int G729Codec::pcmLength()
{
return 10 * 8 * 2;
}
int G729Codec::rtpLength()
{
return 10;
}
int G729Codec::frameTime()
{
return 10;
}
int G729Codec::samplerate()
{
return 8000;
}
int G729Codec::channels()
{
return 1;
return {
.mName = "G729",
.mSamplerate = 8000,
.mChannels = 1,
.mPcmLength = 10 * 8 * 2,
.mFrameTime = 10,
.mRtpLength = 10
};
}
// static const int SamplesPerFrame = 80;
int G729Codec::encode(const void* input, int inputBytes, void* output, int outputCapacity)
Codec::EncodeResult G729Codec::encode(std::span<const uint8_t> input, std::span<uint8_t> output)
{
// Create encoder if it is not done yet
if (!mEncoder)
@@ -131,24 +113,24 @@ int G729Codec::encode(const void* input, int inputBytes, void* output, int outpu
if (mEncoder)
Init_Pre_Process(mEncoder);
}
int result = 0;
size_t result = 0;
if (mEncoder)
{
int nrOfFrames = inputBytes / 160; // 10ms frames
Word16 parm[PRM_SIZE]; // ITU's service buffer
int nrOfFrames = input.size_bytes() / 160; // 10ms frames
Word16 parm[PRM_SIZE]; // ITU's service buffer
for (int frameIndex = 0; frameIndex < nrOfFrames; frameIndex++)
{
Copy((int16_t*)input + frameIndex * pcmLength() / 2, mEncoder->new_speech, pcmLength() / 2);
Pre_Process(mEncoder, mEncoder->new_speech, pcmLength() / 2);
Copy((int16_t*)input.data() + frameIndex * info().mPcmLength / 2, mEncoder->new_speech, info().mPcmLength / 2);
Pre_Process(mEncoder, mEncoder->new_speech, info().mPcmLength / 2);
Coder_ld8a(mEncoder, parm);
Store_Params(parm, (uint8_t*)output + frameIndex * rtpLength());
result += rtpLength();
Store_Params(parm, output.data() + frameIndex * info().mRtpLength);
result += info().mRtpLength;
}
}
return result;
return {result};
}
int G729Codec::decode(const void* input, int inputBytes, void* output, int outputCapacity)
Codec::DecodeResult G729Codec::decode(std::span<const uint8_t> input, std::span<uint8_t> output)
{
if (!mDecoder)
{
@@ -160,29 +142,29 @@ int G729Codec::decode(const void* input, int inputBytes, void* output, int outpu
}
}
int result = 0;
size_t result = 0;
if (mDecoder)
{
// See if there are silence bytes in the end
bool isSilence = (inputBytes % rtpLength()) / 2 != 0;
bool isSilence = (input.size_bytes() % info().mRtpLength) / 2 != 0;
// Find number of frames
int nrOfFrames = inputBytes / rtpLength();
nrOfFrames = std::min(outputCapacity / pcmLength(), nrOfFrames);
int nrOfFrames = input.size_bytes() / info().mRtpLength;
nrOfFrames = std::min(output.size_bytes() / info().mPcmLength, (size_t)nrOfFrames);
for (int frameIndex = 0; frameIndex < nrOfFrames; frameIndex++)
decodeFrame((const uint8_t*)input + frameIndex * rtpLength(), (int16_t*)output + frameIndex * pcmLength());
decodeFrame(input.data() + frameIndex * info().mRtpLength, (int16_t*)output.data() + frameIndex * info().mPcmLength);
result += nrOfFrames * pcmLength();
result += nrOfFrames * info().mPcmLength;
if (isSilence && nrOfFrames < outputCapacity / pcmLength())
if (isSilence && nrOfFrames < output.size_bytes() / info().mPcmLength)
{
memset((uint8_t*)output + nrOfFrames * pcmLength(), 0, pcmLength());
result += pcmLength();
memset(output.data() + nrOfFrames * info().mPcmLength, 0, info().mPcmLength);
result += info().mPcmLength;
}
}
return result;
return {.mDecoded = result, .mIsCng = false};
}
void G729Codec::decodeFrame(const uint8_t* rtp, int16_t* pcm)
@@ -217,7 +199,7 @@ void G729Codec::decodeFrame(const uint8_t* rtp, int16_t* pcm)
}
int G729Codec::plc(int lostFrames, void* output, int outputCapacity)
size_t G729Codec::plc(int lostFrames, std::span<uint8_t> output)
{
return 0;
}
@@ -390,7 +372,7 @@ int OpusCodec::OpusFactory::processSdp(const resip::SdpContents::Session::Medium
PCodec OpusCodec::OpusFactory::create()
{
OpusCodec* result = new OpusCodec(mSamplerate, mChannels, mParams.mPtime);
OpusCodec* result = new OpusCodec(Audio::Format(mSamplerate, mChannels), mParams.mPtime);
result->applyParams(mParams);
PCodec c(result);
mCodecList.push_back(c);
@@ -398,8 +380,8 @@ PCodec OpusCodec::OpusFactory::create()
return c;
}
OpusCodec::OpusCodec(int samplerate, int channels, int ptime)
:mEncoderCtx(nullptr), mDecoderCtx(nullptr), mChannels(channels), mPTime(ptime), mSamplerate(samplerate), mDecoderChannels(0)
OpusCodec::OpusCodec(Audio::Format fmt, int ptime)
:mEncoderCtx(nullptr), mDecoderCtx(nullptr), mChannels(fmt.channels()), mPTime(ptime), mSamplerate(fmt.rate()), mDecoderChannels(0)
{
int status;
mEncoderCtx = opus_encoder_create(mSamplerate, mChannels, OPUS_APPLICATION_VOIP, &status);
@@ -441,52 +423,34 @@ OpusCodec::~OpusCodec()
}
}
const char* OpusCodec::name()
{
return OPUS_CODEC_NAME;
Codec::Info OpusCodec::info() {
return {
.mName = OPUS_CODEC_NAME,
.mSamplerate = mSamplerate,
.mChannels = mChannels,
.mPcmLength = (int)(mSamplerate / 1000 * sizeof(short) * mChannels * mPTime),
.mFrameTime = mPTime,
.mRtpLength = 0 /* VBR */
};
}
int OpusCodec::pcmLength()
{
return (samplerate() / 1000 ) * frameTime() * sizeof(short) * channels();
}
int OpusCodec::channels()
{
return mChannels;
}
int OpusCodec::rtpLength()
{
return 0; // VBR
}
int OpusCodec::frameTime()
{
return mPTime;
}
int OpusCodec::samplerate()
{
return mSamplerate;
}
int OpusCodec::encode(const void* input, int inputBytes, void* output, int outputCapacity)
Codec::EncodeResult OpusCodec::encode(std::span<const uint8_t> input, std::span<uint8_t> output)
{
// Send number of samples for input and number of bytes for output
int written = opus_encode(mEncoderCtx, (const opus_int16*)input, inputBytes / (sizeof(short) * channels()), (unsigned char*)output, outputCapacity / (sizeof(short) * channels()));
int written = opus_encode(mEncoderCtx, (const opus_int16*)input.data(), input.size_bytes() / (sizeof(short) * channels()),
output.data(), output.size_bytes() / (sizeof(short) * channels()));
if (written < 0)
return 0;
return {.mEncoded = 0};
else
return written;
return {.mEncoded = (size_t)written};
}
int OpusCodec::decode(const void* input, int inputBytes, void* output, int outputCapacity)
Codec::DecodeResult OpusCodec::decode(std::span<const uint8_t> input, std::span<uint8_t> output)
{
int result = 0;
// Examine the number of channels available in incoming packet
int nr_of_channels = opus_packet_get_nb_channels((const unsigned char *) input);
int nr_of_channels = opus_packet_get_nb_channels(input.data());
// Recreate decoder if needed
if (mDecoderChannels != nr_of_channels)
@@ -504,24 +468,22 @@ int OpusCodec::decode(const void* input, int inputBytes, void* output, int outpu
int status = 0;
mDecoderCtx = opus_decoder_create(mSamplerate, mDecoderChannels, &status);
if (status)
return 0;
return {0};
}
int nr_of_frames = opus_decoder_get_nb_samples(mDecoderCtx, (const unsigned char *) input,
inputBytes);
int nr_of_frames = opus_decoder_get_nb_samples(mDecoderCtx, input.data(), input.size_bytes());
if (nr_of_frames <= 0)
return 0;
return {0};
// We support stereo and mono here.
int buffer_capacity = nr_of_frames * sizeof(opus_int16) * nr_of_channels;
opus_int16 *buffer_decode = (opus_int16 *)alloca(buffer_capacity);
int decoded = opus_decode(mDecoderCtx,
reinterpret_cast<const unsigned char *>(input), inputBytes,
int decoded = opus_decode(mDecoderCtx, input.data(), input.size_bytes(),
buffer_decode, nr_of_frames, 0);
if (decoded < 0)
{
ICELogCritical(<< "opus_decode() returned " << decoded);
return 0;
return {0};
}
opus_int16 *buffer_stereo = nullptr;
@@ -535,14 +497,14 @@ int OpusCodec::decode(const void* input, int inputBytes, void* output, int outpu
buffer_stereo[i * 2 + 1] = buffer_decode[i];
buffer_stereo[i * 2] = buffer_decode[i];
}
assert(buffer_stereo_capacity <= outputCapacity);
memcpy(output, buffer_stereo, buffer_stereo_capacity);
assert(buffer_stereo_capacity <= output.size_bytes());
memcpy(output.data(), buffer_stereo, buffer_stereo_capacity);
result = buffer_stereo_capacity;
break;
case 2:
assert(buffer_capacity <= outputCapacity);
memcpy(output, buffer_decode, buffer_capacity);
assert(buffer_capacity <= output.size_bytes());
memcpy(output.data(), buffer_decode, buffer_capacity);
result = buffer_capacity;
break;
@@ -550,17 +512,17 @@ int OpusCodec::decode(const void* input, int inputBytes, void* output, int outpu
assert(0);
}
return result;
return {.mDecoded = (size_t)result};
}
int OpusCodec::plc(int lostPackets, void* output, int outputCapacity)
size_t OpusCodec::plc(int lostPackets, std::span<uint8_t> output)
{
// Find how much frames do we need to produce and prefill it with silence
int frames_per_packet = (int)pcmLength() / (sizeof(opus_int16) * channels());
memset(output, 0, outputCapacity);
memset(output.data(), 0, output.size_bytes());
// Use this pointer as output
opus_int16* data_output = reinterpret_cast<opus_int16*>(output);
opus_int16* data_output = reinterpret_cast<opus_int16*>(output.data());
int nr_of_decoded_frames = 0;
@@ -575,10 +537,7 @@ int OpusCodec::plc(int lostPackets, void* output, int outputCapacity)
case 1:
// Convert mono to stereo
for (int i=0; i < nr_of_decoded_frames; i++)
{
data_output[i * 2] = buffer_plc[i];
data_output[i * 2 + 1] = buffer_plc[i+1];
}
data_output[i * 2] = data_output[i * 2 + 1] = buffer_plc[i];
data_output += frames_per_packet * mChannels;
break;
@@ -589,14 +548,14 @@ int OpusCodec::plc(int lostPackets, void* output, int outputCapacity)
break;
}
}
return ((char*)data_output - (char*)output) * sizeof(opus_int16);
return ((uint8_t*)data_output - output.data());
}
// -------------- ILBC -------------------
#define ILBC_CODEC_NAME "ILBC"
IlbcCodec::IlbcCodec(int packetTime)
:mPacketTime(packetTime), mEncoderCtx(nullptr), mDecoderCtx(nullptr)
:mPacketTime(packetTime)
{
WebRtcIlbcfix_EncoderCreate(&mEncoderCtx);
WebRtcIlbcfix_DecoderCreate(&mDecoderCtx);
@@ -610,44 +569,31 @@ IlbcCodec::~IlbcCodec()
WebRtcIlbcfix_EncoderFree(mEncoderCtx);
}
const char* IlbcCodec::name()
Codec::Info IlbcCodec::info()
{
return "ilbc";
return {
.mName = ILBC_CODEC_NAME,
.mSamplerate = 8000,
.mChannels = 1,
.mPcmLength = mPacketTime * 8 * (int)sizeof(short),
.mFrameTime = mPacketTime,
.mRtpLength = (mPacketTime == 20) ? 38 : 50
};
}
int IlbcCodec::rtpLength()
Codec::EncodeResult IlbcCodec::encode(std::span<const uint8_t> input, std::span<uint8_t> output)
{
return (mPacketTime == 20 ) ? 38 : 50;
}
int IlbcCodec::pcmLength()
{
return mPacketTime * 16;
}
int IlbcCodec::frameTime()
{
return mPacketTime;
}
int IlbcCodec::samplerate()
{
return 8000;
}
int IlbcCodec::encode(const void *input, int inputBytes, void* outputBuffer, int outputCapacity)
{
if (inputBytes % pcmLength())
return 0;
if (input.size_bytes() % pcmLength())
return {};
// Declare the data input pointer
short *dataIn = (short *)input;
short *dataIn = (short *)input.data();
// Declare the data output pointer
char *dataOut = (char *)outputBuffer;
char *dataOut = (char *)output.data();
// Find how much RTP frames will be generated
unsigned int frames = inputBytes / pcmLength();
unsigned int frames = input.size_bytes() / pcmLength();
// Generate frames
for (unsigned int i=0; i<frames; i++)
@@ -657,15 +603,15 @@ int IlbcCodec::encode(const void *input, int inputBytes, void* outputBuffer, int
dataOut += rtpLength();
}
return frames * rtpLength();
return {frames * rtpLength()};
}
int IlbcCodec::decode(const void* input, int inputBytes, void* output, int outputCapacity)
Codec::DecodeResult IlbcCodec::decode(std::span<const uint8_t> input, std::span<uint8_t> output)
{
unsigned frames = inputBytes / rtpLength();
unsigned frames = input.size_bytes() / rtpLength();
char* dataIn = (char*)input;
short* dataOut = (short*)output;
char* dataIn = (char*)input.data();
short* dataOut = (short*)output.data();
for (unsigned i=0; i < frames; ++i)
{
@@ -675,12 +621,12 @@ int IlbcCodec::decode(const void* input, int inputBytes, void* output, int outpu
dataOut += pcmLength() / 2;
}
return frames * pcmLength();
return {frames * pcmLength()};
}
int IlbcCodec::plc(int lostFrames, void* output, int outputCapacity)
size_t IlbcCodec::plc(int lostFrames, std::span<uint8_t> output)
{
return 2 * WebRtcIlbcfix_DecodePlc(mDecoderCtx, (WebRtc_Word16*)output, lostFrames);
return sizeof(short) * WebRtcIlbcfix_DecodePlc(mDecoderCtx, (WebRtc_Word16*)output.data(), lostFrames);
}
// --- IlbcFactory ---
@@ -795,38 +741,24 @@ IsacCodec::~IsacCodec()
WebRtcIsacfix_Free(mDecoderCtx); mDecoderCtx = NULL;
}
const char* IsacCodec::name()
{
return "isac";
Codec::Info IsacCodec::info() {
return {
.mName = "isac",
.mSamplerate = mSamplerate,
.mChannels = 1,
.mPcmLength = 60 * mSamplerate / 1000 * 2,
.mFrameTime = 60,
.mRtpLength = 0
};
}
int IsacCodec::frameTime()
Codec::EncodeResult IsacCodec::encode(std::span<const uint8_t> input, std::span<uint8_t> output)
{
return 60;
}
int IsacCodec::samplerate()
{
return mSamplerate;
}
int IsacCodec::pcmLength()
{
return frameTime() * samplerate() / 1000 * sizeof(short);
}
int IsacCodec::rtpLength()
{
return 0;
}
int IsacCodec::encode(const void* input, int inputBytes, void* output, int outputCapacity)
{
unsigned nrOfSamples = inputBytes / 2;
unsigned nrOfSamples = input.size_bytes() / 2;
unsigned timeLength = nrOfSamples / (mSamplerate / 1000);
int encoded = 0;
char* dataOut = (char*)output;
const WebRtc_Word16* dataIn = (const WebRtc_Word16*)input;
char* dataOut = (char*)output.data();
const WebRtc_Word16* dataIn = (const WebRtc_Word16*)input.data();
// Iterate 10 milliseconds chunks
for (unsigned i=0; i<timeLength/10; i++)
@@ -835,25 +767,25 @@ int IsacCodec::encode(const void* input, int inputBytes, void* output, int outpu
if (encoded > 0)
dataOut += encoded;
}
return dataOut - (char*)output;
return {.mEncoded = (size_t)(dataOut - (char*)output.data())};
}
int IsacCodec::decode(const void* input, int inputBytes, void* output, int outputCapacity)
Codec::DecodeResult IsacCodec::decode(std::span<const uint8_t> input, std::span<uint8_t> output)
{
WebRtc_Word16 speechType = 0;
unsigned produced = WebRtcIsacfix_Decode(mDecoderCtx, (const WebRtc_UWord16*)input, inputBytes, (WebRtc_Word16*)output, &speechType);
unsigned produced = WebRtcIsacfix_Decode(mDecoderCtx, (const WebRtc_UWord16*)input.data(), input.size_bytes(), (WebRtc_Word16*)output.data(), &speechType);
if (produced == (unsigned)-1)
return 0;
return {.mDecoded = 0};
return produced * 2;
return {.mDecoded = produced * 2};
}
int IsacCodec::plc(int lostFrames, void* output, int outputCapacity)
size_t IsacCodec::plc(int lostFrames, std::span<uint8_t> output)
{
// lostFrames are 30-milliseconds frames; but used encoding mode is 60 milliseconds.
// So lostFrames * 2
lostFrames *=2 ;
if (-1 == WebRtcIsacfix_DecodePlc(mDecoderCtx, (WebRtc_Word16*)output, lostFrames ))
if (-1 == WebRtcIsacfix_DecodePlc(mDecoderCtx, (WebRtc_Word16*)output.data(), lostFrames ))
return 0;
return lostFrames * 30 * (samplerate()/1000 * sizeof(short));
@@ -916,71 +848,55 @@ PCodec IsacCodec::IsacFactory32K::create()
G711Codec::G711Codec(int type)
:mType(type)
{
}
{}
G711Codec::~G711Codec()
{
{}
Codec::Info G711Codec::info() {
return {
.mName = mType == ALaw ? "PCMA" : "PCMU",
.mSamplerate = 8000,
.mChannels = 1,
.mPcmLength = 10 * 16,
.mFrameTime = 10,
.mRtpLength = 10 * 8
};
}
const char* G711Codec::name()
{
return "g711";
}
int G711Codec::pcmLength()
{
return frameTime() * 16;
}
int G711Codec::rtpLength()
{
return frameTime() * 8;
}
int G711Codec::frameTime()
{
return 10;
}
int G711Codec::samplerate()
{
return 8000;
}
int G711Codec::encode(const void* input, int inputBytes, void* output, int outputCapacity)
Codec::EncodeResult G711Codec::encode(std::span<const uint8_t> input, std::span<uint8_t> output)
{
int result;
if (mType == ALaw)
result = WebRtcG711_EncodeA(NULL, (WebRtc_Word16*)input, inputBytes/2, (WebRtc_Word16*)output);
result = WebRtcG711_EncodeA(nullptr, (WebRtc_Word16*)input.data(), input.size_bytes() / 2, (WebRtc_Word16*)output.data());
else
result = WebRtcG711_EncodeU(NULL, (WebRtc_Word16*)input, inputBytes/2, (WebRtc_Word16*)output);
result = WebRtcG711_EncodeU(nullptr, (WebRtc_Word16*)input.data(), input.size_bytes() / 2, (WebRtc_Word16*)output.data());
if (result == -1)
throw Exception(ERR_WEBRTC, -1);
if (result < 0)
return {.mEncoded = 0};
return result;
return {.mEncoded = (size_t) result};
}
int G711Codec::decode(const void* input, int inputBytes, void* output, int outputCapacity)
Codec::DecodeResult G711Codec::decode(std::span<const uint8_t> input, std::span<uint8_t> output)
{
assert(outputCapacity >= inputBytes * 2);
assert(output.size_bytes() >= input.size_bytes() * 2);
int result;
WebRtc_Word16 speechType;
if (mType == ALaw)
result = WebRtcG711_DecodeA(NULL, (WebRtc_Word16*)input, inputBytes, (WebRtc_Word16*)output, &speechType);
result = WebRtcG711_DecodeA(nullptr, (WebRtc_Word16*)input.data(), input.size_bytes(), (WebRtc_Word16*)output.data(), &speechType);
else
result = WebRtcG711_DecodeU(NULL, (WebRtc_Word16*)input, inputBytes, (WebRtc_Word16*)output, &speechType);
result = WebRtcG711_DecodeU(nullptr, (WebRtc_Word16*)input.data(), input.size_bytes(), (WebRtc_Word16*)output.data(), &speechType);
if (result == -1)
throw Exception(ERR_WEBRTC, -1);
if (result < 0)
return {.mDecoded = 0};
return result * 2;
return {.mDecoded = (size_t)result * 2};
}
int G711Codec::plc(int lostSamples, void* output, int outputCapacity)
size_t G711Codec::plc(int lostSamples, std::span<uint8_t> output)
{
return 0;
}
@@ -1060,86 +976,64 @@ GsmCodec::GsmCodec(Type codecType)
GsmCodec::~GsmCodec()
{
gsm_destroy(mGSM);
gsm_destroy(mGSM); mGSM = nullptr;
}
const char* GsmCodec::name()
{
return "GSM-06.10";
}
int GsmCodec::rtpLength()
{
Codec::Info GsmCodec::info() {
int rtpLength = 0;
switch (mCodecType)
{
case Type::Bytes_31:
return GSM_RTPFRAME_SIZE_31;
break;
case Type::Bytes_32:
return GSM_RTPFRAME_SIZE_32;
case Type::Bytes_33:
return GSM_RTPFRAME_SIZE_33;
case Type::Bytes_65:
return GSM_RTPFRAME_SIZE_32 + GSM_RTPFRAME_SIZE_33;
case Type::Bytes_31: rtpLength = GSM_RTPFRAME_SIZE_31; break;
case Type::Bytes_32: rtpLength = GSM_RTPFRAME_SIZE_32; break;
case Type::Bytes_33: rtpLength = GSM_RTPFRAME_SIZE_33; break;
case Type::Bytes_65: rtpLength = GSM_RTPFRAME_SIZE_32 + GSM_RTPFRAME_SIZE_33; break;
default: rtpLength = GSM_RTPFRAME_SIZE_33;
}
return GSM_RTPFRAME_SIZE_33;
return {
.mName = "GSM-06.10",
.mSamplerate = 8000,
.mChannels = 1,
.mPcmLength = GSM_AUDIOFRAME_TIME * 16,
.mFrameTime = GSM_AUDIOFRAME_TIME,
.mRtpLength = rtpLength
};
}
int GsmCodec::pcmLength()
{
return GSM_AUDIOFRAME_TIME * 16;
}
int GsmCodec::frameTime()
{
return GSM_AUDIOFRAME_TIME;
}
int GsmCodec::samplerate()
{
return 8000;
}
int GsmCodec::encode(const void* input, int inputBytes, void* output, int outputCapacity)
Codec::EncodeResult GsmCodec::encode(std::span<const uint8_t> input, std::span<uint8_t> output)
{
int outputBytes = 0;
char* outputBuffer = (char*)output.data();
char* outputBuffer = (char*)output;
for (int i = 0; i < inputBytes/pcmLength(); i++)
for (int i = 0; i < input.size_bytes() / pcmLength(); i++)
{
gsm_encode(mGSM, (gsm_signal *)input+160*i, (gsm_byte*)outputBuffer);
gsm_encode(mGSM, (gsm_signal *)input.data()+160*i, (gsm_byte*)outputBuffer);
outputBuffer += rtpLength();
outputBytes += rtpLength();
}
return outputBytes;
return {.mEncoded = (size_t)outputBytes};
}
int GsmCodec::decode(const void* input, int inputBytes, void* output, int outputCapacity)
Codec::DecodeResult GsmCodec::decode(std::span<const uint8_t> input, std::span<uint8_t> output)
{
if (inputBytes % rtpLength() != 0)
return 0;
if (input.size_bytes() % rtpLength() != 0)
return {.mDecoded = 0};
int i=0;
for (i = 0; i < inputBytes/rtpLength(); i++)
gsm_decode(mGSM, (gsm_byte *)input + 33 * i, (gsm_signal *)output + 160 * i);
for (i = 0; i < input.size_bytes() / rtpLength(); i++)
gsm_decode(mGSM, (gsm_byte *)input.data() + 33 * i, (gsm_signal *)output.data() + 160 * i);
return i * 320;
return {.mDecoded = (size_t)i * 320};
}
int GsmCodec::plc(int lostFrames, void* output, int outputCapacity)
size_t GsmCodec::plc(int lostFrames, std::span<uint8_t> output)
{
if (outputCapacity < lostFrames * pcmLength())
if (output.size_bytes() < lostFrames * pcmLength())
return 0;
// Return silence frames
memset(output, 0, lostFrames * pcmLength());
memset(output.data(), 0, lostFrames * pcmLength());
return lostFrames * pcmLength();
}
@@ -1155,58 +1049,52 @@ G722Codec::G722Codec()
G722Codec::~G722Codec()
{
g722_decode_release((g722_decode_state_t*)mDecoder);
g722_encode_release((g722_encode_state_t*)mEncoder);
g722_decode_release((g722_decode_state_t*)mDecoder); mDecoder = nullptr;
g722_encode_release((g722_encode_state_t*)mEncoder); mEncoder = nullptr;
}
const char* G722Codec::name()
{
return G722_MIME_NAME;
Codec::Info G722Codec::info() {
// ToDo: double check the G722 calls - remember RFC has bug about samplerate
return {
.mName = G722_MIME_NAME,
.mSamplerate = 8000,
.mChannels = 1,
.mPcmLength = 640,
.mFrameTime = 20,
.mRtpLength = 160
};
}
int G722Codec::pcmLength()
Codec::EncodeResult G722Codec::encode(std::span<const uint8_t> input, std::span<uint8_t> output)
{
return 640;
if (output.size_bytes() < input.size_bytes() / 4)
return {.mEncoded = 0}; // Destination buffer not big enough
int r = g722_encode((g722_encode_state_t *)mEncoder, (unsigned char*)output.data(), ( short*)input.data(), input.size_bytes() / 2);
if (r < 0)
return {.mEncoded = 0};
return {.mEncoded = (size_t)r};
}
int G722Codec::frameTime()
Codec::DecodeResult G722Codec::decode(std::span<const uint8_t> input, std::span<uint8_t> output)
{
return 20;
if (output.size_bytes() < input.size_bytes() * 4)
return {.mDecoded = 0}; // Destination buffer not big enough
int r = g722_decode((g722_decode_state_t *)mDecoder, (short*)output.data(), (unsigned char*)input.data(), input.size_bytes()) * 2;
if (r < 0)
return {.mDecoded = 0};
return {.mDecoded = (size_t)r};
}
int G722Codec::rtpLength()
size_t G722Codec::plc(int lostFrames, std::span<uint8_t> output)
{
return 160;
}
int G722Codec::samplerate()
{
return 8000;
}
int G722Codec::encode(const void* input, int inputBytes, void* output, int outputCapacity)
{
if (outputCapacity < inputBytes / 4)
return 0; // Destination buffer not big enough
return g722_encode((g722_encode_state_t *)mEncoder, (unsigned char*)output, ( short*)input, inputBytes / 2);
}
int G722Codec::decode(const void* input, int inputBytes, void* output, int outputCapacity)
{
if (outputCapacity < inputBytes * 4)
return 0; // Destination buffer not big enough
return g722_decode((g722_decode_state_t *)mDecoder, ( short*)output, (unsigned char*)input, inputBytes) * 2;
}
int G722Codec::plc(int lostFrames, void* output, int outputCapacity)
{
if (outputCapacity < lostFrames * pcmLength())
if (output.size_bytes() < lostFrames * pcmLength())
return 0;
// Return silence frames
memset(output, 0, lostFrames * pcmLength());
memset(output.data(), 0, lostFrames * pcmLength());
return lostFrames * pcmLength();
}
@@ -1318,7 +1206,6 @@ static bool repackHalfRate(BitReader& br, uint16_t frame[22], bool& lastItem)
}
GsmHrCodec::GsmHrCodec()
:mDecoder(nullptr)
{
mDecoder = new GsmHr::Codec();
}
@@ -1329,34 +1216,21 @@ GsmHrCodec::~GsmHrCodec()
mDecoder = nullptr;
}
const char* GsmHrCodec::name()
{
return "GSM-HR-08";
Codec::Info GsmHrCodec::info() {
return {
.mName = "GSM-HR-08",
.mSamplerate = 8000,
.mChannels = 1,
.mPcmLength = 20 * 8 * 2,
.mFrameTime = 20,
.mRtpLength = 0
};
}
int GsmHrCodec::pcmLength()
Codec::EncodeResult GsmHrCodec::encode(std::span<const uint8_t> input, std::span<uint8_t> output)
{
return frameTime() * 8 * 2;
}
int GsmHrCodec::rtpLength()
{
return 0;
}
int GsmHrCodec::frameTime()
{
return 20;
}
int GsmHrCodec::samplerate()
{
return 8000;
}
int GsmHrCodec::encode(const void* input, int inputBytes, void* output, int outputCapacity)
{
return 0;
// Not supported yet
return {.mEncoded = 0};
}
static const int params_unvoiced[] = {
@@ -1447,23 +1321,23 @@ hr_ref_from_canon(uint16_t *hr_ref, const uint8_t *canon)
[+] PQ: Adding conversion from canon to rawpcm-s16le (for codec pcm)
[+] PQ: Adding file output (blk_len=320)
*/
int GsmHrCodec::decode(const void* input, int inputBytes, void* output, int outputCapacity)
Codec::DecodeResult GsmHrCodec::decode(std::span<const uint8_t> input, std::span<uint8_t> output)
{
ByteBuffer bb(input, inputBytes, ByteBuffer::CopyBehavior::UseExternal);
ByteBuffer bb(input, ByteBuffer::CopyBehavior::UseExternal);
BitReader br(bb);
uint16_t hr_ref[22];
hr_ref_from_canon(hr_ref, (const uint8_t*)input + 1);
hr_ref_from_canon(hr_ref, input.data() + 1);
hr_ref[18] = 0; /* BFI : 1 bit */
hr_ref[19] = 0; /* UFI : 1 bit */
hr_ref[20] = 0; /* SID : 2 bit */
hr_ref[21] = 0; /* TAF : 1 bit */
reinterpret_cast<GsmHr::Codec*>(mDecoder)->speechDecoder((int16_t*)hr_ref, (int16_t*)output);
return 320;
reinterpret_cast<GsmHr::Codec*>(mDecoder)->speechDecoder((int16_t*)hr_ref, (int16_t*)output.data());
return {.mDecoded = 320};
}
int GsmHrCodec::plc(int lostFrames, void* output, int outputCapacity)
size_t GsmHrCodec::plc(int lostFrames, std::span<uint8_t> output)
{
return 0;
}

View File

@@ -40,42 +40,41 @@ public:
{
public:
const char* name() override;
int channels() override;
int samplerate() override;
int payloadType() override;
int channels() override;
int samplerate() override;
int payloadType() override;
void updateSdp(resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction) override;
int processSdp(const resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction) override;
int processSdp(const resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction) override;
PCodec create() override;
};
G729Codec();
~G729Codec() override;
const char* name() override;
int pcmLength() override;
int rtpLength() override;
int frameTime() override;
int samplerate() override;
int channels() override;
int encode(const void* input, int inputBytes, void* output, int outputCapacity) override;
int decode(const void* input, int inputBytes, void* output, int outputCapacity) override;
int plc(int lostFrames, void* output, int outputCapacity) override;
Info info() override;
EncodeResult encode(std::span<const uint8_t> input, std::span<uint8_t> output) override;
DecodeResult decode(std::span<const uint8_t> input, std::span<uint8_t> output) override;
size_t plc(int lostFrames, std::span<uint8_t> output) override;
};
class OpusCodec: public Codec
{
protected:
OpusEncoder *mEncoderCtx;
OpusDecoder *mDecoderCtx;
int mPTime, mSamplerate, mChannels;
// Audio::SpeexResampler mDecodeResampler;
int mDecoderChannels;
OpusEncoder *mEncoderCtx = nullptr;
OpusDecoder *mDecoderCtx = nullptr;
int mPTime = 0, mSamplerate = 0, mChannels = 0;
int mDecoderChannels = 0;
public:
struct Params
{
bool mUseDtx, mUseInbandFec, mStereo;
int mPtime, mTargetBitrate, mExpectedPacketLoss;
bool mUseDtx = false,
mUseInbandFec = false,
mStereo = false;
int mPtime = 0,
mTargetBitrate = 0,
mExpectedPacketLoss = 0;
Params();
resip::Data toString() const;
@@ -102,28 +101,24 @@ public:
PCodec create() override;
};
OpusCodec(int samplerate, int channels, int ptime);
OpusCodec(Audio::Format fmt, int ptime);
~OpusCodec();
void applyParams(const Params& params);
const char* name();
int pcmLength();
int rtpLength();
int frameTime();
int samplerate();
int channels();
int encode(const void* input, int inputBytes, void* output, int outputCapacity);
int decode(const void* input, int inputBytes, void* output, int outputCapacity);
int plc(int lostFrames, void* output, int outputCapacity);
Info info() override;
EncodeResult encode(std::span<const uint8_t> input, std::span<uint8_t> output) override;
DecodeResult decode(std::span<const uint8_t> input, std::span<uint8_t> output) override;
size_t plc(int lostFrames, std::span<uint8_t> output) override;
};
class IlbcCodec: public Codec
{
protected:
int mPacketTime; /// Single frame time (20 or 30 ms)
iLBC_encinst_t* mEncoderCtx;
iLBC_decinst_t* mDecoderCtx;
int mPacketTime = 0; /// Single frame time (20 or 30 ms)
iLBC_encinst_t* mEncoderCtx = nullptr;
iLBC_decinst_t* mDecoderCtx = nullptr;
public:
class IlbcFactory: public Factory
@@ -146,14 +141,11 @@ public:
IlbcCodec(int packetTime);
virtual ~IlbcCodec();
const char* name();
int pcmLength();
int rtpLength();
int frameTime();
int samplerate();
int encode(const void* input, int inputBytes, void* output, int outputCapacity);
int decode(const void* input, int inputBytes, void* output, int outputCapacity);
int plc(int lostFrames, void* output, int outputCapacity);
Info info() override;
EncodeResult encode(std::span<const uint8_t> input, std::span<uint8_t> output) override;
DecodeResult decode(std::span<const uint8_t> input, std::span<uint8_t> output) override;
size_t plc(int lostFrames, std::span<uint8_t> output) override;
};
class G711Codec: public Codec
@@ -186,15 +178,11 @@ public:
G711Codec(int type);
~G711Codec();
const char* name();
int pcmLength();
int frameTime();
int rtpLength();
int samplerate();
Info info() override;
int encode(const void* input, int inputBytes, void* output, int outputCapacity);
int decode(const void* input, int inputBytes, void* output, int outputCapacity);
int plc(int lostSamples, void* output, int outputCapacity);
EncodeResult encode(std::span<const uint8_t> input, std::span<uint8_t> output) override;
DecodeResult decode(std::span<const uint8_t> input, std::span<uint8_t> output) override;
size_t plc(int lostSamples, std::span<uint8_t> output) override ;
protected:
int mType; /// Determines if it is u-law or a-law codec. Its value is ALaw or ULaw.
@@ -203,9 +191,9 @@ protected:
class IsacCodec: public Codec
{
protected:
int mSamplerate;
ISACFIX_MainStruct* mEncoderCtx;
ISACFIX_MainStruct* mDecoderCtx;
int mSamplerate = 0;
ISACFIX_MainStruct* mEncoderCtx = nullptr;
ISACFIX_MainStruct* mDecoderCtx = nullptr;
public:
class IsacFactory16K: public Factory
{
@@ -237,15 +225,11 @@ public:
IsacCodec(int sampleRate);
~IsacCodec();
const char* name();
int pcmLength();
int rtpLength();
int frameTime();
int samplerate();
Info info() override;
int encode(const void* input, int inputBytes, void* output, int outputCapacity);
int decode(const void* input, int inputBytes, void* output, int outputCapacity);
int plc(int lostFrames, void* output, int outputCapacity);
EncodeResult encode(std::span<const uint8_t> input, std::span<uint8_t> output) override;
DecodeResult decode(std::span<const uint8_t> input, std::span<uint8_t> output) override;
size_t plc(int lostFrames, std::span<uint8_t> output) override;
};
@@ -309,17 +293,13 @@ public:
GsmCodec(Type codecType);
/*! Destructor. */
virtual ~GsmCodec();
~GsmCodec();
const char* name();
int pcmLength();
int rtpLength();
int frameTime();
int samplerate();
Info info() override;
int encode(const void* input, int inputBytes, void* output, int outputCapacity);
int decode(const void* input, int inputBytes, void* output, int outputCapacity);
int plc(int lostFrames, void* output, int outputCapacity);
EncodeResult encode(std::span<const uint8_t> input, std::span<uint8_t> output) override;
DecodeResult decode(std::span<const uint8_t> input, std::span<uint8_t> output) override;
size_t plc(int lostFrames, std::span<uint8_t> output) override;
};
/// GSM MIME name
@@ -358,25 +338,19 @@ public:
PCodec create();
};
G722Codec();
virtual ~G722Codec();
~G722Codec();
const char* name();
int pcmLength();
int rtpLength();
int frameTime();
int samplerate();
Info info() override;
int encode(const void* input, int inputBytes, void* output, int outputCapacity);
int decode(const void* input, int inputBytes, void* output, int outputCapacity);
int plc(int lostFrames, void* output, int outputCapacity);
//unsigned GetSamplerate() { return 16000; }
EncodeResult encode(std::span<const uint8_t> input, std::span<uint8_t> output) override;
DecodeResult decode(std::span<const uint8_t> input, std::span<uint8_t> output) override;
size_t plc(int lostFrames, std::span<uint8_t> output) override;
};
class GsmHrCodec: public Codec
{
protected:
void* mDecoder;
void* mDecoder = nullptr;
public:
class GsmHrFactory: public Factory
@@ -396,15 +370,11 @@ public:
GsmHrCodec();
~GsmHrCodec() override;
const char* name() override;
int pcmLength() override;
int rtpLength() override;
int frameTime() override;
int samplerate() override;
Info info() override;
int encode(const void* input, int inputBytes, void* output, int outputCapacity) override;
int decode(const void* input, int inputBytes, void* output, int outputCapacity) override;
int plc(int lostFrames, void* output, int outputCapacity) override;
EncodeResult encode(std::span<const uint8_t> input, std::span<uint8_t> output) override;
DecodeResult decode(std::span<const uint8_t> input, std::span<uint8_t> output) override;
size_t plc(int lostFrames, std::span<uint8_t> output) override;
};
}

View File

@@ -11,6 +11,7 @@
#include "MT_AudioReceiver.h"
#include "MT_AudioCodec.h"
#include "MT_CngHelper.h"
#include "MT_Dtmf.h"
#include "../helper/HL_Log.h"
#include "../helper/HL_Time.h"
#include "../audio/Audio_Interface.h"
@@ -115,7 +116,7 @@ bool SequenceSort(const std::shared_ptr<RtpBuffer::Packet>& p1, const std::share
return p1->rtp()->GetExtendedSequenceNumber() < p2->rtp()->GetExtendedSequenceNumber();
}
std::shared_ptr<RtpBuffer::Packet> RtpBuffer::add(std::shared_ptr<jrtplib::RTPPacket> packet, std::chrono::milliseconds timelength, int rate)
std::shared_ptr<RtpBuffer::Packet> RtpBuffer::add(const std::shared_ptr<jrtplib::RTPPacket>& packet, std::chrono::milliseconds timelength, int rate)
{
if (!packet)
return std::shared_ptr<Packet>();
@@ -191,24 +192,24 @@ std::shared_ptr<RtpBuffer::Packet> RtpBuffer::add(std::shared_ptr<jrtplib::RTPPa
return std::shared_ptr<Packet>();
}
RtpBuffer::FetchResult RtpBuffer::fetch(ResultList& rl)
RtpBuffer::FetchResult RtpBuffer::fetch()
{
Lock l(mGuard);
FetchResult result = FetchResult::NoPacket;
rl.clear();
FetchResult result;
// See if there is enough information in buffer
auto total = findTimelength();
while (total > mHigh && mPacketList.size() && 0ms != mHigh)
while (total > mHigh && mPacketList.size() > 1 && 0ms != mHigh)
{
ICELogMedia( << "Dropping RTP packets from jitter buffer");
total -= mPacketList.front()->timelength();
// Save it as last packet however - to not confuse loss packet counter
mFetchedPacket = mPacketList.front();
mLastSeqno = mPacketList.front()->rtp()->GetExtendedSequenceNumber();
mLastSeqno = mFetchedPacket->rtp()->GetExtendedSequenceNumber();
mLastReceiveTime = mFetchedPacket->rtp()->GetReceiveTime();
// Erase from packet list
mPacketList.erase(mPacketList.begin());
@@ -217,10 +218,10 @@ RtpBuffer::FetchResult RtpBuffer::fetch(ResultList& rl)
mStat.mPacketDropped++;
}
if (total < mLow)
if (total < mLow || total == 0ms)
{
// Still not prebuffered
result = FetchResult::NoPacket;
result = {FetchResult::Status::NoPacket};
}
else
{
@@ -228,8 +229,8 @@ RtpBuffer::FetchResult RtpBuffer::fetch(ResultList& rl)
{
if (mPacketList.empty())
{
result = FetchResult::NoPacket;
// Don't increase counter of lost packets here; maybe it is DTX
result = {FetchResult::Status::NoPacket};
}
else
{
@@ -237,34 +238,39 @@ RtpBuffer::FetchResult RtpBuffer::fetch(ResultList& rl)
auto& packet = *mPacketList.front();
uint32_t seqno = packet.rtp()->GetExtendedSequenceNumber();
// Gap between new packet and previous on
int gap = (int64_t)seqno - (int64_t)*mLastSeqno - 1;
gap = std::min(gap, 127);
if (gap > 0)
{
// std::cout << "Increase the packet loss for SSRC " << std::hex << mSsrc << std::endl;
mStat.mPacketLoss++;
auto currentTimestamp = std::chrono::microseconds(uint64_t(packet.rtp()->GetReceiveTime().GetDouble() * 1000000));
mStat.mPacketLoss += gap;
// Report is the onetime; there is no many sequential 1-packet gap reports
if (mStat.mPacketLossTimeline.empty() || (mStat.mPacketLossTimeline.back().mEndSeqno != seqno))
mStat.mPacketLossTimeline.push_back({.mStartSeqno = *mLastSeqno,
.mEndSeqno = seqno,
.mGap = gap,
.mTimestamp = currentTimestamp});
{
auto gapStart = RtpHelper::toMicroseconds(*mLastReceiveTime);
auto gapEnd = RtpHelper::toMicroseconds(packet.rtp()->GetReceiveTime());
mStat.mPacketLossTimeline.emplace_back(PacketLossEvent{.mStartSeqno = *mLastSeqno,
.mEndSeqno = seqno,
.mGap = gap,
.mTimestampStart = gapStart,
.mTimestampEnd = gapEnd});
}
mLastSeqno = *mLastSeqno + 1; // As we deal with the audio gap - return the silence and increase last seqno
result = FetchResult::Gap;
// ToDo: here we should decide smth - 2-packet gap shoud report Status::Gap two times at least; but current implementation gives only one.
// It is not big problem - as gap is detected when we have smth to return usually
mLastSeqno = seqno;
mLastReceiveTime = packet.rtp()->GetReceiveTime();
result = {FetchResult::Status::Gap};
}
else
{
result = FetchResult::RegularPacket;
rl.push_back(mPacketList.front());
result = {FetchResult::Status::RegularPacket, mPacketList.front()};
// Save last returned normal packet
mFetchedPacket = mPacketList.front();
mLastSeqno = mPacketList.front()->rtp()->GetExtendedSequenceNumber();
mFetchedPacket = result.mPacket;
mLastSeqno = result.mPacket->rtp()->GetExtendedSequenceNumber();
mLastReceiveTime = result.mPacket->rtp()->GetReceiveTime();
// Remove returned packet from the list
mPacketList.erase(mPacketList.begin());
@@ -277,14 +283,12 @@ RtpBuffer::FetchResult RtpBuffer::fetch(ResultList& rl)
if (findTimelength() >= mPrebuffer && !mPacketList.empty())
{
// Normal packet will be returned
result = FetchResult::RegularPacket;
// Put it to output list
rl.push_back(mPacketList.front());
result = {FetchResult::Status::RegularPacket, mPacketList.front()};
// Remember returned packet
mFetchedPacket = mPacketList.front();
mLastSeqno = mPacketList.front()->rtp()->GetExtendedSequenceNumber();
mFetchedPacket = result.mPacket;
mLastSeqno = result.mPacket->rtp()->GetExtendedSequenceNumber();
mLastReceiveTime = result.mPacket->rtp()->GetReceiveTime();
// Remove returned packet from buffer list
mPacketList.erase(mPacketList.begin());
@@ -292,12 +296,12 @@ RtpBuffer::FetchResult RtpBuffer::fetch(ResultList& rl)
else
{
ICELogMedia(<< "Jitter buffer was not prebuffered yet; resulting no packet");
result = FetchResult::NoPacket;
result = {FetchResult::Status::NoPacket};
}
}
}
if (result != FetchResult::NoPacket)
if (result.mStatus != FetchResult::Status::NoPacket)
mReturnedCounter++;
return result;
@@ -333,8 +337,7 @@ Receiver::~Receiver()
//-------------- AudioReceiver ----------------
AudioReceiver::AudioReceiver(const CodecList::Settings& settings, MT::Statistics &stat)
:Receiver(stat), mBuffer(stat), mCodecSettings(settings),
mCodecList(settings)
:Receiver(stat), mBuffer(stat), mDtmfBuffer(stat), mCodecSettings(settings), mCodecList(settings), mDtmfReceiver(stat)
{
// Init resamplers
mResampler8.start(AUDIO_CHANNELS, 8000, AUDIO_SAMPLERATE);
@@ -346,6 +349,12 @@ AudioReceiver::AudioReceiver(const CodecList::Settings& settings, MT::Statistics
mCodecList.setSettings(settings);
mCodecList.fillCodecMap(mCodecMap);
mAvailable.setCapacity(AUDIO_SAMPLERATE * sizeof(short));
mDtmfBuffer.setPrebuffer(0ms);
mDtmfBuffer.setLow(0ms);
mDtmfBuffer.setHigh(1ms);
#if defined(DUMP_DECODED)
mDecodedDump = std::make_shared<Audio::WavFileWriter>();
mDecodedDump->open("decoded.wav", 8000 /*G711*/, AUDIO_CHANNELS);
@@ -401,12 +410,10 @@ size_t decode_packet(Codec& codec, RTPPacket& p, void* output_buffer, size_t out
for (int i=0; i < frame_count; i++)
{
auto decoded_length = codec.decode(p.GetPayloadData() + i * codec.rtpLength(),
frame_length,
output_buffer,
output_capacity);
auto r = codec.decode({p.GetPayloadData() + i * codec.rtpLength(), (size_t)frame_length},
{(uint8_t*)output_buffer, output_capacity});
result += decoded_length;
result += r.mDecoded;
}
}
else
@@ -428,66 +435,72 @@ bool AudioReceiver::add(const std::shared_ptr<jrtplib::RTPPacket>& p, Codec** de
// Increase codec counter
mStat.mCodecCount[ptype]++;
// Check if codec can be handled
Codec* codec = nullptr;
auto codecIter = mCodecMap.find(ptype);
if (codecIter == mCodecMap.end())
// Check if we deal with telephone-event
if (p->GetPayloadType() == mCodecSettings.mTelephoneEvent)
{
// Well, there is no information about the codec; skip this packet
*detectedCodec = nullptr;
mDtmfBuffer.add(p, 10ms, 8000);
}
else
{
// Check if codec is creating lazily
if (!codecIter->second)
// Look for codec
// Check if codec can be handled
Codec* codec = nullptr;
auto codecIter = mCodecMap.find(ptype);
if (codecIter != mCodecMap.end())
{
codecIter->second = mCodecList.createCodecByPayloadType(ptype);
// Check if codec is creating lazily
if (!codecIter->second)
{
codecIter->second = mCodecList.createCodecByPayloadType(ptype);
}
codec = codecIter->second.get();
// Return pointer to codec if needed.get()
if (detectedCodec)
*detectedCodec = codec;
if (mStat.mCodecName.empty() && codec)
mStat.mCodecName = codec->name();
if (!codec)
time_length = 10;
else
if (!codec->rtpLength())
time_length = codec->frameTime();
else
time_length = lround(double(payloadLength) / codec->rtpLength() * codec->frameTime());
if (codec)
samplerate = codec->samplerate();
}
codec = codecIter->second.get();
// Return pointer to codec if needed.get()
if (detectedCodec)
*detectedCodec = codec;
if (mStat.mCodecName.empty() && codec)
mStat.mCodecName = codec->name();
// Process jitter anyway - can we decode payload or not
mJitterStats.process(p.get(), samplerate);
mStat.mJitter = static_cast<float>(mJitterStats.get());
if (!codec)
time_length = 10;
return false; // There is no sense to add this packet into jitter buffer - we can't decode this
// Check if packet is CNG
if (payloadLength >= 1 && payloadLength <= 6 && (ptype == 0 || ptype == 8))
time_length = mLastPacketTimeLength ? mLastPacketTimeLength : 20;
else
if (!codec->rtpLength())
time_length = codec->frameTime();
else
time_length = lround(double(payloadLength) / codec->rtpLength() * codec->frameTime());
// Check if packet is too short from time length side
if (time_length < 2)
{
// It will cause statistics to report about bad RTP packet
// I have to replay last packet payload here to avoid report about lost packet
mBuffer.add(p, std::chrono::milliseconds(time_length), samplerate);
return false;
}
if (codec)
samplerate = codec->samplerate();
// Queue packet to buffer
auto packet = mBuffer.add(p, std::chrono::milliseconds(time_length), samplerate).get();
return packet;
}
// Process jitter
mJitterStats.process(p.get(), samplerate);
mStat.mJitter = static_cast<float>(mJitterStats.get());
if (!codec)
return false; // There is no sense to add this packet into jitter buffer - we can't decode this
// Check if packet is CNG
if (payloadLength >= 1 && payloadLength <= 6 && (ptype == 0 || ptype == 8))
time_length = mLastPacketTimeLength ? mLastPacketTimeLength : 20;
else
// Check if packet is too short from time length side
if (time_length < 2)
{
// It will cause statistics to report about bad RTP packet
// I have to replay last packet payload here to avoid report about lost packet
mBuffer.add(p, std::chrono::milliseconds(time_length), samplerate);
return false;
}
// Queue packet to buffer
auto packet = mBuffer.add(p, std::chrono::milliseconds(time_length), samplerate).get();
return packet;
return {};
}
void AudioReceiver::processDecoded(Audio::DataWindow& output, DecodeOptions options)
@@ -553,17 +566,21 @@ void AudioReceiver::produceCNG(std::chrono::milliseconds length, Audio::DataWind
}
}
AudioReceiver::DecodeResult AudioReceiver::decodeGap(Audio::DataWindow& output, DecodeOptions options)
AudioReceiver::DecodeResult AudioReceiver::decodeGapTo(Audio::DataWindow& output, DecodeOptions options)
{
ICELogDebug(<< "Gap detected.");
mDecodedLength = mResampledLength = 0;
if (mCngPacket && mCodec)
{
// Synthesize comfort noise. It will be done on AUDIO_SAMPLERATE rate directly to mResampledFrame buffer.
// Do not forget to send this noise to analysis
mDecodedLength = mCngDecoder.produce(mCodec->samplerate(), mLastPacketTimeLength,
reinterpret_cast<short*>(mDecodedFrame), false);
if (mCngPacket->rtp()->GetPayloadType() == 13)
{
// Synthesize comfort noise. It will be done on AUDIO_SAMPLERATE rate directly to mResampledFrame buffer.
// Do not forget to send this noise to analysis
mDecodedLength = mCngDecoder.produce(mCodec->samplerate(), mLastPacketTimeLength, reinterpret_cast<short*>(mDecodedFrame), false);
}
else
decodePacketTo(output, options, mCngPacket);
}
else
if (mCodec && mFrameCount && !mCodecSettings.mSkipDecode)
@@ -573,7 +590,7 @@ AudioReceiver::DecodeResult AudioReceiver::decodeGap(Audio::DataWindow& output,
mDecodedLength = 0;
else
{
mDecodedLength = mCodec->plc(mFrameCount, mDecodedFrame, sizeof mDecodedFrame);
mDecodedLength = mCodec->plc(mFrameCount, {(uint8_t*)mDecodedFrame, sizeof mDecodedFrame});
if (!mDecodedLength)
{
// PLC is not support or failed
@@ -588,158 +605,258 @@ AudioReceiver::DecodeResult AudioReceiver::decodeGap(Audio::DataWindow& output,
if (mDecodedLength)
{
processDecoded(output, options);
return DecodeResult_Ok;
return {.mStatus = DecodeResult::Status::Ok, .mSamplerate = mCodec->samplerate(), .mChannels = mCodec->channels()};
}
else
return DecodeResult_Skip;
return {.mStatus = DecodeResult::Status::Skip};
}
AudioReceiver::DecodeResult AudioReceiver::decodePacket(const RtpBuffer::ResultList& rl, Audio::DataWindow& output, DecodeOptions options, int* rate)
AudioReceiver::DecodeResult AudioReceiver::decodePacketTo(Audio::DataWindow& output, DecodeOptions options, const std::shared_ptr<RtpBuffer::Packet>& packet)
{
DecodeResult result = DecodeResult_Skip;
if (!packet || !packet->rtp())
return {DecodeResult::Status::Skip};
DecodeResult result = {.mStatus = DecodeResult::Status::Skip};
auto& rtp = *packet->rtp(); // Syntax sugar
mFailedCount = 0;
for (const std::shared_ptr<RtpBuffer::Packet>& p: rl)
// Check if we need to emit silence or CNG - previously CNG packet was detected. Emit CNG audio here if needed.
if (mLastPacketTimestamp && mLastPacketTimeLength && mCodec)
{
assert(p);
// Check if we need to emit silence or CNG - previously CNG packet was detected. Emit CNG audio here if needed.
if (mLastPacketTimestamp && mLastPacketTimeLength && mCodec)
int units = rtp.GetTimestamp() - *mLastPacketTimestamp;
int milliseconds = units / (mCodec->samplerate() / 1000);
if (milliseconds > mLastPacketTimeLength)
{
int units = p->rtp()->GetTimestamp() - *mLastPacketTimestamp;
int milliseconds = units / (mCodec->samplerate() / 1000);
if (milliseconds > mLastPacketTimeLength)
{
auto silenceLength = std::chrono::milliseconds(milliseconds - mLastPacketTimeLength);
auto silenceLength = std::chrono::milliseconds(milliseconds - mLastPacketTimeLength);
if (mCngPacket && options.mFillGapByCNG)
produceCNG(silenceLength, output, options);
else
produceSilence(silenceLength, output, options);
}
if (mCngPacket && options.mFillGapByCNG)
produceCNG(silenceLength, output, options);
else
produceSilence(silenceLength, output, options);
}
}
mLastPacketTimestamp = p->rtp()->GetTimestamp();
mLastPacketTimestamp = rtp.GetTimestamp();
// Find codec by payload type
int ptype = p->rtp()->GetPayloadType();
// Find codec by payload type
int ptype = rtp.GetPayloadType();
// Look into mCodecMap if exists
auto codecIter = mCodecMap.find(ptype);
if (codecIter == mCodecMap.end())
return {};
// Look into mCodecMap if exists
auto codecIter = mCodecMap.find(ptype);
if (codecIter == mCodecMap.end())
return {};
if (!codecIter->second)
codecIter->second = mCodecList.createCodecByPayloadType(ptype);
if (!codecIter->second)
codecIter->second = mCodecList.createCodecByPayloadType(ptype);
mCodec = codecIter->second;
if (mCodec)
{
result.mChannels = mCodec->channels();
result.mSamplerate = mCodec->samplerate();
mCodec = codecIter->second;
if (mCodec)
// Check if it is CNG packet
if (((ptype == 0 || ptype == 8) && rtp.GetPayloadLength() >= 1 && rtp.GetPayloadLength() <= 6) || rtp.GetPayloadType() == 13)
{
if (rate)
*rate = mCodec->samplerate();
// Check if it is CNG packet
if ((ptype == 0 || ptype == 8) && p->rtp()->GetPayloadLength() >= 1 && p->rtp()->GetPayloadLength() <= 6)
if (options.mSkipDecode)
mDecodedLength = 0;
else
{
if (options.mSkipDecode)
mDecodedLength = 0;
else
{
mCngPacket = p->rtp();
mCngDecoder.decode3389(p->rtp()->GetPayloadData(), p->rtp()->GetPayloadLength());
mCngPacket = packet;
mCngDecoder.decode3389(rtp.GetPayloadData(), rtp.GetPayloadLength());
// Emit CNG mLastPacketLength milliseconds
mDecodedLength = mCngDecoder.produce(mCodec->samplerate(), mLastPacketTimeLength,
(short*)mDecodedFrame, true);
if (mDecodedLength)
processDecoded(output, options);
// Emit CNG mLastPacketLength milliseconds
mDecodedLength = mCngDecoder.produce(mCodec->samplerate(), mLastPacketTimeLength, (short*)mDecodedFrame, true);
if (mDecodedLength)
processDecoded(output, options);
}
result.mStatus = DecodeResult::Status::Ok;
}
else
{
// Reset CNG packet as we get regular RTP packet
mCngPacket.reset();
// Handle here regular RTP packets
// Check if payload length is ok
size_t payload_length = rtp.GetPayloadLength();
size_t rtp_frame_length = mCodec->rtpLength();
int tail = rtp_frame_length ? payload_length % rtp_frame_length : 0;
if (!tail)
{
// Find number of frames
mFrameCount = mCodec->rtpLength() ? rtp.GetPayloadLength() / mCodec->rtpLength() : 1;
int frameLength = mCodec->rtpLength() ? mCodec->rtpLength() : (int)rtp.GetPayloadLength();
// Save last packet time length
mLastPacketTimeLength = mFrameCount * mCodec->frameTime();
// Decode
for (int i=0; i<mFrameCount && !mCodecSettings.mSkipDecode; i++)
{
if (options.mSkipDecode)
mDecodedLength = 0;
else
{
// Decode frame by frame
auto codecInput = std::span{rtp.GetPayloadData() + i * mCodec->rtpLength(), (size_t)frameLength};
auto codecOutput = std::span{(uint8_t*)mDecodedFrame, sizeof mDecodedFrame};
auto r = mCodec->decode(codecInput, codecOutput);
mDecodedLength = r.mDecoded;
if (mDecodedLength > 0)
processDecoded(output, options);
// What is important - here we may have packet marked as CNG
if (r.mIsCng)
mCngPacket = packet;
}
}
result = DecodeResult_Ok;
result.mStatus = mFrameCount > 0 ? DecodeResult::Status::Ok : DecodeResult::Status::Skip;
// Check for bitrate counter
updateAmrCodecStats(mCodec.get());
}
else
{
// Reset CNG packet as we get regular RTP packet
mCngPacket.reset();
// Handle here regular RTP packets
// Check if payload length is ok
size_t payload_length = p->rtp()->GetPayloadLength();
size_t rtp_frame_length = mCodec->rtpLength();
int tail = rtp_frame_length ? payload_length % rtp_frame_length : 0;
if (!tail)
{
// Find number of frames
mFrameCount = mCodec->rtpLength() ? p->rtp()->GetPayloadLength() / mCodec->rtpLength() : 1;
int frameLength = mCodec->rtpLength() ? mCodec->rtpLength() : (int)p->rtp()->GetPayloadLength();
// Save last packet time length
mLastPacketTimeLength = mFrameCount * mCodec->frameTime();
// Decode
for (int i=0; i<mFrameCount && !mCodecSettings.mSkipDecode; i++)
{
if (options.mSkipDecode)
mDecodedLength = 0;
else
{
// Decode frame by frame
mDecodedLength = mCodec->decode(p->rtp()->GetPayloadData() + i * mCodec->rtpLength(),
frameLength, mDecodedFrame, sizeof mDecodedFrame);
if (mDecodedLength > 0)
processDecoded(output, options);
}
}
result = mFrameCount > 0 ? DecodeResult_Ok : DecodeResult_Skip;
// Check for bitrate counter
updateAmrCodecStats(mCodec.get());
}
else
{
// RTP packet with tail - it should not happen
result = DecodeResult_BadPacket;
}
// RTP packet with tail - it should not happen
result.mStatus = DecodeResult::Status::BadPacket;
}
}
}
return result;
}
AudioReceiver::DecodeResult AudioReceiver::decodeNone(Audio::DataWindow& output, DecodeOptions options)
AudioReceiver::DecodeResult AudioReceiver::decodeEmptyTo(Audio::DataWindow& output, DecodeOptions options)
{
// ICELogDebug(<< "No packet available in jitter buffer");
mFailedCount++;
return DecodeResult_Skip;
}
// There are two cases
// First is we have no ready time estimated how much audio should be emitted i.e. audio is decoded right after the next packet arrives.
// In this case we just skip the analysis - we should not be called in this situation
if (options.mElapsed == 0ms || !mCodec)
return {.mStatus = DecodeResult::Status::Skip};
AudioReceiver::DecodeResult AudioReceiver::getAudio(Audio::DataWindow& output, DecodeOptions options, int* rate)
{
DecodeResult result = DecodeResult_Skip;
// Get next packet from buffer
RtpBuffer::ResultList rl;
RtpBuffer::FetchResult fr = mBuffer.fetch(rl);
switch (fr)
// No packet available at all (and no previous CNG packet) - so return the silence
if (options.mElapsed != 0ms && mCodec)
{
case RtpBuffer::FetchResult::Gap: result = decodeGap(output, options); break;
case RtpBuffer::FetchResult::NoPacket: result = decodeNone(output, options); break;
case RtpBuffer::FetchResult::RegularPacket: result = decodePacket(rl, output, options, rate); break;
default:
assert(0);
Audio::Format fmt = options.mResampleToMainRate ? Audio::Format(AUDIO_SAMPLERATE, 1) : mCodec->getAudioFormat();
if (mCngPacket)
{
// Try to decode it - replay previous audio decoded or use CNG decoder (if payload type is 13)
if (mCngPacket->rtp()->GetPayloadType() == 13)
{
// Using latest CNG packet to produce comfort noise
auto produced = mCngDecoder.produce(fmt.rate(), options.mElapsed.count(), (short*)(output.data() + output.filled()), false);
output.setFilled(output.filled() + produced);
return {.mStatus = DecodeResult::Status::Ok, .mSamplerate = fmt.rate(), .mChannels = fmt.channels()};
}
else
{
// Here we have another packet marked as CNG - for another decoder
// Just decode it +1 time
return decodePacketTo(output, options, mCngPacket);
}
}
else
{
// Emit silence if codec information is available - it is to properly handle the gaps
auto avail = output.getTimeLength(fmt.rate(), fmt.channels());
if (options.mElapsed > avail)
output.addZero(fmt.sizeFromTime(options.mElapsed - avail));
}
}
if (result == DecodeResult_Ok)
mFailedCount++;
return {.mStatus = DecodeResult::Status::Skip};
}
AudioReceiver::DecodeResult AudioReceiver::getAudioTo(Audio::DataWindow& output, DecodeOptions options)
{
DecodeResult result = {.mStatus = DecodeResult::Status::Skip};
// Process RFC2833 here; it doesn't result in any audio - only callbacks and statistics
auto fr = mDtmfBuffer.fetch();
if (fr.mPacket && fr.mStatus == RtpBuffer::FetchResult::Status::RegularPacket)
mDtmfReceiver.add(fr.mPacket->rtp());
auto produced = 0ms;
if (mAvailable.filled() && mCodec && options.mElapsed != 0ms)
{
Audio::Format fmt = options.mResampleToMainRate ? Audio::Format(AUDIO_SAMPLERATE, 1) : mCodec->getAudioFormat();
auto initiallyAvailable = mCodec ? mAvailable.getTimeLength(fmt.rate(), fmt.channels()) : 0ms;
if (initiallyAvailable != 0ms)
{
std::chrono::milliseconds resultTime = std::min(initiallyAvailable, options.mElapsed);
auto resultLen = fmt.sizeFromTime(resultTime);
mAvailable.moveTo(output, resultLen);
produced += resultTime;
// Maybe request is satisfied ?
if (produced >= options.mElapsed)
return {.mStatus = DecodeResult::Status::Ok, .mSamplerate = fmt.rate(), .mChannels = fmt.channels()};
}
}
std::chrono::milliseconds decoded = 0ms;
do
{
// Get next packet from buffer
RtpBuffer::ResultList rl;
RtpBuffer::FetchResult fr = mBuffer.fetch();
// ICELogDebug(<< fr.toString() << " " << mBuffer.findTimelength());
switch (fr.mStatus)
{
case RtpBuffer::FetchResult::Status::Gap: result = decodeGapTo(mAvailable, options); break;
case RtpBuffer::FetchResult::Status::NoPacket: result = decodeEmptyTo(mAvailable, options); break;
case RtpBuffer::FetchResult::Status::RegularPacket: result = decodePacketTo(mAvailable, options, fr.mPacket); break;
default:
assert(0);
}
// Was there decoding at all ?
if (!mCodec)
break; // No sense to continue - we have no information at all
Audio::Format fmt = options.mResampleToMainRate ? Audio::Format(AUDIO_SAMPLERATE, 1) : mCodec->getAudioFormat();
result.mSamplerate = fmt.rate();
result.mChannels = fmt.channels();
// Have we anything interesting in the buffer ?
auto bufferAvailable = mAvailable.getTimeLength(fmt.rate(), fmt.channels());
if (bufferAvailable == 0ms)
break; // No sense to continue - decoding / CNG / PLC stopped totally
// How much data should be moved to result buffer ?
if (options.mElapsed != 0ms)
{
std::chrono::milliseconds resultTime = std::min(bufferAvailable, options.mElapsed - produced);
auto resultLen = fmt.sizeFromTime(resultTime);
mAvailable.moveTo(output, resultLen);
produced += resultTime;
}
else
mAvailable.moveTo(output, mAvailable.filled());
decoded += bufferAvailable;
}
while (produced < options.mElapsed);
if (produced != 0ms)
result.mStatus = DecodeResult::Status::Ok;
// Time statistics
if (result.mStatus == DecodeResult::Status::Ok)
{
// Decode statistics
if (!mLastDecodeTimestamp)
mLastDecodeTimestamp = std::chrono::steady_clock::now();
if (!mDecodeTimestamp)
mDecodeTimestamp = std::chrono::steady_clock::now();
else
{
auto t = std::chrono::steady_clock::now();
mStat.mDecodingInterval.process(std::chrono::duration_cast<std::chrono::milliseconds>(t - *mLastDecodeTimestamp).count());
mLastDecodeTimestamp = t;
mStat.mDecodingInterval.process(std::chrono::duration_cast<std::chrono::milliseconds>(t - *mDecodeTimestamp).count());
mDecodeTimestamp = t;
}
}
return result;
@@ -795,21 +912,26 @@ void AudioReceiver::updateAmrCodecStats(Codec* c)
AmrWbCodec* wb = dynamic_cast<AmrWbCodec*>(c);
if (nb != nullptr)
{
mStat.mBitrateSwitchCounter = nb->getSwitchCounter();
mStat.mCng = nb->getCngCounter();
}
else
if (wb != nullptr)
{
mStat.mBitrateSwitchCounter = wb->getSwitchCounter();
mStat.mCng = wb->getCngCounter();
}
#endif
}
int AudioReceiver::getSize() const
{
int result = 0;
result += sizeof(*this) + mResampler8.getSize() + mResampler16.getSize() + mResampler32.getSize()
+ mResampler48.getSize();
result += sizeof(*this) + mResampler8.getSize() + mResampler16.getSize() + mResampler32.getSize() + mResampler48.getSize();
if (mCodec)
result += mCodec->getSize();
; // ToDo: need the way to calculate size of codec instances
return result;
}
@@ -860,5 +982,24 @@ DtmfReceiver::DtmfReceiver(Statistics& stat)
DtmfReceiver::~DtmfReceiver()
{}
void DtmfReceiver::add(std::shared_ptr<RTPPacket> /*p*/)
{}
void DtmfReceiver::add(const std::shared_ptr<RTPPacket>& p)
{
auto ev = DtmfBuilder::parseRfc2833({p->GetPayloadData(), p->GetPayloadLength()});
if (ev.mTone != mEvent || ev.mEnd != mEventEnded)
{
if (!(mEvent == ev.mTone && !mEventEnded && ev.mEnd))
{
// New tone is here
if (mCallback)
mCallback(ev.mTone);
// Queue statistics item
mStat.mDtmf2833Timeline.emplace_back(Dtmf2833Event{.mTone = ev.mTone,
.mTimestamp = RtpHelper::toMicroseconds(p->GetReceiveTime())});
// Store to avoid triggering on the packet
mEvent = ev.mTone;
mEventEnded = ev.mEnd;
}
}
}

View File

@@ -28,13 +28,6 @@ using jrtplib::RTPPacket;
class RtpBuffer
{
public:
enum class FetchResult
{
RegularPacket,
Gap,
NoPacket
};
// Owns rtp packet data
class Packet
{
@@ -59,6 +52,29 @@ public:
std::chrono::microseconds mTimestamp = 0us;
};
struct FetchResult
{
enum class Status
{
RegularPacket,
Gap,
NoPacket
};
Status mStatus = Status::NoPacket;
std::shared_ptr<Packet> mPacket;
std::string toString() const
{
switch (mStatus)
{
case Status::RegularPacket: return "packet";
case Status::Gap: return "gap";
case Status::NoPacket: return "empty";
}
}
};
RtpBuffer(Statistics& stat);
~RtpBuffer();
@@ -81,12 +97,12 @@ public:
int getCount() const;
// Returns false if packet was not add - maybe too old or too new or duplicate
std::shared_ptr<Packet> add(std::shared_ptr<RTPPacket> packet, std::chrono::milliseconds timelength, int rate);
std::shared_ptr<Packet> add(const std::shared_ptr<RTPPacket>& packet, std::chrono::milliseconds timelength, int rate);
typedef std::vector<std::shared_ptr<Packet>> ResultList;
typedef std::shared_ptr<ResultList> PResultList;
FetchResult fetch(ResultList& rl);
FetchResult fetch();
protected:
unsigned mSsrc = 0;
@@ -104,6 +120,7 @@ protected:
jrtplib::RTPSourceStats mRtpStats;
std::shared_ptr<Packet> mFetchedPacket;
std::optional<uint32_t> mLastSeqno;
std::optional<jrtplib::RTPTime> mLastReceiveTime;
// To calculate average interval between packet add. It is close to jitter but more useful in debugging.
float mLastAddTime = 0.0f;
@@ -119,6 +136,23 @@ protected:
Statistics& mStat;
};
class DtmfReceiver: public Receiver
{
private:
char mEvent = 0;
bool mEventEnded = false;
std::chrono::milliseconds mEventStart = 0ms;
std::function<void(char)> mCallback;
public:
DtmfReceiver(Statistics& stat);
~DtmfReceiver();
void add(const std::shared_ptr<RTPPacket>& p);
void setCallback(std::function<void(char tone)> callback);
};
class AudioReceiver: public Receiver
{
public:
@@ -133,34 +167,33 @@ public:
// Lifetime of pointer to codec is limited by lifetime of AudioReceiver (it is container).
bool add(const std::shared_ptr<jrtplib::RTPPacket>& p, Codec** codec = nullptr);
// Returns false when there is no rtp data from jitter
/*enum DecodeOptions
{
DecodeOptions_ResampleToMainRate = 0,
DecodeOptions_DontResample = 1,
DecodeOptions_FillCngGap = 2,
DecodeOptions_SkipDecode = 4
};*/
struct DecodeOptions
{
bool mResampleToMainRate = true;
bool mFillGapByCNG = false;
bool mSkipDecode = false;
bool mResampleToMainRate = true; // Resample all decoded audio to AUDIO_SAMPLERATE
bool mFillGapByCNG = false; // Use CNG information if available
bool mSkipDecode = false; // Don't do decode, just dry run - fetch packets, remove them from the jitter buffer
std::chrono::milliseconds mElapsed = 0ms; // How much milliseconds should be decoded; zero value means "decode just next packet from the buffer"
};
enum DecodeResult
struct DecodeResult
{
DecodeResult_Ok, // Decoded ok
DecodeResult_Skip, // Just no data - emit silence instead
DecodeResult_BadPacket // Error happened during the decode
enum class Status
{
Ok, // Decoded ok
Skip, // Just no data - emit silence instead
BadPacket // Error happened during the decode
};
Status mStatus = Status::Ok;
int mSamplerate = 0;
int mChannels = 0;
};
DecodeResult getAudio(Audio::DataWindow& output, DecodeOptions options = {.mResampleToMainRate = true, .mFillGapByCNG = false, .mSkipDecode = false}, int* rate = nullptr);
DecodeResult getAudioTo(Audio::DataWindow& output, DecodeOptions options);
// Looks for codec by payload type
Codec* findCodec(int payloadType);
RtpBuffer& getRtpBuffer() { return mBuffer; }
RtpBuffer& getRtpBuffer() { return mBuffer; }
// Returns size of AudioReceiver's instance in bytes (including size of all data + codecs + etc.)
int getSize() const;
@@ -173,17 +206,23 @@ public:
protected:
RtpBuffer mBuffer; // Jitter buffer itself
RtpBuffer mDtmfBuffer; // These two (mDtmfBuffer / mDtmfReceiver) are for our analyzer stack only; in normal softphone logic DTMF packets goes via SingleAudioStream::mDtmfReceiver
DtmfReceiver mDtmfReceiver;
CodecMap mCodecMap;
PCodec mCodec;
int mFrameCount = 0;
CodecList::Settings mCodecSettings;
CodecList mCodecList;
JitterStatistics mJitterStats;
std::shared_ptr<jrtplib::RTPPacket> mCngPacket;
std::shared_ptr<RtpBuffer::Packet> mCngPacket;
CngDecoder mCngDecoder;
size_t mDTXSamplesToEmit = 0; // How much silence (or CNG) should be emited before next RTP packet gets into the action
// Buffer to hold decoded data
// Already decoded data that can be retrieved without actual decoding - it may happen because of getAudioTo() may be limited by time interval
Audio::DataWindow mAvailable;
// Temporary buffer to hold decoded data (it is better than allocate data on stack)
int16_t mDecodedFrame[MT_MAX_DECODEBUFFER];
size_t mDecodedLength = 0;
@@ -200,11 +239,14 @@ protected:
std::optional<uint32_t> mLastPacketTimestamp;
int mFailedCount = 0;
Audio::Resampler mResampler8, mResampler16, mResampler32, mResampler48;
Audio::Resampler mResampler8,
mResampler16,
mResampler32,
mResampler48;
Audio::PWavFileWriter mDecodedDump;
std::optional<std::chrono::steady_clock::time_point> mLastDecodeTimestamp; // Time last call happened to codec->decode()
std::optional<std::chrono::steady_clock::time_point> mDecodeTimestamp; // Time last call happened to codec->decode()
float mIntervalSum = 0.0f;
int mIntervalCount = 0;
@@ -220,19 +262,11 @@ protected:
// Calculate bitrate switch statistics for AMR codecs
void updateAmrCodecStats(Codec* c);
DecodeResult decodeGap(Audio::DataWindow& output, DecodeOptions options);
DecodeResult decodePacket(const RtpBuffer::ResultList& rl, Audio::DataWindow& output, DecodeOptions options, int* rate = nullptr);
DecodeResult decodeNone(Audio::DataWindow& output, DecodeOptions options);
DecodeResult decodeGapTo(Audio::DataWindow& output, DecodeOptions options);
DecodeResult decodePacketTo(Audio::DataWindow& output, DecodeOptions options, const std::shared_ptr<RtpBuffer::Packet>& p);
DecodeResult decodeEmptyTo(Audio::DataWindow& output, DecodeOptions options);
};
class DtmfReceiver: public Receiver
{
public:
DtmfReceiver(Statistics& stat);
~DtmfReceiver();
void add(std::shared_ptr<RTPPacket> p);
};
}
#endif

View File

@@ -210,18 +210,17 @@ void AudioStream::addData(const void* buffer, int bytes)
if (mSendingDump)
mSendingDump->write((const char*)mCapturedAudio.data() + codec->pcmLength() * i, codec->pcmLength());
int produced;
produced = codec->encode((const char*)mCapturedAudio.data() + codec->pcmLength()*i,
codec->pcmLength(), mFrameBuffer, MT_MAXAUDIOFRAME);
auto r = codec->encode({(const uint8_t*)mCapturedAudio.data() + codec->pcmLength()*i, (size_t)codec->pcmLength()},
{(uint8_t*)mFrameBuffer, MT_MAXAUDIOFRAME});
// Counter of processed input bytes of raw pcm data from microphone
processed += codec->pcmLength();
encodedTime += codec->frameTime();
mEncodedTime += codec->frameTime();
if (produced)
if (r.mEncoded)
{
mEncodedAudio.appendBuffer(mFrameBuffer, produced);
mEncodedAudio.appendBuffer(mFrameBuffer, r.mEncoded);
if (packetTime <= encodedTime)
{
// Time to send packet

View File

@@ -1,4 +1,4 @@
/* Copyright(C) 2007-2014 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,31 +9,31 @@ using namespace MT;
int Codec::Factory::channels()
{
return 1;
return 1;
}
void Codec::Factory::create(CodecMap& codecs)
{
codecs[payloadType()] = std::shared_ptr<Codec>(create());
codecs[payloadType()] = std::shared_ptr<Codec>(create());
}
void Codec::Factory::updateSdp(resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction)
{
codecs.push_back(resipCodec());
codecs.push_back(resipCodec());
}
resip::Codec Codec::Factory::resipCodec()
{
resip::Codec c(this->name(), this->payloadType(), this->samplerate());
return c;
resip::Codec c(this->name(), this->payloadType(), this->samplerate());
return c;
}
int Codec::Factory::processSdp(const resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction)
{
for (resip::SdpContents::Session::Medium::CodecContainer::const_iterator codecIter = codecs.begin(); codecIter != codecs.end(); ++codecIter)
{
if (resipCodec() == *codecIter)
return codecIter->payloadType();
}
return -1;
for (resip::SdpContents::Session::Medium::CodecContainer::const_iterator codecIter = codecs.begin(); codecIter != codecs.end(); ++codecIter)
{
if (resipCodec() == *codecIter)
return codecIter->payloadType();
}
return -1;
}

View File

@@ -1,4 +1,4 @@
/* Copyright(C) 2007-2016 VoIP objects (voipobjects.com)
/* Copyright(C) 2007-2026 VoIP objects (voipobjects.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
@@ -6,11 +6,12 @@
#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
{
@@ -18,12 +19,12 @@ class Codec;
typedef std::shared_ptr<Codec> PCodec;
class CodecMap: public std::map<int, PCodec>
{
};
{};
class Codec
{
public:
class Factory
{
public:
@@ -42,34 +43,51 @@ public:
resip::Codec resipCodec();
};
virtual ~Codec() {}
virtual const char* name() = 0;
virtual int samplerate() = 0;
virtual float timestampUnit() { return float(1.0 / samplerate()); }
// Size of decoded audio frame in bytes
virtual int pcmLength() = 0;
struct Info
{
std::string mName;
int mSamplerate = 0; // Hz
int mChannels = 0;
int mPcmLength = 0; // In bytes
int mFrameTime = 0; // In milliseconds
int mRtpLength = 0; // In bytes
float mTimestampUnit = 0.0f;
};
// Returns information about this codec instance
virtual Info info() = 0;
// Time length of single audio frame
virtual int frameTime() = 0;
// Size of RTP frame in bytes. Can be zero for variable sized codecs.
virtual int rtpLength() = 0;
// Number of audio channels
virtual int channels() { return 1; }
// Helper functions to return information - they are based on info() method
int pcmLength() { return info().mPcmLength; }
int rtpLength() { return info().mRtpLength; }
int channels() { return info().mChannels; }
int samplerate() { return info().mSamplerate; }
int frameTime() { return info().mFrameTime; }
std::string name() { return info().mName; }
float timestampUnit() { return info().mTimestampUnit == 0.0f ? 1.0f / info().mSamplerate : info().mTimestampUnit; }
Audio::Format getAudioFormat() {
return Audio::Format(this->info().mSamplerate, this->info().mChannels);
}
// Returns size of encoded data (RTP) in bytes
virtual int encode(const void* input, int inputBytes, void* output, int outputCapacity) = 0;
struct EncodeResult
{
size_t mEncoded = 0; // Number of encoded bytes
};
virtual EncodeResult encode(std::span<const uint8_t> input, std::span<uint8_t> output) = 0;
// Returns size of decoded data (PCM signed short) in bytes
virtual int decode(const void* input, int inputBytes, void* output, int outputCapacity) = 0;
struct DecodeResult
{
size_t mDecoded = 0; // Number of decoded bytes
bool mIsCng = false; // Should this packet to be used as CNG ? (used for AMR codecs)
};
virtual DecodeResult decode(std::span<const uint8_t> input, std::span<uint8_t> output) = 0;
// Returns size of produced data (PCM signed short) in bytes
virtual int plc(int lostFrames, void* output, int outputCapacity) = 0;
virtual size_t plc(int lostFrames, std::span<uint8_t> output) = 0;
// Returns size of codec in memory
virtual int getSize() const { return 0; };
};
}
#endif

View File

@@ -65,6 +65,8 @@ bool CodecList::Settings::contains(int ptype) const
if (mGsmEfrPayloadType == ptype || mGsmFrPayloadType == ptype || mGsmHrPayloadType == ptype)
return true;
if (mTelephoneEvent == ptype)
return true;
return false;
}
@@ -122,6 +124,9 @@ std::string CodecList::Settings::toString() const
if (mGsmEfrPayloadType != -1)
oss << "GSM EFR ptype: " << mGsmEfrPayloadType << " ";
if (mTelephoneEvent != -1)
oss << "RFC2833 DTMF ptype: " << mTelephoneEvent;
for (auto& spec: mEvsSpec)
{
oss << "EVS ptype: " << spec.mPayloadType << ", bw: " << spec.mBandwidth << ", enc: " << (spec.mEncodingType == EvsSpec::Encoding_MIME ? "mime" : "g192") << " ";
@@ -132,6 +137,7 @@ std::string CodecList::Settings::toString() const
oss << "OPUS ptype: " << spec.mPayloadType << ", rate: " << spec.mRate << ", channels: " << spec.mChannels << std::endl;
}
return oss.str();
}
@@ -151,6 +157,7 @@ void CodecList::Settings::clear()
mGsmEfrPayloadType = -1;
mGsmFrPayloadType = -1;
mGsmHrPayloadType = -1;
mTelephoneEvent = -1;
}
bool CodecList::Settings::EvsSpec::isValid() const
@@ -268,15 +275,16 @@ CodecList::Settings CodecList::Settings::parseSdp(const std::list<resip::Codec>&
}
} else if (codec_name == "EVS") {
r.mEvsSpec.push_back({ptype});
}
} else if (codec_name == "TELEPHONE-EVENT")
r.mTelephoneEvent = ptype;
}
return r;
}
bool CodecList::Settings::operator == (const Settings& rhs) const
{
if (std::tie(mWrapIuUP, mSkipDecode, mIsac16KPayloadType, mIsac32KPayloadType, mIlbc20PayloadType, mIlbc30PayloadType, mGsmFrPayloadType, mGsmFrPayloadLength, mGsmEfrPayloadType, mGsmHrPayloadType) !=
std::tie(rhs.mWrapIuUP, rhs.mSkipDecode, rhs.mIsac16KPayloadType, rhs.mIsac32KPayloadType, rhs.mIlbc20PayloadType, rhs.mIlbc30PayloadType, rhs.mGsmFrPayloadType, rhs.mGsmFrPayloadLength, rhs.mGsmEfrPayloadType, rhs.mGsmHrPayloadType))
if (std::tie(mWrapIuUP, mSkipDecode, mIsac16KPayloadType, mIsac32KPayloadType, mIlbc20PayloadType, mIlbc30PayloadType, mGsmFrPayloadType, mGsmFrPayloadLength, mGsmEfrPayloadType, mGsmHrPayloadType, mTelephoneEvent) !=
std::tie(rhs.mWrapIuUP, rhs.mSkipDecode, rhs.mIsac16KPayloadType, rhs.mIsac32KPayloadType, rhs.mIlbc20PayloadType, rhs.mIlbc30PayloadType, rhs.mGsmFrPayloadType, rhs.mGsmFrPayloadLength, rhs.mGsmEfrPayloadType, rhs.mGsmHrPayloadType, rhs.mTelephoneEvent))
return false;
if (mAmrNbOctetPayloadType != rhs.mAmrNbOctetPayloadType)
@@ -306,6 +314,9 @@ bool CodecList::Settings::operator == (const Settings& rhs) const
if (mOpusSpec[i] != rhs.mOpusSpec[i])
return false;
if (mTelephoneEvent != rhs.mTelephoneEvent)
return false;
return true;
}

View File

@@ -28,6 +28,9 @@ public:
bool mWrapIuUP = false;
bool mSkipDecode = false;
// RFC2833 DTMF
int mTelephoneEvent = -1;
// AMR payload types
std::set<int64_t> mAmrWbPayloadType = { };
std::set<int64_t> mAmrNbPayloadType = { };

View File

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

View File

@@ -15,18 +15,32 @@
namespace MT
{
class DtmfBuilder
{
public:
class DtmfBuilder
{
public:
struct Rfc2833Event
{
char mTone = 0;
int mDuration = 0;
int mVolume = 0;
bool mEnd = false;
bool isValid() const {
return mTone != 0;
}
};
// Output should be 4 bytes length
static void buildRfc2833(int tone, int duration, int volume, bool endOfEvent, void* output);
static void buildRfc2833(const Rfc2833Event& ev, void* output);
static Rfc2833Event parseRfc2833(std::span<uint8_t> payload);
// Buf receives PCM audio
static void buildInband(int tone, int startTime, int finishTime, int rate, short* buf);
};
};
struct Dtmf
{
struct Dtmf
{
Dtmf(): mTone(0), mDuration(0), mVolume(0), mFinishCount(3), mCurrentTime(0), mStopped(false) {}
Dtmf(int tone, int volume, int duration): mTone(tone), mVolume(volume), mDuration(duration), mFinishCount(3), mCurrentTime(0), mStopped(false) {}
@@ -36,17 +50,17 @@ namespace MT
int mVolume;
int mFinishCount;
int mCurrentTime;
};
};
typedef std::vector<Dtmf> DtmfQueue;
typedef std::vector<Dtmf> DtmfQueue;
class DtmfContext
{
public:
class DtmfContext
{
public:
enum Type
{
Dtmf_Inband,
Dtmf_Rfc2833
Dtmf_Inband,
Dtmf_Rfc2833
};
DtmfContext();
@@ -65,33 +79,34 @@ namespace MT
bool getInband(int milliseconds, int rate, ByteBuffer& output);
bool getRfc2833(int milliseconds, ByteBuffer& output, ByteBuffer& stopPacket);
protected:
protected:
Mutex mGuard;
Type mType;
DtmfQueue mQueue;
};
};
class DTMFDetector
class InbandDtmfDetector
{
public:
/*! The default constructor. Allocates space for detector context. */
DTMFDetector();
/*! The default constructor. Allocates space for detector context. */
InbandDtmfDetector();
/*! The destructor. Free the detector context's memory. */
~DTMFDetector();
/*! The destructor. Free the detector context's memory. */
~InbandDtmfDetector();
/*! This method receives the input PCM 16-bit data and returns found DTMF event(s) in string representation.
/*! This method receives the input PCM 16-bit data and returns found DTMF event(s) in string representation.
* @param samples Input PCM buffer pointer.
* @param size Size of input buffer in bytes
* @return Found DTMF event(s) in string representation. The returned value has variable length.
*/
std::string streamPut(unsigned char* samples, unsigned int size);
std::string streamPut(unsigned char* samples, unsigned int size);
void resetState();
void resetState();
protected:
void* mState; /// DTMF detector context
void* mState; /// DTMF detector context
};
}
#endif

View File

@@ -1,6 +1,6 @@
#include "MT_EvsCodec.h"
#include <set>
#include <map>
/*-------------------------------------------------------------------*
* rate2AMRWB_IOmode()
@@ -167,67 +167,58 @@ EVSCodec::~EVSCodec()
}
}
int EVSCodec::samplerate()
{
return st_dec->output_Fs;
Codec::Info EVSCodec::info() {
return {
.mName = MT_EVS_CODECNAME,
.mSamplerate = st_dec->output_Fs,
.mChannels = 1,
.mPcmLength = st_dec->output_Fs / 1000 * sp.ptime * 2,
.mFrameTime = sp.ptime,
.mRtpLength = 0
};
}
int EVSCodec::pcmLength()
{
return samplerate() / 50 * 2;
}
int EVSCodec::frameTime()
{
return sp.ptime;
}
int EVSCodec::rtpLength()
{
// Variable sized codec - bitrate can be changed during the call
return 0;
}
int EVSCodec::encode(const void* input, int inputBytes, void* output, int outputCapacity)
Codec::EncodeResult EVSCodec::encode(std::span<const uint8_t> input, std::span<uint8_t> output)
{
// Encoding is not supported yet.
return 0;
return {.mEncoded = 0};
}
int EVSCodec::decode(const void* input, int input_length, void* output, int outputCapacity)
Codec::DecodeResult EVSCodec::decode(std::span<const uint8_t> input, std::span<uint8_t> output)
{
if (outputCapacity < pcmLength())
return 0;
if (output.size_bytes() < pcmLength())
return {.mDecoded = 0};
std::string buffer;
// Check if we get payload with CMR
auto payload_iter = FixedPayload_EVSPrimary.find((input_length - 2) * 8);
auto payload_iter = FixedPayload_EVSPrimary.find((input.size_bytes() - 2) * 8);
if (payload_iter == FixedPayload_EVSPrimary.end())
{
// Check if we get payload with ToC and without CMR
payload_iter = FixedPayload_EVSPrimary.find((input_length - 1) * 8);
payload_iter = FixedPayload_EVSPrimary.find((input.size_bytes() - 1) * 8);
if (payload_iter == FixedPayload_EVSPrimary.end())
{
// Maybe there is no ToC ?
payload_iter = FixedPayload_EVSPrimary.find(input_length * 8);
payload_iter = FixedPayload_EVSPrimary.find(input.size_bytes() * 8);
if (payload_iter == FixedPayload_EVSPrimary.end())
{
// Bad payload size at all
return 0;
return {.mDecoded = 0};
}
/* Add ToC byte.
* WARNING maybe it will be work incorrect with 56bit payload,
* see 3GPP TS 26.445 Annex A, A.2.1.3 */
char c = evs::rate2EVSmode(FixedPayload_EVSPrimary.find(input_length * 8)->second);
char c = evs::rate2EVSmode(FixedPayload_EVSPrimary.find(input.size_bytes() * 8)->second);
buffer += c;
buffer += std::string(reinterpret_cast<const char*>(input), input_length);
buffer += std::string(reinterpret_cast<const char*>(input.data()), input.size_bytes());
}
else
buffer = std::string(reinterpret_cast<const char*>(input), input_length);
buffer = std::string(reinterpret_cast<const char*>(input.data()), input.size_bytes());
}
else // Skip CMR byte
buffer = std::string(reinterpret_cast<const char*>(input) + 1, input_length-1);
buffer = std::string(reinterpret_cast<const char*>(input.data()) + 1, input.size_bytes()-1);
// Output buffer for 48 KHz
@@ -263,7 +254,7 @@ int EVSCodec::decode(const void* input, int input_length, void* output, int outp
}
/* convert 'float' output data to 'short' */
evs::syn_output(data, static_cast<short>(pcmLength() / 2), static_cast<short*>(output) + offset);
evs::syn_output(data, static_cast<short>(pcmLength() / 2), reinterpret_cast<short*>(output.data()) + offset);
offset += pcmLength() / 2;
if (st_dec->ini_frame < MAX_FRAME_COUNTER)
{
@@ -271,12 +262,12 @@ int EVSCodec::decode(const void* input, int input_length, void* output, int outp
}
}
return pcmLength();
return {.mDecoded = (size_t)pcmLength()};
}
int EVSCodec::plc(int lostFrames, void* output, int outputCapacity)
size_t EVSCodec::plc(int lostFrames, std::span<uint8_t> output)
{
return 0;
return 0;
}
void EVSCodec::initDecoder(const StreamParameters& sp)

View File

@@ -2,15 +2,11 @@
#define __MT_EVS_CODEC_H
#include "../engine_config.h"
#include <set>
#include <map>
#include <memory>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <assert.h>
#include <sstream>
#include "MT_Codec.h"
@@ -52,18 +48,14 @@ public:
EVSCodec(const StreamParameters& sp);
~EVSCodec() override;
const char* name() override { return MT_EVS_CODECNAME; }
int samplerate() override;
int pcmLength() override;
int frameTime() override;
int rtpLength() override;
int encode(const void* input, int inputBytes, void* output, int outputCapacity) override;
int decode(const void* input, int inputBytes, void* output, int outputCapacity) override;
int plc(int lostFrames, void* output, int outputCapacity) override;
Info info() override;
EncodeResult encode(std::span<const uint8_t> input, std::span<uint8_t> output) override;
DecodeResult decode(std::span<const uint8_t> input, std::span<uint8_t> output) override;
size_t plc(int lostFrames, std::span<uint8_t> output) override;
private:
evs::Decoder_State* st_dec;
//Encoder_State_fx* st_enc;
evs::Decoder_State* st_dec = nullptr;
StreamParameters sp;
void initDecoder(const StreamParameters& sp);
};

View File

@@ -13,19 +13,17 @@
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)
{
ICELogMedia(<< "Processing incoming RTP/RTCP packet");
if (packet->GetPayloadType() == 101/*resip::Codec::TelephoneEvent.payloadType()*/)
if (packet->GetPayloadType() == mReceiver.getCodecSettings().mTelephoneEvent)
mDtmfReceiver.add(packet);
else
mReceiver.add(packet);
@@ -33,9 +31,10 @@ void SingleAudioStream::process(const std::shared_ptr<jrtplib::RTPPacket>& packe
void SingleAudioStream::copyPcmTo(Audio::DataWindow& output, int needed)
{
// Packet by packet
while (output.filled() < needed)
{
if (mReceiver.getAudio(output, {}) != AudioReceiver::DecodeResult_Ok)
if (mReceiver.getAudioTo(output, {}).mStatus != AudioReceiver::DecodeResult::Status::Ok)
break;
}

View File

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

View File

@@ -212,15 +212,13 @@ void SrtpSession::close()
SrtpKeySalt& SrtpSession::outgoingKey(SrtpSuite suite)
{
Lock l(mGuard);
assert(suite > SRTP_NONE && suite <= SRTP_LAST);
return mOutgoingKey[int(suite)-1];
Lock l(mGuard);
return mOutgoingKey[int(suite)-1]; // The automated review sometimes give the hints about the possible underflow array index access
}
bool SrtpSession::protectRtp(void* buffer, int* length)
{
// addSsrc(RtpHelper::findSsrc(buffer, *length), sdOutgoing);
Lock l(mGuard);
if (mOutboundSession)
{

View File

@@ -20,20 +20,17 @@
enum SrtpSuite
{
SRTP_NONE,
SRTP_AES_128_AUTH_80,
SRTP_AES_256_AUTH_80,
SRTP_AES_192_AUTH_80,
SRTP_AES_128_AUTH_32,
SRTP_AES_256_AUTH_32,
SRTP_AES_192_AUTH_32,
SRTP_AES_128_AUTH_NULL,
SRTP_AED_AES_256_GCM,
SRTP_AED_AES_128_GCM,
SRTP_NONE = 0,
SRTP_AES_128_AUTH_80 = 1,
SRTP_AES_256_AUTH_80 = 2,
SRTP_AES_192_AUTH_80 = 3,
SRTP_AES_128_AUTH_32 = 4,
SRTP_AES_256_AUTH_32 = 5,
SRTP_AES_192_AUTH_32 = 6,
SRTP_AES_128_AUTH_NULL = 7,
SRTP_AED_AES_256_GCM = 8,
SRTP_AED_AES_128_GCM = 9,
SRTP_LAST = SRTP_AED_AES_128_GCM
// ToDo:
// a=crypto:1 AEAD_AES_256_GCM_8 inline:tN2A0vRjFBimpQsW2GasuJuPe7hKE26gki30APC8DVuySqCOYTs8lYBPR5I=
// a=crypto:3 AEAD_AES_128_GCM_8 inline:Ok7VL8SmBHSbZLw4dK6iQgpliYKGdY9BHLJcRw==
};
extern SrtpSuite toSrtpSuite(const std::string_view& s);

View File

@@ -74,13 +74,16 @@ Statistics::~Statistics()
void Statistics::calculateBurstr(double* burstr, double* lossr) const
{
int lost = 0;
int bursts = 0;
for (int i = 0; i < 128; i++)
{
lost += i * mLoss[i];
bursts += mLoss[i];
}
int lost = 0; // Total packet lost
for (const auto& item: mPacketLossTimeline)
lost += item.mGap;
int bursts = mPacketLossTimeline.size(); // number of events
// for (const auto& entry: mLoss)
// {
// lost += entry.first * entry.second;
// bursts += entry.second;
// }
if (lost < 5)
{
@@ -109,14 +112,14 @@ void Statistics::calculateBurstr(double* burstr, double* lossr) const
double Statistics::calculateMos(double maximalMos) const
{
// calculate lossrate and burst rate
double burstr, lossr;
double burstr = 0, lossr = 0;
calculateBurstr(&burstr, &lossr);
double r;
double r = 0.0;
double bpl = 8.47627; //mos = -4.23836 + 0.29873 * r - 0.00416744 * r * r + 0.0000209855 * r * r * r;
double mos;
double mos = 0.0;
if (mReceivedRtp < 100)
if (mReceivedRtp < 10)
return 0.0;
if (lossr == 0.0 || burstr == 0.0)

View File

@@ -4,16 +4,15 @@
#include <chrono>
#include <map>
#include <optional>
#include <array>
#include "audio/Audio_DataWindow.h"
#include "helper/HL_Optional.hpp"
#include "helper/HL_Statistics.h"
#include "helper/HL_Types.h"
#include "helper/HL_InternetAddress.h"
#include "jrtplib/src/rtptimeutilities.h"
#include "jrtplib/src/rtppacket.h"
using namespace std::chrono_literals;
namespace MT
{
@@ -56,46 +55,55 @@ struct PacketLossEvent
uint32_t mStartSeqno = 0,
mEndSeqno = 0;
int mGap = 0;
std::chrono::microseconds mTimestampStart = 0us,
mTimestampEnd = 0us;
};
struct Dtmf2833Event
{
char mTone;
std::chrono::microseconds mTimestamp;
};
class Statistics
{
public:
size_t mReceived = 0, // Received traffic in bytes
mSent = 0, // Sent traffic in bytes
mReceivedRtp = 0, // Number of received rtp packets
mSentRtp = 0, // Number of sent rtp packets
mReceivedRtcp = 0, // Number of received rtcp packets
mSentRtcp = 0, // Number of sent rtcp packets
mDuplicatedRtp = 0, // Number of received duplicated rtp packets
mOldRtp = 0, // Number of late rtp packets
mPacketLoss = 0, // Number of lost packets
mPacketDropped = 0, // Number of dropped packets (due to time unsync when playing)б
mIllegalRtp = 0; // Number of rtp packets with bad payload type
size_t mReceived = 0, // Received traffic in bytes
mSent = 0, // Sent traffic in bytes
mReceivedRtp = 0, // Number of received rtp packets
mSentRtp = 0, // Number of sent rtp packets
mReceivedRtcp = 0, // Number of received rtcp packets
mSentRtcp = 0, // Number of sent rtcp packets
mDuplicatedRtp = 0, // Number of received duplicated rtp packets
mOldRtp = 0, // Number of late rtp packets
mPacketLoss = 0, // Number of lost packets
mPacketDropped = 0, // Number of dropped packets (due to time unsync when playing)б
mIllegalRtp = 0; // Number of rtp packets with bad payload type
TestResult<float> mDecodingInterval, // Average interval on call to packet decode
mDecodeRequested, // Average amount of requested audio frames to play
mPacketInterval; // Average interval between packet adding to jitter buffer
TestResult<float> mDecodingInterval, // Average interval on call to packet decode
mDecodeRequested, // Average amount of requested audio frames to play
mPacketInterval; // Average interval between packet adding to jitter buffer
std::array<float, 128> mLoss = {0}; // Every item is number of loss of corresping length
size_t mAudioTime = 0; // Decoded/found time in milliseconds
size_t mDecodedSize = 0; // Number of decoded bytes
uint16_t mSsrc = 0; // Last known SSRC ID in a RTP stream
ice::NetworkAddress mRemotePeer; // Last known remote RTP address
std::map<int,int> mLoss; // Every item is number of loss of corresping length
size_t mAudioTime = 0; // Decoded/found time in milliseconds
size_t mDecodedSize = 0; // Number of decoded bytes
uint16_t mSsrc = 0; // Last known SSRC ID in a RTP stream
ice::NetworkAddress mRemotePeer; // Last known remote RTP address
// AMR codec bitrate switch counter
int mBitrateSwitchCounter = 0;
std::string mCodecName;
float mJitter = 0.0f; // Jitter
TestResult<float> mRttDelay; // RTT delay
int mBitrateSwitchCounter = 0;
int mCng = 0;
std::string mCodecName;
float mJitter = 0.0f; // Jitter
TestResult<float> mRttDelay; // RTT delay
// Timestamp when first RTP packet has arrived
std::optional<timepoint_t> mFirstRtpTime;
std::optional<timepoint_t> mFirstRtpTime;
std::map<int, int> mCodecCount; // Stats on used codecs
std::map<int, int> mCodecCount; // Stats on used codecs
std::vector<PacketLossEvent> mPacketLossTimeline; // Packet loss timeline
std::vector<PacketLossEvent> mPacketLossTimeline; // Packet loss timeline
std::vector<Dtmf2833Event> mDtmf2833Timeline;
// It is to calculate network MOS
void calculateBurstr(double* burstr, double* loss) const;

View File

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

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

@@ -0,0 +1,291 @@
/* 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\n"
" amrnb amrwb amrnb-bwe amrwb-bwe 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 },
{ "amrnb-bwe", -1, true },
{ "amrwb-bwe", -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 == "amrnb-bwe") {
s.mAmrNbPayloadType.insert(pt);
} else if (codecName == "amrwb-bwe") {
s.mAmrWbPayloadType.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;
}