Merge branch 'dev'
This commit is contained in:
@@ -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')
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -16,6 +16,7 @@ const std::string Status_FailedToOpenFile = "failed to open file";
|
||||
const std::string Status_NoActiveProvider = "no active provider";
|
||||
const std::string Status_NoMediaAction = "no valid media action";
|
||||
const std::string Status_NoCommand = "no valid command";
|
||||
const std::string Status_NoAudioManager = "no audio manager";
|
||||
|
||||
#define LOG_SUBSYSTEM "Agent"
|
||||
|
||||
@@ -336,7 +337,7 @@ void AgentImpl::processStartSession(JsonCpp::Value& request, JsonCpp::Value& ans
|
||||
{
|
||||
// Agent was not started
|
||||
ICELogError(<< "No audio manager installed.");
|
||||
answer["status"] = "Audio manager not started. Most probably agent is not started.";
|
||||
answer["status"] = Status_NoAudioManager;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -430,6 +431,13 @@ void AgentImpl::processAcceptSession(JsonCpp::Value& request, JsonCpp::Value& an
|
||||
std::unique_lock<std::recursive_mutex> l(mAgentMutex);
|
||||
auto sessionIter = mSessionMap.find(request["session_id"].asInt());
|
||||
if (sessionIter != mSessionMap.end())
|
||||
{
|
||||
if (!mAudioManager)
|
||||
{
|
||||
ICELogError(<< "No audio manager installed.");
|
||||
answer["status"] = Status_NoAudioManager;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Ensure audio manager is here
|
||||
mAudioManager->start(mUseNativeAudio ? AudioManager::atReceiver : AudioManager::atNull);
|
||||
@@ -450,9 +458,9 @@ void AgentImpl::processAcceptSession(JsonCpp::Value& request, JsonCpp::Value& an
|
||||
|
||||
answer["status"] = Status_Ok;
|
||||
}
|
||||
}
|
||||
else
|
||||
answer["status"] = Status_SessionNotFound;
|
||||
|
||||
}
|
||||
|
||||
void AgentImpl::processDestroySession(JsonCpp::Value& request, JsonCpp::Value& answer)
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -11,9 +11,9 @@
|
||||
|
||||
namespace Audio
|
||||
{
|
||||
class DataWindow
|
||||
{
|
||||
public:
|
||||
class DataWindow
|
||||
{
|
||||
public:
|
||||
DataWindow();
|
||||
~DataWindow();
|
||||
|
||||
@@ -34,14 +34,17 @@ namespace Audio
|
||||
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
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -72,6 +72,7 @@ enum SessionInfo
|
||||
SessionInfo_BitrateSwitchCounter, // It is for AMR codecs only
|
||||
SessionInfo_RemotePeer,
|
||||
SessionInfo_SSRC,
|
||||
SessionInfo_CngCounter // For AMR codecs only
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -36,6 +36,9 @@ uint16_t update_crc10_by_bytes(uint16_t crc10, const uint8_t *data_blk_ptr, int
|
||||
|
||||
bool IuUP::parse2(const uint8_t* packet, int size, Frame& result)
|
||||
{
|
||||
if (size < 2)
|
||||
return false;
|
||||
|
||||
if (TwoBytePseudoheader)
|
||||
{
|
||||
packet += 2;
|
||||
@@ -88,7 +91,7 @@ bool IuUP::parse2(const uint8_t* packet, int size, Frame& result)
|
||||
|
||||
|
||||
static const unsigned char crc6_table[] =
|
||||
{
|
||||
{
|
||||
0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f,
|
||||
0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f,
|
||||
0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2a,0x2b,0x2c,0x2d,0x2e,0x2f,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright(C) 2007-2017 VoIP objects (voipobjects.com)
|
||||
/* Copyright(C) 2007-2026 VoIP objects (voipobjects.com)
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
#include "../engine_config.h"
|
||||
#include "HL_NetworkSocket.h"
|
||||
#include "HL_Log.h"
|
||||
|
||||
#if defined(TARGET_OSX) || defined(TARGET_LINUX)
|
||||
# include <fcntl.h>
|
||||
@@ -19,11 +20,11 @@
|
||||
#endif
|
||||
#include <assert.h>
|
||||
|
||||
#define LOG_SUBSYSTEM "network"
|
||||
|
||||
DatagramSocket::DatagramSocket()
|
||||
:mFamily(AF_INET), mHandle(INVALID_SOCKET), mLocalPort(0)
|
||||
{
|
||||
|
||||
}
|
||||
{}
|
||||
|
||||
DatagramSocket::~DatagramSocket()
|
||||
{
|
||||
@@ -160,6 +161,12 @@ void DatagramAgreggator::addSocket(PDatagramSocket socket)
|
||||
if (socket->mHandle == INVALID_SOCKET)
|
||||
return;
|
||||
|
||||
if (mSocketVector.size() >= 62)
|
||||
{
|
||||
ICELogError(<< "fd_set overflow; too much sockets");
|
||||
return;
|
||||
}
|
||||
|
||||
FD_SET(socket->mHandle, &mReadSet);
|
||||
if (socket->mHandle > mMaxHandle)
|
||||
mMaxHandle = socket->mHandle;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright(C) 2007-2017 VoIPobjects (voipobjects.com)
|
||||
/* Copyright(C) 2007-2026 VoIPobjects (voipobjects.com)
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
@@ -8,26 +8,29 @@
|
||||
# include <Windows.h>
|
||||
#endif
|
||||
|
||||
#if defined(TARGET_LINUX) || defined(TARGET_ANDROID)
|
||||
#if defined(TARGET_LINUX) || defined(TARGET_ANDROID) || defined(TARGET_OSX)
|
||||
# include <arpa/inet.h>
|
||||
#endif
|
||||
|
||||
#include "HL_Rtp.h"
|
||||
#include "HL_Exception.h"
|
||||
#include "HL_String.h"
|
||||
#include "HL_Log.h"
|
||||
|
||||
#if defined(USE_RTP_DUMP)
|
||||
# include "jrtplib/src/rtprawpacket.h"
|
||||
# include "jrtplib/src/rtpipv4address.h"
|
||||
#endif
|
||||
#include "jrtplib/src/rtprawpacket.h"
|
||||
#include "jrtplib/src/rtpipv4address.h"
|
||||
|
||||
#if !defined(TARGET_WIN)
|
||||
# include <alloca.h>
|
||||
#endif
|
||||
#include <stdexcept>
|
||||
#include <fstream>
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
#include <chrono>
|
||||
|
||||
#include <sstream>
|
||||
#include <tuple>
|
||||
#define LOG_SUBSYSTEM "RtpDump"
|
||||
|
||||
static constexpr size_t MAX_RTP_PACKET_SIZE = 65535;
|
||||
static const char RTPDUMP_SHEBANG[] = "#!rtpplay1.0";
|
||||
|
||||
// RTP fixed header (little-endian bit-field layout)
|
||||
struct RtpHeader
|
||||
{
|
||||
unsigned char cc:4; /* CSRC count */
|
||||
@@ -46,32 +49,54 @@ struct RtcpHeader
|
||||
unsigned char rc:5; /* reception report count */
|
||||
unsigned char p:1; /* padding flag */
|
||||
unsigned char version:2; /* protocol version */
|
||||
unsigned char pt:8; /* payload type */
|
||||
unsigned char pt; /* payload type */
|
||||
uint16_t len; /* length */
|
||||
uint32_t ssrc; /* synchronization source */
|
||||
};
|
||||
|
||||
// --- IPv4 address helpers ---
|
||||
|
||||
static std::string ipToString(uint32_t ip)
|
||||
{
|
||||
// ip in host byte order → dotted-decimal
|
||||
return std::to_string((ip >> 24) & 0xFF) + "." +
|
||||
std::to_string((ip >> 16) & 0xFF) + "." +
|
||||
std::to_string((ip >> 8) & 0xFF) + "." +
|
||||
std::to_string( ip & 0xFF);
|
||||
}
|
||||
|
||||
static uint32_t stringToIp(const std::string& s)
|
||||
{
|
||||
unsigned a = 0, b = 0, c = 0, d = 0;
|
||||
if (std::sscanf(s.c_str(), "%u.%u.%u.%u", &a, &b, &c, &d) != 4)
|
||||
return 0;
|
||||
if (a > 255 || b > 255 || c > 255 || d > 255)
|
||||
return 0;
|
||||
return (a << 24) | (b << 16) | (c << 8) | d;
|
||||
}
|
||||
|
||||
// --- RtpHelper implementation ---
|
||||
|
||||
bool RtpHelper::isRtp(const void* buffer, size_t length)
|
||||
{
|
||||
if (length < 12)
|
||||
return false;
|
||||
|
||||
const RtpHeader* h = reinterpret_cast<const RtpHeader*>(buffer);
|
||||
if (h->version != 0b10)
|
||||
if (h->version != 2)
|
||||
return false;
|
||||
|
||||
unsigned char pt = h->pt;
|
||||
bool rtp = ( (pt & 0x7F) >= 96 && (pt & 0x7F) <= 127) || ((pt & 0x7F) < 35);
|
||||
bool rtp = (pt >= 96 && pt <= 127) || (pt < 35);
|
||||
return rtp;
|
||||
}
|
||||
|
||||
|
||||
bool RtpHelper::isRtpOrRtcp(const void* buffer, size_t length)
|
||||
{
|
||||
if (length < 12)
|
||||
return false;
|
||||
const RtcpHeader* h = reinterpret_cast<const RtcpHeader*>(buffer);
|
||||
return h->version == 0b10;
|
||||
return h->version == 2;
|
||||
}
|
||||
|
||||
bool RtpHelper::isRtcp(const void* buffer, size_t length)
|
||||
@@ -83,15 +108,16 @@ unsigned RtpHelper::findSsrc(const void* buffer, size_t length)
|
||||
{
|
||||
if (isRtp(buffer, length))
|
||||
return ntohl(reinterpret_cast<const RtpHeader*>(buffer)->ssrc);
|
||||
else
|
||||
else if (isRtpOrRtcp(buffer, length))
|
||||
return ntohl(reinterpret_cast<const RtcpHeader*>(buffer)->ssrc);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void RtpHelper::setSsrc(void* buffer, size_t length, uint32_t ssrc)
|
||||
{
|
||||
if (isRtp(buffer, length))
|
||||
reinterpret_cast<RtpHeader*>(buffer)->ssrc = htonl(ssrc);
|
||||
else
|
||||
else if (isRtpOrRtcp(buffer, length))
|
||||
reinterpret_cast<RtcpHeader*>(buffer)->ssrc = htonl(ssrc);
|
||||
}
|
||||
|
||||
@@ -113,47 +139,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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -65,6 +65,7 @@ void thread_pool::run_worker()
|
||||
tasks.pop();
|
||||
}
|
||||
}
|
||||
if (t)
|
||||
t(); // function<void()> type
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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,15 +559,15 @@ 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;
|
||||
@@ -600,13 +580,13 @@ int AmrWbCodec::decodePlain(std::span<const uint8_t> input, std::span<uint8_t> o
|
||||
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,45 +595,55 @@ 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())
|
||||
// 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;
|
||||
@@ -666,7 +656,6 @@ int AmrWbCodec::plc(int lostFrames, void* output, int outputCapacity)
|
||||
dataOut += L_FRAME;
|
||||
}
|
||||
*/
|
||||
return lostFrames * pcmLength();
|
||||
}
|
||||
|
||||
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,59 +819,22 @@ 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;
|
||||
}
|
||||
|
||||
// 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 */
|
||||
{
|
||||
if (outputCapacity < pcmLength())
|
||||
return 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
|
||||
Decoder_Interface_Decode(mDecoderCtx, buffer, (short*)output.data(), 0); // Handle missing data
|
||||
}
|
||||
else
|
||||
{
|
||||
// Reorder bytes from input to dst
|
||||
uint8_t dst[GSM_EFR_FRAME_LEN];
|
||||
const uint8_t* src = (const uint8_t*)input;
|
||||
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;
|
||||
@@ -914,29 +853,27 @@ int GsmEfrCodec::decode(const void* input, int inputBytes, void* output, int out
|
||||
}
|
||||
|
||||
// Decode
|
||||
memset(output, 0, pcmLength());
|
||||
Decoder_Interface_Decode(mDecoderCtx, in, (short*)output, 0);
|
||||
memset(output.data(), 0, pcmLength());
|
||||
Decoder_Interface_Decode(mDecoderCtx, in, (short*)output.data(), 0);
|
||||
|
||||
|
||||
uint8_t* pcm = (uint8_t*)output;
|
||||
uint8_t* pcm = (uint8_t*)output.data();
|
||||
for (int i=0; i<160; i++)
|
||||
{
|
||||
uint16_t w = ((uint16_t*)output)[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++)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -52,30 +52,29 @@ public:
|
||||
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;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
{
|
||||
auto gapStart = RtpHelper::toMicroseconds(*mLastReceiveTime);
|
||||
auto gapEnd = RtpHelper::toMicroseconds(packet.rtp()->GetReceiveTime());
|
||||
mStat.mPacketLossTimeline.emplace_back(PacketLossEvent{.mStartSeqno = *mLastSeqno,
|
||||
.mEndSeqno = seqno,
|
||||
.mGap = gap,
|
||||
.mTimestamp = currentTimestamp});
|
||||
.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,14 +435,19 @@ bool AudioReceiver::add(const std::shared_ptr<jrtplib::RTPPacket>& p, Codec** de
|
||||
// Increase codec counter
|
||||
mStat.mCodecCount[ptype]++;
|
||||
|
||||
// Check if we deal with telephone-event
|
||||
if (p->GetPayloadType() == mCodecSettings.mTelephoneEvent)
|
||||
{
|
||||
*detectedCodec = nullptr;
|
||||
mDtmfBuffer.add(p, 10ms, 8000);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Look for codec
|
||||
// Check if codec can be handled
|
||||
Codec* codec = nullptr;
|
||||
auto codecIter = mCodecMap.find(ptype);
|
||||
if (codecIter == mCodecMap.end())
|
||||
{
|
||||
// Well, there is no information about the codec; skip this packet
|
||||
}
|
||||
else
|
||||
if (codecIter != mCodecMap.end())
|
||||
{
|
||||
// Check if codec is creating lazily
|
||||
if (!codecIter->second)
|
||||
@@ -464,7 +476,7 @@ bool AudioReceiver::add(const std::shared_ptr<jrtplib::RTPPacket>& p, Codec** de
|
||||
samplerate = codec->samplerate();
|
||||
}
|
||||
|
||||
// Process jitter
|
||||
// Process jitter anyway - can we decode payload or not
|
||||
mJitterStats.process(p.get(), samplerate);
|
||||
mStat.mJitter = static_cast<float>(mJitterStats.get());
|
||||
|
||||
@@ -486,8 +498,9 @@ bool AudioReceiver::add(const std::shared_ptr<jrtplib::RTPPacket>& p, Codec** de
|
||||
|
||||
// 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)
|
||||
{
|
||||
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);
|
||||
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,24 +605,25 @@ 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)
|
||||
{
|
||||
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 = p->rtp()->GetTimestamp() - *mLastPacketTimestamp;
|
||||
int units = rtp.GetTimestamp() - *mLastPacketTimestamp;
|
||||
int milliseconds = units / (mCodec->samplerate() / 1000);
|
||||
if (milliseconds > mLastPacketTimeLength)
|
||||
{
|
||||
@@ -618,43 +636,41 @@ AudioReceiver::DecodeResult AudioReceiver::decodePacket(const RtpBuffer::ResultL
|
||||
}
|
||||
}
|
||||
|
||||
mLastPacketTimestamp = p->rtp()->GetTimestamp();
|
||||
mLastPacketTimestamp = rtp.GetTimestamp();
|
||||
|
||||
// Find codec by payload type
|
||||
int ptype = p->rtp()->GetPayloadType();
|
||||
int ptype = rtp.GetPayloadType();
|
||||
|
||||
// Look into mCodecMap if exists
|
||||
auto codecIter = mCodecMap.find(ptype);
|
||||
if (codecIter == mCodecMap.end())
|
||||
return {};
|
||||
|
||||
|
||||
if (!codecIter->second)
|
||||
codecIter->second = mCodecList.createCodecByPayloadType(ptype);
|
||||
|
||||
mCodec = codecIter->second;
|
||||
if (mCodec)
|
||||
{
|
||||
if (rate)
|
||||
*rate = mCodec->samplerate();
|
||||
result.mChannels = mCodec->channels();
|
||||
result.mSamplerate = mCodec->samplerate();
|
||||
|
||||
// Check if it is CNG packet
|
||||
if ((ptype == 0 || ptype == 8) && p->rtp()->GetPayloadLength() >= 1 && p->rtp()->GetPayloadLength() <= 6)
|
||||
if (((ptype == 0 || ptype == 8) && rtp.GetPayloadLength() >= 1 && rtp.GetPayloadLength() <= 6) || rtp.GetPayloadType() == 13)
|
||||
{
|
||||
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);
|
||||
mDecodedLength = mCngDecoder.produce(mCodec->samplerate(), mLastPacketTimeLength, (short*)mDecodedFrame, true);
|
||||
if (mDecodedLength)
|
||||
processDecoded(output, options);
|
||||
}
|
||||
result = DecodeResult_Ok;
|
||||
result.mStatus = DecodeResult::Status::Ok;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -663,7 +679,7 @@ AudioReceiver::DecodeResult AudioReceiver::decodePacket(const RtpBuffer::ResultL
|
||||
|
||||
// Handle here regular RTP packets
|
||||
// Check if payload length is ok
|
||||
size_t payload_length = p->rtp()->GetPayloadLength();
|
||||
size_t payload_length = rtp.GetPayloadLength();
|
||||
size_t rtp_frame_length = mCodec->rtpLength();
|
||||
|
||||
int tail = rtp_frame_length ? payload_length % rtp_frame_length : 0;
|
||||
@@ -671,8 +687,8 @@ AudioReceiver::DecodeResult AudioReceiver::decodePacket(const RtpBuffer::ResultL
|
||||
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();
|
||||
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();
|
||||
@@ -685,13 +701,19 @@ AudioReceiver::DecodeResult AudioReceiver::decodePacket(const RtpBuffer::ResultL
|
||||
else
|
||||
{
|
||||
// Decode frame by frame
|
||||
mDecodedLength = mCodec->decode(p->rtp()->GetPayloadData() + i * mCodec->rtpLength(),
|
||||
frameLength, mDecodedFrame, sizeof mDecodedFrame);
|
||||
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 = mFrameCount > 0 ? DecodeResult_Ok : DecodeResult_Skip;
|
||||
result.mStatus = mFrameCount > 0 ? DecodeResult::Status::Ok : DecodeResult::Status::Skip;
|
||||
|
||||
// Check for bitrate counter
|
||||
updateAmrCodecStats(mCodec.get());
|
||||
@@ -699,47 +721,142 @@ AudioReceiver::DecodeResult AudioReceiver::decodePacket(const RtpBuffer::ResultL
|
||||
else
|
||||
{
|
||||
// RTP packet with tail - it should not happen
|
||||
result = DecodeResult_BadPacket;
|
||||
}
|
||||
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");
|
||||
// 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};
|
||||
|
||||
// No packet available at all (and no previous CNG packet) - so return the silence
|
||||
if (options.mElapsed != 0ms && mCodec)
|
||||
{
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
mFailedCount++;
|
||||
return DecodeResult_Skip;
|
||||
return {.mStatus = DecodeResult::Status::Skip};
|
||||
}
|
||||
|
||||
AudioReceiver::DecodeResult AudioReceiver::getAudio(Audio::DataWindow& output, DecodeOptions options, int* rate)
|
||||
AudioReceiver::DecodeResult AudioReceiver::getAudioTo(Audio::DataWindow& output, DecodeOptions options)
|
||||
{
|
||||
DecodeResult result = DecodeResult_Skip;
|
||||
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(rl);
|
||||
switch (fr)
|
||||
RtpBuffer::FetchResult fr = mBuffer.fetch();
|
||||
// ICELogDebug(<< fr.toString() << " " << mBuffer.findTimelength());
|
||||
|
||||
switch (fr.mStatus)
|
||||
{
|
||||
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;
|
||||
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);
|
||||
}
|
||||
|
||||
if (result == DecodeResult_Ok)
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,30 +167,29 @@ 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
|
||||
};
|
||||
|
||||
DecodeResult getAudio(Audio::DataWindow& output, DecodeOptions options = {.mResampleToMainRate = true, .mFillGapByCNG = false, .mSkipDecode = false}, int* rate = nullptr);
|
||||
Status mStatus = Status::Ok;
|
||||
int mSamplerate = 0;
|
||||
int mChannels = 0;
|
||||
};
|
||||
|
||||
DecodeResult getAudioTo(Audio::DataWindow& output, DecodeOptions options);
|
||||
|
||||
// Looks for codec by payload type
|
||||
Codec* findCodec(int payloadType);
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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/. */
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 = { };
|
||||
|
||||
@@ -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(ev.mDuration != 0);
|
||||
assert(output);
|
||||
assert(tone);
|
||||
assert(ev.mTone != 0);
|
||||
|
||||
unsigned char toneValue = 0;
|
||||
if (tone >= '0' && tone <='9')
|
||||
toneValue = tone - '0';
|
||||
if (ev.mTone >= '0' && ev.mTone <='9')
|
||||
toneValue = ev.mTone - '0';
|
||||
else
|
||||
if (tone >= 'A' && tone <='D' )
|
||||
toneValue = tone - 'A' + 12;
|
||||
if (ev.mTone >= 'A' && ev.mTone <='D' )
|
||||
toneValue = ev.mTone - 'A' + 12;
|
||||
else
|
||||
if (tone == '*')
|
||||
if (ev.mTone == '*')
|
||||
toneValue = 10;
|
||||
else
|
||||
if (tone == '#')
|
||||
if (ev.mTone == '#')
|
||||
toneValue = 11;
|
||||
|
||||
char* packet = (char*)output;
|
||||
|
||||
packet[0] = toneValue;
|
||||
packet[1] = 1 | (volume << 2);
|
||||
if (endOfEvent)
|
||||
packet[1] = 1 | (ev.mVolume << 2);
|
||||
if (ev.mEnd)
|
||||
packet[1] |= 128;
|
||||
else
|
||||
packet[1] &= 127;
|
||||
|
||||
unsigned short durationValue = htons(duration);
|
||||
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
|
||||
@@ -212,7 +242,7 @@ void DtmfBuilder::buildInband(int tone, int startTime, int finishTime, int rate,
|
||||
|
||||
#pragma region DtmfContext
|
||||
DtmfContext::DtmfContext()
|
||||
:mType(Dtmf_Rfc2833)
|
||||
:mType(Dtmf_Rfc2833)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -284,7 +314,7 @@ bool DtmfContext::getInband(int milliseconds, int rate, ByteBuffer& output)
|
||||
//
|
||||
Dtmf& d = mQueue.front();
|
||||
|
||||
output.resize(milliseconds * rate / 1000 * 2);
|
||||
output.resize((uint64_t)milliseconds * rate / 1000 * 2);
|
||||
DtmfBuilder::buildInband(d.mTone, d.mCurrentTime, d.mCurrentTime + milliseconds, rate, (short*)output.mutableData());
|
||||
d.mCurrentTime += milliseconds;
|
||||
return true;
|
||||
@@ -302,7 +332,7 @@ bool DtmfContext::getRfc2833(int milliseconds, ByteBuffer& output, ByteBuffer& s
|
||||
{
|
||||
// Emit rfc2833 packet
|
||||
output.resize(4);
|
||||
DtmfBuilder::buildRfc2833(d.mTone, milliseconds, d.mVolume, false, output.mutableData());
|
||||
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;
|
||||
@@ -311,7 +341,7 @@ bool DtmfContext::getRfc2833(int milliseconds, ByteBuffer& output, ByteBuffer& s
|
||||
if (!d.mStopped)
|
||||
{
|
||||
output.resize(4);
|
||||
DtmfBuilder::buildRfc2833(d.mTone, milliseconds, d.mVolume, false, output.mutableData());
|
||||
DtmfBuilder::buildRfc2833({.mTone = (char)d.mTone, .mDuration = milliseconds, .mVolume = d.mVolume, .mEnd = false}, output.mutableData());
|
||||
}
|
||||
else
|
||||
output.clear();
|
||||
@@ -319,7 +349,7 @@ bool DtmfContext::getRfc2833(int milliseconds, ByteBuffer& output, ByteBuffer& s
|
||||
if (d.mStopped)
|
||||
{
|
||||
stopPacket.resize(4);
|
||||
DtmfBuilder::buildRfc2833(d.mTone, milliseconds, d.mVolume, true, stopPacket.mutableData());
|
||||
DtmfBuilder::buildRfc2833({.mTone = (char)d.mTone, .mDuration = milliseconds, .mVolume = d.mVolume, .mEnd = true}, stopPacket.mutableData());
|
||||
}
|
||||
else
|
||||
stopPacket.clear();
|
||||
@@ -375,8 +405,8 @@ 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));
|
||||
|
||||
@@ -384,13 +414,13 @@ DTMFDetector::DTMFDetector()
|
||||
zap_dtmf_detect_init((dtmf_detect_state_t*)mState);
|
||||
}
|
||||
|
||||
DTMFDetector::~DTMFDetector()
|
||||
InbandDtmfDetector::~InbandDtmfDetector()
|
||||
{
|
||||
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))
|
||||
@@ -398,7 +428,7 @@ std::string DTMFDetector::streamPut(unsigned char* samples, unsigned int size)
|
||||
return buf;
|
||||
}
|
||||
|
||||
void DTMFDetector::resetState()
|
||||
void InbandDtmfDetector::resetState()
|
||||
{
|
||||
zap_dtmf_detect_init((dtmf_detect_state_t*)mState);
|
||||
}
|
||||
@@ -448,11 +478,11 @@ static tone_detection_descriptor_t fax_detect;
|
||||
static tone_detection_descriptor_t fax_detect_2nd;
|
||||
|
||||
static float dtmf_row[] =
|
||||
{
|
||||
{
|
||||
697.0, 770.0, 852.0, 941.0
|
||||
};
|
||||
static float dtmf_col[] =
|
||||
{
|
||||
{
|
||||
1209.0, 1336.0, 1477.0, 1633.0
|
||||
};
|
||||
|
||||
@@ -603,7 +633,7 @@ void zap_dtmf_detect_init (dtmf_detect_state_t *s)
|
||||
goertzel_init (&s->row_out2nd[i], &dtmf_detect_row_2nd[i]);
|
||||
goertzel_init (&s->col_out2nd[i], &dtmf_detect_col_2nd[i]);
|
||||
|
||||
s->energy = 0.0;
|
||||
s->energy = 0.0;
|
||||
}
|
||||
|
||||
/* Same for the fax dector */
|
||||
@@ -734,7 +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,16 +778,16 @@ int zap_dtmf_detect (dtmf_detect_state_t *s,
|
||||
if (s->current_sample < 102)
|
||||
continue;
|
||||
|
||||
/* Detect the fax energy, too */
|
||||
fax_energy = zap_goertzel_result(&s->fax_tone);
|
||||
/* Detect the fax energy, too */
|
||||
fax_energy = zap_goertzel_result(&s->fax_tone);
|
||||
|
||||
/* We are at the end of a DTMF detection block */
|
||||
/* Find the peak row and the peak column */
|
||||
row_energy[0] = zap_goertzel_result (&s->row_out[0]);
|
||||
col_energy[0] = zap_goertzel_result (&s->col_out[0]);
|
||||
|
||||
for (best_row = best_col = 0, i = 1; i < 4; i++)
|
||||
{
|
||||
for (best_row = best_col = 0, i = 1; i < 4; i++)
|
||||
{
|
||||
row_energy[i] = zap_goertzel_result (&s->row_out[i]);
|
||||
if (row_energy[i] > row_energy[best_row])
|
||||
best_row = i;
|
||||
@@ -788,7 +818,7 @@ for (best_row = best_col = 0, i = 1; i < 4; i++)
|
||||
/* ... and second harmonic test */
|
||||
if (i >= 4
|
||||
&&
|
||||
(row_energy[best_row] + col_energy[best_col]) > 42.0*s->energy
|
||||
(row_energy[best_row] + col_energy[best_col]) > 42.0*s->energy
|
||||
&&
|
||||
zap_goertzel_result (&s->col_out2nd[best_col])*DTMF_2ND_HARMONIC_COL < col_energy[best_col]
|
||||
&&
|
||||
@@ -819,18 +849,18 @@ for (best_row = best_col = 0, i = 1; i < 4; i++)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!hit && (fax_energy >= FAX_THRESHOLD) && (fax_energy > s->energy * 21.0)) {
|
||||
fax_energy_2nd = zap_goertzel_result(&s->fax_tone2nd);
|
||||
if (fax_energy_2nd * FAX_2ND_HARMONIC < fax_energy) {
|
||||
if (!hit && (fax_energy >= FAX_THRESHOLD) && (fax_energy > s->energy * 21.0)) {
|
||||
fax_energy_2nd = zap_goertzel_result(&s->fax_tone2nd);
|
||||
if (fax_energy_2nd * FAX_2ND_HARMONIC < fax_energy) {
|
||||
#if 0
|
||||
printf("Fax energy/Second Harmonic: %f/%f\n", fax_energy, fax_energy_2nd);
|
||||
#endif
|
||||
/* XXX Probably need better checking than just this the energy XXX */
|
||||
hit = 'f';
|
||||
s->fax_hits++;
|
||||
} /* Don't reset fax hits counter */
|
||||
} else {
|
||||
if (s->fax_hits > 5) {
|
||||
/* XXX Probably need better checking than just this the energy XXX */
|
||||
hit = 'f';
|
||||
s->fax_hits++;
|
||||
} /* Don't reset fax hits counter */
|
||||
} else {
|
||||
if (s->fax_hits > 5) {
|
||||
s->mhit = 'f';
|
||||
s->detected_digits++;
|
||||
if (s->current_digits < MAX_DTMF_DIGITS)
|
||||
@@ -842,9 +872,9 @@ if (s->fax_hits > 5) {
|
||||
{
|
||||
s->lost_digits++;
|
||||
}
|
||||
}
|
||||
s->fax_hits = 0;
|
||||
}
|
||||
}
|
||||
s->fax_hits = 0;
|
||||
}
|
||||
s->hit1 = s->hit2;
|
||||
s->hit2 = s->hit3;
|
||||
s->hit3 = hit;
|
||||
@@ -858,13 +888,13 @@ s->fax_hits = 0;
|
||||
}
|
||||
goertzel_init (&s->fax_tone, &fax_detect);
|
||||
goertzel_init (&s->fax_tone2nd, &fax_detect_2nd);
|
||||
s->energy = 0.0;
|
||||
s->energy = 0.0;
|
||||
s->current_sample = 0;
|
||||
}
|
||||
if ((!s->mhit) || (s->mhit != hit))
|
||||
{
|
||||
s->mhit = 0;
|
||||
return(0);
|
||||
s->mhit = 0;
|
||||
return(0);
|
||||
}
|
||||
return (hit);
|
||||
}
|
||||
|
||||
@@ -15,18 +15,32 @@
|
||||
namespace MT
|
||||
{
|
||||
|
||||
class DtmfBuilder
|
||||
class DtmfBuilder
|
||||
{
|
||||
public:
|
||||
struct Rfc2833Event
|
||||
{
|
||||
public:
|
||||
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,13 +50,13 @@ 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,
|
||||
@@ -65,20 +79,20 @@ 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();
|
||||
InbandDtmfDetector();
|
||||
|
||||
/*! The destructor. Free the detector context's memory. */
|
||||
~DTMFDetector();
|
||||
~InbandDtmfDetector();
|
||||
|
||||
/*! This method receives the input PCM 16-bit data and returns found DTMF event(s) in string representation.
|
||||
* @param samples Input PCM buffer pointer.
|
||||
@@ -92,6 +106,7 @@ public:
|
||||
protected:
|
||||
void* mState; /// DTMF detector context
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -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,10 +262,10 @@ 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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,6 +55,13 @@ 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;
|
||||
};
|
||||
|
||||
@@ -78,7 +84,7 @@ public:
|
||||
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
|
||||
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
|
||||
@@ -86,6 +92,7 @@ public:
|
||||
|
||||
// AMR codec bitrate switch counter
|
||||
int mBitrateSwitchCounter = 0;
|
||||
int mCng = 0;
|
||||
std::string mCodecName;
|
||||
float mJitter = 0.0f; // Jitter
|
||||
TestResult<float> mRttDelay; // RTT delay
|
||||
@@ -96,6 +103,7 @@ public:
|
||||
std::map<int, int> mCodecCount; // Stats on used codecs
|
||||
|
||||
std::vector<PacketLossEvent> mPacketLossTimeline; // Packet loss timeline
|
||||
std::vector<Dtmf2833Event> mDtmf2833Timeline;
|
||||
|
||||
// It is to calculate network MOS
|
||||
void calculateBurstr(double* burstr, double* loss) const;
|
||||
|
||||
10
test/rtp_decode/CMakeLists.txt
Normal file
10
test/rtp_decode/CMakeLists.txt
Normal 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
291
test/rtp_decode/main.cpp
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user