Compare commits
10 Commits
cc470f7cf6
...
273c72498e
| Author | SHA1 | Date | |
|---|---|---|---|
| 273c72498e | |||
| 34fd9121ed | |||
| 06b39dd629 | |||
| 03f662e5ce | |||
| 72a7042ab9 | |||
| b5fe9e59c4 | |||
| bdc4858bcc | |||
| 783359c616 | |||
| 78d77c4e69 | |||
| 94f30b25e9 |
@@ -17,18 +17,19 @@ def make_build() -> Path:
|
|||||||
if Path(DIR_BUILD).exists():
|
if Path(DIR_BUILD).exists():
|
||||||
shutil.rmtree(DIR_BUILD)
|
shutil.rmtree(DIR_BUILD)
|
||||||
os.mkdir(DIR_BUILD)
|
os.mkdir(DIR_BUILD)
|
||||||
os.chdir(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)
|
retcode = os.system(cmd)
|
||||||
if retcode != 0:
|
if retcode != 0:
|
||||||
raise RuntimeError('Problem when configuring the project')
|
raise RuntimeError('Problem when configuring the project')
|
||||||
|
|
||||||
cmd = f'cmake --build . -j {multiprocessing.cpu_count()}'
|
cmd = f'cmake --build . -j {multiprocessing.cpu_count()}'
|
||||||
retcode = os.system(cmd)
|
retcode = os.system(cmd)
|
||||||
if retcode != 0:
|
if retcode != 0:
|
||||||
raise RuntimeError('Problem when building the project')
|
raise RuntimeError('Problem when building the project')
|
||||||
|
|
||||||
os.chdir('..')
|
os.chdir('..')
|
||||||
return Path(DIR_BUILD) / 'librtphone.a'
|
return Path(DIR_BUILD) / 'librtphone.a'
|
||||||
|
|
||||||
|
|||||||
@@ -339,6 +339,8 @@ set (LIBS_STATIC ${LIBS_STATIC} jrtplib g729_codec gsm_codec opus
|
|||||||
|
|
||||||
if (USE_AMR_CODEC)
|
if (USE_AMR_CODEC)
|
||||||
#include (${LIB_PLATFORM}/platform_libs.cmake)
|
#include (${LIB_PLATFORM}/platform_libs.cmake)
|
||||||
|
set (OPENCORE_AMRNB opencore-amrnb)
|
||||||
|
set (OPENCORE_AMRWB opencore-amrwb)
|
||||||
message("Media: AMR NB and WB codecs will be included.")
|
message("Media: AMR NB and WB codecs will be included.")
|
||||||
set (DEFINES ${DEFINES} -DUSE_AMR_CODEC)
|
set (DEFINES ${DEFINES} -DUSE_AMR_CODEC)
|
||||||
set (LIBS_STATIC ${LIBS_STATIC} ${OPENCORE_AMRNB} ${OPENCORE_AMRWB})
|
set (LIBS_STATIC ${LIBS_STATIC} ${OPENCORE_AMRNB} ${OPENCORE_AMRWB})
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ const std::string Status_FailedToOpenFile = "failed to open file";
|
|||||||
const std::string Status_NoActiveProvider = "no active provider";
|
const std::string Status_NoActiveProvider = "no active provider";
|
||||||
const std::string Status_NoMediaAction = "no valid media action";
|
const std::string Status_NoMediaAction = "no valid media action";
|
||||||
const std::string Status_NoCommand = "no valid command";
|
const std::string Status_NoCommand = "no valid command";
|
||||||
|
const std::string Status_NoAudioManager = "no audio manager";
|
||||||
|
|
||||||
#define LOG_SUBSYSTEM "Agent"
|
#define LOG_SUBSYSTEM "Agent"
|
||||||
|
|
||||||
@@ -336,7 +337,7 @@ void AgentImpl::processStartSession(JsonCpp::Value& request, JsonCpp::Value& ans
|
|||||||
{
|
{
|
||||||
// Agent was not started
|
// Agent was not started
|
||||||
ICELogError(<< "No audio manager installed.");
|
ICELogError(<< "No audio manager installed.");
|
||||||
answer["status"] = "Audio manager not started. Most probably agent is not started.";
|
answer["status"] = Status_NoAudioManager;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -431,28 +432,35 @@ void AgentImpl::processAcceptSession(JsonCpp::Value& request, JsonCpp::Value& an
|
|||||||
auto sessionIter = mSessionMap.find(request["session_id"].asInt());
|
auto sessionIter = mSessionMap.find(request["session_id"].asInt());
|
||||||
if (sessionIter != mSessionMap.end())
|
if (sessionIter != mSessionMap.end())
|
||||||
{
|
{
|
||||||
// Ensure audio manager is here
|
if (!mAudioManager)
|
||||||
mAudioManager->start(mUseNativeAudio ? AudioManager::atReceiver : AudioManager::atNull);
|
{
|
||||||
|
ICELogError(<< "No audio manager installed.");
|
||||||
|
answer["status"] = Status_NoAudioManager;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Ensure audio manager is here
|
||||||
|
mAudioManager->start(mUseNativeAudio ? AudioManager::atReceiver : AudioManager::atNull);
|
||||||
|
|
||||||
// Accept session on SIP level
|
// Accept session on SIP level
|
||||||
PSession session = sessionIter->second;
|
PSession session = sessionIter->second;
|
||||||
|
|
||||||
// Get user headers
|
// Get user headers
|
||||||
Session::UserHeaders info;
|
Session::UserHeaders info;
|
||||||
JsonCpp::Value& arg = request["userinfo"];
|
JsonCpp::Value& arg = request["userinfo"];
|
||||||
std::vector<std::string> keys = arg.getMemberNames();
|
std::vector<std::string> keys = arg.getMemberNames();
|
||||||
for (const std::string& k: keys)
|
for (const std::string& k: keys)
|
||||||
info[k] = arg[k].asString();
|
info[k] = arg[k].asString();
|
||||||
session->setUserHeaders(info);
|
session->setUserHeaders(info);
|
||||||
|
|
||||||
// Accept finally
|
// Accept finally
|
||||||
session->accept();
|
session->accept();
|
||||||
|
|
||||||
answer["status"] = Status_Ok;
|
answer["status"] = Status_Ok;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
answer["status"] = Status_SessionNotFound;
|
answer["status"] = Status_SessionNotFound;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AgentImpl::processDestroySession(JsonCpp::Value& request, JsonCpp::Value& answer)
|
void AgentImpl::processDestroySession(JsonCpp::Value& request, JsonCpp::Value& answer)
|
||||||
@@ -526,6 +534,8 @@ void AgentImpl::processGetMediaStats(JsonCpp::Value& request, JsonCpp::Value& an
|
|||||||
answer["rtt"] = result[SessionInfo_Rtt].asFloat();
|
answer["rtt"] = result[SessionInfo_Rtt].asFloat();
|
||||||
if (result.exists(SessionInfo_BitrateSwitchCounter))
|
if (result.exists(SessionInfo_BitrateSwitchCounter))
|
||||||
answer["bitrate_switch_counter"] = result[SessionInfo_BitrateSwitchCounter].asInt();
|
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))
|
if (result.exists(SessionInfo_SSRC))
|
||||||
answer["rtp_ssrc"] = result[SessionInfo_SSRC].asInt();
|
answer["rtp_ssrc"] = result[SessionInfo_SSRC].asInt();
|
||||||
if (result.exists(SessionInfo_RemotePeer))
|
if (result.exists(SessionInfo_RemotePeer))
|
||||||
@@ -685,7 +695,7 @@ void AgentImpl::processUseStreamForSession(JsonCpp::Value& request, JsonCpp::Val
|
|||||||
answer["status"] = Status_Ok;
|
answer["status"] = Status_Ok;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
answer["status"] = Status_AccountNotFound;
|
answer["status"] = Status_NoCommand;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
answer["status"] = Status_NoMediaAction;
|
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)
|
PDataProvider AgentImpl::onProviderNeeded(const std::string& name)
|
||||||
{
|
{
|
||||||
|
assert(mTerminal);
|
||||||
|
|
||||||
EVENT_WITH_NAME("provider_needed");
|
EVENT_WITH_NAME("provider_needed");
|
||||||
v["provider_name"] = name;
|
v["provider_name"] = name;
|
||||||
addEvent(v);
|
addEvent(v);
|
||||||
|
|
||||||
return PDataProvider(new AudioProvider(*this, *mTerminal));
|
return std::make_shared<AudioProvider>(*this, *mTerminal);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called on new session offer
|
// Called on new session offer
|
||||||
|
|||||||
@@ -11,21 +11,31 @@ using namespace Audio;
|
|||||||
DataWindow::DataWindow()
|
DataWindow::DataWindow()
|
||||||
{
|
{
|
||||||
mFilled = 0;
|
mFilled = 0;
|
||||||
mData = NULL;
|
mData = nullptr;
|
||||||
mCapacity = 0;
|
mCapacity = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
DataWindow::~DataWindow()
|
DataWindow::~DataWindow()
|
||||||
{
|
{
|
||||||
if (mData)
|
if (mData)
|
||||||
|
{
|
||||||
free(mData);
|
free(mData);
|
||||||
|
mData = nullptr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DataWindow::setCapacity(int capacity)
|
void DataWindow::setCapacity(int capacity)
|
||||||
{
|
{
|
||||||
Lock l(mMutex);
|
Lock l(mMutex);
|
||||||
int tail = capacity - mCapacity;
|
int tail = capacity - mCapacity;
|
||||||
|
char* buffer = mData;
|
||||||
mData = (char*)realloc(mData, capacity);
|
mData = (char*)realloc(mData, capacity);
|
||||||
|
if (!mData)
|
||||||
|
{
|
||||||
|
// Realloc failed
|
||||||
|
mData = buffer;
|
||||||
|
throw std::bad_alloc();
|
||||||
|
}
|
||||||
if (tail > 0)
|
if (tail > 0)
|
||||||
memset(mData + mCapacity, 0, tail);
|
memset(mData + mCapacity, 0, tail);
|
||||||
mCapacity = capacity;
|
mCapacity = capacity;
|
||||||
@@ -166,6 +176,25 @@ void DataWindow::zero(int length)
|
|||||||
memset(mData, 0, mFilled);
|
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)
|
void DataWindow::makeStereoFromMono(DataWindow& dst, DataWindow& src)
|
||||||
{
|
{
|
||||||
Lock lockDst(dst.mMutex), lockSrc(src.mMutex);
|
Lock lockDst(dst.mMutex), lockSrc(src.mMutex);
|
||||||
|
|||||||
@@ -11,37 +11,40 @@
|
|||||||
|
|
||||||
namespace Audio
|
namespace Audio
|
||||||
{
|
{
|
||||||
class DataWindow
|
class DataWindow
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
DataWindow();
|
DataWindow();
|
||||||
~DataWindow();
|
~DataWindow();
|
||||||
|
|
||||||
void setCapacity(int capacity);
|
void setCapacity(int capacity);
|
||||||
int capacity() const;
|
int capacity() const;
|
||||||
|
|
||||||
void addZero(int length);
|
void addZero(int length);
|
||||||
void add(const void* data, int length);
|
void add(const void* data, int length);
|
||||||
void add(short sample);
|
void add(short sample);
|
||||||
int read(void* buffer, int length);
|
int read(void* buffer, int length);
|
||||||
void erase(int length = -1);
|
void erase(int length = -1);
|
||||||
const char* data() const;
|
const char* data() const;
|
||||||
char* mutableData();
|
char* mutableData();
|
||||||
int filled() const;
|
int filled() const;
|
||||||
void setFilled(int filled);
|
void setFilled(int filled);
|
||||||
void clear();
|
void clear();
|
||||||
|
|
||||||
short shortAt(int index) const;
|
short shortAt(int index) const;
|
||||||
void setShortAt(short value, int index);
|
void setShortAt(short value, int index);
|
||||||
void zero(int length);
|
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);
|
static void makeStereoFromMono(DataWindow& dst, DataWindow& src);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
mutable Mutex mMutex;
|
mutable Mutex mMutex;
|
||||||
char* mData;
|
char* mData;
|
||||||
int mFilled;
|
int mFilled;
|
||||||
int mCapacity;
|
int mCapacity;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -51,6 +51,11 @@ struct Format
|
|||||||
return float((milliseconds * mRate) / 500.0 * mChannels);
|
return float((milliseconds * mRate) / 500.0 * mChannels);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t sizeFromTime(std::chrono::milliseconds ms) const
|
||||||
|
{
|
||||||
|
return sizeFromTime(ms.count());
|
||||||
|
}
|
||||||
|
|
||||||
std::string toString()
|
std::string toString()
|
||||||
{
|
{
|
||||||
char buffer[64];
|
char buffer[64];
|
||||||
|
|||||||
@@ -483,6 +483,7 @@ void Session::getSessionInfo(Session::InfoOptions options, VariantMap& info)
|
|||||||
info[SessionInfo_Rtt] = static_cast<float>(stat.mRttDelay * 1000);
|
info[SessionInfo_Rtt] = static_cast<float>(stat.mRttDelay * 1000);
|
||||||
#if defined(USE_AMR_CODEC)
|
#if defined(USE_AMR_CODEC)
|
||||||
info[SessionInfo_BitrateSwitchCounter] = stat.mBitrateSwitchCounter;
|
info[SessionInfo_BitrateSwitchCounter] = stat.mBitrateSwitchCounter;
|
||||||
|
info[SessionInfo_CngCounter] = stat.mCng;
|
||||||
#endif
|
#endif
|
||||||
info[SessionInfo_SSRC] = stat.mSsrc;
|
info[SessionInfo_SSRC] = stat.mSsrc;
|
||||||
info[SessionInfo_RemotePeer] = stat.mRemotePeer.toStdString();
|
info[SessionInfo_RemotePeer] = stat.mRemotePeer.toStdString();
|
||||||
@@ -882,7 +883,7 @@ void Session::refreshMediaPath()
|
|||||||
|
|
||||||
// Bring new socket to provider and stream
|
// Bring new socket to provider and stream
|
||||||
RtpPair<PDatagramSocket> s4 = SocketHeap::instance().allocSocketPair(AF_INET, this, IS_MULTIPLEX() ),
|
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);
|
p->setSocket(s4, s6);
|
||||||
s.setSocket4(s4);
|
s.setSocket4(s4);
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ enum SessionInfo
|
|||||||
SessionInfo_BitrateSwitchCounter, // It is for AMR codecs only
|
SessionInfo_BitrateSwitchCounter, // It is for AMR codecs only
|
||||||
SessionInfo_RemotePeer,
|
SessionInfo_RemotePeer,
|
||||||
SessionInfo_SSRC,
|
SessionInfo_SSRC,
|
||||||
|
SessionInfo_CngCounter // For AMR codecs only
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -38,11 +38,11 @@
|
|||||||
//#define AUDIO_DUMPOUTPUT
|
//#define AUDIO_DUMPOUTPUT
|
||||||
|
|
||||||
|
|
||||||
#define UA_REGISTRATION_TIME 3600
|
#define UA_REGISTRATION_TIME 3600
|
||||||
#define UA_MEDIA_PORT_START 20000
|
#define UA_MEDIA_PORT_START 20000
|
||||||
#define UA_MEDIA_PORT_FINISH 30000
|
#define UA_MEDIA_PORT_FINISH 30000
|
||||||
#define UA_MAX_UDP_PACKET_SIZE 576
|
#define UA_MAX_UDP_PACKET_SIZE 576
|
||||||
#define UA_PUBLICATION_ID "314"
|
#define UA_PUBLICATION_ID "314"
|
||||||
|
|
||||||
#define MT_SAMPLERATE AUDIO_SAMPLERATE
|
#define MT_SAMPLERATE AUDIO_SAMPLERATE
|
||||||
|
|
||||||
@@ -50,13 +50,11 @@
|
|||||||
#define MT_MAXRTPPACKET 1500
|
#define MT_MAXRTPPACKET 1500
|
||||||
#define MT_DTMF_END_PACKETS 3
|
#define MT_DTMF_END_PACKETS 3
|
||||||
|
|
||||||
#define RTP_BUFFER_HIGH 0
|
// Milliseconds before
|
||||||
#define RTP_BUFFER_LOW 0
|
#define RTP_BUFFER_HIGH (2000)
|
||||||
#define RTP_BUFFER_PREBUFFER 0
|
#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 RTP_DECODED_CAPACITY 2048
|
||||||
|
|
||||||
#define DEFAULT_SUBSCRIPTION_TIME 1200
|
#define DEFAULT_SUBSCRIPTION_TIME 1200
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright(C) 2007-2017 VoIP objects (voipobjects.com)
|
/* Copyright(C) 2007-2026 VoIP objects (voipobjects.com)
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
#include "../engine_config.h"
|
#include "../engine_config.h"
|
||||||
#include "HL_NetworkSocket.h"
|
#include "HL_NetworkSocket.h"
|
||||||
|
#include "HL_Log.h"
|
||||||
|
|
||||||
#if defined(TARGET_OSX) || defined(TARGET_LINUX)
|
#if defined(TARGET_OSX) || defined(TARGET_LINUX)
|
||||||
# include <fcntl.h>
|
# include <fcntl.h>
|
||||||
@@ -19,11 +20,11 @@
|
|||||||
#endif
|
#endif
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
|
#define LOG_SUBSYSTEM "network"
|
||||||
|
|
||||||
DatagramSocket::DatagramSocket()
|
DatagramSocket::DatagramSocket()
|
||||||
:mFamily(AF_INET), mHandle(INVALID_SOCKET), mLocalPort(0)
|
:mFamily(AF_INET), mHandle(INVALID_SOCKET), mLocalPort(0)
|
||||||
{
|
{}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
DatagramSocket::~DatagramSocket()
|
DatagramSocket::~DatagramSocket()
|
||||||
{
|
{
|
||||||
@@ -160,6 +161,12 @@ void DatagramAgreggator::addSocket(PDatagramSocket socket)
|
|||||||
if (socket->mHandle == INVALID_SOCKET)
|
if (socket->mHandle == INVALID_SOCKET)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (mSocketVector.size() >= 62)
|
||||||
|
{
|
||||||
|
ICELogError(<< "fd_set overflow; too much sockets");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
FD_SET(socket->mHandle, &mReadSet);
|
FD_SET(socket->mHandle, &mReadSet);
|
||||||
if (socket->mHandle > mMaxHandle)
|
if (socket->mHandle > mMaxHandle)
|
||||||
mMaxHandle = socket->mHandle;
|
mMaxHandle = socket->mHandle;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright(C) 2007-2017 VoIPobjects (voipobjects.com)
|
/* Copyright(C) 2007-2026 VoIPobjects (voipobjects.com)
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
@@ -8,70 +8,95 @@
|
|||||||
# include <Windows.h>
|
# include <Windows.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(TARGET_LINUX) || defined(TARGET_ANDROID)
|
#if defined(TARGET_LINUX) || defined(TARGET_ANDROID) || defined(TARGET_OSX)
|
||||||
# include <arpa/inet.h>
|
# include <arpa/inet.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "HL_Rtp.h"
|
#include "HL_Rtp.h"
|
||||||
#include "HL_Exception.h"
|
#include "HL_Exception.h"
|
||||||
#include "HL_String.h"
|
#include "HL_Log.h"
|
||||||
|
|
||||||
#if defined(USE_RTP_DUMP)
|
#include "jrtplib/src/rtprawpacket.h"
|
||||||
# include "jrtplib/src/rtprawpacket.h"
|
#include "jrtplib/src/rtpipv4address.h"
|
||||||
# include "jrtplib/src/rtpipv4address.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if !defined(TARGET_WIN)
|
#include <stdexcept>
|
||||||
# include <alloca.h>
|
#include <fstream>
|
||||||
#endif
|
#include <cstring>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
#include <sstream>
|
#define LOG_SUBSYSTEM "RtpDump"
|
||||||
#include <tuple>
|
|
||||||
|
|
||||||
|
static constexpr size_t MAX_RTP_PACKET_SIZE = 65535;
|
||||||
|
static const char RTPDUMP_SHEBANG[] = "#!rtpplay1.0";
|
||||||
|
|
||||||
|
// RTP fixed header (little-endian bit-field layout)
|
||||||
struct RtpHeader
|
struct RtpHeader
|
||||||
{
|
{
|
||||||
unsigned char cc:4; /* CSRC count */
|
unsigned char cc:4; /* CSRC count */
|
||||||
unsigned char x:1; /* header extension flag */
|
unsigned char x:1; /* header extension flag */
|
||||||
unsigned char p:1; /* padding flag */
|
unsigned char p:1; /* padding flag */
|
||||||
unsigned char version:2; /* protocol version */
|
unsigned char version:2; /* protocol version */
|
||||||
unsigned char pt:7; /* payload type */
|
unsigned char pt:7; /* payload type */
|
||||||
unsigned char m:1; /* marker bit */
|
unsigned char m:1; /* marker bit */
|
||||||
unsigned short seq; /* sequence number */
|
unsigned short seq; /* sequence number */
|
||||||
unsigned int ts; /* timestamp */
|
unsigned int ts; /* timestamp */
|
||||||
unsigned int ssrc; /* synchronization source */
|
unsigned int ssrc; /* synchronization source */
|
||||||
};
|
};
|
||||||
|
|
||||||
struct RtcpHeader
|
struct RtcpHeader
|
||||||
{
|
{
|
||||||
unsigned char rc:5; /* reception report count */
|
unsigned char rc:5; /* reception report count */
|
||||||
unsigned char p:1; /* padding flag */
|
unsigned char p:1; /* padding flag */
|
||||||
unsigned char version:2; /* protocol version */
|
unsigned char version:2; /* protocol version */
|
||||||
unsigned char pt:8; /* payload type */
|
unsigned char pt; /* payload type */
|
||||||
uint16_t len; /* length */
|
uint16_t len; /* length */
|
||||||
uint32_t ssrc; /* synchronization source */
|
uint32_t ssrc; /* synchronization source */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// --- IPv4 address helpers ---
|
||||||
|
|
||||||
|
static std::string ipToString(uint32_t ip)
|
||||||
|
{
|
||||||
|
// ip in host byte order → dotted-decimal
|
||||||
|
return std::to_string((ip >> 24) & 0xFF) + "." +
|
||||||
|
std::to_string((ip >> 16) & 0xFF) + "." +
|
||||||
|
std::to_string((ip >> 8) & 0xFF) + "." +
|
||||||
|
std::to_string( ip & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t stringToIp(const std::string& s)
|
||||||
|
{
|
||||||
|
unsigned a = 0, b = 0, c = 0, d = 0;
|
||||||
|
if (std::sscanf(s.c_str(), "%u.%u.%u.%u", &a, &b, &c, &d) != 4)
|
||||||
|
return 0;
|
||||||
|
if (a > 255 || b > 255 || c > 255 || d > 255)
|
||||||
|
return 0;
|
||||||
|
return (a << 24) | (b << 16) | (c << 8) | d;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- RtpHelper implementation ---
|
||||||
|
|
||||||
bool RtpHelper::isRtp(const void* buffer, size_t length)
|
bool RtpHelper::isRtp(const void* buffer, size_t length)
|
||||||
{
|
{
|
||||||
if (length < 12)
|
if (length < 12)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
const RtpHeader* h = reinterpret_cast<const RtpHeader*>(buffer);
|
const RtpHeader* h = reinterpret_cast<const RtpHeader*>(buffer);
|
||||||
if (h->version != 0b10)
|
if (h->version != 2)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
unsigned char pt = h->pt;
|
unsigned char pt = h->pt;
|
||||||
bool rtp = ( (pt & 0x7F) >= 96 && (pt & 0x7F) <= 127) || ((pt & 0x7F) < 35);
|
bool rtp = (pt >= 96 && pt <= 127) || (pt < 35);
|
||||||
return rtp;
|
return rtp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool RtpHelper::isRtpOrRtcp(const void* buffer, size_t length)
|
bool RtpHelper::isRtpOrRtcp(const void* buffer, size_t length)
|
||||||
{
|
{
|
||||||
if (length < 12)
|
if (length < 12)
|
||||||
return false;
|
return false;
|
||||||
const RtcpHeader* h = reinterpret_cast<const RtcpHeader*>(buffer);
|
const RtcpHeader* h = reinterpret_cast<const RtcpHeader*>(buffer);
|
||||||
return h->version == 0b10;
|
return h->version == 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RtpHelper::isRtcp(const void* buffer, size_t length)
|
bool RtpHelper::isRtcp(const void* buffer, size_t length)
|
||||||
@@ -83,15 +108,16 @@ unsigned RtpHelper::findSsrc(const void* buffer, size_t length)
|
|||||||
{
|
{
|
||||||
if (isRtp(buffer, length))
|
if (isRtp(buffer, length))
|
||||||
return ntohl(reinterpret_cast<const RtpHeader*>(buffer)->ssrc);
|
return ntohl(reinterpret_cast<const RtpHeader*>(buffer)->ssrc);
|
||||||
else
|
else if (isRtpOrRtcp(buffer, length))
|
||||||
return ntohl(reinterpret_cast<const RtcpHeader*>(buffer)->ssrc);
|
return ntohl(reinterpret_cast<const RtcpHeader*>(buffer)->ssrc);
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RtpHelper::setSsrc(void* buffer, size_t length, uint32_t ssrc)
|
void RtpHelper::setSsrc(void* buffer, size_t length, uint32_t ssrc)
|
||||||
{
|
{
|
||||||
if (isRtp(buffer, length))
|
if (isRtp(buffer, length))
|
||||||
reinterpret_cast<RtpHeader*>(buffer)->ssrc = htonl(ssrc);
|
reinterpret_cast<RtpHeader*>(buffer)->ssrc = htonl(ssrc);
|
||||||
else
|
else if (isRtpOrRtcp(buffer, length))
|
||||||
reinterpret_cast<RtcpHeader*>(buffer)->ssrc = htonl(ssrc);
|
reinterpret_cast<RtcpHeader*>(buffer)->ssrc = htonl(ssrc);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,47 +139,191 @@ int RtpHelper::findPacketNo(const void *buffer, size_t length)
|
|||||||
|
|
||||||
int RtpHelper::findPayloadLength(const void* buffer, size_t length)
|
int RtpHelper::findPayloadLength(const void* buffer, size_t length)
|
||||||
{
|
{
|
||||||
if (isRtp(buffer, length))
|
if (!isRtp(buffer, length))
|
||||||
{
|
|
||||||
return length - 12;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
|
const RtpHeader* h = reinterpret_cast<const RtpHeader*>(buffer);
|
||||||
|
const uint8_t* p = static_cast<const uint8_t*>(buffer);
|
||||||
|
|
||||||
|
// Fixed header (12 bytes) + CSRC list (4 * CC bytes)
|
||||||
|
size_t offset = 12 + 4u * h->cc;
|
||||||
|
if (offset > length)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
// Header extension
|
||||||
|
if (h->x) {
|
||||||
|
if (offset + 4 > length)
|
||||||
|
return -1;
|
||||||
|
uint16_t extWords = (static_cast<uint16_t>(p[offset + 2]) << 8) | p[offset + 3];
|
||||||
|
offset += 4 + 4u * extWords;
|
||||||
|
if (offset > length)
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t payloadLen = length - offset;
|
||||||
|
|
||||||
|
// Padding
|
||||||
|
if (h->p && payloadLen > 0) {
|
||||||
|
uint8_t padBytes = p[length - 1];
|
||||||
|
if (padBytes > payloadLen)
|
||||||
|
return -1;
|
||||||
|
payloadLen -= padBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
return static_cast<int>(payloadLen);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(USE_RTPDUMP)
|
std::chrono::microseconds RtpHelper::toMicroseconds(const jrtplib::RTPTime& t)
|
||||||
RtpDump::RtpDump(const char *filename)
|
|
||||||
:mFilename(filename)
|
|
||||||
{}
|
|
||||||
|
|
||||||
RtpDump::~RtpDump()
|
|
||||||
{
|
{
|
||||||
flush();
|
return std::chrono::microseconds(uint64_t(t.GetDouble() * 1000000));
|
||||||
for (PacketList::iterator packetIter=mPacketList.begin(); packetIter!=mPacketList.end(); ++packetIter)
|
}
|
||||||
{
|
|
||||||
//free(packetIter->mData);
|
// --- RtpDump implementation ---
|
||||||
delete packetIter->mPacket;
|
|
||||||
|
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()
|
void RtpDump::load()
|
||||||
{
|
{
|
||||||
FILE* f = fopen(mFilename.c_str(), "rb");
|
if (mFilename.empty())
|
||||||
if (!f)
|
throw std::runtime_error("No filename specified");
|
||||||
throw Exception(ERR_WAVFILE_FAILED);
|
|
||||||
|
|
||||||
while (!feof(f))
|
std::ifstream input(mFilename, std::ios::binary);
|
||||||
{
|
if (!input.is_open())
|
||||||
RtpData data;
|
throw std::runtime_error("Failed to open RTP dump file: " + mFilename);
|
||||||
fread(&data.mLength, sizeof data.mLength, 1, f);
|
|
||||||
data.mData = new char[data.mLength];
|
mPacketList.clear();
|
||||||
fread(data.mData, 1, data.mLength, f);
|
|
||||||
jrtplib::RTPIPv4Address addr(jrtplib::RTPAddress::IPv4Address);
|
// --- 1. Text header: "#!rtpplay1.0 <ip>/<port>\n" ---
|
||||||
jrtplib::RTPTime t(0);
|
std::string textLine;
|
||||||
jrtplib::RTPRawPacket* raw = new jrtplib::RTPRawPacket((unsigned char*)data.mData, data.mLength, &addr, t, true);
|
std::getline(input, textLine);
|
||||||
data.mPacket = new jrtplib::RTPPacket(*raw);
|
if (textLine.compare(0, sizeof(RTPDUMP_SHEBANG) - 1, RTPDUMP_SHEBANG) != 0)
|
||||||
mPacketList.push_back(data);
|
throw std::runtime_error("Invalid rtpdump header: expected " + std::string(RTPDUMP_SHEBANG));
|
||||||
|
|
||||||
|
// Parse source address from the text line
|
||||||
|
size_t spacePos = textLine.find(' ');
|
||||||
|
if (spacePos != std::string::npos) {
|
||||||
|
std::string addrPart = textLine.substr(spacePos + 1);
|
||||||
|
size_t slashPos = addrPart.find('/');
|
||||||
|
if (slashPos != std::string::npos) {
|
||||||
|
mSourceIp = stringToIp(addrPart.substr(0, slashPos));
|
||||||
|
try {
|
||||||
|
mSourcePort = static_cast<uint16_t>(std::stoi(addrPart.substr(slashPos + 1)));
|
||||||
|
} catch (...) {
|
||||||
|
mSourcePort = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- 2. Binary file header (RD_hdr_t, 16 bytes) ---
|
||||||
|
uint32_t buf32;
|
||||||
|
uint16_t buf16;
|
||||||
|
|
||||||
|
input.read(reinterpret_cast<char*>(&buf32), 4);
|
||||||
|
mStartSec = ntohl(buf32);
|
||||||
|
|
||||||
|
input.read(reinterpret_cast<char*>(&buf32), 4);
|
||||||
|
mStartUsec = ntohl(buf32);
|
||||||
|
|
||||||
|
input.read(reinterpret_cast<char*>(&buf32), 4); // source IP (already NBO in file)
|
||||||
|
// The binary header stores IP in network byte order; convert to host
|
||||||
|
mSourceIp = ntohl(buf32);
|
||||||
|
|
||||||
|
input.read(reinterpret_cast<char*>(&buf16), 2);
|
||||||
|
mSourcePort = ntohs(buf16);
|
||||||
|
|
||||||
|
input.read(reinterpret_cast<char*>(&buf16), 2); // padding — discard
|
||||||
|
|
||||||
|
if (!input.good())
|
||||||
|
throw std::runtime_error("Failed to read rtpdump binary header");
|
||||||
|
|
||||||
|
// --- 3. Packet records ---
|
||||||
|
size_t packetCount = 0;
|
||||||
|
|
||||||
|
while (input.good() && input.peek() != EOF) {
|
||||||
|
// Packet header: length(2) + plen(2) + offset(4) = 8 bytes
|
||||||
|
uint16_t recLength, plen;
|
||||||
|
uint32_t offsetMs;
|
||||||
|
|
||||||
|
input.read(reinterpret_cast<char*>(&recLength), 2);
|
||||||
|
if (input.gcount() != 2) break;
|
||||||
|
recLength = ntohs(recLength);
|
||||||
|
|
||||||
|
input.read(reinterpret_cast<char*>(&plen), 2);
|
||||||
|
if (input.gcount() != 2) break;
|
||||||
|
plen = ntohs(plen);
|
||||||
|
|
||||||
|
input.read(reinterpret_cast<char*>(&offsetMs), 4);
|
||||||
|
if (input.gcount() != 4) break;
|
||||||
|
offsetMs = ntohl(offsetMs);
|
||||||
|
|
||||||
|
// All-zeros record signals end of file in some implementations
|
||||||
|
if (recLength == 0 && plen == 0 && offsetMs == 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (plen == 0 || plen > MAX_RTP_PACKET_SIZE)
|
||||||
|
throw std::runtime_error("Invalid packet payload length: " + std::to_string(plen));
|
||||||
|
|
||||||
|
if (recLength < plen + 8)
|
||||||
|
throw std::runtime_error("Record length (" + std::to_string(recLength) +
|
||||||
|
") smaller than payload + header (" + std::to_string(plen + 8) + ")");
|
||||||
|
|
||||||
|
// Read body
|
||||||
|
std::vector<uint8_t> body(plen);
|
||||||
|
input.read(reinterpret_cast<char*>(body.data()), plen);
|
||||||
|
if (static_cast<size_t>(input.gcount()) != plen)
|
||||||
|
throw std::runtime_error("Incomplete packet data in rtpdump file");
|
||||||
|
|
||||||
|
// Skip any padding between plen and recLength-8
|
||||||
|
size_t pad = static_cast<size_t>(recLength) - 8 - plen;
|
||||||
|
if (pad > 0)
|
||||||
|
input.seekg(static_cast<std::streamoff>(pad), std::ios::cur);
|
||||||
|
|
||||||
|
RtpData entry;
|
||||||
|
entry.mRawData = std::move(body);
|
||||||
|
entry.mOffsetMs = offsetMs;
|
||||||
|
entry.mPacket = parseRtpData(entry.mRawData.data(), entry.mRawData.size());
|
||||||
|
|
||||||
|
mPacketList.push_back(std::move(entry));
|
||||||
|
packetCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
ICELogInfo(<< "Loaded " << packetCount << " packets from " << mFilename);
|
||||||
|
mLoaded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t RtpDump::count() const
|
size_t RtpDump::count() const
|
||||||
@@ -163,39 +333,142 @@ size_t RtpDump::count() const
|
|||||||
|
|
||||||
jrtplib::RTPPacket& RtpDump::packetAt(size_t index)
|
jrtplib::RTPPacket& RtpDump::packetAt(size_t index)
|
||||||
{
|
{
|
||||||
|
if (index >= mPacketList.size())
|
||||||
|
throw std::out_of_range("Packet index out of range: " + std::to_string(index));
|
||||||
|
|
||||||
|
if (!mPacketList[index].mPacket)
|
||||||
|
throw std::runtime_error("No parsed RTP data at index " + std::to_string(index));
|
||||||
|
|
||||||
return *mPacketList[index].mPacket;
|
return *mPacketList[index].mPacket;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const std::vector<uint8_t>& RtpDump::rawDataAt(size_t index) const
|
||||||
|
{
|
||||||
|
if (index >= mPacketList.size())
|
||||||
|
throw std::out_of_range("Packet index out of range: " + std::to_string(index));
|
||||||
|
|
||||||
|
return mPacketList[index].mRawData;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t RtpDump::offsetAt(size_t index) const
|
||||||
|
{
|
||||||
|
if (index >= mPacketList.size())
|
||||||
|
throw std::out_of_range("Packet index out of range: " + std::to_string(index));
|
||||||
|
|
||||||
|
return mPacketList[index].mOffsetMs;
|
||||||
|
}
|
||||||
|
|
||||||
void RtpDump::add(const void* buffer, size_t len)
|
void RtpDump::add(const void* buffer, size_t len)
|
||||||
{
|
{
|
||||||
RtpData data;
|
if (!buffer || len == 0)
|
||||||
data.mData = malloc(len);
|
return;
|
||||||
memcpy(data.mData, buffer, len);
|
|
||||||
data.mLength = len;
|
|
||||||
|
|
||||||
jrtplib::RTPIPv4Address addr(jrtplib::RTPAddress::IPv4Address);
|
uint32_t offsetMs = 0;
|
||||||
jrtplib::RTPTime t(0);
|
auto now = std::chrono::steady_clock::now();
|
||||||
jrtplib::RTPRawPacket* raw = new jrtplib::RTPRawPacket((unsigned char*)const_cast<void*>(data.mData), data.mLength, &addr, t, true);
|
|
||||||
data.mPacket = new jrtplib::RTPPacket(*raw);
|
if (!mRecording) {
|
||||||
//delete raw;
|
mRecording = true;
|
||||||
mPacketList.push_back(data);
|
mRecordStart = now;
|
||||||
|
|
||||||
|
// Capture wall-clock start time
|
||||||
|
auto wallNow = std::chrono::system_clock::now();
|
||||||
|
auto epoch = wallNow.time_since_epoch();
|
||||||
|
auto sec = std::chrono::duration_cast<std::chrono::seconds>(epoch);
|
||||||
|
auto usec = std::chrono::duration_cast<std::chrono::microseconds>(epoch - sec);
|
||||||
|
mStartSec = static_cast<uint32_t>(sec.count());
|
||||||
|
mStartUsec = static_cast<uint32_t>(usec.count());
|
||||||
|
} else {
|
||||||
|
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - mRecordStart);
|
||||||
|
offsetMs = static_cast<uint32_t>(elapsed.count());
|
||||||
|
}
|
||||||
|
|
||||||
|
add(buffer, len, offsetMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RtpDump::add(const void* buffer, size_t len, uint32_t offsetMs)
|
||||||
|
{
|
||||||
|
if (!buffer || len == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (len > MAX_RTP_PACKET_SIZE)
|
||||||
|
throw std::runtime_error("Packet too large: " + std::to_string(len));
|
||||||
|
|
||||||
|
RtpData entry;
|
||||||
|
entry.mRawData.assign(static_cast<const uint8_t*>(buffer),
|
||||||
|
static_cast<const uint8_t*>(buffer) + len);
|
||||||
|
entry.mOffsetMs = offsetMs;
|
||||||
|
entry.mPacket = parseRtpData(entry.mRawData.data(), entry.mRawData.size());
|
||||||
|
|
||||||
|
mPacketList.push_back(std::move(entry));
|
||||||
}
|
}
|
||||||
|
|
||||||
void RtpDump::flush()
|
void RtpDump::flush()
|
||||||
{
|
{
|
||||||
FILE* f = fopen(mFilename.c_str(), "wb");
|
if (mFilename.empty())
|
||||||
if (!f)
|
throw std::runtime_error("No filename specified");
|
||||||
throw Exception(ERR_WAVFILE_FAILED);
|
|
||||||
|
|
||||||
PacketList::iterator packetIter = mPacketList.begin();
|
std::ofstream output(mFilename, std::ios::binary);
|
||||||
for (;packetIter != mPacketList.end(); ++packetIter)
|
if (!output.is_open())
|
||||||
{
|
throw std::runtime_error("Failed to open file for writing: " + mFilename);
|
||||||
RtpData& data = *packetIter;
|
|
||||||
// Disabled for debugging only
|
// --- 1. Text header ---
|
||||||
//fwrite(&data.mLength, sizeof data.mLength, 1, f);
|
std::string textLine = std::string(RTPDUMP_SHEBANG) + " " +
|
||||||
fwrite(data.mData, data.mLength, 1, f);
|
ipToString(mSourceIp) + "/" +
|
||||||
|
std::to_string(mSourcePort) + "\n";
|
||||||
|
output.write(textLine.data(), static_cast<std::streamsize>(textLine.size()));
|
||||||
|
|
||||||
|
// --- 2. Binary file header (16 bytes) ---
|
||||||
|
uint32_t buf32;
|
||||||
|
uint16_t buf16;
|
||||||
|
|
||||||
|
buf32 = htonl(mStartSec);
|
||||||
|
output.write(reinterpret_cast<const char*>(&buf32), 4);
|
||||||
|
|
||||||
|
buf32 = htonl(mStartUsec);
|
||||||
|
output.write(reinterpret_cast<const char*>(&buf32), 4);
|
||||||
|
|
||||||
|
buf32 = htonl(mSourceIp);
|
||||||
|
output.write(reinterpret_cast<const char*>(&buf32), 4);
|
||||||
|
|
||||||
|
buf16 = htons(mSourcePort);
|
||||||
|
output.write(reinterpret_cast<const char*>(&buf16), 2);
|
||||||
|
|
||||||
|
buf16 = 0; // padding
|
||||||
|
output.write(reinterpret_cast<const char*>(&buf16), 2);
|
||||||
|
|
||||||
|
// --- 3. Packet records ---
|
||||||
|
size_t written = 0;
|
||||||
|
|
||||||
|
for (const auto& pkt : mPacketList) {
|
||||||
|
if (pkt.mRawData.empty())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
uint16_t plen = static_cast<uint16_t>(pkt.mRawData.size());
|
||||||
|
uint16_t recLength = static_cast<uint16_t>(plen + 8);
|
||||||
|
|
||||||
|
buf16 = htons(recLength);
|
||||||
|
output.write(reinterpret_cast<const char*>(&buf16), 2);
|
||||||
|
|
||||||
|
buf16 = htons(plen);
|
||||||
|
output.write(reinterpret_cast<const char*>(&buf16), 2);
|
||||||
|
|
||||||
|
buf32 = htonl(pkt.mOffsetMs);
|
||||||
|
output.write(reinterpret_cast<const char*>(&buf32), 4);
|
||||||
|
|
||||||
|
output.write(reinterpret_cast<const char*>(pkt.mRawData.data()), plen);
|
||||||
|
|
||||||
|
written++;
|
||||||
}
|
}
|
||||||
fclose(f);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
if (!output.good())
|
||||||
|
throw std::runtime_error("Failed to write rtpdump file: " + mFilename);
|
||||||
|
|
||||||
|
ICELogInfo(<< "Wrote " << written << " packets to " << mFilename);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RtpDump::clear()
|
||||||
|
{
|
||||||
|
mPacketList.clear();
|
||||||
|
mLoaded = false;
|
||||||
|
mRecording = false;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright(C) 2007-2025 VoIPobjects (voipobjects.com)
|
/* Copyright(C) 2007-2026 VoIPobjects (voipobjects.com)
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
@@ -6,12 +6,14 @@
|
|||||||
#ifndef __HL_RTP_H
|
#ifndef __HL_RTP_H
|
||||||
#define __HL_RTP_H
|
#define __HL_RTP_H
|
||||||
|
|
||||||
#if defined(USE_RTPDUMP)
|
#include "jrtplib/src/rtppacket.h"
|
||||||
# include "jrtplib/src/rtppacket.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <stdlib.h>
|
#include <cstdlib>
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include <memory>
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
// Class to carry rtp/rtcp socket pair
|
// Class to carry rtp/rtcp socket pair
|
||||||
template<class T>
|
template<class T>
|
||||||
@@ -27,7 +29,7 @@ struct RtpPair
|
|||||||
:mRtp(rtp), mRtcp(rtcp)
|
:mRtp(rtp), mRtcp(rtcp)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
bool multiplexed() { return mRtp == mRtcp; }
|
bool multiplexed() const { return mRtp == mRtcp; }
|
||||||
};
|
};
|
||||||
|
|
||||||
class RtpHelper
|
class RtpHelper
|
||||||
@@ -35,39 +37,114 @@ class RtpHelper
|
|||||||
public:
|
public:
|
||||||
static bool isRtp(const void* buffer, size_t length);
|
static bool isRtp(const void* buffer, size_t length);
|
||||||
static int findPtype(const void* buffer, size_t length);
|
static int findPtype(const void* buffer, size_t length);
|
||||||
static int findPacketNo(const void* buffer, size_t length);
|
static int findPacketNo(const void *buffer, size_t length);
|
||||||
static bool isRtpOrRtcp(const void* buffer, size_t length);
|
static bool isRtpOrRtcp(const void* buffer, size_t length);
|
||||||
static bool isRtcp(const void* buffer, size_t length);
|
static bool isRtcp(const void* buffer, size_t length);
|
||||||
static unsigned findSsrc(const void* buffer, size_t length);
|
static unsigned findSsrc(const void* buffer, size_t length);
|
||||||
static void setSsrc(void* buffer, size_t length, uint32_t ssrc);
|
static void setSsrc(void* buffer, size_t length, uint32_t ssrc);
|
||||||
static int findPayloadLength(const void* buffer, size_t length);
|
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
|
class RtpDump
|
||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
struct RtpData
|
struct RtpData
|
||||||
{
|
{
|
||||||
jrtplib::RTPPacket* mPacket;
|
std::shared_ptr<jrtplib::RTPPacket> mPacket;
|
||||||
void* mData;
|
std::vector<uint8_t> mRawData;
|
||||||
size_t mLength;
|
uint32_t mOffsetMs = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef std::vector<RtpData> PacketList;
|
typedef std::vector<RtpData> PacketList;
|
||||||
PacketList mPacketList;
|
PacketList mPacketList;
|
||||||
std::string mFilename;
|
std::string mFilename;
|
||||||
|
bool mLoaded = false;
|
||||||
|
|
||||||
|
// File header fields
|
||||||
|
uint32_t mSourceIp = 0;
|
||||||
|
uint16_t mSourcePort = 0;
|
||||||
|
uint32_t mStartSec = 0;
|
||||||
|
uint32_t mStartUsec = 0;
|
||||||
|
|
||||||
|
// Auto-compute packet offsets during recording
|
||||||
|
bool mRecording = false;
|
||||||
|
std::chrono::steady_clock::time_point mRecordStart;
|
||||||
|
|
||||||
|
std::shared_ptr<jrtplib::RTPPacket> parseRtpData(const uint8_t* data, size_t len);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
RtpDump(const char* filename);
|
explicit RtpDump(const char* filename);
|
||||||
~RtpDump();
|
~RtpDump();
|
||||||
|
|
||||||
|
/** Set source address for the file header (host byte order). */
|
||||||
|
void setSource(uint32_t ip, uint16_t port);
|
||||||
|
uint32_t sourceIp() const { return mSourceIp; }
|
||||||
|
uint16_t sourcePort() const { return mSourcePort; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Load packets from an rtpdump file
|
||||||
|
* @throws std::runtime_error on file/format error
|
||||||
|
*/
|
||||||
void load();
|
void load();
|
||||||
|
bool isLoaded() const { return mLoaded; }
|
||||||
|
|
||||||
size_t count() const;
|
size_t count() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get parsed RTP packet at index
|
||||||
|
* @throws std::out_of_range if index is invalid
|
||||||
|
* @throws std::runtime_error if packet could not be parsed as RTP
|
||||||
|
*/
|
||||||
jrtplib::RTPPacket& packetAt(size_t index);
|
jrtplib::RTPPacket& packetAt(size_t index);
|
||||||
|
|
||||||
|
/** @brief Get raw packet bytes at index */
|
||||||
|
const std::vector<uint8_t>& rawDataAt(size_t index) const;
|
||||||
|
|
||||||
|
/** @brief Get packet time offset in milliseconds */
|
||||||
|
uint32_t offsetAt(size_t index) const;
|
||||||
|
|
||||||
|
/** @brief Add a packet; time offset is auto-computed from first add() call */
|
||||||
void add(const void* data, size_t len);
|
void add(const void* data, size_t len);
|
||||||
|
|
||||||
|
/** @brief Add a packet with an explicit millisecond offset */
|
||||||
|
void add(const void* data, size_t len, uint32_t offsetMs);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Write all packets to file in rtpdump format
|
||||||
|
* @throws std::runtime_error on file error
|
||||||
|
*/
|
||||||
void flush();
|
void flush();
|
||||||
|
|
||||||
|
void clear();
|
||||||
|
const std::string& filename() const { return mFilename; }
|
||||||
};
|
};
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -280,7 +280,8 @@ size_t TimerQueue::cancel(uint64_t id) {
|
|||||||
//! Cancels all timers
|
//! Cancels all timers
|
||||||
// \return
|
// \return
|
||||||
// The number of timers cancelled
|
// The number of timers cancelled
|
||||||
size_t TimerQueue::cancelAll() {
|
size_t TimerQueue::cancelAll()
|
||||||
|
{
|
||||||
// Setting all "end" to 0 (for immediate execution) is ok,
|
// Setting all "end" to 0 (for immediate execution) is ok,
|
||||||
// since it maintains the heap integrity
|
// since it maintains the heap integrity
|
||||||
std::unique_lock<std::mutex> lk(m_mtx);
|
std::unique_lock<std::mutex> lk(m_mtx);
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ void thread_pool::run_worker()
|
|||||||
tasks.pop();
|
tasks.pop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
t(); // function<void()> type
|
if (t)
|
||||||
|
t(); // function<void()> type
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -240,7 +240,7 @@ int64_t Variant::asInt64() const
|
|||||||
if (mType != VTYPE_INT64)
|
if (mType != VTYPE_INT64)
|
||||||
throw Exception(ERR_BAD_VARIANT_TYPE);
|
throw Exception(ERR_BAD_VARIANT_TYPE);
|
||||||
|
|
||||||
return mInt;
|
return mInt64;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Variant::asBool() const
|
bool Variant::asBool() const
|
||||||
|
|||||||
@@ -3,11 +3,8 @@
|
|||||||
|
|
||||||
#include "MT_AmrCodec.h"
|
#include "MT_AmrCodec.h"
|
||||||
#include "../helper/HL_ByteBuffer.h"
|
#include "../helper/HL_ByteBuffer.h"
|
||||||
#include "../helper/HL_Log.h"
|
|
||||||
#include "../helper/HL_IuUP.h"
|
#include "../helper/HL_IuUP.h"
|
||||||
#include "../helper/HL_Exception.h"
|
#include "../helper/HL_Log.h"
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
#define LOG_SUBSYSTEM "AmrCodec"
|
#define LOG_SUBSYSTEM "AmrCodec"
|
||||||
using namespace MT;
|
using namespace MT;
|
||||||
@@ -31,37 +28,37 @@ const uint16_t amrwb_framelenbits[10] =
|
|||||||
|
|
||||||
struct AmrPayloadInfo
|
struct AmrPayloadInfo
|
||||||
{
|
{
|
||||||
const uint8_t* mPayload;
|
const uint8_t* mPayload = nullptr;
|
||||||
int mPayloadLength;
|
int mPayloadLength = 0;
|
||||||
bool mOctetAligned;
|
bool mOctetAligned = false;
|
||||||
bool mInterleaving;
|
bool mInterleaving = false;
|
||||||
bool mWideband;
|
bool mWideband = false;
|
||||||
uint64_t mCurrentTimestamp;
|
uint64_t mCurrentTimestamp = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
struct AmrFrame
|
struct AmrFrame
|
||||||
{
|
{
|
||||||
uint8_t mFrameType;
|
uint8_t mFrameType = 0;
|
||||||
uint8_t mMode;
|
uint8_t mMode = 0;
|
||||||
bool mGoodQuality;
|
bool mGoodQuality = false;
|
||||||
uint64_t mTimestamp;
|
uint64_t mTimestamp = 0;
|
||||||
std::shared_ptr<ByteBuffer> mData;
|
std::shared_ptr<ByteBuffer> mData;
|
||||||
uint8_t mSTI;
|
uint8_t mSTI = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct AmrPayload
|
struct AmrPayload
|
||||||
{
|
{
|
||||||
uint8_t mCodeModeRequest;
|
uint8_t mCodeModeRequest = 0;
|
||||||
std::vector<AmrFrame> mFrames;
|
std::vector<AmrFrame> mFrames;
|
||||||
bool mDiscardPacket;
|
bool mDiscardPacket = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ARM RTP payload has next structure
|
// ARM RTP payload has next structure
|
||||||
// Header
|
// Header
|
||||||
// Table of Contents
|
// Table of Contents
|
||||||
// Frames
|
// Frames
|
||||||
static AmrPayload parseAmrPayload(AmrPayloadInfo& input)
|
static AmrPayload parseAmrPayload(AmrPayloadInfo& input, size_t& cngCounter)
|
||||||
{
|
{
|
||||||
AmrPayload result;
|
AmrPayload result;
|
||||||
|
|
||||||
@@ -128,6 +125,8 @@ static AmrPayload parseAmrPayload(AmrPayloadInfo& input)
|
|||||||
frame.mTimestamp = input.mCurrentTimestamp;
|
frame.mTimestamp = input.mCurrentTimestamp;
|
||||||
result.mFrames.push_back(frame);
|
result.mFrames.push_back(frame);
|
||||||
input.mCurrentTimestamp += input.mWideband ? 320 : 160;
|
input.mCurrentTimestamp += input.mWideband ? 320 : 160;
|
||||||
|
if (FT == SID_FT)
|
||||||
|
cngCounter++;
|
||||||
}
|
}
|
||||||
while (F != 0);
|
while (F != 0);
|
||||||
|
|
||||||
@@ -140,13 +139,17 @@ static AmrPayload parseAmrPayload(AmrPayloadInfo& input)
|
|||||||
// avoid the loss of data synchronization in the depacketization
|
// avoid the loss of data synchronization in the depacketization
|
||||||
// process, which can result in a huge degradation in speech quality.
|
// 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);
|
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)
|
if (discard)
|
||||||
{
|
{
|
||||||
result.mDiscardPacket = true;
|
result.mDiscardPacket = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if (input.mWideband && f.mMode == 0xFF /* CNG */)
|
||||||
|
// {
|
||||||
|
// int a = 1;
|
||||||
|
// }`
|
||||||
|
|
||||||
if (input.mWideband && f.mFrameType == 15)
|
if (input.mWideband && f.mFrameType == 15)
|
||||||
{
|
{
|
||||||
// DTX, no sense to decode the data
|
// DTX, no sense to decode the data
|
||||||
@@ -165,8 +168,8 @@ static AmrPayload parseAmrPayload(AmrPayloadInfo& input)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t bitsLength = input.mWideband ? amrwb_framelenbits[f.mFrameType] : amrnb_framelenbits[f.mFrameType];
|
size_t bitsLength = input.mWideband ? amrwb_framelenbits[f.mFrameType] : amrnb_framelenbits[f.mFrameType];
|
||||||
size_t byteLength = input.mWideband ? amrwb_framelen[f.mFrameType] : amrnb_framelen[f.mFrameType];
|
size_t byteLength = input.mWideband ? amrwb_framelen[f.mFrameType] : amrnb_framelen[f.mFrameType];
|
||||||
|
|
||||||
if (bitsLength > 0)
|
if (bitsLength > 0)
|
||||||
{
|
{
|
||||||
@@ -260,8 +263,7 @@ PCodec AmrNbCodec::CodecFactory::create()
|
|||||||
|
|
||||||
|
|
||||||
AmrNbCodec::AmrNbCodec(const AmrCodecConfig& config)
|
AmrNbCodec::AmrNbCodec(const AmrCodecConfig& config)
|
||||||
:mEncoderCtx(nullptr), mDecoderCtx(nullptr), mConfig(config), mCurrentDecoderTimestamp(0),
|
:mConfig(config)
|
||||||
mSwitchCounter(0), mPreviousPacketLength(0)
|
|
||||||
{
|
{
|
||||||
mEncoderCtx = Encoder_Interface_init(1);
|
mEncoderCtx = Encoder_Interface_init(1);
|
||||||
mDecoderCtx = Decoder_Interface_init();
|
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;
|
if (input.size_bytes() % pcmLength())
|
||||||
}
|
return {.mEncoded = 0};
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
// Declare the data input pointer
|
// Declare the data input pointer
|
||||||
auto *dataIn = (const short *)input;
|
auto *dataIn = (const short *)input.data();
|
||||||
|
|
||||||
// Declare the data output pointer
|
// Declare the data output pointer
|
||||||
auto *dataOut = (unsigned char *)output;
|
auto *dataOut = (unsigned char *)output.data();
|
||||||
|
|
||||||
// Find how much RTP frames will be generated
|
// Find how much RTP frames will be generated
|
||||||
unsigned int frames = inputBytes / pcmLength();
|
unsigned int frames = input.size_bytes() / pcmLength();
|
||||||
|
|
||||||
// Generate frames
|
// Generate frames
|
||||||
for (unsigned int i = 0; i < frames; i++)
|
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;
|
dataIn += pcmLength() / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
return dataOut - (unsigned char*)output;
|
return {.mEncoded = (size_t)(dataOut - (unsigned char*)output.data())};
|
||||||
}
|
}
|
||||||
|
|
||||||
#define L_FRAME 160
|
#define L_FRAME 160
|
||||||
#define AMR_BITRATE_DTX 15
|
#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)
|
if (mConfig.mOctetAligned)
|
||||||
return 0;
|
return {.mDecoded = 0};
|
||||||
|
|
||||||
if (mConfig.mIuUP)
|
if (mConfig.mIuUP)
|
||||||
{
|
{
|
||||||
// Try to parse IuUP frame
|
// Try to parse IuUP frame
|
||||||
IuUP::Frame frame;
|
IuUP::Frame frame;
|
||||||
if (!IuUP::parse2((const uint8_t*)input, inputBytes, frame))
|
if (!IuUP::parse2((const uint8_t*)input.data(), input.size_bytes(), frame))
|
||||||
return 0;
|
return {0};
|
||||||
|
|
||||||
// Check if CRC failed - it is check from IuUP data
|
// Check if CRC failed - it is check from IuUP data
|
||||||
if (!frame.mHeaderCrcOk || !frame.mPayloadCrcOk)
|
if (!frame.mHeaderCrcOk || !frame.mPayloadCrcOk)
|
||||||
{
|
{
|
||||||
ICELogInfo(<< "CRC check failed.");
|
ICELogInfo(<< "CRC check failed.");
|
||||||
return 0;
|
return {0};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build NB frame to decode
|
// 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
|
// Check if frameType comparing is correct
|
||||||
if (frameType == 0xFF)
|
if (frameType == 0xFF)
|
||||||
return 0;
|
return {0};
|
||||||
|
|
||||||
dataToDecode.mutableData()[0] = (frameType << 3) | (1 << 2);
|
dataToDecode.mutableData()[0] = (frameType << 3) | (1 << 2);
|
||||||
|
|
||||||
Decoder_Interface_Decode(mDecoderCtx, (const unsigned char*)dataToDecode.data(), (short*)output, 0);
|
Decoder_Interface_Decode(mDecoderCtx, (const unsigned char*)dataToDecode.data(), (short*)output.data(), 0);
|
||||||
return pcmLength();
|
return {.mDecoded = (size_t)pcmLength()};
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (outputCapacity < pcmLength())
|
if (output.size_bytes() < pcmLength())
|
||||||
return 0;
|
return {.mDecoded = 0};
|
||||||
|
|
||||||
if (inputBytes == 0)
|
if (input.size_bytes() == 0)
|
||||||
{ // PLC part
|
{ // PLC part
|
||||||
unsigned char buffer[32];
|
unsigned char buffer[32];
|
||||||
buffer[0] = (AMR_BITRATE_DTX << 3)|4;
|
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
|
||||||
return pcmLength();
|
return {.mDecoded = (size_t)pcmLength()};
|
||||||
}
|
}
|
||||||
|
|
||||||
AmrPayloadInfo info;
|
AmrPayloadInfo info;
|
||||||
info.mCurrentTimestamp = mCurrentDecoderTimestamp;
|
info.mCurrentTimestamp = mCurrentDecoderTimestamp;
|
||||||
info.mOctetAligned = mConfig.mOctetAligned;
|
info.mOctetAligned = mConfig.mOctetAligned;
|
||||||
info.mPayload = (const uint8_t*)input;
|
info.mPayload = input.data();
|
||||||
info.mPayloadLength = inputBytes;
|
info.mPayloadLength = input.size_bytes();
|
||||||
info.mWideband = false;
|
info.mWideband = false;
|
||||||
info.mInterleaving = false;
|
info.mInterleaving = false;
|
||||||
|
|
||||||
AmrPayload ap;
|
AmrPayload ap;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ap = parseAmrPayload(info);
|
ap = parseAmrPayload(info, mCngCounter);
|
||||||
}
|
}
|
||||||
catch(...)
|
catch(...)
|
||||||
{
|
{
|
||||||
ICELogDebug(<< "Failed to decode AMR payload.");
|
ICELogDebug(<< "Failed to decode AMR payload.");
|
||||||
return 0;
|
return {.mDecoded = 0};
|
||||||
}
|
}
|
||||||
// Save current timestamp
|
// Save current timestamp
|
||||||
mCurrentDecoderTimestamp = info.mCurrentTimestamp;
|
mCurrentDecoderTimestamp = info.mCurrentTimestamp;
|
||||||
|
|
||||||
// Check if packet is corrupted
|
// Check if packet is corrupted
|
||||||
if (ap.mDiscardPacket)
|
if (ap.mDiscardPacket)
|
||||||
return 0;
|
return {.mDecoded = 0};
|
||||||
|
|
||||||
|
|
||||||
// Check for output buffer capacity
|
// Check for output buffer capacity
|
||||||
if (outputCapacity < (int)ap.mFrames.size() * pcmLength())
|
if (output.size_bytes() < (int)ap.mFrames.size() * pcmLength())
|
||||||
return 0;
|
return {.mDecoded = 0};
|
||||||
|
|
||||||
if (ap.mFrames.empty())
|
if (ap.mFrames.empty())
|
||||||
{
|
{
|
||||||
ICELogError(<< "No AMR frames");
|
ICELogError(<< "No AMR frames");
|
||||||
}
|
}
|
||||||
short* dataOut = (short*)output;
|
short* dataOut = (short*)output.data();
|
||||||
for (AmrFrame& frame: ap.mFrames)
|
for (AmrFrame& frame: ap.mFrames)
|
||||||
{
|
{
|
||||||
if (frame.mData)
|
if (frame.mData)
|
||||||
@@ -430,18 +419,18 @@ int AmrNbCodec::decode(const void* input, int inputBytes, void* output, int outp
|
|||||||
dataOut += pcmLength() / 2;
|
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;
|
return 0;
|
||||||
|
|
||||||
short* dataOut = (short*)output;
|
short* dataOut = (short*)output.data();
|
||||||
|
|
||||||
for (int i=0; i < lostFrames; i++)
|
for (int i=0; i < lostFrames; i++)
|
||||||
{
|
{
|
||||||
@@ -459,6 +448,11 @@ int AmrNbCodec::getSwitchCounter() const
|
|||||||
return mSwitchCounter;
|
return mSwitchCounter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int AmrNbCodec::getCngCounter() const
|
||||||
|
{
|
||||||
|
return mCngCounter;
|
||||||
|
}
|
||||||
|
|
||||||
// -------- AMR WB codec
|
// -------- AMR WB codec
|
||||||
AmrWbCodec::CodecFactory::CodecFactory(const AmrCodecConfig& config)
|
AmrWbCodec::CodecFactory::CodecFactory(const AmrCodecConfig& config)
|
||||||
:mConfig(config)
|
:mConfig(config)
|
||||||
@@ -500,11 +494,9 @@ PCodec AmrWbCodec::CodecFactory::create()
|
|||||||
AmrWbStatistics MT::GAmrWbStatistics;
|
AmrWbStatistics MT::GAmrWbStatistics;
|
||||||
|
|
||||||
AmrWbCodec::AmrWbCodec(const AmrCodecConfig& config)
|
AmrWbCodec::AmrWbCodec(const AmrCodecConfig& config)
|
||||||
:mEncoderCtx(nullptr), mDecoderCtx(nullptr), mConfig(config),
|
:mConfig(config)
|
||||||
mSwitchCounter(0), mPreviousPacketLength(0)
|
|
||||||
{
|
{
|
||||||
mDecoderCtx = D_IF_init();
|
mDecoderCtx = D_IF_init();
|
||||||
mCurrentDecoderTimestamp = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AmrWbCodec::~AmrWbCodec()
|
AmrWbCodec::~AmrWbCodec()
|
||||||
@@ -522,49 +514,37 @@ AmrWbCodec::~AmrWbCodec()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* AmrWbCodec::name()
|
Codec::Info AmrWbCodec::info() {
|
||||||
{
|
return {
|
||||||
return MT_AMRWB_CODECNAME;
|
.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
|
// Still no support for encoding - emit silence instead
|
||||||
}
|
return {.mEncoded = 0};
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#define L_FRAME 160
|
#define L_FRAME 160
|
||||||
#define AMR_BITRATE_DTX 15
|
#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;
|
IuUP::Frame frame;
|
||||||
if (!IuUP::parse2(input.data(), input.size(), frame))
|
if (!IuUP::parse2(input.data(), input.size(), frame))
|
||||||
return 0;
|
return {.mDecoded = 0};
|
||||||
|
|
||||||
if (!frame.mHeaderCrcOk || !frame.mPayloadCrcOk)
|
if (!frame.mHeaderCrcOk || !frame.mPayloadCrcOk)
|
||||||
{
|
{
|
||||||
ICELogInfo(<< "CRC check failed.");
|
ICELogInfo(<< "CRC check failed.");
|
||||||
return 0;
|
return {.mDecoded = 0};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reserve space
|
// Reserve space
|
||||||
@@ -579,34 +559,34 @@ int AmrWbCodec::decodeIuup(std::span<const uint8_t> input, std::span<uint8_t> ou
|
|||||||
frameType = ftIndex;
|
frameType = ftIndex;
|
||||||
|
|
||||||
if (frameType == 0xFF)
|
if (frameType == 0xFF)
|
||||||
return 0;
|
return {.mDecoded = 0, .mIsCng = true};
|
||||||
|
|
||||||
dataToDecode.mutableData()[0] = (frameType << 3) | (1 << 2);
|
dataToDecode.mutableData()[0] = (frameType << 3) | (1 << 2);
|
||||||
|
|
||||||
D_IF_decode(mDecoderCtx, (const unsigned char*)dataToDecode.data(), (short*)output.data(), 0);
|
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;
|
AmrPayloadInfo info;
|
||||||
info.mCurrentTimestamp = mCurrentDecoderTimestamp;
|
info.mCurrentTimestamp = mCurrentDecoderTimestamp;
|
||||||
info.mOctetAligned = mConfig.mOctetAligned;
|
info.mOctetAligned = mConfig.mOctetAligned;
|
||||||
info.mPayload = input.data();
|
info.mPayload = input.data();
|
||||||
info.mPayloadLength = input.size();
|
info.mPayloadLength = input.size();
|
||||||
info.mWideband = true;
|
info.mWideband = true;
|
||||||
info.mInterleaving = false;
|
info.mInterleaving = false;
|
||||||
|
|
||||||
AmrPayload ap;
|
AmrPayload ap;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ap = parseAmrPayload(info);
|
ap = parseAmrPayload(info, mCngCounter);
|
||||||
}
|
}
|
||||||
catch(...)
|
catch(...)
|
||||||
{
|
{
|
||||||
GAmrWbStatistics.mNonParsed++;
|
GAmrWbStatistics.mNonParsed++;
|
||||||
ICELogDebug(<< "Failed to decode AMR payload");
|
ICELogDebug(<< "Failed to decode AMR payload");
|
||||||
return 0;
|
return {.mDecoded = 0};
|
||||||
}
|
}
|
||||||
// Save current timestamp
|
// Save current timestamp
|
||||||
mCurrentDecoderTimestamp = info.mCurrentTimestamp;
|
mCurrentDecoderTimestamp = info.mCurrentTimestamp;
|
||||||
@@ -615,58 +595,67 @@ int AmrWbCodec::decodePlain(std::span<const uint8_t> input, std::span<uint8_t> o
|
|||||||
if (ap.mDiscardPacket)
|
if (ap.mDiscardPacket)
|
||||||
{
|
{
|
||||||
GAmrWbStatistics.mDiscarded++;
|
GAmrWbStatistics.mDiscarded++;
|
||||||
return 0;
|
return {.mDecoded = 0};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for output buffer capacity
|
// Find the required output capacity
|
||||||
if (output.size() < (int)ap.mFrames.size() * pcmLength())
|
size_t capacity = 0;
|
||||||
return 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();
|
short* dataOut = (short*)output.data();
|
||||||
size_t dataOutSizeInBytes = 0;
|
size_t dataOutSizeInBytes = 0;
|
||||||
for (AmrFrame& frame: ap.mFrames)
|
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.mData)
|
||||||
{
|
{
|
||||||
|
if (frame.mMode == 0xFF)
|
||||||
|
{
|
||||||
|
// int bp = 1;
|
||||||
|
}
|
||||||
D_IF_decode(mDecoderCtx, (const unsigned char*)frame.mData->data(), (short*)dataOut, 0);
|
D_IF_decode(mDecoderCtx, (const unsigned char*)frame.mData->data(), (short*)dataOut, 0);
|
||||||
dataOut += pcmLength() / 2;
|
dataOut += frameOutputSize / 2;
|
||||||
dataOutSizeInBytes += pcmLength();
|
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)
|
if (mConfig.mIuUP)
|
||||||
return decodeIuup(inputBuffer, outputBuffer);
|
return decodeIuup(input, output);
|
||||||
else
|
else
|
||||||
return decodePlain(inputBuffer, outputBuffer);
|
return decodePlain(input, output);
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
return 0;
|
// For now return the silence
|
||||||
|
memset(output.data(), 0, output.size_bytes());
|
||||||
short* dataOut = (short*)output;
|
|
||||||
|
|
||||||
for (int i=0; i < lostFrames; i++)
|
|
||||||
{
|
|
||||||
unsigned char buffer[32];
|
|
||||||
buffer[0] = (AMR_BITRATE_DTX << 3)|4;
|
|
||||||
Decoder_Interface_Decode(mDecoderCtx, buffer, dataOut, 0); // Handle missing data
|
|
||||||
dataOut += L_FRAME;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
return lostFrames * pcmLength();
|
return lostFrames * pcmLength();
|
||||||
|
/*
|
||||||
|
if (outputCapacity < lostFrames * pcmLength())
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
short* dataOut = (short*)output;
|
||||||
|
|
||||||
|
for (int i=0; i < lostFrames; i++)
|
||||||
|
{
|
||||||
|
unsigned char buffer[32];
|
||||||
|
buffer[0] = (AMR_BITRATE_DTX << 3)|4;
|
||||||
|
Decoder_Interface_Decode(mDecoderCtx, buffer, dataOut, 0); // Handle missing data
|
||||||
|
dataOut += L_FRAME;
|
||||||
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
int AmrWbCodec::getSwitchCounter() const
|
int AmrWbCodec::getSwitchCounter() const
|
||||||
@@ -674,14 +663,16 @@ int AmrWbCodec::getSwitchCounter() const
|
|||||||
return mSwitchCounter;
|
return mSwitchCounter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int AmrWbCodec::getCngCounter() const
|
||||||
|
{
|
||||||
|
return mCngCounter;
|
||||||
|
}
|
||||||
|
|
||||||
// ------------- GSM EFR -----------------
|
// ------------- GSM EFR -----------------
|
||||||
|
|
||||||
GsmEfrCodec::GsmEfrFactory::GsmEfrFactory(bool iuup, int ptype)
|
GsmEfrCodec::GsmEfrFactory::GsmEfrFactory(bool iuup, int ptype)
|
||||||
:mIuUP(iuup), mPayloadType(ptype)
|
:mIuUP(iuup), mPayloadType(ptype)
|
||||||
{
|
{}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* GsmEfrCodec::GsmEfrFactory::name()
|
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)
|
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)
|
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)
|
GsmEfrCodec::GsmEfrCodec(bool iuup)
|
||||||
:mEncoderCtx(nullptr), mDecoderCtx(nullptr), mIuUP(iuup)
|
:mIuUP(iuup)
|
||||||
{
|
{
|
||||||
mEncoderCtx = Encoder_Interface_init(1);
|
mEncoderCtx = Encoder_Interface_init(1);
|
||||||
mDecoderCtx = Decoder_Interface_init();
|
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;
|
if (input.size_bytes() % pcmLength())
|
||||||
}
|
return {.mEncoded = 0};
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
// Declare the data input pointer
|
// Declare the data input pointer
|
||||||
const short *dataIn = (const short *)input;
|
const short *dataIn = (const short *)input.data();
|
||||||
|
|
||||||
// Declare the data output pointer
|
// 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
|
// Find how much RTP frames will be generated
|
||||||
unsigned int frames = inputBytes / pcmLength();
|
unsigned int frames = input.size_bytes() / pcmLength();
|
||||||
|
|
||||||
// Generate frames
|
// Generate frames
|
||||||
for (unsigned int i = 0; i < frames; i++)
|
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;
|
dataIn += pcmLength() / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
return frames * rtpLength();
|
return {.mEncoded = frames * rtpLength()};
|
||||||
}
|
}
|
||||||
|
|
||||||
#define L_FRAME 160
|
#define L_FRAME 160
|
||||||
@@ -843,100 +819,61 @@ const uint16_t gsm690_12_2_bitorder[244] = {
|
|||||||
237, 236, 96, 199,
|
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)
|
if (output.size_bytes() < pcmLength())
|
||||||
{
|
return {.mDecoded = 0};
|
||||||
// Try to parse IuUP frame
|
|
||||||
IuUP::Frame frame;
|
|
||||||
if (!IuUP::parse2((const uint8_t*)input, inputBytes, frame))
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
// Check if CRC failed - it is check from IuUP data
|
if (input.size_bytes() == 0)
|
||||||
if (!frame.mHeaderCrcOk || !frame.mPayloadCrcOk)
|
{ // PLC part
|
||||||
{
|
unsigned char buffer[32];
|
||||||
ICELogInfo(<< "CRC check failed.");
|
buffer[0] = (AMR_BITRATE_DTX << 3)|4;
|
||||||
return 0;
|
Decoder_Interface_Decode(mDecoderCtx, buffer, (short*)output.data(), 0); // Handle missing data
|
||||||
}
|
}
|
||||||
|
else
|
||||||
// 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())
|
// Reorder bytes from input to dst
|
||||||
return 0;
|
uint8_t dst[GSM_EFR_FRAME_LEN];
|
||||||
|
const uint8_t* src = input.data();
|
||||||
|
for (int i=0; i<(GSM_EFR_FRAME_LEN-1); i++)
|
||||||
|
dst[i] = (src[i] << 4) | (src[i+1] >> 4);
|
||||||
|
dst[GSM_EFR_FRAME_LEN-1] = src[GSM_EFR_FRAME_LEN-1] << 4;
|
||||||
|
|
||||||
if (inputBytes == 0)
|
unsigned char in[GSM_EFR_FRAME_LEN + 1];
|
||||||
{ // PLC part
|
|
||||||
unsigned char buffer[32];
|
// Reorder bits
|
||||||
buffer[0] = (AMR_BITRATE_DTX << 3)|4;
|
in[0] = 0x3c; /* AMR mode 7 = GSM-EFR, Quality bit is set */
|
||||||
Decoder_Interface_Decode(mDecoderCtx, buffer, (short*)output, 0); // Handle missing data
|
in[GSM_EFR_FRAME_LEN] = 0x0;
|
||||||
}
|
|
||||||
else
|
for (int i=0; i<244; i++)
|
||||||
{
|
{
|
||||||
// Reorder bytes from input to dst
|
int si = gsm690_12_2_bitorder[i];
|
||||||
uint8_t dst[GSM_EFR_FRAME_LEN];
|
int di = i;
|
||||||
const uint8_t* src = (const uint8_t*)input;
|
msb_put_bit(in + 1, di, msb_get_bit(dst, si));
|
||||||
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;
|
|
||||||
|
|
||||||
unsigned char in[GSM_EFR_FRAME_LEN + 1];
|
// Decode
|
||||||
|
memset(output.data(), 0, pcmLength());
|
||||||
|
Decoder_Interface_Decode(mDecoderCtx, in, (short*)output.data(), 0);
|
||||||
|
|
||||||
// Reorder bits
|
uint8_t* pcm = (uint8_t*)output.data();
|
||||||
in[0] = 0x3c; /* AMR mode 7 = GSM-EFR, Quality bit is set */
|
for (int i=0; i<160; i++)
|
||||||
in[GSM_EFR_FRAME_LEN] = 0x0;
|
{
|
||||||
|
uint16_t w = ((uint16_t*)output.data())[i];
|
||||||
for (int i=0; i<244; i++)
|
pcm[(i<<1) ] = w & 0xff;
|
||||||
{
|
pcm[(i<<1)+1] = (w >> 8) & 0xff;
|
||||||
int si = gsm690_12_2_bitorder[i];
|
|
||||||
int di = i;
|
|
||||||
msb_put_bit(in + 1, di, msb_get_bit(dst, si));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decode
|
|
||||||
memset(output, 0, pcmLength());
|
|
||||||
Decoder_Interface_Decode(mDecoderCtx, in, (short*)output, 0);
|
|
||||||
|
|
||||||
|
|
||||||
uint8_t* pcm = (uint8_t*)output;
|
|
||||||
for (int i=0; i<160; i++)
|
|
||||||
{
|
|
||||||
uint16_t w = ((uint16_t*)output)[i];
|
|
||||||
pcm[(i<<1) ] = w & 0xff;
|
|
||||||
pcm[(i<<1)+1] = (w >> 8) & 0xff;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
return 0;
|
||||||
|
|
||||||
short* dataOut = (short*)output;
|
short* dataOut = (short*)output.data();
|
||||||
|
|
||||||
for (int i=0; i < lostFrames; i++)
|
for (int i=0; i < lostFrames; i++)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -26,13 +26,13 @@ struct AmrCodecConfig
|
|||||||
class AmrNbCodec : public Codec
|
class AmrNbCodec : public Codec
|
||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
void* mEncoderCtx;
|
void* mEncoderCtx = nullptr;
|
||||||
void* mDecoderCtx;
|
void* mDecoderCtx = nullptr;
|
||||||
AmrCodecConfig mConfig;
|
AmrCodecConfig mConfig;
|
||||||
unsigned mCurrentDecoderTimestamp;
|
unsigned mCurrentDecoderTimestamp = 0;
|
||||||
int mSwitchCounter;
|
int mPreviousPacketLength = 0;
|
||||||
int mPreviousPacketLength;
|
size_t mCngCounter = 0;
|
||||||
|
size_t mSwitchCounter = 0;
|
||||||
public:
|
public:
|
||||||
class CodecFactory: public Factory
|
class CodecFactory: public Factory
|
||||||
{
|
{
|
||||||
@@ -54,17 +54,16 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
AmrNbCodec(const AmrCodecConfig& config);
|
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 getSwitchCounter() const;
|
||||||
|
int getCngCounter() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct AmrWbStatistics
|
struct AmrWbStatistics
|
||||||
@@ -77,15 +76,17 @@ extern AmrWbStatistics GAmrWbStatistics;
|
|||||||
class AmrWbCodec : public Codec
|
class AmrWbCodec : public Codec
|
||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
void* mEncoderCtx;
|
void* mEncoderCtx = nullptr;
|
||||||
void* mDecoderCtx;
|
void* mDecoderCtx = nullptr;
|
||||||
AmrCodecConfig mConfig;
|
AmrCodecConfig mConfig;
|
||||||
uint64_t mCurrentDecoderTimestamp;
|
uint64_t mCurrentDecoderTimestamp = 0;
|
||||||
int mSwitchCounter;
|
size_t mSwitchCounter = 0;
|
||||||
|
size_t mCngCounter = 0;
|
||||||
|
|
||||||
int mPreviousPacketLength;
|
int mPreviousPacketLength;
|
||||||
|
|
||||||
int decodeIuup(std::span<const uint8_t> input, std::span<uint8_t> output);
|
DecodeResult 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 decodePlain(std::span<const uint8_t> input, std::span<uint8_t> output);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
class CodecFactory: public Factory
|
class CodecFactory: public Factory
|
||||||
@@ -110,23 +111,21 @@ public:
|
|||||||
AmrWbCodec(const AmrCodecConfig& config);
|
AmrWbCodec(const AmrCodecConfig& config);
|
||||||
virtual ~AmrWbCodec();
|
virtual ~AmrWbCodec();
|
||||||
|
|
||||||
const char* name() override;
|
Info info() override;
|
||||||
int pcmLength() override;
|
|
||||||
int rtpLength() override;
|
EncodeResult encode(std::span<const uint8_t> input, std::span<uint8_t> output) override;
|
||||||
int frameTime() override;
|
DecodeResult decode(std::span<const uint8_t> input, std::span<uint8_t> output) override;
|
||||||
int samplerate() override;
|
size_t plc(int lostFrames, std::span<uint8_t> output) 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 getSwitchCounter() const;
|
||||||
|
int getCngCounter() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
class GsmEfrCodec : public Codec
|
class GsmEfrCodec : public Codec
|
||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
void* mEncoderCtx;
|
void* mEncoderCtx = nullptr;
|
||||||
void* mDecoderCtx;
|
void* mDecoderCtx = nullptr;
|
||||||
bool mIuUP;
|
bool mIuUP = false;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
class GsmEfrFactory: public Factory
|
class GsmEfrFactory: public Factory
|
||||||
@@ -143,23 +142,19 @@ public:
|
|||||||
void create(CodecMap& codecs) override;
|
void create(CodecMap& codecs) override;
|
||||||
|
|
||||||
PCodec create() override;
|
PCodec create() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool mIuUP;
|
bool mIuUP;
|
||||||
int mPayloadType;
|
int mPayloadType;
|
||||||
};
|
};
|
||||||
|
|
||||||
GsmEfrCodec(bool iuup = false);
|
GsmEfrCodec(bool iuup = false);
|
||||||
|
~GsmEfrCodec();
|
||||||
|
|
||||||
virtual ~GsmEfrCodec();
|
Info info() override;
|
||||||
const char* name() override;
|
|
||||||
int pcmLength() override;
|
EncodeResult encode(std::span<const uint8_t> input, std::span<uint8_t> output) override;
|
||||||
int rtpLength() override;
|
DecodeResult decode(std::span<const uint8_t> input, std::span<uint8_t> output) override;
|
||||||
int frameTime() override;
|
size_t plc(int lostFrames, std::span<uint8_t> output) 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;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // End of MT namespace
|
} // End of MT namespace
|
||||||
|
|||||||
@@ -91,38 +91,20 @@ G729Codec::~G729Codec()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* G729Codec::name()
|
Codec::Info G729Codec::info()
|
||||||
{
|
{
|
||||||
return "G729";
|
return {
|
||||||
}
|
.mName = "G729",
|
||||||
|
.mSamplerate = 8000,
|
||||||
int G729Codec::pcmLength()
|
.mChannels = 1,
|
||||||
{
|
.mPcmLength = 10 * 8 * 2,
|
||||||
return 10 * 8 * 2;
|
.mFrameTime = 10,
|
||||||
}
|
.mRtpLength = 10
|
||||||
|
};
|
||||||
int G729Codec::rtpLength()
|
|
||||||
{
|
|
||||||
return 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
int G729Codec::frameTime()
|
|
||||||
{
|
|
||||||
return 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
int G729Codec::samplerate()
|
|
||||||
{
|
|
||||||
return 8000;
|
|
||||||
}
|
|
||||||
|
|
||||||
int G729Codec::channels()
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// static const int SamplesPerFrame = 80;
|
// 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
|
// Create encoder if it is not done yet
|
||||||
if (!mEncoder)
|
if (!mEncoder)
|
||||||
@@ -131,24 +113,24 @@ int G729Codec::encode(const void* input, int inputBytes, void* output, int outpu
|
|||||||
if (mEncoder)
|
if (mEncoder)
|
||||||
Init_Pre_Process(mEncoder);
|
Init_Pre_Process(mEncoder);
|
||||||
}
|
}
|
||||||
int result = 0;
|
size_t result = 0;
|
||||||
if (mEncoder)
|
if (mEncoder)
|
||||||
{
|
{
|
||||||
int nrOfFrames = inputBytes / 160; // 10ms frames
|
int nrOfFrames = input.size_bytes() / 160; // 10ms frames
|
||||||
Word16 parm[PRM_SIZE]; // ITU's service buffer
|
Word16 parm[PRM_SIZE]; // ITU's service buffer
|
||||||
for (int frameIndex = 0; frameIndex < nrOfFrames; frameIndex++)
|
for (int frameIndex = 0; frameIndex < nrOfFrames; frameIndex++)
|
||||||
{
|
{
|
||||||
Copy((int16_t*)input + frameIndex * pcmLength() / 2, 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, pcmLength() / 2);
|
Pre_Process(mEncoder, mEncoder->new_speech, info().mPcmLength / 2);
|
||||||
Coder_ld8a(mEncoder, parm);
|
Coder_ld8a(mEncoder, parm);
|
||||||
Store_Params(parm, (uint8_t*)output + frameIndex * rtpLength());
|
Store_Params(parm, output.data() + frameIndex * info().mRtpLength);
|
||||||
result += rtpLength();
|
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)
|
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)
|
if (mDecoder)
|
||||||
{
|
{
|
||||||
// See if there are silence bytes in the end
|
// 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
|
// Find number of frames
|
||||||
int nrOfFrames = inputBytes / rtpLength();
|
int nrOfFrames = input.size_bytes() / info().mRtpLength;
|
||||||
nrOfFrames = std::min(outputCapacity / pcmLength(), nrOfFrames);
|
nrOfFrames = std::min(output.size_bytes() / info().mPcmLength, (size_t)nrOfFrames);
|
||||||
|
|
||||||
for (int frameIndex = 0; frameIndex < nrOfFrames; frameIndex++)
|
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());
|
memset(output.data() + nrOfFrames * info().mPcmLength, 0, info().mPcmLength);
|
||||||
result += pcmLength();
|
result += info().mPcmLength;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return {.mDecoded = result, .mIsCng = false};
|
||||||
}
|
}
|
||||||
|
|
||||||
void G729Codec::decodeFrame(const uint8_t* rtp, int16_t* pcm)
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -390,7 +372,7 @@ int OpusCodec::OpusFactory::processSdp(const resip::SdpContents::Session::Medium
|
|||||||
|
|
||||||
PCodec OpusCodec::OpusFactory::create()
|
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);
|
result->applyParams(mParams);
|
||||||
PCodec c(result);
|
PCodec c(result);
|
||||||
mCodecList.push_back(c);
|
mCodecList.push_back(c);
|
||||||
@@ -398,8 +380,8 @@ PCodec OpusCodec::OpusFactory::create()
|
|||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
OpusCodec::OpusCodec(int samplerate, int channels, int ptime)
|
OpusCodec::OpusCodec(Audio::Format fmt, int ptime)
|
||||||
:mEncoderCtx(nullptr), mDecoderCtx(nullptr), mChannels(channels), mPTime(ptime), mSamplerate(samplerate), mDecoderChannels(0)
|
:mEncoderCtx(nullptr), mDecoderCtx(nullptr), mChannels(fmt.channels()), mPTime(ptime), mSamplerate(fmt.rate()), mDecoderChannels(0)
|
||||||
{
|
{
|
||||||
int status;
|
int status;
|
||||||
mEncoderCtx = opus_encoder_create(mSamplerate, mChannels, OPUS_APPLICATION_VOIP, &status);
|
mEncoderCtx = opus_encoder_create(mSamplerate, mChannels, OPUS_APPLICATION_VOIP, &status);
|
||||||
@@ -441,52 +423,34 @@ OpusCodec::~OpusCodec()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* OpusCodec::name()
|
Codec::Info OpusCodec::info() {
|
||||||
{
|
return {
|
||||||
return OPUS_CODEC_NAME;
|
.mName = OPUS_CODEC_NAME,
|
||||||
|
.mSamplerate = mSamplerate,
|
||||||
|
.mChannels = mChannels,
|
||||||
|
.mPcmLength = (int)(mSamplerate / 1000 * sizeof(short) * mChannels * mPTime),
|
||||||
|
.mFrameTime = mPTime,
|
||||||
|
.mRtpLength = 0 /* VBR */
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
int OpusCodec::pcmLength()
|
Codec::EncodeResult OpusCodec::encode(std::span<const uint8_t> input, std::span<uint8_t> output)
|
||||||
{
|
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
// Send number of samples for input and number of bytes for 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)
|
if (written < 0)
|
||||||
return 0;
|
return {.mEncoded = 0};
|
||||||
else
|
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;
|
int result = 0;
|
||||||
|
|
||||||
// Examine the number of channels available in incoming packet
|
// 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
|
// Recreate decoder if needed
|
||||||
if (mDecoderChannels != nr_of_channels)
|
if (mDecoderChannels != nr_of_channels)
|
||||||
@@ -504,24 +468,22 @@ int OpusCodec::decode(const void* input, int inputBytes, void* output, int outpu
|
|||||||
int status = 0;
|
int status = 0;
|
||||||
mDecoderCtx = opus_decoder_create(mSamplerate, mDecoderChannels, &status);
|
mDecoderCtx = opus_decoder_create(mSamplerate, mDecoderChannels, &status);
|
||||||
if (status)
|
if (status)
|
||||||
return 0;
|
return {0};
|
||||||
}
|
}
|
||||||
|
|
||||||
int nr_of_frames = opus_decoder_get_nb_samples(mDecoderCtx, (const unsigned char *) input,
|
int nr_of_frames = opus_decoder_get_nb_samples(mDecoderCtx, input.data(), input.size_bytes());
|
||||||
inputBytes);
|
|
||||||
if (nr_of_frames <= 0)
|
if (nr_of_frames <= 0)
|
||||||
return 0;
|
return {0};
|
||||||
|
|
||||||
// We support stereo and mono here.
|
// We support stereo and mono here.
|
||||||
int buffer_capacity = nr_of_frames * sizeof(opus_int16) * nr_of_channels;
|
int buffer_capacity = nr_of_frames * sizeof(opus_int16) * nr_of_channels;
|
||||||
opus_int16 *buffer_decode = (opus_int16 *)alloca(buffer_capacity);
|
opus_int16 *buffer_decode = (opus_int16 *)alloca(buffer_capacity);
|
||||||
int decoded = opus_decode(mDecoderCtx,
|
int decoded = opus_decode(mDecoderCtx, input.data(), input.size_bytes(),
|
||||||
reinterpret_cast<const unsigned char *>(input), inputBytes,
|
|
||||||
buffer_decode, nr_of_frames, 0);
|
buffer_decode, nr_of_frames, 0);
|
||||||
if (decoded < 0)
|
if (decoded < 0)
|
||||||
{
|
{
|
||||||
ICELogCritical(<< "opus_decode() returned " << decoded);
|
ICELogCritical(<< "opus_decode() returned " << decoded);
|
||||||
return 0;
|
return {0};
|
||||||
}
|
}
|
||||||
|
|
||||||
opus_int16 *buffer_stereo = nullptr;
|
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 + 1] = buffer_decode[i];
|
||||||
buffer_stereo[i * 2] = buffer_decode[i];
|
buffer_stereo[i * 2] = buffer_decode[i];
|
||||||
}
|
}
|
||||||
assert(buffer_stereo_capacity <= outputCapacity);
|
assert(buffer_stereo_capacity <= output.size_bytes());
|
||||||
memcpy(output, buffer_stereo, buffer_stereo_capacity);
|
memcpy(output.data(), buffer_stereo, buffer_stereo_capacity);
|
||||||
result = buffer_stereo_capacity;
|
result = buffer_stereo_capacity;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 2:
|
case 2:
|
||||||
assert(buffer_capacity <= outputCapacity);
|
assert(buffer_capacity <= output.size_bytes());
|
||||||
memcpy(output, buffer_decode, buffer_capacity);
|
memcpy(output.data(), buffer_decode, buffer_capacity);
|
||||||
result = buffer_capacity;
|
result = buffer_capacity;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -550,17 +512,17 @@ int OpusCodec::decode(const void* input, int inputBytes, void* output, int outpu
|
|||||||
assert(0);
|
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
|
// Find how much frames do we need to produce and prefill it with silence
|
||||||
int frames_per_packet = (int)pcmLength() / (sizeof(opus_int16) * channels());
|
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
|
// 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;
|
int nr_of_decoded_frames = 0;
|
||||||
|
|
||||||
@@ -575,10 +537,7 @@ int OpusCodec::plc(int lostPackets, void* output, int outputCapacity)
|
|||||||
case 1:
|
case 1:
|
||||||
// Convert mono to stereo
|
// Convert mono to stereo
|
||||||
for (int i=0; i < nr_of_decoded_frames; i++)
|
for (int i=0; i < nr_of_decoded_frames; i++)
|
||||||
{
|
data_output[i * 2] = data_output[i * 2 + 1] = buffer_plc[i];
|
||||||
data_output[i * 2] = buffer_plc[i];
|
|
||||||
data_output[i * 2 + 1] = buffer_plc[i+1];
|
|
||||||
}
|
|
||||||
data_output += frames_per_packet * mChannels;
|
data_output += frames_per_packet * mChannels;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -589,14 +548,14 @@ int OpusCodec::plc(int lostPackets, void* output, int outputCapacity)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ((char*)data_output - (char*)output) * sizeof(opus_int16);
|
return ((uint8_t*)data_output - output.data());
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------- ILBC -------------------
|
// -------------- ILBC -------------------
|
||||||
#define ILBC_CODEC_NAME "ILBC"
|
#define ILBC_CODEC_NAME "ILBC"
|
||||||
|
|
||||||
IlbcCodec::IlbcCodec(int packetTime)
|
IlbcCodec::IlbcCodec(int packetTime)
|
||||||
:mPacketTime(packetTime), mEncoderCtx(nullptr), mDecoderCtx(nullptr)
|
:mPacketTime(packetTime)
|
||||||
{
|
{
|
||||||
WebRtcIlbcfix_EncoderCreate(&mEncoderCtx);
|
WebRtcIlbcfix_EncoderCreate(&mEncoderCtx);
|
||||||
WebRtcIlbcfix_DecoderCreate(&mDecoderCtx);
|
WebRtcIlbcfix_DecoderCreate(&mDecoderCtx);
|
||||||
@@ -610,44 +569,31 @@ IlbcCodec::~IlbcCodec()
|
|||||||
WebRtcIlbcfix_EncoderFree(mEncoderCtx);
|
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;
|
if (input.size_bytes() % pcmLength())
|
||||||
}
|
return {};
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
// Declare the data input pointer
|
// Declare the data input pointer
|
||||||
short *dataIn = (short *)input;
|
short *dataIn = (short *)input.data();
|
||||||
|
|
||||||
// Declare the data output pointer
|
// Declare the data output pointer
|
||||||
char *dataOut = (char *)outputBuffer;
|
char *dataOut = (char *)output.data();
|
||||||
|
|
||||||
// Find how much RTP frames will be generated
|
// Find how much RTP frames will be generated
|
||||||
unsigned int frames = inputBytes / pcmLength();
|
unsigned int frames = input.size_bytes() / pcmLength();
|
||||||
|
|
||||||
// Generate frames
|
// Generate frames
|
||||||
for (unsigned int i=0; i<frames; i++)
|
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();
|
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;
|
char* dataIn = (char*)input.data();
|
||||||
short* dataOut = (short*)output;
|
short* dataOut = (short*)output.data();
|
||||||
|
|
||||||
for (unsigned i=0; i < frames; ++i)
|
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;
|
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 ---
|
// --- IlbcFactory ---
|
||||||
@@ -795,38 +741,24 @@ IsacCodec::~IsacCodec()
|
|||||||
WebRtcIsacfix_Free(mDecoderCtx); mDecoderCtx = NULL;
|
WebRtcIsacfix_Free(mDecoderCtx); mDecoderCtx = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* IsacCodec::name()
|
Codec::Info IsacCodec::info() {
|
||||||
{
|
return {
|
||||||
return "isac";
|
.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;
|
unsigned nrOfSamples = input.size_bytes() / 2;
|
||||||
}
|
|
||||||
|
|
||||||
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 timeLength = nrOfSamples / (mSamplerate / 1000);
|
unsigned timeLength = nrOfSamples / (mSamplerate / 1000);
|
||||||
int encoded = 0;
|
int encoded = 0;
|
||||||
char* dataOut = (char*)output;
|
char* dataOut = (char*)output.data();
|
||||||
const WebRtc_Word16* dataIn = (const WebRtc_Word16*)input;
|
const WebRtc_Word16* dataIn = (const WebRtc_Word16*)input.data();
|
||||||
|
|
||||||
// Iterate 10 milliseconds chunks
|
// Iterate 10 milliseconds chunks
|
||||||
for (unsigned i=0; i<timeLength/10; i++)
|
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)
|
if (encoded > 0)
|
||||||
dataOut += encoded;
|
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;
|
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)
|
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.
|
// lostFrames are 30-milliseconds frames; but used encoding mode is 60 milliseconds.
|
||||||
// So lostFrames * 2
|
// So lostFrames * 2
|
||||||
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 0;
|
||||||
|
|
||||||
return lostFrames * 30 * (samplerate()/1000 * sizeof(short));
|
return lostFrames * 30 * (samplerate()/1000 * sizeof(short));
|
||||||
@@ -916,71 +848,55 @@ PCodec IsacCodec::IsacFactory32K::create()
|
|||||||
|
|
||||||
G711Codec::G711Codec(int type)
|
G711Codec::G711Codec(int type)
|
||||||
:mType(type)
|
:mType(type)
|
||||||
{
|
{}
|
||||||
}
|
|
||||||
|
|
||||||
G711Codec::~G711Codec()
|
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()
|
Codec::EncodeResult G711Codec::encode(std::span<const uint8_t> input, std::span<uint8_t> output)
|
||||||
{
|
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
int result;
|
int result;
|
||||||
if (mType == ALaw)
|
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
|
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)
|
if (result < 0)
|
||||||
throw Exception(ERR_WEBRTC, -1);
|
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;
|
int result;
|
||||||
WebRtc_Word16 speechType;
|
WebRtc_Word16 speechType;
|
||||||
|
|
||||||
if (mType == ALaw)
|
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
|
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)
|
if (result < 0)
|
||||||
throw Exception(ERR_WEBRTC, -1);
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -1060,86 +976,64 @@ GsmCodec::GsmCodec(Type codecType)
|
|||||||
|
|
||||||
GsmCodec::~GsmCodec()
|
GsmCodec::~GsmCodec()
|
||||||
{
|
{
|
||||||
gsm_destroy(mGSM);
|
gsm_destroy(mGSM); mGSM = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* GsmCodec::name()
|
Codec::Info GsmCodec::info() {
|
||||||
{
|
int rtpLength = 0;
|
||||||
return "GSM-06.10";
|
|
||||||
}
|
|
||||||
|
|
||||||
int GsmCodec::rtpLength()
|
|
||||||
{
|
|
||||||
switch (mCodecType)
|
switch (mCodecType)
|
||||||
{
|
{
|
||||||
case Type::Bytes_31:
|
case Type::Bytes_31: rtpLength = GSM_RTPFRAME_SIZE_31; break;
|
||||||
return GSM_RTPFRAME_SIZE_31;
|
case Type::Bytes_32: rtpLength = GSM_RTPFRAME_SIZE_32; break;
|
||||||
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;
|
||||||
case Type::Bytes_32:
|
default: rtpLength = GSM_RTPFRAME_SIZE_33;
|
||||||
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;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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()
|
Codec::EncodeResult GsmCodec::encode(std::span<const uint8_t> input, std::span<uint8_t> output)
|
||||||
{
|
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
int outputBytes = 0;
|
int outputBytes = 0;
|
||||||
|
char* outputBuffer = (char*)output.data();
|
||||||
|
|
||||||
char* outputBuffer = (char*)output;
|
for (int i = 0; i < input.size_bytes() / pcmLength(); i++)
|
||||||
|
|
||||||
for (int i = 0; i < inputBytes/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();
|
outputBuffer += rtpLength();
|
||||||
outputBytes += 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)
|
if (input.size_bytes() % rtpLength() != 0)
|
||||||
return 0;
|
return {.mDecoded = 0};
|
||||||
|
|
||||||
int i=0;
|
int i=0;
|
||||||
for (i = 0; i < inputBytes/rtpLength(); i++)
|
for (i = 0; i < input.size_bytes() / rtpLength(); i++)
|
||||||
gsm_decode(mGSM, (gsm_byte *)input + 33 * i, (gsm_signal *)output + 160 * 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 0;
|
||||||
|
|
||||||
// Return silence frames
|
// Return silence frames
|
||||||
memset(output, 0, lostFrames * pcmLength());
|
memset(output.data(), 0, lostFrames * pcmLength());
|
||||||
return lostFrames * pcmLength();
|
return lostFrames * pcmLength();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1155,58 +1049,52 @@ G722Codec::G722Codec()
|
|||||||
|
|
||||||
G722Codec::~G722Codec()
|
G722Codec::~G722Codec()
|
||||||
{
|
{
|
||||||
g722_decode_release((g722_decode_state_t*)mDecoder);
|
g722_decode_release((g722_decode_state_t*)mDecoder); mDecoder = nullptr;
|
||||||
g722_encode_release((g722_encode_state_t*)mEncoder);
|
g722_encode_release((g722_encode_state_t*)mEncoder); mEncoder = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* G722Codec::name()
|
Codec::Info G722Codec::info() {
|
||||||
{
|
// ToDo: double check the G722 calls - remember RFC has bug about samplerate
|
||||||
return G722_MIME_NAME;
|
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;
|
if (output.size_bytes() < lostFrames * pcmLength())
|
||||||
}
|
|
||||||
|
|
||||||
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())
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
// Return silence frames
|
// Return silence frames
|
||||||
memset(output, 0, lostFrames * pcmLength());
|
memset(output.data(), 0, lostFrames * pcmLength());
|
||||||
return lostFrames * pcmLength();
|
return lostFrames * pcmLength();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1318,7 +1206,6 @@ static bool repackHalfRate(BitReader& br, uint16_t frame[22], bool& lastItem)
|
|||||||
}
|
}
|
||||||
|
|
||||||
GsmHrCodec::GsmHrCodec()
|
GsmHrCodec::GsmHrCodec()
|
||||||
:mDecoder(nullptr)
|
|
||||||
{
|
{
|
||||||
mDecoder = new GsmHr::Codec();
|
mDecoder = new GsmHr::Codec();
|
||||||
}
|
}
|
||||||
@@ -1329,34 +1216,21 @@ GsmHrCodec::~GsmHrCodec()
|
|||||||
mDecoder = nullptr;
|
mDecoder = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* GsmHrCodec::name()
|
Codec::Info GsmHrCodec::info() {
|
||||||
{
|
return {
|
||||||
return "GSM-HR-08";
|
.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;
|
// Not supported yet
|
||||||
}
|
return {.mEncoded = 0};
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static const int params_unvoiced[] = {
|
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 conversion from canon to rawpcm-s16le (for codec pcm)
|
||||||
[+] PQ: Adding file output (blk_len=320)
|
[+] 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);
|
BitReader br(bb);
|
||||||
uint16_t hr_ref[22];
|
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[18] = 0; /* BFI : 1 bit */
|
||||||
hr_ref[19] = 0; /* UFI : 1 bit */
|
hr_ref[19] = 0; /* UFI : 1 bit */
|
||||||
hr_ref[20] = 0; /* SID : 2 bit */
|
hr_ref[20] = 0; /* SID : 2 bit */
|
||||||
hr_ref[21] = 0; /* TAF : 1 bit */
|
hr_ref[21] = 0; /* TAF : 1 bit */
|
||||||
|
|
||||||
reinterpret_cast<GsmHr::Codec*>(mDecoder)->speechDecoder((int16_t*)hr_ref, (int16_t*)output);
|
reinterpret_cast<GsmHr::Codec*>(mDecoder)->speechDecoder((int16_t*)hr_ref, (int16_t*)output.data());
|
||||||
return 320;
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,42 +40,41 @@ public:
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
const char* name() override;
|
const char* name() override;
|
||||||
int channels() override;
|
int channels() override;
|
||||||
int samplerate() override;
|
int samplerate() override;
|
||||||
int payloadType() override;
|
int payloadType() override;
|
||||||
|
|
||||||
void updateSdp(resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction) override;
|
void updateSdp(resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction) override;
|
||||||
int processSdp(const resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction) override;
|
int processSdp(const resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction) override;
|
||||||
|
|
||||||
PCodec create() override;
|
PCodec create() override;
|
||||||
};
|
};
|
||||||
G729Codec();
|
G729Codec();
|
||||||
~G729Codec() override;
|
~G729Codec() override;
|
||||||
|
|
||||||
const char* name() override;
|
Info info() override;
|
||||||
int pcmLength() override;
|
|
||||||
int rtpLength() override;
|
EncodeResult encode(std::span<const uint8_t> input, std::span<uint8_t> output) override;
|
||||||
int frameTime() override;
|
DecodeResult decode(std::span<const uint8_t> input, std::span<uint8_t> output) override;
|
||||||
int samplerate() override;
|
size_t plc(int lostFrames, std::span<uint8_t> output) 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;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class OpusCodec: public Codec
|
class OpusCodec: public Codec
|
||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
OpusEncoder *mEncoderCtx;
|
OpusEncoder *mEncoderCtx = nullptr;
|
||||||
OpusDecoder *mDecoderCtx;
|
OpusDecoder *mDecoderCtx = nullptr;
|
||||||
int mPTime, mSamplerate, mChannels;
|
int mPTime = 0, mSamplerate = 0, mChannels = 0;
|
||||||
// Audio::SpeexResampler mDecodeResampler;
|
int mDecoderChannels = 0;
|
||||||
int mDecoderChannels;
|
|
||||||
public:
|
public:
|
||||||
struct Params
|
struct Params
|
||||||
{
|
{
|
||||||
bool mUseDtx, mUseInbandFec, mStereo;
|
bool mUseDtx = false,
|
||||||
int mPtime, mTargetBitrate, mExpectedPacketLoss;
|
mUseInbandFec = false,
|
||||||
|
mStereo = false;
|
||||||
|
int mPtime = 0,
|
||||||
|
mTargetBitrate = 0,
|
||||||
|
mExpectedPacketLoss = 0;
|
||||||
|
|
||||||
Params();
|
Params();
|
||||||
resip::Data toString() const;
|
resip::Data toString() const;
|
||||||
@@ -102,28 +101,24 @@ public:
|
|||||||
PCodec create() override;
|
PCodec create() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
OpusCodec(int samplerate, int channels, int ptime);
|
OpusCodec(Audio::Format fmt, int ptime);
|
||||||
~OpusCodec();
|
~OpusCodec();
|
||||||
void applyParams(const Params& params);
|
void applyParams(const Params& params);
|
||||||
|
|
||||||
const char* name();
|
Info info() override;
|
||||||
int pcmLength();
|
|
||||||
int rtpLength();
|
EncodeResult encode(std::span<const uint8_t> input, std::span<uint8_t> output) override;
|
||||||
int frameTime();
|
DecodeResult decode(std::span<const uint8_t> input, std::span<uint8_t> output) override;
|
||||||
int samplerate();
|
size_t plc(int lostFrames, std::span<uint8_t> output) override;
|
||||||
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);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
class IlbcCodec: public Codec
|
class IlbcCodec: public Codec
|
||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
int mPacketTime; /// Single frame time (20 or 30 ms)
|
int mPacketTime = 0; /// Single frame time (20 or 30 ms)
|
||||||
iLBC_encinst_t* mEncoderCtx;
|
iLBC_encinst_t* mEncoderCtx = nullptr;
|
||||||
iLBC_decinst_t* mDecoderCtx;
|
iLBC_decinst_t* mDecoderCtx = nullptr;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
class IlbcFactory: public Factory
|
class IlbcFactory: public Factory
|
||||||
@@ -146,14 +141,11 @@ public:
|
|||||||
|
|
||||||
IlbcCodec(int packetTime);
|
IlbcCodec(int packetTime);
|
||||||
virtual ~IlbcCodec();
|
virtual ~IlbcCodec();
|
||||||
const char* name();
|
Info info() override;
|
||||||
int pcmLength();
|
|
||||||
int rtpLength();
|
EncodeResult encode(std::span<const uint8_t> input, std::span<uint8_t> output) override;
|
||||||
int frameTime();
|
DecodeResult decode(std::span<const uint8_t> input, std::span<uint8_t> output) override;
|
||||||
int samplerate();
|
size_t plc(int lostFrames, std::span<uint8_t> output) 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);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class G711Codec: public Codec
|
class G711Codec: public Codec
|
||||||
@@ -186,15 +178,11 @@ public:
|
|||||||
G711Codec(int type);
|
G711Codec(int type);
|
||||||
~G711Codec();
|
~G711Codec();
|
||||||
|
|
||||||
const char* name();
|
Info info() override;
|
||||||
int pcmLength();
|
|
||||||
int frameTime();
|
|
||||||
int rtpLength();
|
|
||||||
int samplerate();
|
|
||||||
|
|
||||||
int encode(const void* input, int inputBytes, void* output, int outputCapacity);
|
EncodeResult encode(std::span<const uint8_t> input, std::span<uint8_t> output) override;
|
||||||
int decode(const void* input, int inputBytes, void* output, int outputCapacity);
|
DecodeResult decode(std::span<const uint8_t> input, std::span<uint8_t> output) override;
|
||||||
int plc(int lostSamples, void* output, int outputCapacity);
|
size_t plc(int lostSamples, std::span<uint8_t> output) override ;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
int mType; /// Determines if it is u-law or a-law codec. Its value is ALaw or ULaw.
|
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
|
class IsacCodec: public Codec
|
||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
int mSamplerate;
|
int mSamplerate = 0;
|
||||||
ISACFIX_MainStruct* mEncoderCtx;
|
ISACFIX_MainStruct* mEncoderCtx = nullptr;
|
||||||
ISACFIX_MainStruct* mDecoderCtx;
|
ISACFIX_MainStruct* mDecoderCtx = nullptr;
|
||||||
public:
|
public:
|
||||||
class IsacFactory16K: public Factory
|
class IsacFactory16K: public Factory
|
||||||
{
|
{
|
||||||
@@ -237,15 +225,11 @@ public:
|
|||||||
IsacCodec(int sampleRate);
|
IsacCodec(int sampleRate);
|
||||||
~IsacCodec();
|
~IsacCodec();
|
||||||
|
|
||||||
const char* name();
|
Info info() override;
|
||||||
int pcmLength();
|
|
||||||
int rtpLength();
|
|
||||||
int frameTime();
|
|
||||||
int samplerate();
|
|
||||||
|
|
||||||
int encode(const void* input, int inputBytes, void* output, int outputCapacity);
|
EncodeResult encode(std::span<const uint8_t> input, std::span<uint8_t> output) override;
|
||||||
int decode(const void* input, int inputBytes, void* output, int outputCapacity);
|
DecodeResult decode(std::span<const uint8_t> input, std::span<uint8_t> output) override;
|
||||||
int plc(int lostFrames, void* output, int outputCapacity);
|
size_t plc(int lostFrames, std::span<uint8_t> output) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -309,17 +293,13 @@ public:
|
|||||||
GsmCodec(Type codecType);
|
GsmCodec(Type codecType);
|
||||||
|
|
||||||
/*! Destructor. */
|
/*! Destructor. */
|
||||||
virtual ~GsmCodec();
|
~GsmCodec();
|
||||||
|
|
||||||
const char* name();
|
Info info() override;
|
||||||
int pcmLength();
|
|
||||||
int rtpLength();
|
|
||||||
int frameTime();
|
|
||||||
int samplerate();
|
|
||||||
|
|
||||||
int encode(const void* input, int inputBytes, void* output, int outputCapacity);
|
EncodeResult encode(std::span<const uint8_t> input, std::span<uint8_t> output) override;
|
||||||
int decode(const void* input, int inputBytes, void* output, int outputCapacity);
|
DecodeResult decode(std::span<const uint8_t> input, std::span<uint8_t> output) override;
|
||||||
int plc(int lostFrames, void* output, int outputCapacity);
|
size_t plc(int lostFrames, std::span<uint8_t> output) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// GSM MIME name
|
/// GSM MIME name
|
||||||
@@ -358,25 +338,19 @@ public:
|
|||||||
PCodec create();
|
PCodec create();
|
||||||
};
|
};
|
||||||
G722Codec();
|
G722Codec();
|
||||||
virtual ~G722Codec();
|
~G722Codec();
|
||||||
|
|
||||||
const char* name();
|
Info info() override;
|
||||||
int pcmLength();
|
|
||||||
int rtpLength();
|
|
||||||
int frameTime();
|
|
||||||
int samplerate();
|
|
||||||
|
|
||||||
int encode(const void* input, int inputBytes, void* output, int outputCapacity);
|
EncodeResult encode(std::span<const uint8_t> input, std::span<uint8_t> output) override;
|
||||||
int decode(const void* input, int inputBytes, void* output, int outputCapacity);
|
DecodeResult decode(std::span<const uint8_t> input, std::span<uint8_t> output) override;
|
||||||
int plc(int lostFrames, void* output, int outputCapacity);
|
size_t plc(int lostFrames, std::span<uint8_t> output) override;
|
||||||
|
|
||||||
//unsigned GetSamplerate() { return 16000; }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class GsmHrCodec: public Codec
|
class GsmHrCodec: public Codec
|
||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
void* mDecoder;
|
void* mDecoder = nullptr;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
class GsmHrFactory: public Factory
|
class GsmHrFactory: public Factory
|
||||||
@@ -396,15 +370,11 @@ public:
|
|||||||
GsmHrCodec();
|
GsmHrCodec();
|
||||||
~GsmHrCodec() override;
|
~GsmHrCodec() override;
|
||||||
|
|
||||||
const char* name() override;
|
Info info() 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;
|
EncodeResult encode(std::span<const uint8_t> input, std::span<uint8_t> output) override;
|
||||||
int decode(const void* input, int inputBytes, void* output, int outputCapacity) override;
|
DecodeResult decode(std::span<const uint8_t> input, std::span<uint8_t> output) override;
|
||||||
int plc(int lostFrames, void* output, int outputCapacity) override;
|
size_t plc(int lostFrames, std::span<uint8_t> output) override;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#include "MT_AudioReceiver.h"
|
#include "MT_AudioReceiver.h"
|
||||||
#include "MT_AudioCodec.h"
|
#include "MT_AudioCodec.h"
|
||||||
#include "MT_CngHelper.h"
|
#include "MT_CngHelper.h"
|
||||||
|
#include "MT_Dtmf.h"
|
||||||
#include "../helper/HL_Log.h"
|
#include "../helper/HL_Log.h"
|
||||||
#include "../helper/HL_Time.h"
|
#include "../helper/HL_Time.h"
|
||||||
#include "../audio/Audio_Interface.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();
|
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)
|
if (!packet)
|
||||||
return std::shared_ptr<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>();
|
return std::shared_ptr<Packet>();
|
||||||
}
|
}
|
||||||
|
|
||||||
RtpBuffer::FetchResult RtpBuffer::fetch(ResultList& rl)
|
RtpBuffer::FetchResult RtpBuffer::fetch()
|
||||||
{
|
{
|
||||||
Lock l(mGuard);
|
Lock l(mGuard);
|
||||||
|
|
||||||
FetchResult result = FetchResult::NoPacket;
|
FetchResult result;
|
||||||
rl.clear();
|
|
||||||
|
|
||||||
// See if there is enough information in buffer
|
// See if there is enough information in buffer
|
||||||
auto total = findTimelength();
|
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");
|
ICELogMedia( << "Dropping RTP packets from jitter buffer");
|
||||||
total -= mPacketList.front()->timelength();
|
total -= mPacketList.front()->timelength();
|
||||||
|
|
||||||
// Save it as last packet however - to not confuse loss packet counter
|
// Save it as last packet however - to not confuse loss packet counter
|
||||||
mFetchedPacket = mPacketList.front();
|
mFetchedPacket = mPacketList.front();
|
||||||
mLastSeqno = mPacketList.front()->rtp()->GetExtendedSequenceNumber();
|
mLastSeqno = mFetchedPacket->rtp()->GetExtendedSequenceNumber();
|
||||||
|
mLastReceiveTime = mFetchedPacket->rtp()->GetReceiveTime();
|
||||||
|
|
||||||
// Erase from packet list
|
// Erase from packet list
|
||||||
mPacketList.erase(mPacketList.begin());
|
mPacketList.erase(mPacketList.begin());
|
||||||
@@ -217,10 +218,10 @@ RtpBuffer::FetchResult RtpBuffer::fetch(ResultList& rl)
|
|||||||
mStat.mPacketDropped++;
|
mStat.mPacketDropped++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (total < mLow)
|
if (total < mLow || total == 0ms)
|
||||||
{
|
{
|
||||||
// Still not prebuffered
|
// Still not prebuffered
|
||||||
result = FetchResult::NoPacket;
|
result = {FetchResult::Status::NoPacket};
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -228,8 +229,8 @@ RtpBuffer::FetchResult RtpBuffer::fetch(ResultList& rl)
|
|||||||
{
|
{
|
||||||
if (mPacketList.empty())
|
if (mPacketList.empty())
|
||||||
{
|
{
|
||||||
result = FetchResult::NoPacket;
|
|
||||||
// Don't increase counter of lost packets here; maybe it is DTX
|
// Don't increase counter of lost packets here; maybe it is DTX
|
||||||
|
result = {FetchResult::Status::NoPacket};
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -237,34 +238,39 @@ RtpBuffer::FetchResult RtpBuffer::fetch(ResultList& rl)
|
|||||||
auto& packet = *mPacketList.front();
|
auto& packet = *mPacketList.front();
|
||||||
uint32_t seqno = packet.rtp()->GetExtendedSequenceNumber();
|
uint32_t seqno = packet.rtp()->GetExtendedSequenceNumber();
|
||||||
|
|
||||||
|
|
||||||
// Gap between new packet and previous on
|
// Gap between new packet and previous on
|
||||||
int gap = (int64_t)seqno - (int64_t)*mLastSeqno - 1;
|
int gap = (int64_t)seqno - (int64_t)*mLastSeqno - 1;
|
||||||
gap = std::min(gap, 127);
|
|
||||||
if (gap > 0)
|
if (gap > 0)
|
||||||
{
|
{
|
||||||
// std::cout << "Increase the packet loss for SSRC " << std::hex << mSsrc << std::endl;
|
// std::cout << "Increase the packet loss for SSRC " << std::hex << mSsrc << std::endl;
|
||||||
mStat.mPacketLoss++;
|
mStat.mPacketLoss += gap;
|
||||||
auto currentTimestamp = std::chrono::microseconds(uint64_t(packet.rtp()->GetReceiveTime().GetDouble() * 1000000));
|
|
||||||
|
|
||||||
|
// Report is the onetime; there is no many sequential 1-packet gap reports
|
||||||
if (mStat.mPacketLossTimeline.empty() || (mStat.mPacketLossTimeline.back().mEndSeqno != seqno))
|
if (mStat.mPacketLossTimeline.empty() || (mStat.mPacketLossTimeline.back().mEndSeqno != seqno))
|
||||||
mStat.mPacketLossTimeline.push_back({.mStartSeqno = *mLastSeqno,
|
{
|
||||||
.mEndSeqno = seqno,
|
auto gapStart = RtpHelper::toMicroseconds(*mLastReceiveTime);
|
||||||
.mGap = gap,
|
auto gapEnd = RtpHelper::toMicroseconds(packet.rtp()->GetReceiveTime());
|
||||||
.mTimestamp = currentTimestamp});
|
mStat.mPacketLossTimeline.emplace_back(PacketLossEvent{.mStartSeqno = *mLastSeqno,
|
||||||
|
.mEndSeqno = seqno,
|
||||||
|
.mGap = gap,
|
||||||
|
.mTimestampStart = gapStart,
|
||||||
|
.mTimestampEnd = gapEnd});
|
||||||
|
}
|
||||||
|
|
||||||
mLastSeqno = *mLastSeqno + 1; // As we deal with the audio gap - return the silence and increase last seqno
|
// 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
|
||||||
result = FetchResult::Gap;
|
mLastSeqno = seqno;
|
||||||
|
mLastReceiveTime = packet.rtp()->GetReceiveTime();
|
||||||
|
result = {FetchResult::Status::Gap};
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
result = FetchResult::RegularPacket;
|
result = {FetchResult::Status::RegularPacket, mPacketList.front()};
|
||||||
rl.push_back(mPacketList.front());
|
|
||||||
|
|
||||||
// Save last returned normal packet
|
// Save last returned normal packet
|
||||||
mFetchedPacket = mPacketList.front();
|
mFetchedPacket = result.mPacket;
|
||||||
mLastSeqno = mPacketList.front()->rtp()->GetExtendedSequenceNumber();
|
mLastSeqno = result.mPacket->rtp()->GetExtendedSequenceNumber();
|
||||||
|
mLastReceiveTime = result.mPacket->rtp()->GetReceiveTime();
|
||||||
|
|
||||||
// Remove returned packet from the list
|
// Remove returned packet from the list
|
||||||
mPacketList.erase(mPacketList.begin());
|
mPacketList.erase(mPacketList.begin());
|
||||||
@@ -277,14 +283,12 @@ RtpBuffer::FetchResult RtpBuffer::fetch(ResultList& rl)
|
|||||||
if (findTimelength() >= mPrebuffer && !mPacketList.empty())
|
if (findTimelength() >= mPrebuffer && !mPacketList.empty())
|
||||||
{
|
{
|
||||||
// Normal packet will be returned
|
// Normal packet will be returned
|
||||||
result = FetchResult::RegularPacket;
|
result = {FetchResult::Status::RegularPacket, mPacketList.front()};
|
||||||
|
|
||||||
// Put it to output list
|
|
||||||
rl.push_back(mPacketList.front());
|
|
||||||
|
|
||||||
// Remember returned packet
|
// Remember returned packet
|
||||||
mFetchedPacket = mPacketList.front();
|
mFetchedPacket = result.mPacket;
|
||||||
mLastSeqno = mPacketList.front()->rtp()->GetExtendedSequenceNumber();
|
mLastSeqno = result.mPacket->rtp()->GetExtendedSequenceNumber();
|
||||||
|
mLastReceiveTime = result.mPacket->rtp()->GetReceiveTime();
|
||||||
|
|
||||||
// Remove returned packet from buffer list
|
// Remove returned packet from buffer list
|
||||||
mPacketList.erase(mPacketList.begin());
|
mPacketList.erase(mPacketList.begin());
|
||||||
@@ -292,12 +296,12 @@ RtpBuffer::FetchResult RtpBuffer::fetch(ResultList& rl)
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
ICELogMedia(<< "Jitter buffer was not prebuffered yet; resulting no packet");
|
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++;
|
mReturnedCounter++;
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@@ -333,8 +337,7 @@ Receiver::~Receiver()
|
|||||||
|
|
||||||
//-------------- AudioReceiver ----------------
|
//-------------- AudioReceiver ----------------
|
||||||
AudioReceiver::AudioReceiver(const CodecList::Settings& settings, MT::Statistics &stat)
|
AudioReceiver::AudioReceiver(const CodecList::Settings& settings, MT::Statistics &stat)
|
||||||
:Receiver(stat), mBuffer(stat), mCodecSettings(settings),
|
:Receiver(stat), mBuffer(stat), mDtmfBuffer(stat), mCodecSettings(settings), mCodecList(settings), mDtmfReceiver(stat)
|
||||||
mCodecList(settings)
|
|
||||||
{
|
{
|
||||||
// Init resamplers
|
// Init resamplers
|
||||||
mResampler8.start(AUDIO_CHANNELS, 8000, AUDIO_SAMPLERATE);
|
mResampler8.start(AUDIO_CHANNELS, 8000, AUDIO_SAMPLERATE);
|
||||||
@@ -346,6 +349,12 @@ AudioReceiver::AudioReceiver(const CodecList::Settings& settings, MT::Statistics
|
|||||||
mCodecList.setSettings(settings);
|
mCodecList.setSettings(settings);
|
||||||
mCodecList.fillCodecMap(mCodecMap);
|
mCodecList.fillCodecMap(mCodecMap);
|
||||||
|
|
||||||
|
mAvailable.setCapacity(AUDIO_SAMPLERATE * sizeof(short));
|
||||||
|
|
||||||
|
mDtmfBuffer.setPrebuffer(0ms);
|
||||||
|
mDtmfBuffer.setLow(0ms);
|
||||||
|
mDtmfBuffer.setHigh(1ms);
|
||||||
|
|
||||||
#if defined(DUMP_DECODED)
|
#if defined(DUMP_DECODED)
|
||||||
mDecodedDump = std::make_shared<Audio::WavFileWriter>();
|
mDecodedDump = std::make_shared<Audio::WavFileWriter>();
|
||||||
mDecodedDump->open("decoded.wav", 8000 /*G711*/, AUDIO_CHANNELS);
|
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++)
|
for (int i=0; i < frame_count; i++)
|
||||||
{
|
{
|
||||||
auto decoded_length = codec.decode(p.GetPayloadData() + i * codec.rtpLength(),
|
auto r = codec.decode({p.GetPayloadData() + i * codec.rtpLength(), (size_t)frame_length},
|
||||||
frame_length,
|
{(uint8_t*)output_buffer, output_capacity});
|
||||||
output_buffer,
|
|
||||||
output_capacity);
|
|
||||||
|
|
||||||
result += decoded_length;
|
result += r.mDecoded;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -428,66 +435,72 @@ bool AudioReceiver::add(const std::shared_ptr<jrtplib::RTPPacket>& p, Codec** de
|
|||||||
// Increase codec counter
|
// Increase codec counter
|
||||||
mStat.mCodecCount[ptype]++;
|
mStat.mCodecCount[ptype]++;
|
||||||
|
|
||||||
// Check if codec can be handled
|
// Check if we deal with telephone-event
|
||||||
Codec* codec = nullptr;
|
if (p->GetPayloadType() == mCodecSettings.mTelephoneEvent)
|
||||||
auto codecIter = mCodecMap.find(ptype);
|
|
||||||
if (codecIter == mCodecMap.end())
|
|
||||||
{
|
{
|
||||||
// Well, there is no information about the codec; skip this packet
|
*detectedCodec = nullptr;
|
||||||
|
mDtmfBuffer.add(p, 10ms, 8000);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Check if codec is creating lazily
|
// Look for codec
|
||||||
if (!codecIter->second)
|
// Check if codec can be handled
|
||||||
|
Codec* codec = nullptr;
|
||||||
|
auto codecIter = mCodecMap.find(ptype);
|
||||||
|
if (codecIter != mCodecMap.end())
|
||||||
{
|
{
|
||||||
codecIter->second = mCodecList.createCodecByPayloadType(ptype);
|
// Check if codec is creating lazily
|
||||||
|
if (!codecIter->second)
|
||||||
|
{
|
||||||
|
codecIter->second = mCodecList.createCodecByPayloadType(ptype);
|
||||||
|
}
|
||||||
|
codec = codecIter->second.get();
|
||||||
|
|
||||||
|
// Return pointer to codec if needed.get()
|
||||||
|
if (detectedCodec)
|
||||||
|
*detectedCodec = codec;
|
||||||
|
|
||||||
|
if (mStat.mCodecName.empty() && codec)
|
||||||
|
mStat.mCodecName = codec->name();
|
||||||
|
|
||||||
|
|
||||||
|
if (!codec)
|
||||||
|
time_length = 10;
|
||||||
|
else
|
||||||
|
if (!codec->rtpLength())
|
||||||
|
time_length = codec->frameTime();
|
||||||
|
else
|
||||||
|
time_length = lround(double(payloadLength) / codec->rtpLength() * codec->frameTime());
|
||||||
|
|
||||||
|
if (codec)
|
||||||
|
samplerate = codec->samplerate();
|
||||||
}
|
}
|
||||||
codec = codecIter->second.get();
|
|
||||||
|
|
||||||
// Return pointer to codec if needed.get()
|
|
||||||
if (detectedCodec)
|
|
||||||
*detectedCodec = codec;
|
|
||||||
|
|
||||||
if (mStat.mCodecName.empty() && codec)
|
|
||||||
mStat.mCodecName = codec->name();
|
|
||||||
|
|
||||||
|
// Process jitter anyway - can we decode payload or not
|
||||||
|
mJitterStats.process(p.get(), samplerate);
|
||||||
|
mStat.mJitter = static_cast<float>(mJitterStats.get());
|
||||||
|
|
||||||
if (!codec)
|
if (!codec)
|
||||||
time_length = 10;
|
return false; // There is no sense to add this packet into jitter buffer - we can't decode this
|
||||||
|
|
||||||
|
// Check if packet is CNG
|
||||||
|
if (payloadLength >= 1 && payloadLength <= 6 && (ptype == 0 || ptype == 8))
|
||||||
|
time_length = mLastPacketTimeLength ? mLastPacketTimeLength : 20;
|
||||||
else
|
else
|
||||||
if (!codec->rtpLength())
|
// Check if packet is too short from time length side
|
||||||
time_length = codec->frameTime();
|
if (time_length < 2)
|
||||||
else
|
{
|
||||||
time_length = lround(double(payloadLength) / codec->rtpLength() * codec->frameTime());
|
// It will cause statistics to report about bad RTP packet
|
||||||
|
// I have to replay last packet payload here to avoid report about lost packet
|
||||||
|
mBuffer.add(p, std::chrono::milliseconds(time_length), samplerate);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (codec)
|
// Queue packet to buffer
|
||||||
samplerate = codec->samplerate();
|
auto packet = mBuffer.add(p, std::chrono::milliseconds(time_length), samplerate).get();
|
||||||
|
return packet;
|
||||||
}
|
}
|
||||||
|
return {};
|
||||||
// Process jitter
|
|
||||||
mJitterStats.process(p.get(), samplerate);
|
|
||||||
mStat.mJitter = static_cast<float>(mJitterStats.get());
|
|
||||||
|
|
||||||
if (!codec)
|
|
||||||
return false; // There is no sense to add this packet into jitter buffer - we can't decode this
|
|
||||||
|
|
||||||
// Check if packet is CNG
|
|
||||||
if (payloadLength >= 1 && payloadLength <= 6 && (ptype == 0 || ptype == 8))
|
|
||||||
time_length = mLastPacketTimeLength ? mLastPacketTimeLength : 20;
|
|
||||||
else
|
|
||||||
// Check if packet is too short from time length side
|
|
||||||
if (time_length < 2)
|
|
||||||
{
|
|
||||||
// It will cause statistics to report about bad RTP packet
|
|
||||||
// I have to replay last packet payload here to avoid report about lost packet
|
|
||||||
mBuffer.add(p, std::chrono::milliseconds(time_length), samplerate);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Queue packet to buffer
|
|
||||||
auto packet = mBuffer.add(p, std::chrono::milliseconds(time_length), samplerate).get();
|
|
||||||
|
|
||||||
return packet;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioReceiver::processDecoded(Audio::DataWindow& output, DecodeOptions options)
|
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.");
|
ICELogDebug(<< "Gap detected.");
|
||||||
|
|
||||||
mDecodedLength = mResampledLength = 0;
|
mDecodedLength = mResampledLength = 0;
|
||||||
if (mCngPacket && mCodec)
|
if (mCngPacket && mCodec)
|
||||||
{
|
{
|
||||||
// Synthesize comfort noise. It will be done on AUDIO_SAMPLERATE rate directly to mResampledFrame buffer.
|
if (mCngPacket->rtp()->GetPayloadType() == 13)
|
||||||
// Do not forget to send this noise to analysis
|
{
|
||||||
mDecodedLength = mCngDecoder.produce(mCodec->samplerate(), mLastPacketTimeLength,
|
// Synthesize comfort noise. It will be done on AUDIO_SAMPLERATE rate directly to mResampledFrame buffer.
|
||||||
reinterpret_cast<short*>(mDecodedFrame), false);
|
// Do not forget to send this noise to analysis
|
||||||
|
mDecodedLength = mCngDecoder.produce(mCodec->samplerate(), mLastPacketTimeLength, reinterpret_cast<short*>(mDecodedFrame), false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
decodePacketTo(output, options, mCngPacket);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
if (mCodec && mFrameCount && !mCodecSettings.mSkipDecode)
|
if (mCodec && mFrameCount && !mCodecSettings.mSkipDecode)
|
||||||
@@ -573,7 +590,7 @@ AudioReceiver::DecodeResult AudioReceiver::decodeGap(Audio::DataWindow& output,
|
|||||||
mDecodedLength = 0;
|
mDecodedLength = 0;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
mDecodedLength = mCodec->plc(mFrameCount, mDecodedFrame, sizeof mDecodedFrame);
|
mDecodedLength = mCodec->plc(mFrameCount, {(uint8_t*)mDecodedFrame, sizeof mDecodedFrame});
|
||||||
if (!mDecodedLength)
|
if (!mDecodedLength)
|
||||||
{
|
{
|
||||||
// PLC is not support or failed
|
// PLC is not support or failed
|
||||||
@@ -588,158 +605,258 @@ AudioReceiver::DecodeResult AudioReceiver::decodeGap(Audio::DataWindow& output,
|
|||||||
if (mDecodedLength)
|
if (mDecodedLength)
|
||||||
{
|
{
|
||||||
processDecoded(output, options);
|
processDecoded(output, options);
|
||||||
return DecodeResult_Ok;
|
return {.mStatus = DecodeResult::Status::Ok, .mSamplerate = mCodec->samplerate(), .mChannels = mCodec->channels()};
|
||||||
}
|
}
|
||||||
else
|
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;
|
mFailedCount = 0;
|
||||||
for (const std::shared_ptr<RtpBuffer::Packet>& p: rl)
|
// Check if we need to emit silence or CNG - previously CNG packet was detected. Emit CNG audio here if needed.
|
||||||
|
if (mLastPacketTimestamp && mLastPacketTimeLength && mCodec)
|
||||||
{
|
{
|
||||||
assert(p);
|
int units = rtp.GetTimestamp() - *mLastPacketTimestamp;
|
||||||
// Check if we need to emit silence or CNG - previously CNG packet was detected. Emit CNG audio here if needed.
|
int milliseconds = units / (mCodec->samplerate() / 1000);
|
||||||
if (mLastPacketTimestamp && mLastPacketTimeLength && mCodec)
|
if (milliseconds > mLastPacketTimeLength)
|
||||||
{
|
{
|
||||||
int units = p->rtp()->GetTimestamp() - *mLastPacketTimestamp;
|
auto silenceLength = std::chrono::milliseconds(milliseconds - mLastPacketTimeLength);
|
||||||
int milliseconds = units / (mCodec->samplerate() / 1000);
|
|
||||||
if (milliseconds > mLastPacketTimeLength)
|
|
||||||
{
|
|
||||||
auto silenceLength = std::chrono::milliseconds(milliseconds - mLastPacketTimeLength);
|
|
||||||
|
|
||||||
if (mCngPacket && options.mFillGapByCNG)
|
if (mCngPacket && options.mFillGapByCNG)
|
||||||
produceCNG(silenceLength, output, options);
|
produceCNG(silenceLength, output, options);
|
||||||
else
|
else
|
||||||
produceSilence(silenceLength, output, options);
|
produceSilence(silenceLength, output, options);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mLastPacketTimestamp = p->rtp()->GetTimestamp();
|
mLastPacketTimestamp = rtp.GetTimestamp();
|
||||||
|
|
||||||
// Find codec by payload type
|
// Find codec by payload type
|
||||||
int ptype = p->rtp()->GetPayloadType();
|
int ptype = rtp.GetPayloadType();
|
||||||
|
|
||||||
// Look into mCodecMap if exists
|
// Look into mCodecMap if exists
|
||||||
auto codecIter = mCodecMap.find(ptype);
|
auto codecIter = mCodecMap.find(ptype);
|
||||||
if (codecIter == mCodecMap.end())
|
if (codecIter == mCodecMap.end())
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
|
if (!codecIter->second)
|
||||||
|
codecIter->second = mCodecList.createCodecByPayloadType(ptype);
|
||||||
|
|
||||||
if (!codecIter->second)
|
mCodec = codecIter->second;
|
||||||
codecIter->second = mCodecList.createCodecByPayloadType(ptype);
|
if (mCodec)
|
||||||
|
{
|
||||||
|
result.mChannels = mCodec->channels();
|
||||||
|
result.mSamplerate = mCodec->samplerate();
|
||||||
|
|
||||||
mCodec = codecIter->second;
|
// Check if it is CNG packet
|
||||||
if (mCodec)
|
if (((ptype == 0 || ptype == 8) && rtp.GetPayloadLength() >= 1 && rtp.GetPayloadLength() <= 6) || rtp.GetPayloadType() == 13)
|
||||||
{
|
{
|
||||||
if (rate)
|
if (options.mSkipDecode)
|
||||||
*rate = mCodec->samplerate();
|
mDecodedLength = 0;
|
||||||
|
else
|
||||||
// Check if it is CNG packet
|
|
||||||
if ((ptype == 0 || ptype == 8) && p->rtp()->GetPayloadLength() >= 1 && p->rtp()->GetPayloadLength() <= 6)
|
|
||||||
{
|
{
|
||||||
if (options.mSkipDecode)
|
mCngPacket = packet;
|
||||||
mDecodedLength = 0;
|
mCngDecoder.decode3389(rtp.GetPayloadData(), rtp.GetPayloadLength());
|
||||||
else
|
|
||||||
{
|
|
||||||
mCngPacket = p->rtp();
|
|
||||||
mCngDecoder.decode3389(p->rtp()->GetPayloadData(), p->rtp()->GetPayloadLength());
|
|
||||||
|
|
||||||
// Emit CNG mLastPacketLength milliseconds
|
// Emit CNG mLastPacketLength milliseconds
|
||||||
mDecodedLength = mCngDecoder.produce(mCodec->samplerate(), mLastPacketTimeLength,
|
mDecodedLength = mCngDecoder.produce(mCodec->samplerate(), mLastPacketTimeLength, (short*)mDecodedFrame, true);
|
||||||
(short*)mDecodedFrame, true);
|
if (mDecodedLength)
|
||||||
if (mDecodedLength)
|
processDecoded(output, options);
|
||||||
processDecoded(output, options);
|
}
|
||||||
|
result.mStatus = DecodeResult::Status::Ok;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Reset CNG packet as we get regular RTP packet
|
||||||
|
mCngPacket.reset();
|
||||||
|
|
||||||
|
// Handle here regular RTP packets
|
||||||
|
// Check if payload length is ok
|
||||||
|
size_t payload_length = rtp.GetPayloadLength();
|
||||||
|
size_t rtp_frame_length = mCodec->rtpLength();
|
||||||
|
|
||||||
|
int tail = rtp_frame_length ? payload_length % rtp_frame_length : 0;
|
||||||
|
|
||||||
|
if (!tail)
|
||||||
|
{
|
||||||
|
// Find number of frames
|
||||||
|
mFrameCount = mCodec->rtpLength() ? rtp.GetPayloadLength() / mCodec->rtpLength() : 1;
|
||||||
|
int frameLength = mCodec->rtpLength() ? mCodec->rtpLength() : (int)rtp.GetPayloadLength();
|
||||||
|
|
||||||
|
// Save last packet time length
|
||||||
|
mLastPacketTimeLength = mFrameCount * mCodec->frameTime();
|
||||||
|
|
||||||
|
// Decode
|
||||||
|
for (int i=0; i<mFrameCount && !mCodecSettings.mSkipDecode; i++)
|
||||||
|
{
|
||||||
|
if (options.mSkipDecode)
|
||||||
|
mDecodedLength = 0;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Decode frame by frame
|
||||||
|
auto codecInput = std::span{rtp.GetPayloadData() + i * mCodec->rtpLength(), (size_t)frameLength};
|
||||||
|
auto codecOutput = std::span{(uint8_t*)mDecodedFrame, sizeof mDecodedFrame};
|
||||||
|
auto r = mCodec->decode(codecInput, codecOutput);
|
||||||
|
mDecodedLength = r.mDecoded;
|
||||||
|
if (mDecodedLength > 0)
|
||||||
|
processDecoded(output, options);
|
||||||
|
|
||||||
|
// What is important - here we may have packet marked as CNG
|
||||||
|
if (r.mIsCng)
|
||||||
|
mCngPacket = packet;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
result = DecodeResult_Ok;
|
result.mStatus = mFrameCount > 0 ? DecodeResult::Status::Ok : DecodeResult::Status::Skip;
|
||||||
|
|
||||||
|
// Check for bitrate counter
|
||||||
|
updateAmrCodecStats(mCodec.get());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Reset CNG packet as we get regular RTP packet
|
// RTP packet with tail - it should not happen
|
||||||
mCngPacket.reset();
|
result.mStatus = DecodeResult::Status::BadPacket;
|
||||||
|
|
||||||
// Handle here regular RTP packets
|
|
||||||
// Check if payload length is ok
|
|
||||||
size_t payload_length = p->rtp()->GetPayloadLength();
|
|
||||||
size_t rtp_frame_length = mCodec->rtpLength();
|
|
||||||
|
|
||||||
int tail = rtp_frame_length ? payload_length % rtp_frame_length : 0;
|
|
||||||
|
|
||||||
if (!tail)
|
|
||||||
{
|
|
||||||
// Find number of frames
|
|
||||||
mFrameCount = mCodec->rtpLength() ? p->rtp()->GetPayloadLength() / mCodec->rtpLength() : 1;
|
|
||||||
int frameLength = mCodec->rtpLength() ? mCodec->rtpLength() : (int)p->rtp()->GetPayloadLength();
|
|
||||||
|
|
||||||
// Save last packet time length
|
|
||||||
mLastPacketTimeLength = mFrameCount * mCodec->frameTime();
|
|
||||||
|
|
||||||
// Decode
|
|
||||||
for (int i=0; i<mFrameCount && !mCodecSettings.mSkipDecode; i++)
|
|
||||||
{
|
|
||||||
if (options.mSkipDecode)
|
|
||||||
mDecodedLength = 0;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Decode frame by frame
|
|
||||||
mDecodedLength = mCodec->decode(p->rtp()->GetPayloadData() + i * mCodec->rtpLength(),
|
|
||||||
frameLength, mDecodedFrame, sizeof mDecodedFrame);
|
|
||||||
if (mDecodedLength > 0)
|
|
||||||
processDecoded(output, options);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result = mFrameCount > 0 ? DecodeResult_Ok : DecodeResult_Skip;
|
|
||||||
|
|
||||||
// Check for bitrate counter
|
|
||||||
updateAmrCodecStats(mCodec.get());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// RTP packet with tail - it should not happen
|
|
||||||
result = DecodeResult_BadPacket;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
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
|
||||||
mFailedCount++;
|
// 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.
|
||||||
return DecodeResult_Skip;
|
// In this case we just skip the analysis - we should not be called in this situation
|
||||||
}
|
if (options.mElapsed == 0ms || !mCodec)
|
||||||
|
return {.mStatus = DecodeResult::Status::Skip};
|
||||||
|
|
||||||
AudioReceiver::DecodeResult AudioReceiver::getAudio(Audio::DataWindow& output, DecodeOptions options, int* rate)
|
// No packet available at all (and no previous CNG packet) - so return the silence
|
||||||
{
|
if (options.mElapsed != 0ms && mCodec)
|
||||||
DecodeResult result = DecodeResult_Skip;
|
|
||||||
|
|
||||||
// Get next packet from buffer
|
|
||||||
RtpBuffer::ResultList rl;
|
|
||||||
RtpBuffer::FetchResult fr = mBuffer.fetch(rl);
|
|
||||||
switch (fr)
|
|
||||||
{
|
{
|
||||||
case RtpBuffer::FetchResult::Gap: result = decodeGap(output, options); break;
|
Audio::Format fmt = options.mResampleToMainRate ? Audio::Format(AUDIO_SAMPLERATE, 1) : mCodec->getAudioFormat();
|
||||||
case RtpBuffer::FetchResult::NoPacket: result = decodeNone(output, options); break;
|
if (mCngPacket)
|
||||||
case RtpBuffer::FetchResult::RegularPacket: result = decodePacket(rl, output, options, rate); break;
|
{
|
||||||
default:
|
// Try to decode it - replay previous audio decoded or use CNG decoder (if payload type is 13)
|
||||||
assert(0);
|
if (mCngPacket->rtp()->GetPayloadType() == 13)
|
||||||
|
{
|
||||||
|
// Using latest CNG packet to produce comfort noise
|
||||||
|
auto produced = mCngDecoder.produce(fmt.rate(), options.mElapsed.count(), (short*)(output.data() + output.filled()), false);
|
||||||
|
output.setFilled(output.filled() + produced);
|
||||||
|
return {.mStatus = DecodeResult::Status::Ok, .mSamplerate = fmt.rate(), .mChannels = fmt.channels()};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Here we have another packet marked as CNG - for another decoder
|
||||||
|
// Just decode it +1 time
|
||||||
|
return decodePacketTo(output, options, mCngPacket);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Emit silence if codec information is available - it is to properly handle the gaps
|
||||||
|
auto avail = output.getTimeLength(fmt.rate(), fmt.channels());
|
||||||
|
if (options.mElapsed > avail)
|
||||||
|
output.addZero(fmt.sizeFromTime(options.mElapsed - avail));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result == DecodeResult_Ok)
|
mFailedCount++;
|
||||||
|
return {.mStatus = DecodeResult::Status::Skip};
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioReceiver::DecodeResult AudioReceiver::getAudioTo(Audio::DataWindow& output, DecodeOptions options)
|
||||||
|
{
|
||||||
|
DecodeResult result = {.mStatus = DecodeResult::Status::Skip};
|
||||||
|
|
||||||
|
// Process RFC2833 here; it doesn't result in any audio - only callbacks and statistics
|
||||||
|
auto fr = mDtmfBuffer.fetch();
|
||||||
|
if (fr.mPacket && fr.mStatus == RtpBuffer::FetchResult::Status::RegularPacket)
|
||||||
|
mDtmfReceiver.add(fr.mPacket->rtp());
|
||||||
|
|
||||||
|
|
||||||
|
auto produced = 0ms;
|
||||||
|
if (mAvailable.filled() && mCodec && options.mElapsed != 0ms)
|
||||||
|
{
|
||||||
|
Audio::Format fmt = options.mResampleToMainRate ? Audio::Format(AUDIO_SAMPLERATE, 1) : mCodec->getAudioFormat();
|
||||||
|
auto initiallyAvailable = mCodec ? mAvailable.getTimeLength(fmt.rate(), fmt.channels()) : 0ms;
|
||||||
|
if (initiallyAvailable != 0ms)
|
||||||
|
{
|
||||||
|
std::chrono::milliseconds resultTime = std::min(initiallyAvailable, options.mElapsed);
|
||||||
|
auto resultLen = fmt.sizeFromTime(resultTime);
|
||||||
|
mAvailable.moveTo(output, resultLen);
|
||||||
|
produced += resultTime;
|
||||||
|
|
||||||
|
// Maybe request is satisfied ?
|
||||||
|
if (produced >= options.mElapsed)
|
||||||
|
return {.mStatus = DecodeResult::Status::Ok, .mSamplerate = fmt.rate(), .mChannels = fmt.channels()};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::chrono::milliseconds decoded = 0ms;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
// Get next packet from buffer
|
||||||
|
RtpBuffer::ResultList rl;
|
||||||
|
RtpBuffer::FetchResult fr = mBuffer.fetch();
|
||||||
|
// ICELogDebug(<< fr.toString() << " " << mBuffer.findTimelength());
|
||||||
|
|
||||||
|
switch (fr.mStatus)
|
||||||
|
{
|
||||||
|
case RtpBuffer::FetchResult::Status::Gap: result = decodeGapTo(mAvailable, options); break;
|
||||||
|
case RtpBuffer::FetchResult::Status::NoPacket: result = decodeEmptyTo(mAvailable, options); break;
|
||||||
|
case RtpBuffer::FetchResult::Status::RegularPacket: result = decodePacketTo(mAvailable, options, fr.mPacket); break;
|
||||||
|
default:
|
||||||
|
assert(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Was there decoding at all ?
|
||||||
|
if (!mCodec)
|
||||||
|
break; // No sense to continue - we have no information at all
|
||||||
|
|
||||||
|
Audio::Format fmt = options.mResampleToMainRate ? Audio::Format(AUDIO_SAMPLERATE, 1) : mCodec->getAudioFormat();
|
||||||
|
result.mSamplerate = fmt.rate();
|
||||||
|
result.mChannels = fmt.channels();
|
||||||
|
|
||||||
|
// Have we anything interesting in the buffer ?
|
||||||
|
auto bufferAvailable = mAvailable.getTimeLength(fmt.rate(), fmt.channels());
|
||||||
|
if (bufferAvailable == 0ms)
|
||||||
|
break; // No sense to continue - decoding / CNG / PLC stopped totally
|
||||||
|
|
||||||
|
// How much data should be moved to result buffer ?
|
||||||
|
if (options.mElapsed != 0ms)
|
||||||
|
{
|
||||||
|
std::chrono::milliseconds resultTime = std::min(bufferAvailable, options.mElapsed - produced);
|
||||||
|
auto resultLen = fmt.sizeFromTime(resultTime);
|
||||||
|
mAvailable.moveTo(output, resultLen);
|
||||||
|
produced += resultTime;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
mAvailable.moveTo(output, mAvailable.filled());
|
||||||
|
|
||||||
|
decoded += bufferAvailable;
|
||||||
|
}
|
||||||
|
while (produced < options.mElapsed);
|
||||||
|
|
||||||
|
if (produced != 0ms)
|
||||||
|
result.mStatus = DecodeResult::Status::Ok;
|
||||||
|
|
||||||
|
// Time statistics
|
||||||
|
if (result.mStatus == DecodeResult::Status::Ok)
|
||||||
{
|
{
|
||||||
// Decode statistics
|
// Decode statistics
|
||||||
if (!mLastDecodeTimestamp)
|
if (!mDecodeTimestamp)
|
||||||
mLastDecodeTimestamp = std::chrono::steady_clock::now();
|
mDecodeTimestamp = std::chrono::steady_clock::now();
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
auto t = std::chrono::steady_clock::now();
|
auto t = std::chrono::steady_clock::now();
|
||||||
mStat.mDecodingInterval.process(std::chrono::duration_cast<std::chrono::milliseconds>(t - *mLastDecodeTimestamp).count());
|
mStat.mDecodingInterval.process(std::chrono::duration_cast<std::chrono::milliseconds>(t - *mDecodeTimestamp).count());
|
||||||
mLastDecodeTimestamp = t;
|
mDecodeTimestamp = t;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
@@ -795,21 +912,26 @@ void AudioReceiver::updateAmrCodecStats(Codec* c)
|
|||||||
AmrWbCodec* wb = dynamic_cast<AmrWbCodec*>(c);
|
AmrWbCodec* wb = dynamic_cast<AmrWbCodec*>(c);
|
||||||
|
|
||||||
if (nb != nullptr)
|
if (nb != nullptr)
|
||||||
|
{
|
||||||
mStat.mBitrateSwitchCounter = nb->getSwitchCounter();
|
mStat.mBitrateSwitchCounter = nb->getSwitchCounter();
|
||||||
|
mStat.mCng = nb->getCngCounter();
|
||||||
|
}
|
||||||
else
|
else
|
||||||
if (wb != nullptr)
|
if (wb != nullptr)
|
||||||
|
{
|
||||||
mStat.mBitrateSwitchCounter = wb->getSwitchCounter();
|
mStat.mBitrateSwitchCounter = wb->getSwitchCounter();
|
||||||
|
mStat.mCng = wb->getCngCounter();
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
int AudioReceiver::getSize() const
|
int AudioReceiver::getSize() const
|
||||||
{
|
{
|
||||||
int result = 0;
|
int result = 0;
|
||||||
result += sizeof(*this) + mResampler8.getSize() + mResampler16.getSize() + mResampler32.getSize()
|
result += sizeof(*this) + mResampler8.getSize() + mResampler16.getSize() + mResampler32.getSize() + mResampler48.getSize();
|
||||||
+ mResampler48.getSize();
|
|
||||||
|
|
||||||
if (mCodec)
|
if (mCodec)
|
||||||
result += mCodec->getSize();
|
; // ToDo: need the way to calculate size of codec instances
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -860,5 +982,24 @@ DtmfReceiver::DtmfReceiver(Statistics& stat)
|
|||||||
DtmfReceiver::~DtmfReceiver()
|
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
|
class RtpBuffer
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
enum class FetchResult
|
|
||||||
{
|
|
||||||
RegularPacket,
|
|
||||||
Gap,
|
|
||||||
NoPacket
|
|
||||||
};
|
|
||||||
|
|
||||||
// Owns rtp packet data
|
// Owns rtp packet data
|
||||||
class Packet
|
class Packet
|
||||||
{
|
{
|
||||||
@@ -59,6 +52,29 @@ public:
|
|||||||
std::chrono::microseconds mTimestamp = 0us;
|
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(Statistics& stat);
|
||||||
~RtpBuffer();
|
~RtpBuffer();
|
||||||
|
|
||||||
@@ -81,12 +97,12 @@ public:
|
|||||||
int getCount() const;
|
int getCount() const;
|
||||||
|
|
||||||
// Returns false if packet was not add - maybe too old or too new or duplicate
|
// 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::vector<std::shared_ptr<Packet>> ResultList;
|
||||||
typedef std::shared_ptr<ResultList> PResultList;
|
typedef std::shared_ptr<ResultList> PResultList;
|
||||||
|
|
||||||
FetchResult fetch(ResultList& rl);
|
FetchResult fetch();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
unsigned mSsrc = 0;
|
unsigned mSsrc = 0;
|
||||||
@@ -104,6 +120,7 @@ protected:
|
|||||||
jrtplib::RTPSourceStats mRtpStats;
|
jrtplib::RTPSourceStats mRtpStats;
|
||||||
std::shared_ptr<Packet> mFetchedPacket;
|
std::shared_ptr<Packet> mFetchedPacket;
|
||||||
std::optional<uint32_t> mLastSeqno;
|
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.
|
// To calculate average interval between packet add. It is close to jitter but more useful in debugging.
|
||||||
float mLastAddTime = 0.0f;
|
float mLastAddTime = 0.0f;
|
||||||
@@ -119,6 +136,23 @@ protected:
|
|||||||
Statistics& mStat;
|
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
|
class AudioReceiver: public Receiver
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -133,34 +167,33 @@ public:
|
|||||||
// Lifetime of pointer to codec is limited by lifetime of AudioReceiver (it is container).
|
// 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);
|
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
|
struct DecodeOptions
|
||||||
{
|
{
|
||||||
bool mResampleToMainRate = true;
|
bool mResampleToMainRate = true; // Resample all decoded audio to AUDIO_SAMPLERATE
|
||||||
bool mFillGapByCNG = false;
|
bool mFillGapByCNG = false; // Use CNG information if available
|
||||||
bool mSkipDecode = false;
|
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
|
enum class Status
|
||||||
DecodeResult_Skip, // Just no data - emit silence instead
|
{
|
||||||
DecodeResult_BadPacket // Error happened during the decode
|
Ok, // Decoded ok
|
||||||
|
Skip, // Just no data - emit silence instead
|
||||||
|
BadPacket // Error happened during the decode
|
||||||
|
};
|
||||||
|
|
||||||
|
Status mStatus = Status::Ok;
|
||||||
|
int mSamplerate = 0;
|
||||||
|
int mChannels = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
DecodeResult getAudio(Audio::DataWindow& output, DecodeOptions options = {.mResampleToMainRate = true, .mFillGapByCNG = false, .mSkipDecode = false}, int* rate = nullptr);
|
DecodeResult getAudioTo(Audio::DataWindow& output, DecodeOptions options);
|
||||||
|
|
||||||
// Looks for codec by payload type
|
// Looks for codec by payload type
|
||||||
Codec* findCodec(int payloadType);
|
Codec* findCodec(int payloadType);
|
||||||
RtpBuffer& getRtpBuffer() { return mBuffer; }
|
RtpBuffer& getRtpBuffer() { return mBuffer; }
|
||||||
|
|
||||||
// Returns size of AudioReceiver's instance in bytes (including size of all data + codecs + etc.)
|
// Returns size of AudioReceiver's instance in bytes (including size of all data + codecs + etc.)
|
||||||
int getSize() const;
|
int getSize() const;
|
||||||
@@ -173,17 +206,23 @@ public:
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
RtpBuffer mBuffer; // Jitter buffer itself
|
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;
|
CodecMap mCodecMap;
|
||||||
PCodec mCodec;
|
PCodec mCodec;
|
||||||
int mFrameCount = 0;
|
int mFrameCount = 0;
|
||||||
CodecList::Settings mCodecSettings;
|
CodecList::Settings mCodecSettings;
|
||||||
CodecList mCodecList;
|
CodecList mCodecList;
|
||||||
JitterStatistics mJitterStats;
|
JitterStatistics mJitterStats;
|
||||||
std::shared_ptr<jrtplib::RTPPacket> mCngPacket;
|
std::shared_ptr<RtpBuffer::Packet> mCngPacket;
|
||||||
CngDecoder mCngDecoder;
|
CngDecoder mCngDecoder;
|
||||||
size_t mDTXSamplesToEmit = 0; // How much silence (or CNG) should be emited before next RTP packet gets into the action
|
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];
|
int16_t mDecodedFrame[MT_MAX_DECODEBUFFER];
|
||||||
size_t mDecodedLength = 0;
|
size_t mDecodedLength = 0;
|
||||||
|
|
||||||
@@ -200,11 +239,14 @@ protected:
|
|||||||
std::optional<uint32_t> mLastPacketTimestamp;
|
std::optional<uint32_t> mLastPacketTimestamp;
|
||||||
|
|
||||||
int mFailedCount = 0;
|
int mFailedCount = 0;
|
||||||
Audio::Resampler mResampler8, mResampler16, mResampler32, mResampler48;
|
Audio::Resampler mResampler8,
|
||||||
|
mResampler16,
|
||||||
|
mResampler32,
|
||||||
|
mResampler48;
|
||||||
|
|
||||||
Audio::PWavFileWriter mDecodedDump;
|
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;
|
float mIntervalSum = 0.0f;
|
||||||
int mIntervalCount = 0;
|
int mIntervalCount = 0;
|
||||||
@@ -220,19 +262,11 @@ protected:
|
|||||||
// Calculate bitrate switch statistics for AMR codecs
|
// Calculate bitrate switch statistics for AMR codecs
|
||||||
void updateAmrCodecStats(Codec* c);
|
void updateAmrCodecStats(Codec* c);
|
||||||
|
|
||||||
DecodeResult decodeGap(Audio::DataWindow& output, DecodeOptions options);
|
DecodeResult decodeGapTo(Audio::DataWindow& output, DecodeOptions options);
|
||||||
DecodeResult decodePacket(const RtpBuffer::ResultList& rl, Audio::DataWindow& output, DecodeOptions options, int* rate = nullptr);
|
DecodeResult decodePacketTo(Audio::DataWindow& output, DecodeOptions options, const std::shared_ptr<RtpBuffer::Packet>& p);
|
||||||
DecodeResult decodeNone(Audio::DataWindow& output, DecodeOptions options);
|
DecodeResult decodeEmptyTo(Audio::DataWindow& output, DecodeOptions options);
|
||||||
};
|
};
|
||||||
|
|
||||||
class DtmfReceiver: public Receiver
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
DtmfReceiver(Statistics& stat);
|
|
||||||
~DtmfReceiver();
|
|
||||||
|
|
||||||
void add(std::shared_ptr<RTPPacket> p);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -210,18 +210,17 @@ void AudioStream::addData(const void* buffer, int bytes)
|
|||||||
if (mSendingDump)
|
if (mSendingDump)
|
||||||
mSendingDump->write((const char*)mCapturedAudio.data() + codec->pcmLength() * i, codec->pcmLength());
|
mSendingDump->write((const char*)mCapturedAudio.data() + codec->pcmLength() * i, codec->pcmLength());
|
||||||
|
|
||||||
int produced;
|
auto r = codec->encode({(const uint8_t*)mCapturedAudio.data() + codec->pcmLength()*i, (size_t)codec->pcmLength()},
|
||||||
produced = codec->encode((const char*)mCapturedAudio.data() + codec->pcmLength()*i,
|
{(uint8_t*)mFrameBuffer, MT_MAXAUDIOFRAME});
|
||||||
codec->pcmLength(), mFrameBuffer, MT_MAXAUDIOFRAME);
|
|
||||||
|
|
||||||
// Counter of processed input bytes of raw pcm data from microphone
|
// Counter of processed input bytes of raw pcm data from microphone
|
||||||
processed += codec->pcmLength();
|
processed += codec->pcmLength();
|
||||||
encodedTime += codec->frameTime();
|
encodedTime += codec->frameTime();
|
||||||
mEncodedTime += codec->frameTime();
|
mEncodedTime += codec->frameTime();
|
||||||
|
|
||||||
if (produced)
|
if (r.mEncoded)
|
||||||
{
|
{
|
||||||
mEncodedAudio.appendBuffer(mFrameBuffer, produced);
|
mEncodedAudio.appendBuffer(mFrameBuffer, r.mEncoded);
|
||||||
if (packetTime <= encodedTime)
|
if (packetTime <= encodedTime)
|
||||||
{
|
{
|
||||||
// Time to send packet
|
// 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
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
@@ -9,31 +9,31 @@ using namespace MT;
|
|||||||
|
|
||||||
int Codec::Factory::channels()
|
int Codec::Factory::channels()
|
||||||
{
|
{
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
void Codec::Factory::create(CodecMap& codecs)
|
void Codec::Factory::create(CodecMap& codecs)
|
||||||
{
|
{
|
||||||
codecs[payloadType()] = std::shared_ptr<Codec>(create());
|
codecs[payloadType()] = std::shared_ptr<Codec>(create());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Codec::Factory::updateSdp(resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction)
|
void Codec::Factory::updateSdp(resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction)
|
||||||
{
|
{
|
||||||
codecs.push_back(resipCodec());
|
codecs.push_back(resipCodec());
|
||||||
}
|
}
|
||||||
|
|
||||||
resip::Codec Codec::Factory::resipCodec()
|
resip::Codec Codec::Factory::resipCodec()
|
||||||
{
|
{
|
||||||
resip::Codec c(this->name(), this->payloadType(), this->samplerate());
|
resip::Codec c(this->name(), this->payloadType(), this->samplerate());
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Codec::Factory::processSdp(const resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction)
|
int Codec::Factory::processSdp(const resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction)
|
||||||
{
|
{
|
||||||
for (resip::SdpContents::Session::Medium::CodecContainer::const_iterator codecIter = codecs.begin(); codecIter != codecs.end(); ++codecIter)
|
for (resip::SdpContents::Session::Medium::CodecContainer::const_iterator codecIter = codecs.begin(); codecIter != codecs.end(); ++codecIter)
|
||||||
{
|
{
|
||||||
if (resipCodec() == *codecIter)
|
if (resipCodec() == *codecIter)
|
||||||
return codecIter->payloadType();
|
return codecIter->payloadType();
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright(C) 2007-2016 VoIP objects (voipobjects.com)
|
/* Copyright(C) 2007-2026 VoIP objects (voipobjects.com)
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
@@ -6,11 +6,12 @@
|
|||||||
#ifndef __MT_CODEC_H
|
#ifndef __MT_CODEC_H
|
||||||
#define __MT_CODEC_H
|
#define __MT_CODEC_H
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <span>
|
||||||
|
|
||||||
#include "resiprocate/resip/stack/SdpContents.hxx"
|
#include "resiprocate/resip/stack/SdpContents.hxx"
|
||||||
#include "../helper/HL_Types.h"
|
#include "../helper/HL_Types.h"
|
||||||
#include <map>
|
#include "../audio/Audio_Interface.h"
|
||||||
#include "../helper/HL_Pointer.h"
|
|
||||||
|
|
||||||
|
|
||||||
namespace MT
|
namespace MT
|
||||||
{
|
{
|
||||||
@@ -18,12 +19,12 @@ class Codec;
|
|||||||
typedef std::shared_ptr<Codec> PCodec;
|
typedef std::shared_ptr<Codec> PCodec;
|
||||||
|
|
||||||
class CodecMap: public std::map<int, PCodec>
|
class CodecMap: public std::map<int, PCodec>
|
||||||
{
|
{};
|
||||||
};
|
|
||||||
|
|
||||||
class Codec
|
class Codec
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
||||||
class Factory
|
class Factory
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -42,34 +43,51 @@ public:
|
|||||||
resip::Codec resipCodec();
|
resip::Codec resipCodec();
|
||||||
};
|
};
|
||||||
virtual ~Codec() {}
|
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
|
struct Info
|
||||||
virtual int pcmLength() = 0;
|
{
|
||||||
|
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
|
// Helper functions to return information - they are based on info() method
|
||||||
virtual int frameTime() = 0;
|
int pcmLength() { return info().mPcmLength; }
|
||||||
|
int rtpLength() { return info().mRtpLength; }
|
||||||
// Size of RTP frame in bytes. Can be zero for variable sized codecs.
|
int channels() { return info().mChannels; }
|
||||||
virtual int rtpLength() = 0;
|
int samplerate() { return info().mSamplerate; }
|
||||||
|
int frameTime() { return info().mFrameTime; }
|
||||||
// Number of audio channels
|
std::string name() { return info().mName; }
|
||||||
virtual int channels() { return 1; }
|
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
|
// 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
|
// 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
|
// 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
|
#endif
|
||||||
|
|||||||
@@ -65,6 +65,8 @@ bool CodecList::Settings::contains(int ptype) const
|
|||||||
|
|
||||||
if (mGsmEfrPayloadType == ptype || mGsmFrPayloadType == ptype || mGsmHrPayloadType == ptype)
|
if (mGsmEfrPayloadType == ptype || mGsmFrPayloadType == ptype || mGsmHrPayloadType == ptype)
|
||||||
return true;
|
return true;
|
||||||
|
if (mTelephoneEvent == ptype)
|
||||||
|
return true;
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -122,6 +124,9 @@ std::string CodecList::Settings::toString() const
|
|||||||
if (mGsmEfrPayloadType != -1)
|
if (mGsmEfrPayloadType != -1)
|
||||||
oss << "GSM EFR ptype: " << mGsmEfrPayloadType << " ";
|
oss << "GSM EFR ptype: " << mGsmEfrPayloadType << " ";
|
||||||
|
|
||||||
|
if (mTelephoneEvent != -1)
|
||||||
|
oss << "RFC2833 DTMF ptype: " << mTelephoneEvent;
|
||||||
|
|
||||||
for (auto& spec: mEvsSpec)
|
for (auto& spec: mEvsSpec)
|
||||||
{
|
{
|
||||||
oss << "EVS ptype: " << spec.mPayloadType << ", bw: " << spec.mBandwidth << ", enc: " << (spec.mEncodingType == EvsSpec::Encoding_MIME ? "mime" : "g192") << " ";
|
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;
|
oss << "OPUS ptype: " << spec.mPayloadType << ", rate: " << spec.mRate << ", channels: " << spec.mChannels << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return oss.str();
|
return oss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,6 +157,7 @@ void CodecList::Settings::clear()
|
|||||||
mGsmEfrPayloadType = -1;
|
mGsmEfrPayloadType = -1;
|
||||||
mGsmFrPayloadType = -1;
|
mGsmFrPayloadType = -1;
|
||||||
mGsmHrPayloadType = -1;
|
mGsmHrPayloadType = -1;
|
||||||
|
mTelephoneEvent = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CodecList::Settings::EvsSpec::isValid() const
|
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") {
|
} else if (codec_name == "EVS") {
|
||||||
r.mEvsSpec.push_back({ptype});
|
r.mEvsSpec.push_back({ptype});
|
||||||
}
|
} else if (codec_name == "TELEPHONE-EVENT")
|
||||||
|
r.mTelephoneEvent = ptype;
|
||||||
}
|
}
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CodecList::Settings::operator == (const Settings& rhs) const
|
bool CodecList::Settings::operator == (const Settings& rhs) const
|
||||||
{
|
{
|
||||||
if (std::tie(mWrapIuUP, mSkipDecode, mIsac16KPayloadType, mIsac32KPayloadType, mIlbc20PayloadType, mIlbc30PayloadType, mGsmFrPayloadType, mGsmFrPayloadLength, mGsmEfrPayloadType, 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))
|
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;
|
return false;
|
||||||
|
|
||||||
if (mAmrNbOctetPayloadType != rhs.mAmrNbOctetPayloadType)
|
if (mAmrNbOctetPayloadType != rhs.mAmrNbOctetPayloadType)
|
||||||
@@ -306,6 +314,9 @@ bool CodecList::Settings::operator == (const Settings& rhs) const
|
|||||||
if (mOpusSpec[i] != rhs.mOpusSpec[i])
|
if (mOpusSpec[i] != rhs.mOpusSpec[i])
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
if (mTelephoneEvent != rhs.mTelephoneEvent)
|
||||||
|
return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,9 @@ public:
|
|||||||
bool mWrapIuUP = false;
|
bool mWrapIuUP = false;
|
||||||
bool mSkipDecode = false;
|
bool mSkipDecode = false;
|
||||||
|
|
||||||
|
// RFC2833 DTMF
|
||||||
|
int mTelephoneEvent = -1;
|
||||||
|
|
||||||
// AMR payload types
|
// AMR payload types
|
||||||
std::set<int64_t> mAmrWbPayloadType = { };
|
std::set<int64_t> mAmrWbPayloadType = { };
|
||||||
std::set<int64_t> mAmrNbPayloadType = { };
|
std::set<int64_t> mAmrNbPayloadType = { };
|
||||||
|
|||||||
@@ -16,38 +16,68 @@
|
|||||||
|
|
||||||
using namespace MT;
|
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(output);
|
||||||
assert(tone);
|
assert(ev.mTone != 0);
|
||||||
|
|
||||||
unsigned char toneValue = 0;
|
unsigned char toneValue = 0;
|
||||||
if (tone >= '0' && tone <='9')
|
if (ev.mTone >= '0' && ev.mTone <='9')
|
||||||
toneValue = tone - '0';
|
toneValue = ev.mTone - '0';
|
||||||
else
|
else
|
||||||
if (tone >= 'A' && tone <='D' )
|
if (ev.mTone >= 'A' && ev.mTone <='D' )
|
||||||
toneValue = tone - 'A' + 12;
|
toneValue = ev.mTone - 'A' + 12;
|
||||||
else
|
else
|
||||||
if (tone == '*')
|
if (ev.mTone == '*')
|
||||||
toneValue = 10;
|
toneValue = 10;
|
||||||
else
|
else
|
||||||
if (tone == '#')
|
if (ev.mTone == '#')
|
||||||
toneValue = 11;
|
toneValue = 11;
|
||||||
|
|
||||||
char* packet = (char*)output;
|
char* packet = (char*)output;
|
||||||
|
|
||||||
packet[0] = toneValue;
|
packet[0] = toneValue;
|
||||||
packet[1] = 1 | (volume << 2);
|
packet[1] = 1 | (ev.mVolume << 2);
|
||||||
if (endOfEvent)
|
if (ev.mEnd)
|
||||||
packet[1] |= 128;
|
packet[1] |= 128;
|
||||||
else
|
else
|
||||||
packet[1] &= 127;
|
packet[1] &= 127;
|
||||||
|
|
||||||
unsigned short durationValue = htons(duration);
|
unsigned short durationValue = htons(ev.mDuration);
|
||||||
memcpy(packet + 2, &durationValue, 2);
|
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
|
#pragma region Inband DTMF support
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
#ifndef TARGET_WIN
|
#ifndef TARGET_WIN
|
||||||
@@ -62,7 +92,7 @@ static bool sineTabInit = false;
|
|||||||
static double sinetab[1 << 11];
|
static double sinetab[1 << 11];
|
||||||
static inline double sine(unsigned int ptr)
|
static inline double sine(unsigned int ptr)
|
||||||
{
|
{
|
||||||
return sinetab[ptr >> (32-11)];
|
return sinetab[ptr >> (32-11)];
|
||||||
}
|
}
|
||||||
|
|
||||||
#define TWOPI (2.0 * 3.14159265358979323846)
|
#define TWOPI (2.0 * 3.14159265358979323846)
|
||||||
@@ -75,13 +105,13 @@ static inline double sine(unsigned int ptr)
|
|||||||
static double amptab[2] = { 8191.75, 16383.5 };
|
static double amptab[2] = { 8191.75, 16383.5 };
|
||||||
static inline int ifix(double x)
|
static inline int ifix(double x)
|
||||||
{
|
{
|
||||||
return (x >= 0.0) ? (int) (x+0.5) : (int) (x-0.5);
|
return (x >= 0.0) ? (int) (x+0.5) : (int) (x-0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
// given frequency f, return corresponding phase increment
|
// given frequency f, return corresponding phase increment
|
||||||
static inline int phinc(double f)
|
static inline int phinc(double f)
|
||||||
{
|
{
|
||||||
return ifix(TWO32 * f / (double) AUDIO_SAMPLERATE);
|
return ifix(TWO32 * f / (double) AUDIO_SAMPLERATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
static char dtmfSymbols[16] = {
|
static char dtmfSymbols[16] = {
|
||||||
@@ -105,32 +135,32 @@ static char dtmfSymbols[16] = {
|
|||||||
|
|
||||||
char PDTMFEncoder_DtmfChar(int i)
|
char PDTMFEncoder_DtmfChar(int i)
|
||||||
{
|
{
|
||||||
|
|
||||||
if (i < 16)
|
if (i < 16)
|
||||||
return dtmfSymbols[i];
|
return dtmfSymbols[i];
|
||||||
else
|
else
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// DTMF frequencies as per http://www.commlinx.com.au/DTMF_frequencies.htm
|
// DTMF frequencies as per http://www.commlinx.com.au/DTMF_frequencies.htm
|
||||||
|
|
||||||
static double dtmfFreqs[16][2] = {
|
static double dtmfFreqs[16][2] = {
|
||||||
{ 941.0, 1336.0 }, // 0
|
{ 941.0, 1336.0 }, // 0
|
||||||
{ 697.0, 1209.0 }, // 1
|
{ 697.0, 1209.0 }, // 1
|
||||||
{ 697.0, 1336.0 }, // 2
|
{ 697.0, 1336.0 }, // 2
|
||||||
{ 697.0, 1477.0 }, // 3
|
{ 697.0, 1477.0 }, // 3
|
||||||
{ 770.0, 1209.0 }, // 4
|
{ 770.0, 1209.0 }, // 4
|
||||||
{ 770.0, 1336.0 }, // 5
|
{ 770.0, 1336.0 }, // 5
|
||||||
{ 770.0, 1477.0 }, // 6
|
{ 770.0, 1477.0 }, // 6
|
||||||
{ 852.0, 1209.0 }, // 7
|
{ 852.0, 1209.0 }, // 7
|
||||||
{ 852.0, 1336.0 }, // 8
|
{ 852.0, 1336.0 }, // 8
|
||||||
{ 852.0, 1477.0 }, // 9
|
{ 852.0, 1477.0 }, // 9
|
||||||
{ 697.0, 1633.0 }, // A
|
{ 697.0, 1633.0 }, // A
|
||||||
{ 770.0, 1633.0 }, // B
|
{ 770.0, 1633.0 }, // B
|
||||||
{ 852.0, 1633.0 }, // C
|
{ 852.0, 1633.0 }, // C
|
||||||
{ 941.0, 1633.0 }, // D
|
{ 941.0, 1633.0 }, // D
|
||||||
{ 941.0, 1209.0 }, // *
|
{ 941.0, 1209.0 }, // *
|
||||||
{ 941.0, 1477.0 } // #
|
{ 941.0, 1477.0 } // #
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -138,81 +168,81 @@ static Mutex LocalDtmfMutex;
|
|||||||
|
|
||||||
void PDTMFEncoder_MakeSineTable()
|
void PDTMFEncoder_MakeSineTable()
|
||||||
{
|
{
|
||||||
Lock lock(LocalDtmfMutex);
|
Lock lock(LocalDtmfMutex);
|
||||||
|
|
||||||
if (!sineTabInit) {
|
if (!sineTabInit) {
|
||||||
for (int k = 0; k < SINELEN; k++) {
|
for (int k = 0; k < SINELEN; k++) {
|
||||||
double th = TWOPI * (double) k / (double) SINELEN;
|
double th = TWOPI * (double) k / (double) SINELEN;
|
||||||
double v = sin(th);
|
double v = sin(th);
|
||||||
sinetab[k] = v;
|
sinetab[k] = v;
|
||||||
|
}
|
||||||
|
sineTabInit = true;
|
||||||
}
|
}
|
||||||
sineTabInit = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PDTMFEncoder_AddTone(double f1, double f2, unsigned ms1, unsigned ms2, unsigned rate, short* result)
|
void PDTMFEncoder_AddTone(double f1, double f2, unsigned ms1, unsigned ms2, unsigned rate, short* result)
|
||||||
{
|
{
|
||||||
int ak = 0;
|
int ak = 0;
|
||||||
|
|
||||||
PDTMFEncoder_MakeSineTable();
|
PDTMFEncoder_MakeSineTable();
|
||||||
|
|
||||||
int dataPtr = 0;
|
int dataPtr = 0;
|
||||||
|
|
||||||
double amp = amptab[ak];
|
double amp = amptab[ak];
|
||||||
int phinc1 = phinc(f1), phinc2 = phinc(f2);
|
int phinc1 = phinc(f1), phinc2 = phinc(f2);
|
||||||
int ns1 = ms1 * (rate/1000);
|
int ns1 = ms1 * (rate/1000);
|
||||||
int ns2 = ms2 * (rate/1000);
|
int ns2 = ms2 * (rate/1000);
|
||||||
unsigned int ptr1 = 0, ptr2 = 0;
|
unsigned int ptr1 = 0, ptr2 = 0;
|
||||||
ptr1 += phinc1 * ns1;
|
ptr1 += phinc1 * ns1;
|
||||||
ptr2 += phinc2 * ns1;
|
ptr2 += phinc2 * ns1;
|
||||||
|
|
||||||
for (int n = ns1; n < ns2; n++) {
|
for (int n = ns1; n < ns2; n++) {
|
||||||
|
|
||||||
double val = amp * (sine(ptr1) + sine(ptr2));
|
double val = amp * (sine(ptr1) + sine(ptr2));
|
||||||
int ival = ifix(val);
|
int ival = ifix(val);
|
||||||
if (ival < -32768)
|
if (ival < -32768)
|
||||||
ival = -32768;
|
ival = -32768;
|
||||||
else if (val > 32767)
|
else if (val > 32767)
|
||||||
ival = 32767;
|
ival = 32767;
|
||||||
|
|
||||||
result[dataPtr++] = ival / 2;
|
result[dataPtr++] = ival / 2;
|
||||||
|
|
||||||
ptr1 += phinc1;
|
ptr1 += phinc1;
|
||||||
ptr2 += phinc2;
|
ptr2 += phinc2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PDTMFEncoder_AddTone(char _digit, unsigned startTime, unsigned finishTime, unsigned rate, short* result)
|
void PDTMFEncoder_AddTone(char _digit, unsigned startTime, unsigned finishTime, unsigned rate, short* result)
|
||||||
{
|
{
|
||||||
char digit = (char)toupper(_digit);
|
char digit = (char)toupper(_digit);
|
||||||
if ('0' <= digit && digit <= '9')
|
if ('0' <= digit && digit <= '9')
|
||||||
digit = digit - '0';
|
digit = digit - '0';
|
||||||
|
|
||||||
else if ('A' <= digit && digit <= 'D')
|
else if ('A' <= digit && digit <= 'D')
|
||||||
digit = digit + 10 - 'A';
|
digit = digit + 10 - 'A';
|
||||||
|
|
||||||
else if (digit == '*')
|
else if (digit == '*')
|
||||||
digit = 14;
|
digit = 14;
|
||||||
|
|
||||||
else if (digit == '#')
|
else if (digit == '#')
|
||||||
digit = 15;
|
digit = 15;
|
||||||
|
|
||||||
else
|
else
|
||||||
return ;
|
return ;
|
||||||
|
|
||||||
PDTMFEncoder_AddTone(dtmfFreqs[(int)digit][0], dtmfFreqs[(int)digit][1], startTime, finishTime, rate, result);
|
PDTMFEncoder_AddTone(dtmfFreqs[(int)digit][0], dtmfFreqs[(int)digit][1], startTime, finishTime, rate, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma endregion
|
#pragma endregion
|
||||||
|
|
||||||
void DtmfBuilder::buildInband(int tone, int startTime, int finishTime, int rate, short* buf)
|
void DtmfBuilder::buildInband(int tone, int startTime, int finishTime, int rate, short* buf)
|
||||||
{
|
{
|
||||||
PDTMFEncoder_AddTone(tone, startTime, finishTime, rate, buf);
|
PDTMFEncoder_AddTone(tone, startTime, finishTime, rate, buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma region DtmfContext
|
#pragma region DtmfContext
|
||||||
DtmfContext::DtmfContext()
|
DtmfContext::DtmfContext()
|
||||||
:mType(Dtmf_Rfc2833)
|
:mType(Dtmf_Rfc2833)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,112 +252,112 @@ DtmfContext::~DtmfContext()
|
|||||||
|
|
||||||
void DtmfContext::setType(Type t)
|
void DtmfContext::setType(Type t)
|
||||||
{
|
{
|
||||||
mType = t;
|
mType = t;
|
||||||
}
|
}
|
||||||
|
|
||||||
DtmfContext::Type DtmfContext::type()
|
DtmfContext::Type DtmfContext::type()
|
||||||
{
|
{
|
||||||
return mType;
|
return mType;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DtmfContext::startTone(int tone, int volume)
|
void DtmfContext::startTone(int tone, int volume)
|
||||||
{
|
{
|
||||||
Lock l(mGuard);
|
Lock l(mGuard);
|
||||||
|
|
||||||
// Stop current tone if needed
|
// Stop current tone if needed
|
||||||
if (mQueue.size())
|
if (mQueue.size())
|
||||||
stopTone();
|
stopTone();
|
||||||
mQueue.push_back(Dtmf(tone, volume, 0));
|
mQueue.push_back(Dtmf(tone, volume, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
void DtmfContext::stopTone()
|
void DtmfContext::stopTone()
|
||||||
{
|
{
|
||||||
Lock l(mGuard);
|
Lock l(mGuard);
|
||||||
|
|
||||||
// Switch to "emit 3 terminating packets" mode
|
|
||||||
if (mQueue.size())
|
|
||||||
{
|
|
||||||
switch (mType)
|
|
||||||
{
|
|
||||||
case Dtmf_Rfc2833:
|
|
||||||
mQueue.front().mStopped = true;
|
|
||||||
mQueue.erase(mQueue.begin());
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Dtmf_Inband:
|
// Switch to "emit 3 terminating packets" mode
|
||||||
if (!mQueue.front().mFinishCount)
|
if (mQueue.size())
|
||||||
mQueue.front().mFinishCount = MT_DTMF_END_PACKETS;
|
{
|
||||||
break;
|
switch (mType)
|
||||||
|
{
|
||||||
|
case Dtmf_Rfc2833:
|
||||||
|
mQueue.front().mStopped = true;
|
||||||
|
mQueue.erase(mQueue.begin());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Dtmf_Inband:
|
||||||
|
if (!mQueue.front().mFinishCount)
|
||||||
|
mQueue.front().mFinishCount = MT_DTMF_END_PACKETS;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DtmfContext::queueTone(int tone, int volume, int duration)
|
void DtmfContext::queueTone(int tone, int volume, int duration)
|
||||||
{
|
{
|
||||||
Lock l(mGuard);
|
Lock l(mGuard);
|
||||||
mQueue.push_back(Dtmf(tone, volume, duration));
|
mQueue.push_back(Dtmf(tone, volume, duration));
|
||||||
}
|
}
|
||||||
|
|
||||||
void DtmfContext::clearAllTones()
|
void DtmfContext::clearAllTones()
|
||||||
{
|
{
|
||||||
Lock l(mGuard);
|
Lock l(mGuard);
|
||||||
mQueue.clear();
|
mQueue.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DtmfContext::getInband(int milliseconds, int rate, ByteBuffer& output)
|
bool DtmfContext::getInband(int milliseconds, int rate, ByteBuffer& output)
|
||||||
{
|
{
|
||||||
Lock l(mGuard);
|
Lock l(mGuard);
|
||||||
|
|
||||||
if (!mQueue.size() || mType != Dtmf_Inband)
|
if (!mQueue.size() || mType != Dtmf_Inband)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
//
|
//
|
||||||
Dtmf& d = mQueue.front();
|
Dtmf& d = mQueue.front();
|
||||||
|
|
||||||
output.resize(milliseconds * rate / 1000 * 2);
|
output.resize((uint64_t)milliseconds * rate / 1000 * 2);
|
||||||
DtmfBuilder::buildInband(d.mTone, d.mCurrentTime, d.mCurrentTime + milliseconds, rate, (short*)output.mutableData());
|
DtmfBuilder::buildInband(d.mTone, d.mCurrentTime, d.mCurrentTime + milliseconds, rate, (short*)output.mutableData());
|
||||||
d.mCurrentTime += milliseconds;
|
d.mCurrentTime += milliseconds;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DtmfContext::getRfc2833(int milliseconds, ByteBuffer& output, ByteBuffer& stopPacket)
|
bool DtmfContext::getRfc2833(int milliseconds, ByteBuffer& output, ByteBuffer& stopPacket)
|
||||||
{
|
{
|
||||||
Lock l(mGuard);
|
Lock l(mGuard);
|
||||||
if (!mQueue.size() || mType != Dtmf_Rfc2833)
|
if (!mQueue.size() || mType != Dtmf_Rfc2833)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
Dtmf& d = mQueue.front();
|
Dtmf& d = mQueue.front();
|
||||||
// See if tone has enough duration to produce another packet
|
// See if tone has enough duration to produce another packet
|
||||||
if (d.mDuration > 0)
|
if (d.mDuration > 0)
|
||||||
{
|
{
|
||||||
// Emit rfc2833 packet
|
// Emit rfc2833 packet
|
||||||
output.resize(4);
|
output.resize(4);
|
||||||
DtmfBuilder::buildRfc2833(d.mTone, milliseconds, d.mVolume, false, output.mutableData());
|
DtmfBuilder::buildRfc2833({.mTone = (char)d.mTone, .mDuration = milliseconds, .mVolume = d.mVolume, .mEnd = false}, output.mutableData());
|
||||||
d.mDuration -= milliseconds;
|
d.mDuration -= milliseconds;
|
||||||
if(d.mDuration <= 0)
|
if(d.mDuration <= 0)
|
||||||
d.mStopped = true;
|
d.mStopped = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
if (!d.mStopped)
|
if (!d.mStopped)
|
||||||
{
|
{
|
||||||
output.resize(4);
|
output.resize(4);
|
||||||
DtmfBuilder::buildRfc2833(d.mTone, milliseconds, d.mVolume, false, output.mutableData());
|
DtmfBuilder::buildRfc2833({.mTone = (char)d.mTone, .mDuration = milliseconds, .mVolume = d.mVolume, .mEnd = false}, output.mutableData());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
output.clear();
|
output.clear();
|
||||||
|
|
||||||
if (d.mStopped)
|
|
||||||
{
|
|
||||||
stopPacket.resize(4);
|
|
||||||
DtmfBuilder::buildRfc2833(d.mTone, milliseconds, d.mVolume, true, stopPacket.mutableData());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
stopPacket.clear();
|
|
||||||
|
|
||||||
if (d.mStopped)
|
|
||||||
mQueue.erase(mQueue.begin());
|
|
||||||
|
|
||||||
return true;
|
if (d.mStopped)
|
||||||
|
{
|
||||||
|
stopPacket.resize(4);
|
||||||
|
DtmfBuilder::buildRfc2833({.mTone = (char)d.mTone, .mDuration = milliseconds, .mVolume = d.mVolume, .mEnd = true}, stopPacket.mutableData());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
stopPacket.clear();
|
||||||
|
|
||||||
|
if (d.mStopped)
|
||||||
|
mQueue.erase(mQueue.begin());
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
@@ -375,32 +405,32 @@ void zap_dtmf_detect_init(dtmf_detect_state_t *s);
|
|||||||
int zap_dtmf_detect(dtmf_detect_state_t *s, int16_t amp[], int samples, int isradio);
|
int zap_dtmf_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);
|
int zap_dtmf_get(dtmf_detect_state_t *s, char *buf, int max);
|
||||||
|
|
||||||
DTMFDetector::DTMFDetector()
|
InbandDtmfDetector::InbandDtmfDetector()
|
||||||
:mState(NULL)
|
:mState(NULL)
|
||||||
{
|
{
|
||||||
mState = malloc(sizeof(dtmf_detect_state_t));
|
mState = malloc(sizeof(dtmf_detect_state_t));
|
||||||
|
|
||||||
memset(mState, 0, sizeof(dtmf_detect_state_t));
|
memset(mState, 0, sizeof(dtmf_detect_state_t));
|
||||||
zap_dtmf_detect_init((dtmf_detect_state_t*)mState);
|
zap_dtmf_detect_init((dtmf_detect_state_t*)mState);
|
||||||
}
|
}
|
||||||
|
|
||||||
DTMFDetector::~DTMFDetector()
|
InbandDtmfDetector::~InbandDtmfDetector()
|
||||||
{
|
{
|
||||||
if (mState)
|
if (mState)
|
||||||
free(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;
|
char buf[16]; buf[0] = 0;
|
||||||
if (zap_dtmf_detect((dtmf_detect_state_t*)mState, (int16_t*)samples, size/2, 0))
|
if (zap_dtmf_detect((dtmf_detect_state_t*)mState, (int16_t*)samples, size/2, 0))
|
||||||
zap_dtmf_get((dtmf_detect_state_t*)mState, buf, 15);
|
zap_dtmf_get((dtmf_detect_state_t*)mState, buf, 15);
|
||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DTMFDetector::resetState()
|
void InbandDtmfDetector::resetState()
|
||||||
{
|
{
|
||||||
zap_dtmf_detect_init((dtmf_detect_state_t*)mState);
|
zap_dtmf_detect_init((dtmf_detect_state_t*)mState);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef TRUE
|
#ifndef TRUE
|
||||||
@@ -448,12 +478,12 @@ static tone_detection_descriptor_t fax_detect;
|
|||||||
static tone_detection_descriptor_t fax_detect_2nd;
|
static tone_detection_descriptor_t fax_detect_2nd;
|
||||||
|
|
||||||
static float dtmf_row[] =
|
static float dtmf_row[] =
|
||||||
{
|
{
|
||||||
697.0, 770.0, 852.0, 941.0
|
697.0, 770.0, 852.0, 941.0
|
||||||
};
|
};
|
||||||
static float dtmf_col[] =
|
static float dtmf_col[] =
|
||||||
{
|
{
|
||||||
1209.0, 1336.0, 1477.0, 1633.0
|
1209.0, 1336.0, 1477.0, 1633.0
|
||||||
};
|
};
|
||||||
|
|
||||||
static float fax_freq = 1100.0;
|
static float fax_freq = 1100.0;
|
||||||
@@ -461,10 +491,10 @@ static float fax_freq = 1100.0;
|
|||||||
static char dtmf_positions[] = "123A" "456B" "789C" "*0#D";
|
static char dtmf_positions[] = "123A" "456B" "789C" "*0#D";
|
||||||
|
|
||||||
static void goertzel_init(goertzel_state_t *s,
|
static void goertzel_init(goertzel_state_t *s,
|
||||||
tone_detection_descriptor_t *t)
|
tone_detection_descriptor_t *t)
|
||||||
{
|
{
|
||||||
s->v2 =
|
s->v2 =
|
||||||
s->v3 = 0.0;
|
s->v3 = 0.0;
|
||||||
s->fac = t->fac;
|
s->fac = t->fac;
|
||||||
}
|
}
|
||||||
/*- End of function --------------------------------------------------------*/
|
/*- End of function --------------------------------------------------------*/
|
||||||
@@ -497,50 +527,50 @@ static inline void _dtmf_goertzel_update(goertzel_state_t *s,
|
|||||||
//s->v3 = s->fac*s->v2 - v1 + x[0];
|
//s->v3 = s->fac*s->v2 - v1 + x[0];
|
||||||
|
|
||||||
__asm__ __volatile__ (
|
__asm__ __volatile__ (
|
||||||
" femms;\n"
|
" femms;\n"
|
||||||
|
|
||||||
" movq 16(%%edx),%%mm2;\n"
|
" movq 16(%%edx),%%mm2;\n"
|
||||||
" movq 24(%%edx),%%mm3;\n"
|
" movq 24(%%edx),%%mm3;\n"
|
||||||
" movq 32(%%edx),%%mm4;\n"
|
" movq 32(%%edx),%%mm4;\n"
|
||||||
" movq 40(%%edx),%%mm5;\n"
|
" movq 40(%%edx),%%mm5;\n"
|
||||||
" movq 48(%%edx),%%mm6;\n"
|
" movq 48(%%edx),%%mm6;\n"
|
||||||
" movq 56(%%edx),%%mm7;\n"
|
" movq 56(%%edx),%%mm7;\n"
|
||||||
|
|
||||||
" jmp 1f;\n"
|
" jmp 1f;\n"
|
||||||
" .align 32;\n"
|
" .align 32;\n"
|
||||||
|
|
||||||
" 1: ;\n"
|
" 1: ;\n"
|
||||||
" prefetch (%%eax);\n"
|
" prefetch (%%eax);\n"
|
||||||
" movq %%mm3,%%mm1;\n"
|
" movq %%mm3,%%mm1;\n"
|
||||||
" movq %%mm2,%%mm0;\n"
|
" movq %%mm2,%%mm0;\n"
|
||||||
" movq %%mm5,%%mm3;\n"
|
" movq %%mm5,%%mm3;\n"
|
||||||
" movq %%mm4,%%mm2;\n"
|
" movq %%mm4,%%mm2;\n"
|
||||||
|
|
||||||
" pfmul %%mm7,%%mm5;\n"
|
" pfmul %%mm7,%%mm5;\n"
|
||||||
" pfmul %%mm6,%%mm4;\n"
|
" pfmul %%mm6,%%mm4;\n"
|
||||||
" pfsub %%mm1,%%mm5;\n"
|
" pfsub %%mm1,%%mm5;\n"
|
||||||
" pfsub %%mm0,%%mm4;\n"
|
" pfsub %%mm0,%%mm4;\n"
|
||||||
|
|
||||||
" movq (%%eax),%%mm0;\n"
|
" movq (%%eax),%%mm0;\n"
|
||||||
" movq %%mm0,%%mm1;\n"
|
" movq %%mm0,%%mm1;\n"
|
||||||
" punpckldq %%mm0,%%mm1;\n"
|
" punpckldq %%mm0,%%mm1;\n"
|
||||||
" add $4,%%eax;\n"
|
" add $4,%%eax;\n"
|
||||||
" pfadd %%mm1,%%mm5;\n"
|
" pfadd %%mm1,%%mm5;\n"
|
||||||
" pfadd %%mm1,%%mm4;\n"
|
" pfadd %%mm1,%%mm4;\n"
|
||||||
|
|
||||||
" dec %%ecx;\n"
|
" dec %%ecx;\n"
|
||||||
|
|
||||||
" jnz 1b;\n"
|
" jnz 1b;\n"
|
||||||
|
|
||||||
" movq %%mm2,16(%%edx);\n"
|
" movq %%mm2,16(%%edx);\n"
|
||||||
" movq %%mm3,24(%%edx);\n"
|
" movq %%mm3,24(%%edx);\n"
|
||||||
" movq %%mm4,32(%%edx);\n"
|
" movq %%mm4,32(%%edx);\n"
|
||||||
" movq %%mm5,40(%%edx);\n"
|
" movq %%mm5,40(%%edx);\n"
|
||||||
|
|
||||||
" femms;\n"
|
" femms;\n"
|
||||||
:
|
:
|
||||||
: "c" (samples), "a" (x), "d" (vv)
|
: "c" (samples), "a" (x), "d" (vv)
|
||||||
: "memory", "eax", "ecx");
|
: "memory", "eax", "ecx");
|
||||||
|
|
||||||
s[0].v2 = vv[4];
|
s[0].v2 = vv[4];
|
||||||
s[1].v2 = vv[5];
|
s[1].v2 = vv[5];
|
||||||
@@ -555,8 +585,8 @@ static inline void _dtmf_goertzel_update(goertzel_state_t *s,
|
|||||||
/*- End of function --------------------------------------------------------*/
|
/*- End of function --------------------------------------------------------*/
|
||||||
|
|
||||||
void zap_goertzel_update(goertzel_state_t *s,
|
void zap_goertzel_update(goertzel_state_t *s,
|
||||||
int16_t x[],
|
int16_t x[],
|
||||||
int samples)
|
int samples)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
float v1;
|
float v1;
|
||||||
@@ -582,7 +612,7 @@ void zap_dtmf_detect_init (dtmf_detect_state_t *s)
|
|||||||
float theta;
|
float theta;
|
||||||
|
|
||||||
s->hit1 =
|
s->hit1 =
|
||||||
s->hit2 = 0;
|
s->hit2 = 0;
|
||||||
|
|
||||||
for (i = 0; i < 4; i++)
|
for (i = 0; i < 4; i++)
|
||||||
{
|
{
|
||||||
@@ -591,19 +621,19 @@ void zap_dtmf_detect_init (dtmf_detect_state_t *s)
|
|||||||
|
|
||||||
theta = float(2.0*M_PI*(dtmf_col[i]/SAMPLE_RATE));
|
theta = float(2.0*M_PI*(dtmf_col[i]/SAMPLE_RATE));
|
||||||
dtmf_detect_col[i].fac = float(2.0*cos(theta));
|
dtmf_detect_col[i].fac = float(2.0*cos(theta));
|
||||||
|
|
||||||
theta = float(2.0*M_PI*(dtmf_row[i]*2.0/SAMPLE_RATE));
|
theta = float(2.0*M_PI*(dtmf_row[i]*2.0/SAMPLE_RATE));
|
||||||
dtmf_detect_row_2nd[i].fac = float(2.0*cos(theta));
|
dtmf_detect_row_2nd[i].fac = float(2.0*cos(theta));
|
||||||
|
|
||||||
theta = float(2.0*M_PI*(dtmf_col[i]*2.0/SAMPLE_RATE));
|
theta = float(2.0*M_PI*(dtmf_col[i]*2.0/SAMPLE_RATE));
|
||||||
dtmf_detect_col_2nd[i].fac = float(2.0*cos(theta));
|
dtmf_detect_col_2nd[i].fac = float(2.0*cos(theta));
|
||||||
|
|
||||||
goertzel_init (&s->row_out[i], &dtmf_detect_row[i]);
|
|
||||||
goertzel_init (&s->col_out[i], &dtmf_detect_col[i]);
|
|
||||||
goertzel_init (&s->row_out2nd[i], &dtmf_detect_row_2nd[i]);
|
|
||||||
goertzel_init (&s->col_out2nd[i], &dtmf_detect_col_2nd[i]);
|
|
||||||
|
|
||||||
s->energy = 0.0;
|
goertzel_init (&s->row_out[i], &dtmf_detect_row[i]);
|
||||||
|
goertzel_init (&s->col_out[i], &dtmf_detect_col[i]);
|
||||||
|
goertzel_init (&s->row_out2nd[i], &dtmf_detect_row_2nd[i]);
|
||||||
|
goertzel_init (&s->col_out2nd[i], &dtmf_detect_col_2nd[i]);
|
||||||
|
|
||||||
|
s->energy = 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Same for the fax dector */
|
/* Same for the fax dector */
|
||||||
@@ -625,9 +655,9 @@ s->energy = 0.0;
|
|||||||
/*- End of function --------------------------------------------------------*/
|
/*- End of function --------------------------------------------------------*/
|
||||||
|
|
||||||
int zap_dtmf_detect (dtmf_detect_state_t *s,
|
int zap_dtmf_detect (dtmf_detect_state_t *s,
|
||||||
int16_t amp[],
|
int16_t amp[],
|
||||||
int samples,
|
int samples,
|
||||||
int isradio)
|
int isradio)
|
||||||
{
|
{
|
||||||
|
|
||||||
float row_energy[4];
|
float row_energy[4];
|
||||||
@@ -665,35 +695,35 @@ int zap_dtmf_detect (dtmf_detect_state_t *s,
|
|||||||
for (j = sample; j < limit; j++)
|
for (j = sample; j < limit; j++)
|
||||||
{
|
{
|
||||||
famp = amp[j];
|
famp = amp[j];
|
||||||
|
|
||||||
s->energy += famp*famp;
|
s->energy += famp*famp;
|
||||||
|
|
||||||
/* With GCC 2.95, the following unrolled code seems to take about 35%
|
/* With GCC 2.95, the following unrolled code seems to take about 35%
|
||||||
(rough estimate) as long as a neat little 0-3 loop */
|
(rough estimate) as long as a neat little 0-3 loop */
|
||||||
v1 = s->row_out[0].v2;
|
v1 = s->row_out[0].v2;
|
||||||
s->row_out[0].v2 = s->row_out[0].v3;
|
s->row_out[0].v2 = s->row_out[0].v3;
|
||||||
s->row_out[0].v3 = s->row_out[0].fac*s->row_out[0].v2 - v1 + famp;
|
s->row_out[0].v3 = s->row_out[0].fac*s->row_out[0].v2 - v1 + famp;
|
||||||
|
|
||||||
v1 = s->col_out[0].v2;
|
v1 = s->col_out[0].v2;
|
||||||
s->col_out[0].v2 = s->col_out[0].v3;
|
s->col_out[0].v2 = s->col_out[0].v3;
|
||||||
s->col_out[0].v3 = s->col_out[0].fac*s->col_out[0].v2 - v1 + famp;
|
s->col_out[0].v3 = s->col_out[0].fac*s->col_out[0].v2 - v1 + famp;
|
||||||
|
|
||||||
v1 = s->row_out[1].v2;
|
v1 = s->row_out[1].v2;
|
||||||
s->row_out[1].v2 = s->row_out[1].v3;
|
s->row_out[1].v2 = s->row_out[1].v3;
|
||||||
s->row_out[1].v3 = s->row_out[1].fac*s->row_out[1].v2 - v1 + famp;
|
s->row_out[1].v3 = s->row_out[1].fac*s->row_out[1].v2 - v1 + famp;
|
||||||
|
|
||||||
v1 = s->col_out[1].v2;
|
v1 = s->col_out[1].v2;
|
||||||
s->col_out[1].v2 = s->col_out[1].v3;
|
s->col_out[1].v2 = s->col_out[1].v3;
|
||||||
s->col_out[1].v3 = s->col_out[1].fac*s->col_out[1].v2 - v1 + famp;
|
s->col_out[1].v3 = s->col_out[1].fac*s->col_out[1].v2 - v1 + famp;
|
||||||
|
|
||||||
v1 = s->row_out[2].v2;
|
v1 = s->row_out[2].v2;
|
||||||
s->row_out[2].v2 = s->row_out[2].v3;
|
s->row_out[2].v2 = s->row_out[2].v3;
|
||||||
s->row_out[2].v3 = s->row_out[2].fac*s->row_out[2].v2 - v1 + famp;
|
s->row_out[2].v3 = s->row_out[2].fac*s->row_out[2].v2 - v1 + famp;
|
||||||
|
|
||||||
v1 = s->col_out[2].v2;
|
v1 = s->col_out[2].v2;
|
||||||
s->col_out[2].v2 = s->col_out[2].v3;
|
s->col_out[2].v2 = s->col_out[2].v3;
|
||||||
s->col_out[2].v3 = s->col_out[2].fac*s->col_out[2].v2 - v1 + famp;
|
s->col_out[2].v3 = s->col_out[2].fac*s->col_out[2].v2 - v1 + famp;
|
||||||
|
|
||||||
v1 = s->row_out[3].v2;
|
v1 = s->row_out[3].v2;
|
||||||
s->row_out[3].v2 = s->row_out[3].v3;
|
s->row_out[3].v2 = s->row_out[3].v3;
|
||||||
s->row_out[3].v3 = s->row_out[3].fac*s->row_out[3].v2 - v1 + famp;
|
s->row_out[3].v3 = s->row_out[3].fac*s->row_out[3].v2 - v1 + famp;
|
||||||
@@ -705,36 +735,36 @@ int zap_dtmf_detect (dtmf_detect_state_t *s,
|
|||||||
v1 = s->col_out2nd[0].v2;
|
v1 = s->col_out2nd[0].v2;
|
||||||
s->col_out2nd[0].v2 = s->col_out2nd[0].v3;
|
s->col_out2nd[0].v2 = s->col_out2nd[0].v3;
|
||||||
s->col_out2nd[0].v3 = s->col_out2nd[0].fac*s->col_out2nd[0].v2 - v1 + famp;
|
s->col_out2nd[0].v3 = s->col_out2nd[0].fac*s->col_out2nd[0].v2 - v1 + famp;
|
||||||
|
|
||||||
v1 = s->row_out2nd[0].v2;
|
v1 = s->row_out2nd[0].v2;
|
||||||
s->row_out2nd[0].v2 = s->row_out2nd[0].v3;
|
s->row_out2nd[0].v2 = s->row_out2nd[0].v3;
|
||||||
s->row_out2nd[0].v3 = s->row_out2nd[0].fac*s->row_out2nd[0].v2 - v1 + famp;
|
s->row_out2nd[0].v3 = s->row_out2nd[0].fac*s->row_out2nd[0].v2 - v1 + famp;
|
||||||
|
|
||||||
v1 = s->col_out2nd[1].v2;
|
v1 = s->col_out2nd[1].v2;
|
||||||
s->col_out2nd[1].v2 = s->col_out2nd[1].v3;
|
s->col_out2nd[1].v2 = s->col_out2nd[1].v3;
|
||||||
s->col_out2nd[1].v3 = s->col_out2nd[1].fac*s->col_out2nd[1].v2 - v1 + famp;
|
s->col_out2nd[1].v3 = s->col_out2nd[1].fac*s->col_out2nd[1].v2 - v1 + famp;
|
||||||
|
|
||||||
v1 = s->row_out2nd[1].v2;
|
v1 = s->row_out2nd[1].v2;
|
||||||
s->row_out2nd[1].v2 = s->row_out2nd[1].v3;
|
s->row_out2nd[1].v2 = s->row_out2nd[1].v3;
|
||||||
s->row_out2nd[1].v3 = s->row_out2nd[1].fac*s->row_out2nd[1].v2 - v1 + famp;
|
s->row_out2nd[1].v3 = s->row_out2nd[1].fac*s->row_out2nd[1].v2 - v1 + famp;
|
||||||
|
|
||||||
v1 = s->col_out2nd[2].v2;
|
v1 = s->col_out2nd[2].v2;
|
||||||
s->col_out2nd[2].v2 = s->col_out2nd[2].v3;
|
s->col_out2nd[2].v2 = s->col_out2nd[2].v3;
|
||||||
s->col_out2nd[2].v3 = s->col_out2nd[2].fac*s->col_out2nd[2].v2 - v1 + famp;
|
s->col_out2nd[2].v3 = s->col_out2nd[2].fac*s->col_out2nd[2].v2 - v1 + famp;
|
||||||
|
|
||||||
v1 = s->row_out2nd[2].v2;
|
v1 = s->row_out2nd[2].v2;
|
||||||
s->row_out2nd[2].v2 = s->row_out2nd[2].v3;
|
s->row_out2nd[2].v2 = s->row_out2nd[2].v3;
|
||||||
s->row_out2nd[2].v3 = s->row_out2nd[2].fac*s->row_out2nd[2].v2 - v1 + famp;
|
s->row_out2nd[2].v3 = s->row_out2nd[2].fac*s->row_out2nd[2].v2 - v1 + famp;
|
||||||
|
|
||||||
v1 = s->col_out2nd[3].v2;
|
v1 = s->col_out2nd[3].v2;
|
||||||
s->col_out2nd[3].v2 = s->col_out2nd[3].v3;
|
s->col_out2nd[3].v2 = s->col_out2nd[3].v3;
|
||||||
s->col_out2nd[3].v3 = s->col_out2nd[3].fac*s->col_out2nd[3].v2 - v1 + famp;
|
s->col_out2nd[3].v3 = s->col_out2nd[3].fac*s->col_out2nd[3].v2 - v1 + famp;
|
||||||
|
|
||||||
v1 = s->row_out2nd[3].v2;
|
v1 = s->row_out2nd[3].v2;
|
||||||
s->row_out2nd[3].v2 = s->row_out2nd[3].v3;
|
s->row_out2nd[3].v2 = s->row_out2nd[3].v3;
|
||||||
s->row_out2nd[3].v3 = s->row_out2nd[3].fac*s->row_out2nd[3].v2 - v1 + famp;
|
s->row_out2nd[3].v3 = s->row_out2nd[3].fac*s->row_out2nd[3].v2 - v1 + famp;
|
||||||
|
|
||||||
/* Update fax tone */
|
/* Update fax tone */
|
||||||
v1 = s->fax_tone.v2;
|
v1 = s->fax_tone.v2;
|
||||||
s->fax_tone.v2 = s->fax_tone.v3;
|
s->fax_tone.v2 = s->fax_tone.v3;
|
||||||
s->fax_tone.v3 = s->fax_tone.fac*s->fax_tone.v2 - v1 + famp;
|
s->fax_tone.v3 = s->fax_tone.fac*s->fax_tone.v2 - v1 + famp;
|
||||||
@@ -748,28 +778,28 @@ int zap_dtmf_detect (dtmf_detect_state_t *s,
|
|||||||
if (s->current_sample < 102)
|
if (s->current_sample < 102)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
/* Detect the fax energy, too */
|
/* Detect the fax energy, too */
|
||||||
fax_energy = zap_goertzel_result(&s->fax_tone);
|
fax_energy = zap_goertzel_result(&s->fax_tone);
|
||||||
|
|
||||||
/* We are at the end of a DTMF detection block */
|
/* We are at the end of a DTMF detection block */
|
||||||
/* Find the peak row and the peak column */
|
/* Find the peak row and the peak column */
|
||||||
row_energy[0] = zap_goertzel_result (&s->row_out[0]);
|
row_energy[0] = zap_goertzel_result (&s->row_out[0]);
|
||||||
col_energy[0] = zap_goertzel_result (&s->col_out[0]);
|
col_energy[0] = zap_goertzel_result (&s->col_out[0]);
|
||||||
|
|
||||||
for (best_row = best_col = 0, i = 1; i < 4; i++)
|
for (best_row = best_col = 0, i = 1; i < 4; i++)
|
||||||
{
|
{
|
||||||
row_energy[i] = zap_goertzel_result (&s->row_out[i]);
|
row_energy[i] = zap_goertzel_result (&s->row_out[i]);
|
||||||
if (row_energy[i] > row_energy[best_row])
|
if (row_energy[i] > row_energy[best_row])
|
||||||
best_row = i;
|
best_row = i;
|
||||||
col_energy[i] = zap_goertzel_result (&s->col_out[i]);
|
col_energy[i] = zap_goertzel_result (&s->col_out[i]);
|
||||||
if (col_energy[i] > col_energy[best_col])
|
if (col_energy[i] > col_energy[best_col])
|
||||||
best_col = i;
|
best_col = i;
|
||||||
}
|
}
|
||||||
hit = 0;
|
hit = 0;
|
||||||
/* Basic signal level test and the twist test */
|
/* Basic signal level test and the twist test */
|
||||||
if (row_energy[best_row] >= DTMF_THRESHOLD
|
if (row_energy[best_row] >= DTMF_THRESHOLD
|
||||||
&&
|
&&
|
||||||
col_energy[best_col] >= DTMF_THRESHOLD
|
col_energy[best_col] >= DTMF_THRESHOLD
|
||||||
&&
|
&&
|
||||||
col_energy[best_col] < row_energy[best_row]*DTMF_REVERSE_TWIST
|
col_energy[best_col] < row_energy[best_row]*DTMF_REVERSE_TWIST
|
||||||
&&
|
&&
|
||||||
@@ -787,8 +817,8 @@ for (best_row = best_col = 0, i = 1; i < 4; i++)
|
|||||||
}
|
}
|
||||||
/* ... and second harmonic test */
|
/* ... and second harmonic test */
|
||||||
if (i >= 4
|
if (i >= 4
|
||||||
&&
|
&&
|
||||||
(row_energy[best_row] + col_energy[best_col]) > 42.0*s->energy
|
(row_energy[best_row] + col_energy[best_col]) > 42.0*s->energy
|
||||||
&&
|
&&
|
||||||
zap_goertzel_result (&s->col_out2nd[best_col])*DTMF_2ND_HARMONIC_COL < col_energy[best_col]
|
zap_goertzel_result (&s->col_out2nd[best_col])*DTMF_2ND_HARMONIC_COL < col_energy[best_col]
|
||||||
&&
|
&&
|
||||||
@@ -804,7 +834,7 @@ for (best_row = best_col = 0, i = 1; i < 4; i++)
|
|||||||
to a digit. */
|
to a digit. */
|
||||||
if (hit == s->hit3 && s->hit3 != s->hit2)
|
if (hit == s->hit3 && s->hit3 != s->hit2)
|
||||||
{
|
{
|
||||||
s->mhit = hit;
|
s->mhit = hit;
|
||||||
s->digit_hits[(best_row << 2) + best_col]++;
|
s->digit_hits[(best_row << 2) + best_col]++;
|
||||||
s->detected_digits++;
|
s->detected_digits++;
|
||||||
if (s->current_digits < MAX_DTMF_DIGITS)
|
if (s->current_digits < MAX_DTMF_DIGITS)
|
||||||
@@ -819,60 +849,60 @@ for (best_row = best_col = 0, i = 1; i < 4; i++)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!hit && (fax_energy >= FAX_THRESHOLD) && (fax_energy > s->energy * 21.0)) {
|
if (!hit && (fax_energy >= FAX_THRESHOLD) && (fax_energy > s->energy * 21.0)) {
|
||||||
fax_energy_2nd = zap_goertzel_result(&s->fax_tone2nd);
|
fax_energy_2nd = zap_goertzel_result(&s->fax_tone2nd);
|
||||||
if (fax_energy_2nd * FAX_2ND_HARMONIC < fax_energy) {
|
if (fax_energy_2nd * FAX_2ND_HARMONIC < fax_energy) {
|
||||||
#if 0
|
#if 0
|
||||||
printf("Fax energy/Second Harmonic: %f/%f\n", fax_energy, fax_energy_2nd);
|
printf("Fax energy/Second Harmonic: %f/%f\n", fax_energy, fax_energy_2nd);
|
||||||
#endif
|
#endif
|
||||||
/* XXX Probably need better checking than just this the energy XXX */
|
/* XXX Probably need better checking than just this the energy XXX */
|
||||||
hit = 'f';
|
hit = 'f';
|
||||||
s->fax_hits++;
|
s->fax_hits++;
|
||||||
} /* Don't reset fax hits counter */
|
} /* Don't reset fax hits counter */
|
||||||
} else {
|
} else {
|
||||||
if (s->fax_hits > 5) {
|
if (s->fax_hits > 5) {
|
||||||
s->mhit = 'f';
|
s->mhit = 'f';
|
||||||
s->detected_digits++;
|
s->detected_digits++;
|
||||||
if (s->current_digits < MAX_DTMF_DIGITS)
|
if (s->current_digits < MAX_DTMF_DIGITS)
|
||||||
{
|
{
|
||||||
s->digits[s->current_digits++] = hit;
|
s->digits[s->current_digits++] = hit;
|
||||||
s->digits[s->current_digits] = '\0';
|
s->digits[s->current_digits] = '\0';
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
s->lost_digits++;
|
s->lost_digits++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s->fax_hits = 0;
|
s->fax_hits = 0;
|
||||||
}
|
}
|
||||||
s->hit1 = s->hit2;
|
s->hit1 = s->hit2;
|
||||||
s->hit2 = s->hit3;
|
s->hit2 = s->hit3;
|
||||||
s->hit3 = hit;
|
s->hit3 = hit;
|
||||||
/* Reinitialise the detector for the next block */
|
/* Reinitialise the detector for the next block */
|
||||||
for (i = 0; i < 4; i++)
|
for (i = 0; i < 4; i++)
|
||||||
{
|
{
|
||||||
goertzel_init (&s->row_out[i], &dtmf_detect_row[i]);
|
goertzel_init (&s->row_out[i], &dtmf_detect_row[i]);
|
||||||
goertzel_init (&s->col_out[i], &dtmf_detect_col[i]);
|
goertzel_init (&s->col_out[i], &dtmf_detect_col[i]);
|
||||||
goertzel_init (&s->row_out2nd[i], &dtmf_detect_row_2nd[i]);
|
goertzel_init (&s->row_out2nd[i], &dtmf_detect_row_2nd[i]);
|
||||||
goertzel_init (&s->col_out2nd[i], &dtmf_detect_col_2nd[i]);
|
goertzel_init (&s->col_out2nd[i], &dtmf_detect_col_2nd[i]);
|
||||||
}
|
}
|
||||||
goertzel_init (&s->fax_tone, &fax_detect);
|
goertzel_init (&s->fax_tone, &fax_detect);
|
||||||
goertzel_init (&s->fax_tone2nd, &fax_detect_2nd);
|
goertzel_init (&s->fax_tone2nd, &fax_detect_2nd);
|
||||||
s->energy = 0.0;
|
s->energy = 0.0;
|
||||||
s->current_sample = 0;
|
s->current_sample = 0;
|
||||||
}
|
}
|
||||||
if ((!s->mhit) || (s->mhit != hit))
|
if ((!s->mhit) || (s->mhit != hit))
|
||||||
{
|
{
|
||||||
s->mhit = 0;
|
s->mhit = 0;
|
||||||
return(0);
|
return(0);
|
||||||
}
|
}
|
||||||
return (hit);
|
return (hit);
|
||||||
}
|
}
|
||||||
/*- End of function --------------------------------------------------------*/
|
/*- End of function --------------------------------------------------------*/
|
||||||
|
|
||||||
int zap_dtmf_get (dtmf_detect_state_t *s,
|
int zap_dtmf_get (dtmf_detect_state_t *s,
|
||||||
char *buf,
|
char *buf,
|
||||||
int max)
|
int max)
|
||||||
{
|
{
|
||||||
if (max > s->current_digits)
|
if (max > s->current_digits)
|
||||||
max = s->current_digits;
|
max = s->current_digits;
|
||||||
|
|||||||
@@ -15,38 +15,52 @@
|
|||||||
namespace MT
|
namespace MT
|
||||||
{
|
{
|
||||||
|
|
||||||
class DtmfBuilder
|
class DtmfBuilder
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
struct Rfc2833Event
|
||||||
|
{
|
||||||
|
char mTone = 0;
|
||||||
|
int mDuration = 0;
|
||||||
|
int mVolume = 0;
|
||||||
|
bool mEnd = false;
|
||||||
|
|
||||||
|
bool isValid() const {
|
||||||
|
return mTone != 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Output should be 4 bytes length
|
// 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
|
// Buf receives PCM audio
|
||||||
static void buildInband(int tone, int startTime, int finishTime, int rate, short* buf);
|
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(): 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) {}
|
Dtmf(int tone, int volume, int duration): mTone(tone), mVolume(volume), mDuration(duration), mFinishCount(3), mCurrentTime(0), mStopped(false) {}
|
||||||
|
|
||||||
int mStopped;
|
int mStopped;
|
||||||
int mTone;
|
int mTone;
|
||||||
int mDuration; // It is zero for tones generated by startTone()..stopTone() calls.
|
int mDuration; // It is zero for tones generated by startTone()..stopTone() calls.
|
||||||
int mVolume;
|
int mVolume;
|
||||||
int mFinishCount;
|
int mFinishCount;
|
||||||
int mCurrentTime;
|
int mCurrentTime;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef std::vector<Dtmf> DtmfQueue;
|
typedef std::vector<Dtmf> DtmfQueue;
|
||||||
|
|
||||||
class DtmfContext
|
class DtmfContext
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
enum Type
|
enum Type
|
||||||
{
|
{
|
||||||
Dtmf_Inband,
|
Dtmf_Inband,
|
||||||
Dtmf_Rfc2833
|
Dtmf_Rfc2833
|
||||||
};
|
};
|
||||||
|
|
||||||
DtmfContext();
|
DtmfContext();
|
||||||
@@ -65,33 +79,34 @@ namespace MT
|
|||||||
bool getInband(int milliseconds, int rate, ByteBuffer& output);
|
bool getInband(int milliseconds, int rate, ByteBuffer& output);
|
||||||
bool getRfc2833(int milliseconds, ByteBuffer& output, ByteBuffer& stopPacket);
|
bool getRfc2833(int milliseconds, ByteBuffer& output, ByteBuffer& stopPacket);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Mutex mGuard;
|
Mutex mGuard;
|
||||||
Type mType;
|
Type mType;
|
||||||
DtmfQueue mQueue;
|
DtmfQueue mQueue;
|
||||||
};
|
};
|
||||||
|
|
||||||
class DTMFDetector
|
class InbandDtmfDetector
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
/*! The default constructor. Allocates space for detector context. */
|
/*! The default constructor. Allocates space for detector context. */
|
||||||
DTMFDetector();
|
InbandDtmfDetector();
|
||||||
|
|
||||||
/*! The destructor. Free the detector context's memory. */
|
|
||||||
~DTMFDetector();
|
|
||||||
|
|
||||||
/*! This method receives the input PCM 16-bit data and returns found DTMF event(s) in string representation.
|
/*! The destructor. Free the detector context's memory. */
|
||||||
|
~InbandDtmfDetector();
|
||||||
|
|
||||||
|
/*! This method receives the input PCM 16-bit data and returns found DTMF event(s) in string representation.
|
||||||
* @param samples Input PCM buffer pointer.
|
* @param samples Input PCM buffer pointer.
|
||||||
* @param size Size of input buffer in bytes
|
* @param size Size of input buffer in bytes
|
||||||
* @return Found DTMF event(s) in string representation. The returned value has variable length.
|
* @return Found DTMF event(s) in string representation. The returned value has variable length.
|
||||||
*/
|
*/
|
||||||
std::string streamPut(unsigned char* samples, unsigned int size);
|
std::string streamPut(unsigned char* samples, unsigned int size);
|
||||||
|
|
||||||
void resetState();
|
void resetState();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void* mState; /// DTMF detector context
|
void* mState; /// DTMF detector context
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#include "MT_EvsCodec.h"
|
#include "MT_EvsCodec.h"
|
||||||
|
#include <set>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
/*-------------------------------------------------------------------*
|
/*-------------------------------------------------------------------*
|
||||||
* rate2AMRWB_IOmode()
|
* rate2AMRWB_IOmode()
|
||||||
@@ -167,67 +167,58 @@ EVSCodec::~EVSCodec()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int EVSCodec::samplerate()
|
Codec::Info EVSCodec::info() {
|
||||||
{
|
return {
|
||||||
return st_dec->output_Fs;
|
.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()
|
Codec::EncodeResult EVSCodec::encode(std::span<const uint8_t> input, std::span<uint8_t> output)
|
||||||
{
|
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
// Encoding is not supported yet.
|
// 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())
|
if (output.size_bytes() < pcmLength())
|
||||||
return 0;
|
return {.mDecoded = 0};
|
||||||
|
|
||||||
std::string buffer;
|
std::string buffer;
|
||||||
|
|
||||||
// Check if we get payload with CMR
|
// 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())
|
if (payload_iter == FixedPayload_EVSPrimary.end())
|
||||||
{
|
{
|
||||||
// Check if we get payload with ToC and without CMR
|
// 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())
|
if (payload_iter == FixedPayload_EVSPrimary.end())
|
||||||
{
|
{
|
||||||
// Maybe there is no ToC ?
|
// 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())
|
if (payload_iter == FixedPayload_EVSPrimary.end())
|
||||||
{
|
{
|
||||||
// Bad payload size at all
|
// Bad payload size at all
|
||||||
return 0;
|
return {.mDecoded = 0};
|
||||||
}
|
}
|
||||||
/* Add ToC byte.
|
/* Add ToC byte.
|
||||||
* WARNING maybe it will be work incorrect with 56bit payload,
|
* WARNING maybe it will be work incorrect with 56bit payload,
|
||||||
* see 3GPP TS 26.445 Annex A, A.2.1.3 */
|
* 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 += c;
|
||||||
buffer += std::string(reinterpret_cast<const char*>(input), input_length);
|
buffer += std::string(reinterpret_cast<const char*>(input.data()), input.size_bytes());
|
||||||
}
|
}
|
||||||
else
|
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
|
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
|
// 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' */
|
/* 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;
|
offset += pcmLength() / 2;
|
||||||
if (st_dec->ini_frame < MAX_FRAME_COUNTER)
|
if (st_dec->ini_frame < MAX_FRAME_COUNTER)
|
||||||
{
|
{
|
||||||
@@ -271,12 +262,12 @@ int EVSCodec::decode(const void* input, int input_length, void* output, int outp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return pcmLength();
|
return {.mDecoded = (size_t)pcmLength()};
|
||||||
}
|
}
|
||||||
|
|
||||||
int EVSCodec::plc(int lostFrames, void* output, int outputCapacity)
|
size_t EVSCodec::plc(int lostFrames, std::span<uint8_t> output)
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void EVSCodec::initDecoder(const StreamParameters& sp)
|
void EVSCodec::initDecoder(const StreamParameters& sp)
|
||||||
|
|||||||
@@ -2,15 +2,11 @@
|
|||||||
#define __MT_EVS_CODEC_H
|
#define __MT_EVS_CODEC_H
|
||||||
|
|
||||||
#include "../engine_config.h"
|
#include "../engine_config.h"
|
||||||
#include <set>
|
|
||||||
#include <map>
|
|
||||||
#include <memory>
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <sstream>
|
|
||||||
|
|
||||||
#include "MT_Codec.h"
|
#include "MT_Codec.h"
|
||||||
|
|
||||||
@@ -52,18 +48,14 @@ public:
|
|||||||
EVSCodec(const StreamParameters& sp);
|
EVSCodec(const StreamParameters& sp);
|
||||||
~EVSCodec() override;
|
~EVSCodec() override;
|
||||||
|
|
||||||
const char* name() override { return MT_EVS_CODECNAME; }
|
Info info() override;
|
||||||
int samplerate() override;
|
|
||||||
int pcmLength() override;
|
EncodeResult encode(std::span<const uint8_t> input, std::span<uint8_t> output) override;
|
||||||
int frameTime() override;
|
DecodeResult decode(std::span<const uint8_t> input, std::span<uint8_t> output) override;
|
||||||
int rtpLength() override;
|
size_t plc(int lostFrames, std::span<uint8_t> output) 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;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
evs::Decoder_State* st_dec;
|
evs::Decoder_State* st_dec = nullptr;
|
||||||
//Encoder_State_fx* st_enc;
|
|
||||||
StreamParameters sp;
|
StreamParameters sp;
|
||||||
void initDecoder(const StreamParameters& sp);
|
void initDecoder(const StreamParameters& sp);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,19 +13,17 @@
|
|||||||
using namespace MT;
|
using namespace MT;
|
||||||
|
|
||||||
SingleAudioStream::SingleAudioStream(const CodecList::Settings& codecSettings, Statistics& stat)
|
SingleAudioStream::SingleAudioStream(const CodecList::Settings& codecSettings, Statistics& stat)
|
||||||
:mReceiver(codecSettings, stat), mDtmfReceiver(stat)
|
:mReceiver(codecSettings, stat),
|
||||||
{
|
mDtmfReceiver(stat)
|
||||||
}
|
{}
|
||||||
|
|
||||||
SingleAudioStream::~SingleAudioStream()
|
SingleAudioStream::~SingleAudioStream()
|
||||||
{
|
{}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void SingleAudioStream::process(const std::shared_ptr<jrtplib::RTPPacket>& packet)
|
void SingleAudioStream::process(const std::shared_ptr<jrtplib::RTPPacket>& packet)
|
||||||
{
|
{
|
||||||
ICELogMedia(<< "Processing incoming RTP/RTCP packet");
|
ICELogMedia(<< "Processing incoming RTP/RTCP packet");
|
||||||
if (packet->GetPayloadType() == 101/*resip::Codec::TelephoneEvent.payloadType()*/)
|
if (packet->GetPayloadType() == mReceiver.getCodecSettings().mTelephoneEvent)
|
||||||
mDtmfReceiver.add(packet);
|
mDtmfReceiver.add(packet);
|
||||||
else
|
else
|
||||||
mReceiver.add(packet);
|
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)
|
void SingleAudioStream::copyPcmTo(Audio::DataWindow& output, int needed)
|
||||||
{
|
{
|
||||||
|
// Packet by packet
|
||||||
while (output.filled() < needed)
|
while (output.filled() < needed)
|
||||||
{
|
{
|
||||||
if (mReceiver.getAudio(output, {}) != AudioReceiver::DecodeResult_Ok)
|
if (mReceiver.getAudioTo(output, {}).mStatus != AudioReceiver::DecodeResult::Status::Ok)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,19 +13,19 @@
|
|||||||
#include "MT_AudioReceiver.h"
|
#include "MT_AudioReceiver.h"
|
||||||
namespace MT
|
namespace MT
|
||||||
{
|
{
|
||||||
class SingleAudioStream
|
class SingleAudioStream
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
SingleAudioStream(const CodecList::Settings& codecSettings, Statistics& stat);
|
SingleAudioStream(const CodecList::Settings& codecSettings, Statistics& stat);
|
||||||
~SingleAudioStream();
|
~SingleAudioStream();
|
||||||
void process(const std::shared_ptr<jrtplib::RTPPacket>& packet);
|
void process(const std::shared_ptr<jrtplib::RTPPacket>& packet);
|
||||||
void copyPcmTo(Audio::DataWindow& output, int needed);
|
void copyPcmTo(Audio::DataWindow& output, int needed);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
DtmfReceiver mDtmfReceiver;
|
DtmfReceiver mDtmfReceiver;
|
||||||
AudioReceiver mReceiver;
|
AudioReceiver mReceiver;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef std::map<unsigned, SingleAudioStream*> AudioStreamMap;
|
typedef std::map<unsigned, SingleAudioStream*> AudioStreamMap;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -212,15 +212,13 @@ void SrtpSession::close()
|
|||||||
|
|
||||||
SrtpKeySalt& SrtpSession::outgoingKey(SrtpSuite suite)
|
SrtpKeySalt& SrtpSession::outgoingKey(SrtpSuite suite)
|
||||||
{
|
{
|
||||||
Lock l(mGuard);
|
|
||||||
assert(suite > SRTP_NONE && suite <= SRTP_LAST);
|
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)
|
bool SrtpSession::protectRtp(void* buffer, int* length)
|
||||||
{
|
{
|
||||||
// addSsrc(RtpHelper::findSsrc(buffer, *length), sdOutgoing);
|
|
||||||
|
|
||||||
Lock l(mGuard);
|
Lock l(mGuard);
|
||||||
if (mOutboundSession)
|
if (mOutboundSession)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -20,20 +20,17 @@
|
|||||||
|
|
||||||
enum SrtpSuite
|
enum SrtpSuite
|
||||||
{
|
{
|
||||||
SRTP_NONE,
|
SRTP_NONE = 0,
|
||||||
SRTP_AES_128_AUTH_80,
|
SRTP_AES_128_AUTH_80 = 1,
|
||||||
SRTP_AES_256_AUTH_80,
|
SRTP_AES_256_AUTH_80 = 2,
|
||||||
SRTP_AES_192_AUTH_80,
|
SRTP_AES_192_AUTH_80 = 3,
|
||||||
SRTP_AES_128_AUTH_32,
|
SRTP_AES_128_AUTH_32 = 4,
|
||||||
SRTP_AES_256_AUTH_32,
|
SRTP_AES_256_AUTH_32 = 5,
|
||||||
SRTP_AES_192_AUTH_32,
|
SRTP_AES_192_AUTH_32 = 6,
|
||||||
SRTP_AES_128_AUTH_NULL,
|
SRTP_AES_128_AUTH_NULL = 7,
|
||||||
SRTP_AED_AES_256_GCM,
|
SRTP_AED_AES_256_GCM = 8,
|
||||||
SRTP_AED_AES_128_GCM,
|
SRTP_AED_AES_128_GCM = 9,
|
||||||
SRTP_LAST = SRTP_AED_AES_128_GCM
|
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);
|
extern SrtpSuite toSrtpSuite(const std::string_view& s);
|
||||||
|
|||||||
@@ -74,13 +74,16 @@ Statistics::~Statistics()
|
|||||||
|
|
||||||
void Statistics::calculateBurstr(double* burstr, double* lossr) const
|
void Statistics::calculateBurstr(double* burstr, double* lossr) const
|
||||||
{
|
{
|
||||||
int lost = 0;
|
int lost = 0; // Total packet lost
|
||||||
int bursts = 0;
|
for (const auto& item: mPacketLossTimeline)
|
||||||
for (int i = 0; i < 128; i++)
|
lost += item.mGap;
|
||||||
{
|
int bursts = mPacketLossTimeline.size(); // number of events
|
||||||
lost += i * mLoss[i];
|
|
||||||
bursts += mLoss[i];
|
// for (const auto& entry: mLoss)
|
||||||
}
|
// {
|
||||||
|
// lost += entry.first * entry.second;
|
||||||
|
// bursts += entry.second;
|
||||||
|
// }
|
||||||
|
|
||||||
if (lost < 5)
|
if (lost < 5)
|
||||||
{
|
{
|
||||||
@@ -109,14 +112,14 @@ void Statistics::calculateBurstr(double* burstr, double* lossr) const
|
|||||||
double Statistics::calculateMos(double maximalMos) const
|
double Statistics::calculateMos(double maximalMos) const
|
||||||
{
|
{
|
||||||
// calculate lossrate and burst rate
|
// calculate lossrate and burst rate
|
||||||
double burstr, lossr;
|
double burstr = 0, lossr = 0;
|
||||||
calculateBurstr(&burstr, &lossr);
|
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 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;
|
return 0.0;
|
||||||
|
|
||||||
if (lossr == 0.0 || burstr == 0.0)
|
if (lossr == 0.0 || burstr == 0.0)
|
||||||
|
|||||||
@@ -4,16 +4,15 @@
|
|||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <array>
|
|
||||||
|
|
||||||
#include "audio/Audio_DataWindow.h"
|
|
||||||
#include "helper/HL_Optional.hpp"
|
|
||||||
#include "helper/HL_Statistics.h"
|
#include "helper/HL_Statistics.h"
|
||||||
#include "helper/HL_Types.h"
|
#include "helper/HL_Types.h"
|
||||||
|
#include "helper/HL_InternetAddress.h"
|
||||||
|
|
||||||
#include "jrtplib/src/rtptimeutilities.h"
|
#include "jrtplib/src/rtptimeutilities.h"
|
||||||
#include "jrtplib/src/rtppacket.h"
|
#include "jrtplib/src/rtppacket.h"
|
||||||
|
|
||||||
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
namespace MT
|
namespace MT
|
||||||
{
|
{
|
||||||
@@ -56,46 +55,55 @@ struct PacketLossEvent
|
|||||||
uint32_t mStartSeqno = 0,
|
uint32_t mStartSeqno = 0,
|
||||||
mEndSeqno = 0;
|
mEndSeqno = 0;
|
||||||
int mGap = 0;
|
int mGap = 0;
|
||||||
|
std::chrono::microseconds mTimestampStart = 0us,
|
||||||
|
mTimestampEnd = 0us;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Dtmf2833Event
|
||||||
|
{
|
||||||
|
char mTone;
|
||||||
std::chrono::microseconds mTimestamp;
|
std::chrono::microseconds mTimestamp;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Statistics
|
class Statistics
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
size_t mReceived = 0, // Received traffic in bytes
|
size_t mReceived = 0, // Received traffic in bytes
|
||||||
mSent = 0, // Sent traffic in bytes
|
mSent = 0, // Sent traffic in bytes
|
||||||
mReceivedRtp = 0, // Number of received rtp packets
|
mReceivedRtp = 0, // Number of received rtp packets
|
||||||
mSentRtp = 0, // Number of sent rtp packets
|
mSentRtp = 0, // Number of sent rtp packets
|
||||||
mReceivedRtcp = 0, // Number of received rtcp packets
|
mReceivedRtcp = 0, // Number of received rtcp packets
|
||||||
mSentRtcp = 0, // Number of sent rtcp packets
|
mSentRtcp = 0, // Number of sent rtcp packets
|
||||||
mDuplicatedRtp = 0, // Number of received duplicated rtp packets
|
mDuplicatedRtp = 0, // Number of received duplicated rtp packets
|
||||||
mOldRtp = 0, // Number of late rtp packets
|
mOldRtp = 0, // Number of late rtp packets
|
||||||
mPacketLoss = 0, // Number of lost packets
|
mPacketLoss = 0, // Number of lost packets
|
||||||
mPacketDropped = 0, // Number of dropped packets (due to time unsync when playing)б
|
mPacketDropped = 0, // Number of dropped packets (due to time unsync when playing)б
|
||||||
mIllegalRtp = 0; // Number of rtp packets with bad payload type
|
mIllegalRtp = 0; // Number of rtp packets with bad payload type
|
||||||
|
|
||||||
TestResult<float> mDecodingInterval, // Average interval on call to packet decode
|
TestResult<float> mDecodingInterval, // Average interval on call to packet decode
|
||||||
mDecodeRequested, // Average amount of requested audio frames to play
|
mDecodeRequested, // Average amount of requested audio frames to play
|
||||||
mPacketInterval; // Average interval between packet adding to jitter buffer
|
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 mAudioTime = 0; // Decoded/found time in milliseconds
|
||||||
size_t mDecodedSize = 0; // Number of decoded bytes
|
size_t mDecodedSize = 0; // Number of decoded bytes
|
||||||
uint16_t mSsrc = 0; // Last known SSRC ID in a RTP stream
|
uint16_t mSsrc = 0; // Last known SSRC ID in a RTP stream
|
||||||
ice::NetworkAddress mRemotePeer; // Last known remote RTP address
|
ice::NetworkAddress mRemotePeer; // Last known remote RTP address
|
||||||
|
|
||||||
// AMR codec bitrate switch counter
|
// AMR codec bitrate switch counter
|
||||||
int mBitrateSwitchCounter = 0;
|
int mBitrateSwitchCounter = 0;
|
||||||
std::string mCodecName;
|
int mCng = 0;
|
||||||
float mJitter = 0.0f; // Jitter
|
std::string mCodecName;
|
||||||
TestResult<float> mRttDelay; // RTT delay
|
float mJitter = 0.0f; // Jitter
|
||||||
|
TestResult<float> mRttDelay; // RTT delay
|
||||||
|
|
||||||
// Timestamp when first RTP packet has arrived
|
// Timestamp when first RTP packet has arrived
|
||||||
std::optional<timepoint_t> mFirstRtpTime;
|
std::optional<timepoint_t> mFirstRtpTime;
|
||||||
|
|
||||||
std::map<int, int> mCodecCount; // Stats on used codecs
|
std::map<int, int> mCodecCount; // Stats on used codecs
|
||||||
|
|
||||||
std::vector<PacketLossEvent> mPacketLossTimeline; // Packet loss timeline
|
std::vector<PacketLossEvent> mPacketLossTimeline; // Packet loss timeline
|
||||||
|
std::vector<Dtmf2833Event> mDtmf2833Timeline;
|
||||||
|
|
||||||
// It is to calculate network MOS
|
// It is to calculate network MOS
|
||||||
void calculateBurstr(double* burstr, double* loss) const;
|
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