Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c1ab8778bc | |||
| ee96f1144d | |||
| 27eefb34fe | |||
| e7757fa08b |
+10
-2
@@ -360,13 +360,21 @@ endif()
|
|||||||
target_compile_definitions(rtphone PUBLIC ${DEFINES} )
|
target_compile_definitions(rtphone PUBLIC ${DEFINES} )
|
||||||
|
|
||||||
if (TARGET_LINUX)
|
if (TARGET_LINUX)
|
||||||
target_link_options(rtphone PUBLIC -Wl,-Bstatic)
|
# PRIVATE, not PUBLIC: rtphone is a STATIC library, so these link options are
|
||||||
|
# never used to build rtphone itself and must not propagate to consumers.
|
||||||
|
# As PUBLIC they leaked into every consumer's LINK_FLAGS as an adjacent
|
||||||
|
# "-Wl,-Bstatic -Wl,-Bdynamic" pair (the wrapped libraries land in a separate
|
||||||
|
# LINK_LIBRARIES section, so nothing is actually wrapped). The trailing
|
||||||
|
# -Bdynamic forced the linker back into dynamic-search mode, which broke
|
||||||
|
# fully-static consumers (e.g. vq-core built with SERVER_STATIC_LINKING=ON:
|
||||||
|
# "attempted static link of dynamic object libz.so").
|
||||||
|
target_link_options(rtphone PRIVATE -Wl,-Bstatic)
|
||||||
target_compile_options(rtphone PUBLIC -Wno-deprecated -Wno-deprecated-declarations)
|
target_compile_options(rtphone PUBLIC -Wno-deprecated -Wno-deprecated-declarations)
|
||||||
endif()
|
endif()
|
||||||
target_link_libraries(rtphone PUBLIC ${LIBS_STATIC})
|
target_link_libraries(rtphone PUBLIC ${LIBS_STATIC})
|
||||||
|
|
||||||
if (TARGET_LINUX)
|
if (TARGET_LINUX)
|
||||||
target_link_options(rtphone PUBLIC -Wl,-Bdynamic)
|
target_link_options(rtphone PRIVATE -Wl,-Bdynamic)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
target_include_directories(rtphone
|
target_include_directories(rtphone
|
||||||
|
|||||||
@@ -9,11 +9,7 @@
|
|||||||
using namespace Audio;
|
using namespace Audio;
|
||||||
|
|
||||||
DataWindow::DataWindow()
|
DataWindow::DataWindow()
|
||||||
{
|
{}
|
||||||
mFilled = 0;
|
|
||||||
mData = nullptr;
|
|
||||||
mCapacity = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
DataWindow::~DataWindow()
|
DataWindow::~DataWindow()
|
||||||
{
|
{
|
||||||
@@ -24,24 +20,30 @@ DataWindow::~DataWindow()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DataWindow::setCapacity(int capacity)
|
void DataWindow::setCapacity(size_t capacity)
|
||||||
{
|
{
|
||||||
Lock l(mMutex);
|
Lock l(mMutex);
|
||||||
int tail = capacity - mCapacity;
|
|
||||||
char* buffer = mData;
|
if (capacity >= mCapacity)
|
||||||
mData = (char*)realloc(mData, capacity);
|
|
||||||
if (!mData)
|
|
||||||
{
|
{
|
||||||
// Realloc failed
|
size_t tail = capacity - mCapacity;
|
||||||
mData = buffer;
|
char* buffer = mData;
|
||||||
throw std::bad_alloc();
|
mData = (char*)realloc(mData, capacity);
|
||||||
|
if (!mData)
|
||||||
|
{
|
||||||
|
// Realloc failed
|
||||||
|
mData = buffer;
|
||||||
|
throw std::bad_alloc();
|
||||||
|
}
|
||||||
|
if (tail > 0)
|
||||||
|
memset(mData + mCapacity, 0, tail);
|
||||||
|
mCapacity = capacity;
|
||||||
}
|
}
|
||||||
if (tail > 0)
|
else
|
||||||
memset(mData + mCapacity, 0, tail);
|
throw std::bad_alloc();
|
||||||
mCapacity = capacity;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DataWindow::addZero(int length)
|
void DataWindow::addZero(size_t length)
|
||||||
{
|
{
|
||||||
Lock l(mMutex);
|
Lock l(mMutex);
|
||||||
|
|
||||||
@@ -60,7 +62,7 @@ void DataWindow::addZero(int length)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void DataWindow::add(const void* data, int length)
|
void DataWindow::add(const void* data, size_t length)
|
||||||
{
|
{
|
||||||
Lock l(mMutex);
|
Lock l(mMutex);
|
||||||
|
|
||||||
@@ -94,7 +96,7 @@ void DataWindow::add(short sample)
|
|||||||
add(&sample, sizeof sample);
|
add(&sample, sizeof sample);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DataWindow::erase(int length)
|
void DataWindow::erase(size_t length)
|
||||||
{
|
{
|
||||||
Lock l(mMutex);
|
Lock l(mMutex);
|
||||||
if (length > mFilled)
|
if (length > mFilled)
|
||||||
@@ -120,21 +122,21 @@ void DataWindow::clear()
|
|||||||
mFilled = 0;
|
mFilled = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
short DataWindow::shortAt(int index) const
|
short DataWindow::shortAt(size_t index) const
|
||||||
{
|
{
|
||||||
Lock l(mMutex);
|
Lock l(mMutex);
|
||||||
assert(index < mFilled / 2);
|
assert(index < mFilled / 2);
|
||||||
return ((short*)mData)[index];
|
return ((short*)mData)[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
void DataWindow::setShortAt(short value, int index)
|
void DataWindow::setShortAt(short value, size_t index)
|
||||||
{
|
{
|
||||||
Lock l(mMutex);
|
Lock l(mMutex);
|
||||||
assert(index < mFilled / 2);
|
assert(index < mFilled / 2);
|
||||||
((short*)mData)[index] = value;
|
((short*)mData)[index] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
int DataWindow::read(void* buffer, int length)
|
size_t DataWindow::read(void* buffer, size_t length)
|
||||||
{
|
{
|
||||||
Lock l(mMutex);
|
Lock l(mMutex);
|
||||||
if (length > mFilled)
|
if (length > mFilled)
|
||||||
@@ -150,25 +152,27 @@ int DataWindow::read(void* buffer, int length)
|
|||||||
return length;
|
return length;
|
||||||
}
|
}
|
||||||
|
|
||||||
int DataWindow::filled() const
|
size_t DataWindow::filled() const
|
||||||
{
|
{
|
||||||
Lock l(mMutex);
|
Lock l(mMutex);
|
||||||
return mFilled;
|
return mFilled;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DataWindow::setFilled(int filled)
|
void DataWindow::setFilled(size_t filled)
|
||||||
{
|
{
|
||||||
Lock l(mMutex);
|
Lock l(mMutex);
|
||||||
|
if (filled > mCapacity)
|
||||||
|
throw std::bad_alloc();
|
||||||
mFilled = filled;
|
mFilled = filled;
|
||||||
}
|
}
|
||||||
|
|
||||||
int DataWindow::capacity() const
|
size_t DataWindow::capacity() const
|
||||||
{
|
{
|
||||||
Lock l(mMutex);
|
Lock l(mMutex);
|
||||||
return mCapacity;
|
return mCapacity;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DataWindow::zero(int length)
|
void DataWindow::zero(size_t length)
|
||||||
{
|
{
|
||||||
Lock l(mMutex);
|
Lock l(mMutex);
|
||||||
assert(length <= mCapacity);
|
assert(length <= mCapacity);
|
||||||
@@ -189,10 +193,10 @@ size_t DataWindow::moveTo(DataWindow& dst, size_t size)
|
|||||||
return avail;
|
return avail;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::chrono::milliseconds DataWindow::getTimeLength(int samplerate, int channels) const
|
std::chrono::milliseconds DataWindow::getTimeLength(const Audio::Format& fmt) const
|
||||||
{
|
{
|
||||||
Lock l(mMutex);
|
Lock l(mMutex);
|
||||||
return std::chrono::milliseconds(mFilled / sizeof(short) / channels / (samplerate / 1000));
|
return std::chrono::milliseconds(mFilled / sizeof(short) / fmt.channels() / (fmt.rate()/ 1000));
|
||||||
}
|
}
|
||||||
|
|
||||||
void DataWindow::makeStereoFromMono(DataWindow& dst, DataWindow& src)
|
void DataWindow::makeStereoFromMono(DataWindow& dst, DataWindow& src)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
#include "../helper/HL_ByteBuffer.h"
|
#include "../helper/HL_ByteBuffer.h"
|
||||||
#include "../helper/HL_Sync.h"
|
#include "../helper/HL_Sync.h"
|
||||||
|
#include "Audio_Interface.h"
|
||||||
|
|
||||||
namespace Audio
|
namespace Audio
|
||||||
{
|
{
|
||||||
@@ -17,34 +18,34 @@ public:
|
|||||||
DataWindow();
|
DataWindow();
|
||||||
~DataWindow();
|
~DataWindow();
|
||||||
|
|
||||||
void setCapacity(int capacity);
|
void setCapacity(size_t capacity);
|
||||||
int capacity() const;
|
size_t capacity() const;
|
||||||
|
|
||||||
void addZero(int length);
|
void addZero(size_t length);
|
||||||
void add(const void* data, int length);
|
void add(const void* data, size_t length);
|
||||||
void add(short sample);
|
void add(short sample);
|
||||||
int read(void* buffer, int length);
|
size_t read(void* buffer, size_t length);
|
||||||
void erase(int length = -1);
|
void erase(size_t length);
|
||||||
const char* data() const;
|
const char* data() const;
|
||||||
char* mutableData();
|
char* mutableData();
|
||||||
int filled() const;
|
size_t filled() const;
|
||||||
void setFilled(int filled);
|
void setFilled(size_t filled);
|
||||||
void clear();
|
void clear();
|
||||||
|
|
||||||
short shortAt(int index) const;
|
short shortAt(size_t index) const;
|
||||||
void setShortAt(short value, int index);
|
void setShortAt(short value, size_t index);
|
||||||
void zero(int length);
|
void zero(size_t length);
|
||||||
size_t moveTo(DataWindow& dst, size_t size);
|
size_t moveTo(DataWindow& dst, size_t size /* in bytes*/ );
|
||||||
|
|
||||||
std::chrono::milliseconds getTimeLength(int samplerate, int channels) const;
|
std::chrono::milliseconds getTimeLength(const Format& fmt) 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 = nullptr;
|
||||||
int mFilled;
|
size_t mFilled = 0;
|
||||||
int mCapacity;
|
size_t mCapacity = 0;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -28,8 +28,7 @@ using namespace MT;
|
|||||||
// ----------------- RtpBuffer::Packet --------------
|
// ----------------- RtpBuffer::Packet --------------
|
||||||
RtpBuffer::Packet::Packet(const std::shared_ptr<RTPPacket>& packet, std::chrono::milliseconds timelength, int samplerate)
|
RtpBuffer::Packet::Packet(const std::shared_ptr<RTPPacket>& packet, std::chrono::milliseconds timelength, int samplerate)
|
||||||
:mRtp(packet), mTimelength(timelength), mSamplerate(samplerate)
|
:mRtp(packet), mTimelength(timelength), mSamplerate(samplerate)
|
||||||
{
|
{}
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<RTPPacket> RtpBuffer::Packet::rtp() const
|
std::shared_ptr<RTPPacket> RtpBuffer::Packet::rtp() const
|
||||||
{
|
{
|
||||||
@@ -66,7 +65,8 @@ RtpBuffer::RtpBuffer(Statistics& stat)
|
|||||||
|
|
||||||
RtpBuffer::~RtpBuffer()
|
RtpBuffer::~RtpBuffer()
|
||||||
{
|
{
|
||||||
ICELogDebug(<< "Number of add packets: " << mAddCounter << ", number of retrieved packets " << mReturnedCounter);
|
if (mAddCounter)
|
||||||
|
ICELogDebug(<< "Number of add packets: " << mAddCounter << ", number of retrieved packets " << mReturnedCounter);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RtpBuffer::setHigh(std::chrono::milliseconds t)
|
void RtpBuffer::setHigh(std::chrono::milliseconds t)
|
||||||
@@ -129,7 +129,7 @@ std::shared_ptr<RtpBuffer::Packet> RtpBuffer::add(const std::shared_ptr<jrtplib:
|
|||||||
mStat.mSsrc = static_cast<uint16_t>(packet->GetSSRC());
|
mStat.mSsrc = static_cast<uint16_t>(packet->GetSSRC());
|
||||||
|
|
||||||
// Update jitter
|
// Update jitter
|
||||||
ICELogMedia(<< "Adding new packet into jitter buffer");
|
ICELogMedia(<< "Adding new packet seqno " << packet->GetSequenceNumber() << " into jitter buffer");
|
||||||
mAddCounter++;
|
mAddCounter++;
|
||||||
|
|
||||||
// Look for maximum&minimal sequence number; check for dublicates
|
// Look for maximum&minimal sequence number; check for dublicates
|
||||||
@@ -138,7 +138,7 @@ std::shared_ptr<RtpBuffer::Packet> RtpBuffer::add(const std::shared_ptr<jrtplib:
|
|||||||
// New sequence number
|
// New sequence number
|
||||||
unsigned newSeqno = packet->GetExtendedSequenceNumber();
|
unsigned newSeqno = packet->GetExtendedSequenceNumber();
|
||||||
|
|
||||||
for (std::shared_ptr<Packet>& p: mPacketList)
|
for (auto& p: mPacketList)
|
||||||
{
|
{
|
||||||
unsigned seqno = p->rtp()->GetExtendedSequenceNumber();
|
unsigned seqno = p->rtp()->GetExtendedSequenceNumber();
|
||||||
|
|
||||||
@@ -171,7 +171,7 @@ std::shared_ptr<RtpBuffer::Packet> RtpBuffer::add(const std::shared_ptr<jrtplib:
|
|||||||
available = findTimelength();
|
available = findTimelength();
|
||||||
|
|
||||||
if (available > mHigh)
|
if (available > mHigh)
|
||||||
ICELogMedia(<< "Available " << available << "ms with limit " << mHigh << "ms");
|
ICELogMedia(<< "Available " << available << " with limit " << mHigh);
|
||||||
|
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
@@ -346,16 +346,14 @@ int RtpBuffer::getNumberOfAddPackets() const
|
|||||||
//-------------- Receiver ---------------
|
//-------------- Receiver ---------------
|
||||||
Receiver::Receiver(Statistics& stat)
|
Receiver::Receiver(Statistics& stat)
|
||||||
:mStat(stat)
|
:mStat(stat)
|
||||||
{
|
{}
|
||||||
}
|
|
||||||
|
|
||||||
Receiver::~Receiver()
|
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), mDtmfBuffer(stat), mCodecSettings(settings), mCodecList(settings), mDtmfReceiver(stat)
|
:Receiver(stat), mRtpBuffer(stat), mDtmfBuffer(stat), mCodecSettings(settings), mCodecList(settings), mDtmfReceiver(stat)
|
||||||
{
|
{
|
||||||
// Init resamplers
|
// Init resamplers
|
||||||
mResampler8.start(AUDIO_CHANNELS, 8000, AUDIO_SAMPLERATE);
|
mResampler8.start(AUDIO_CHANNELS, 8000, AUDIO_SAMPLERATE);
|
||||||
@@ -367,12 +365,17 @@ 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));
|
// 10 seconds is the maximum length of decoded audio in single step
|
||||||
|
// It is important - DTX may produce silence up to few seconds easily
|
||||||
|
mAvailable.setCapacity(AUDIO_SAMPLERATE * 10 * sizeof(short));
|
||||||
|
|
||||||
mDtmfBuffer.setPrebuffer(0ms);
|
mDtmfBuffer.setPrebuffer(0ms);
|
||||||
mDtmfBuffer.setLow(0ms);
|
mDtmfBuffer.setLow(0ms);
|
||||||
mDtmfBuffer.setHigh(1ms);
|
mDtmfBuffer.setHigh(1ms);
|
||||||
|
|
||||||
|
// Avoid collecting too much data
|
||||||
|
mRtpBuffer.setHigh(240ms);
|
||||||
|
|
||||||
#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);
|
||||||
@@ -386,6 +389,11 @@ AudioReceiver::~AudioReceiver()
|
|||||||
mResampler32.stop();
|
mResampler32.stop();
|
||||||
mResampler48.stop();
|
mResampler48.stop();
|
||||||
mDecodedDump.reset();
|
mDecodedDump.reset();
|
||||||
|
|
||||||
|
if (mRequestedAudio != 0ms)
|
||||||
|
ICELogDebug(<< "Requested " << mRequestedAudio << ", produced " << mProducedAudio);
|
||||||
|
if (mDecodeCount)
|
||||||
|
ICELogDebug(<< "Average interval between packet decoding " << mIntervalBetweenDecode / mDecodeCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update codec settings
|
// Update codec settings
|
||||||
@@ -450,7 +458,7 @@ Codec* AudioReceiver::add(const std::shared_ptr<jrtplib::RTPPacket>& p)
|
|||||||
payloadLength = p->GetPayloadLength(),
|
payloadLength = p->GetPayloadLength(),
|
||||||
ptype = p->GetPayloadType();
|
ptype = p->GetPayloadType();
|
||||||
|
|
||||||
ICELogMedia(<< "Adding packet No " << p->GetSequenceNumber());
|
// ICELogMedia(<< "Adding packet No " << p->GetSequenceNumber());
|
||||||
|
|
||||||
// Increase codec counter
|
// Increase codec counter
|
||||||
mStat.mCodecCount[ptype]++;
|
mStat.mCodecCount[ptype]++;
|
||||||
@@ -508,12 +516,12 @@ Codec* AudioReceiver::add(const std::shared_ptr<jrtplib::RTPPacket>& p)
|
|||||||
{
|
{
|
||||||
// It will cause statistics to report about bad RTP packet
|
// It will cause statistics to report about bad RTP packet
|
||||||
// I have to replay last packet payload here to avoid report about lost packet
|
// I have to replay last packet payload here to avoid report about lost packet
|
||||||
mBuffer.add(p, std::chrono::milliseconds(time_length), samplerate);
|
mRtpBuffer.add(p, std::chrono::milliseconds(time_length), samplerate);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Queue packet to buffer
|
// Queue packet to buffer
|
||||||
mBuffer.add(p, std::chrono::milliseconds(time_length), samplerate).get();
|
mRtpBuffer.add(p, std::chrono::milliseconds(time_length), samplerate).get();
|
||||||
}
|
}
|
||||||
return codec;
|
return codec;
|
||||||
}
|
}
|
||||||
@@ -533,8 +541,12 @@ void AudioReceiver::processDecoded(Audio::DataWindow& output, DecodeOptions opti
|
|||||||
|
|
||||||
void AudioReceiver::produceSilence(std::chrono::milliseconds length, Audio::DataWindow& output, DecodeOptions options)
|
void AudioReceiver::produceSilence(std::chrono::milliseconds length, Audio::DataWindow& output, DecodeOptions options)
|
||||||
{
|
{
|
||||||
|
if (!mCodec)
|
||||||
|
return;
|
||||||
|
|
||||||
// Fill mDecodeBuffer as much as needed and call processDecoded()
|
// Fill mDecodeBuffer as much as needed and call processDecoded()
|
||||||
// Depending on used codec mono or stereo silence should be produced
|
// Depending on used codec mono or stereo silence should be produced
|
||||||
|
|
||||||
size_t chunks = length.count() / 10;
|
size_t chunks = length.count() / 10;
|
||||||
size_t tail = length.count() % 10;
|
size_t tail = length.count() % 10;
|
||||||
size_t chunk_size = 10 * sizeof(int16_t) * mCodec->samplerate() / 1000 * mCodec->channels();
|
size_t chunk_size = 10 * sizeof(int16_t) * mCodec->samplerate() / 1000 * mCodec->channels();
|
||||||
@@ -635,21 +647,23 @@ AudioReceiver::DecodeResult AudioReceiver::decodePacketTo(Audio::DataWindow& out
|
|||||||
auto& rtp = *packet->rtp(); // Syntax sugar
|
auto& rtp = *packet->rtp(); // Syntax sugar
|
||||||
|
|
||||||
mFailedCount = 0;
|
mFailedCount = 0;
|
||||||
// Check if we need to emit silence or CNG - previously CNG packet was detected. Emit CNG audio here if needed.
|
|
||||||
|
// Check if we need to emit silence - it may happen in the case if next packet has RTP timestamp much beyond the previous one; maybe DTX was active.
|
||||||
if (mLastPacketTimestamp && mLastPacketTimeLength && mCodec)
|
if (mLastPacketTimestamp && mLastPacketTimeLength && mCodec)
|
||||||
{
|
{
|
||||||
int units = rtp.GetTimestamp() - *mLastPacketTimestamp;
|
int units = rtp.GetTimestamp() - *mLastPacketTimestamp;
|
||||||
int milliseconds = units / (mCodec->samplerate() / 1000);
|
int milliseconds = units / (mCodec->samplerate() / 1000);
|
||||||
if (milliseconds > mLastPacketTimeLength)
|
if (milliseconds > mLastPacketTimeLength)
|
||||||
{
|
{
|
||||||
auto silenceLength = std::chrono::milliseconds(milliseconds - mLastPacketTimeLength);
|
auto silenceLength = std::chrono::milliseconds(milliseconds - mLastPacketTimeLength);
|
||||||
|
ICELogDebug(<< "Emit " << silenceLength << " silence while requested " << options.mElapsed);
|
||||||
if (mCngPacket && options.mFillGapByCNG)
|
silenceLength = std::min(silenceLength, options.mElapsed);
|
||||||
produceCNG(silenceLength, output, options);
|
if (mCngPacket && options.mFillGapByCNG)
|
||||||
else
|
produceCNG(silenceLength, output, options);
|
||||||
produceSilence(silenceLength, output, options);
|
else
|
||||||
}
|
produceSilence(silenceLength, output, options);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mLastPacketTimestamp = rtp.GetTimestamp();
|
mLastPacketTimestamp = rtp.GetTimestamp();
|
||||||
|
|
||||||
@@ -677,6 +691,7 @@ AudioReceiver::DecodeResult AudioReceiver::decodePacketTo(Audio::DataWindow& out
|
|||||||
mDecodedLength = 0;
|
mDecodedLength = 0;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
ICELogDebug(<< "Decoding CNG");
|
||||||
mCngPacket = packet;
|
mCngPacket = packet;
|
||||||
mCngDecoder.decode3389(rtp.GetPayloadData(), rtp.GetPayloadLength());
|
mCngDecoder.decode3389(rtp.GetPayloadData(), rtp.GetPayloadLength());
|
||||||
|
|
||||||
@@ -775,7 +790,7 @@ AudioReceiver::DecodeResult AudioReceiver::decodeEmptyTo(Audio::DataWindow& outp
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Emit silence if codec information is available - it is to properly handle the gaps
|
// Emit silence if codec information is available - it is to properly handle the gaps
|
||||||
auto avail = output.getTimeLength(fmt.rate(), fmt.channels());
|
auto avail = output.getTimeLength(fmt);
|
||||||
if (options.mElapsed > avail)
|
if (options.mElapsed > avail)
|
||||||
output.addZero(fmt.sizeFromTime(options.mElapsed - avail));
|
output.addZero(fmt.sizeFromTime(options.mElapsed - avail));
|
||||||
}
|
}
|
||||||
@@ -785,47 +800,77 @@ AudioReceiver::DecodeResult AudioReceiver::decodeEmptyTo(Audio::DataWindow& outp
|
|||||||
return {.mStatus = DecodeResult::Status::Skip};
|
return {.mStatus = DecodeResult::Status::Skip};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MT::AudioReceiver::processDtmf()
|
||||||
|
{
|
||||||
|
if (mDtmfBuffer.getCount())
|
||||||
|
{
|
||||||
|
auto fr = mDtmfBuffer.fetch();
|
||||||
|
if (fr.mPacket && fr.mStatus == RtpBuffer::FetchResult::Status::RegularPacket)
|
||||||
|
mDtmfReceiver.add(fr.mPacket->rtp());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MT::AudioReceiver::updateDecodingTimeStatistics()
|
||||||
|
{
|
||||||
|
if (!mDecodeTimestamp)
|
||||||
|
mDecodeTimestamp = std::chrono::steady_clock::now();
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto t = std::chrono::steady_clock::now();
|
||||||
|
mStat.mDecodingInterval.process(std::chrono::duration_cast<std::chrono::milliseconds>(t - *mDecodeTimestamp).count());
|
||||||
|
mDecodeTimestamp = t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
AudioReceiver::DecodeResult AudioReceiver::getAudioTo(Audio::DataWindow& output, DecodeOptions options)
|
AudioReceiver::DecodeResult AudioReceiver::getAudioTo(Audio::DataWindow& output, DecodeOptions options)
|
||||||
{
|
{
|
||||||
|
// ICELogDebug(<< "getAudioTo() for " << options.mElapsed);
|
||||||
|
assert (options.mElapsed != 0ms);
|
||||||
|
|
||||||
|
// Increase counter of requested audio
|
||||||
|
mRequestedAudio += options.mElapsed;
|
||||||
|
|
||||||
DecodeResult result = {.mStatus = DecodeResult::Status::Skip};
|
DecodeResult result = {.mStatus = DecodeResult::Status::Skip};
|
||||||
|
|
||||||
// Process RFC2833 here; it doesn't result in any audio - only callbacks and statistics
|
// Process RFC2833 here; it doesn't result in any audio - only callbacks and statistics
|
||||||
auto fr = mDtmfBuffer.fetch();
|
processDtmf();
|
||||||
if (fr.mPacket && fr.mStatus == RtpBuffer::FetchResult::Status::RegularPacket)
|
|
||||||
mDtmfReceiver.add(fr.mPacket->rtp());
|
|
||||||
|
|
||||||
|
|
||||||
|
// How much time length audio we produced here
|
||||||
auto produced = 0ms;
|
auto produced = 0ms;
|
||||||
if (mAvailable.filled() && mCodec && options.mElapsed != 0ms)
|
Audio::Format fmt;
|
||||||
{
|
|
||||||
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 ?
|
// Have we anything from the previous decode attempts ?
|
||||||
if (produced >= options.mElapsed)
|
if (mAvailable.filled())
|
||||||
return {.mStatus = DecodeResult::Status::Ok, .mSamplerate = fmt.rate(), .mChannels = fmt.channels()};
|
{
|
||||||
|
// Find what audio format is used in mAvailable data
|
||||||
|
fmt = options.mResampleToMainRate ? Audio::Format(AUDIO_SAMPLERATE, 1) : mCodec->getAudioFormat();
|
||||||
|
|
||||||
|
// How much milliseconds are available ?
|
||||||
|
auto availTime = mAvailable.getTimeLength(fmt);
|
||||||
|
if (availTime != 0ms)
|
||||||
|
{
|
||||||
|
// How much we can consume from the mAvailable buffer ?
|
||||||
|
std::chrono::milliseconds resultTime = std::min(availTime, options.mElapsed);
|
||||||
|
|
||||||
|
// Number of bytes
|
||||||
|
mAvailable.moveTo(output, fmt.sizeFromTime(resultTime));
|
||||||
|
|
||||||
|
// Increase the counter of produced milliseconds
|
||||||
|
produced += resultTime;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::chrono::milliseconds decoded = 0ms;
|
while (produced < options.mElapsed)
|
||||||
do
|
|
||||||
{
|
{
|
||||||
// Get next packet from buffer
|
// Get next packet from buffer
|
||||||
RtpBuffer::ResultList rl;
|
RtpBuffer::FetchResult fr = mRtpBuffer.fetch();
|
||||||
RtpBuffer::FetchResult fr = mBuffer.fetch();
|
|
||||||
// ICELogDebug(<< fr.toString() << " " << mBuffer.findTimelength());
|
|
||||||
|
|
||||||
|
// Decode to mAvailable buffer
|
||||||
switch (fr.mStatus)
|
switch (fr.mStatus)
|
||||||
{
|
{
|
||||||
case RtpBuffer::FetchResult::Status::Gap: result = decodeGapTo(mAvailable, options); break;
|
case RtpBuffer::FetchResult::Status::Gap: result = decodeGapTo(mAvailable, options.decreaseElapsedBy(produced)); break;
|
||||||
case RtpBuffer::FetchResult::Status::NoPacket: result = decodeEmptyTo(mAvailable, options); break;
|
case RtpBuffer::FetchResult::Status::NoPacket: result = decodeEmptyTo(mAvailable, options.decreaseElapsedBy(produced)); break;
|
||||||
case RtpBuffer::FetchResult::Status::RegularPacket: result = decodePacketTo(mAvailable, options, fr.mPacket); break;
|
case RtpBuffer::FetchResult::Status::RegularPacket: result = decodePacketTo(mAvailable, options.decreaseElapsedBy(produced), fr.mPacket); updateDecodeIntervalStatistics(); break;
|
||||||
default:
|
default:
|
||||||
assert(0);
|
assert(0);
|
||||||
}
|
}
|
||||||
@@ -834,46 +879,29 @@ AudioReceiver::DecodeResult AudioReceiver::getAudioTo(Audio::DataWindow& output,
|
|||||||
if (!mCodec)
|
if (!mCodec)
|
||||||
break; // No sense to continue - we have no information at all
|
break; // No sense to continue - we have no information at all
|
||||||
|
|
||||||
Audio::Format fmt = options.mResampleToMainRate ? Audio::Format(AUDIO_SAMPLERATE, 1) : mCodec->getAudioFormat();
|
fmt = options.mResampleToMainRate ? Audio::Format(AUDIO_SAMPLERATE, 1) : mCodec->getAudioFormat();
|
||||||
result.mSamplerate = fmt.rate();
|
result.mSamplerate = fmt.rate();
|
||||||
result.mChannels = fmt.channels();
|
result.mChannels = fmt.channels();
|
||||||
|
|
||||||
// Have we anything interesting in the buffer ?
|
// How much milliseconds we have in audio buffer ?
|
||||||
auto bufferAvailable = mAvailable.getTimeLength(fmt.rate(), fmt.channels());
|
auto bufferAvailable = mAvailable.getTimeLength(fmt);
|
||||||
if (bufferAvailable == 0ms)
|
if (bufferAvailable == 0ms)
|
||||||
break; // No sense to continue - decoding / CNG / PLC stopped totally
|
break; // No sense to continue - decoding / CNG / PLC stopped totally
|
||||||
|
|
||||||
// How much data should be moved to result buffer ?
|
// How much data should be moved to result buffer ?
|
||||||
if (options.mElapsed != 0ms)
|
std::chrono::milliseconds resultTime = std::min(bufferAvailable, options.mElapsed - produced);
|
||||||
{
|
mAvailable.moveTo(output, fmt.sizeFromTime(resultTime));
|
||||||
std::chrono::milliseconds resultTime = std::min(bufferAvailable, options.mElapsed - produced);
|
produced += resultTime;
|
||||||
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)
|
if (produced != 0ms)
|
||||||
result.mStatus = DecodeResult::Status::Ok;
|
|
||||||
|
|
||||||
// Time statistics
|
|
||||||
if (result.mStatus == DecodeResult::Status::Ok)
|
|
||||||
{
|
{
|
||||||
// Decode statistics
|
result.mStatus = DecodeResult::Status::Ok;
|
||||||
if (!mDecodeTimestamp)
|
updateDecodingTimeStatistics();
|
||||||
mDecodeTimestamp = std::chrono::steady_clock::now();
|
|
||||||
else
|
|
||||||
{
|
|
||||||
auto t = std::chrono::steady_clock::now();
|
|
||||||
mStat.mDecodingInterval.process(std::chrono::duration_cast<std::chrono::milliseconds>(t - *mDecodeTimestamp).count());
|
|
||||||
mDecodeTimestamp = t;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mProducedAudio += produced;
|
||||||
|
// ICELogDebug(<< "Requested " << options.mElapsed << ", produced " << produced << ", remains " << mAvailable.getTimeLength(fmt) << ", packets " << getRtpBuffer().getCount());
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -987,43 +1015,16 @@ AudioReceiver::MediaInfo AudioReceiver::infoFor(jrtplib::RTPPacket& p)
|
|||||||
return {packetTime, codec->samplerate()};
|
return {packetTime, codec->samplerate()};
|
||||||
}
|
}
|
||||||
|
|
||||||
// int AudioReceiver::timelengthFor(jrtplib::RTPPacket& p)
|
void AudioReceiver::updateDecodeIntervalStatistics()
|
||||||
// {
|
{
|
||||||
// CodecMap::iterator codecIter = mCodecMap.find(p.GetPayloadType());
|
auto now = std::chrono::steady_clock::now();
|
||||||
// if (codecIter == mCodecMap.end())
|
if (mLastDecodeTimestamp)
|
||||||
// return 0;
|
{
|
||||||
|
mIntervalBetweenDecode += std::chrono::duration_cast<std::chrono::microseconds>(now - *mLastDecodeTimestamp);
|
||||||
// PCodec codec = codecIter->second;
|
mDecodeCount ++;
|
||||||
// if (codec)
|
}
|
||||||
// {
|
mLastDecodeTimestamp = now;
|
||||||
// int frame_count = 0;
|
}
|
||||||
// if (codec->rtpLength() != 0)
|
|
||||||
// {
|
|
||||||
// frame_count = static_cast<int>(p.GetPayloadLength() / codec->rtpLength());
|
|
||||||
// if (p.GetPayloadType() == 9/*G729A silence*/ && p.GetPayloadLength() % codec->rtpLength())
|
|
||||||
// frame_count++;
|
|
||||||
// }
|
|
||||||
// else
|
|
||||||
// frame_count = 1;
|
|
||||||
|
|
||||||
// return frame_count * codec->frameTime();
|
|
||||||
// }
|
|
||||||
// else
|
|
||||||
// return 0;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// int AudioReceiver::samplerateFor(jrtplib::RTPPacket& p)
|
|
||||||
// {
|
|
||||||
// CodecMap::iterator codecIter = mCodecMap.find(p.GetPayloadType());
|
|
||||||
// if (codecIter != mCodecMap.end())
|
|
||||||
// {
|
|
||||||
// PCodec codec = codecIter->second;
|
|
||||||
// if (codec)
|
|
||||||
// return codec->samplerate();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return 8000;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// ----------------------- DtmfReceiver -------------------
|
// ----------------------- DtmfReceiver -------------------
|
||||||
DtmfReceiver::DtmfReceiver(Statistics& stat)
|
DtmfReceiver::DtmfReceiver(Statistics& stat)
|
||||||
|
|||||||
@@ -122,6 +122,7 @@ protected:
|
|||||||
std::optional<uint32_t> mLastSeqno;
|
std::optional<uint32_t> mLastSeqno;
|
||||||
std::optional<jrtplib::RTPTime> mLastReceiveTime;
|
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;
|
||||||
};
|
};
|
||||||
@@ -169,10 +170,22 @@ public:
|
|||||||
|
|
||||||
struct DecodeOptions
|
struct DecodeOptions
|
||||||
{
|
{
|
||||||
|
bool mRealtimeProcessing = false; // Target PCAP parsing by default
|
||||||
bool mResampleToMainRate = true; // Resample all decoded audio to AUDIO_SAMPLERATE
|
bool mResampleToMainRate = true; // Resample all decoded audio to AUDIO_SAMPLERATE
|
||||||
bool mFillGapByCNG = false; // Use CNG information if available
|
bool mFillGapByCNG = false; // Use CNG information if available
|
||||||
bool mSkipDecode = false; // Don't do decode, just dry run - fetch packets, remove them from the jitter buffer
|
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"
|
std::chrono::milliseconds mElapsed = 0ms; // How much milliseconds should be decoded; zero value means "decode just next packet from the buffer"
|
||||||
|
DecodeOptions decreaseElapsedBy(std::chrono::milliseconds delta)
|
||||||
|
{
|
||||||
|
return
|
||||||
|
{
|
||||||
|
.mRealtimeProcessing = mRealtimeProcessing,
|
||||||
|
.mResampleToMainRate = mResampleToMainRate,
|
||||||
|
.mFillGapByCNG = mFillGapByCNG,
|
||||||
|
.mSkipDecode = mSkipDecode,
|
||||||
|
.mElapsed = std::max(mElapsed - delta, 0ms)
|
||||||
|
};
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DecodeResult
|
struct DecodeResult
|
||||||
@@ -192,8 +205,8 @@ public:
|
|||||||
DecodeResult getAudioTo(Audio::DataWindow& output, DecodeOptions options);
|
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 mRtpBuffer; }
|
||||||
|
|
||||||
// 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;
|
||||||
@@ -205,14 +218,12 @@ public:
|
|||||||
};
|
};
|
||||||
MediaInfo infoFor(jrtplib::RTPPacket& p);
|
MediaInfo infoFor(jrtplib::RTPPacket& p);
|
||||||
|
|
||||||
// // Returns timelength for given packet
|
void processDtmf();
|
||||||
// int timelengthFor(jrtplib::RTPPacket& p);
|
|
||||||
|
|
||||||
// // Return samplerate for given packet
|
void updateDecodingTimeStatistics();
|
||||||
// int samplerateFor(jrtplib::RTPPacket& p);
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
RtpBuffer mBuffer; // Jitter buffer itself
|
RtpBuffer mRtpBuffer; // RTP jitter buffer itself; here are audio packets
|
||||||
RtpBuffer mDtmfBuffer; // These two (mDtmfBuffer / mDtmfReceiver) are for our analyzer stack only; in normal softphone logic DTMF packets goes via SingleAudioStream::mDtmfReceiver
|
RtpBuffer mDtmfBuffer; // These two (mDtmfBuffer / mDtmfReceiver) are for our analyzer stack only; in normal softphone logic DTMF packets goes via SingleAudioStream::mDtmfReceiver
|
||||||
DtmfReceiver mDtmfReceiver;
|
DtmfReceiver mDtmfReceiver;
|
||||||
|
|
||||||
@@ -258,6 +269,9 @@ protected:
|
|||||||
float mIntervalSum = 0.0f;
|
float mIntervalSum = 0.0f;
|
||||||
int mIntervalCount = 0;
|
int mIntervalCount = 0;
|
||||||
|
|
||||||
|
std::chrono::milliseconds mRequestedAudio = 0ms;
|
||||||
|
std::chrono::milliseconds mProducedAudio = 0ms;
|
||||||
|
|
||||||
// Zero rate will make audio mono but resampling will be skipped
|
// Zero rate will make audio mono but resampling will be skipped
|
||||||
void makeMonoAndResample(int rate, int channels);
|
void makeMonoAndResample(int rate, int channels);
|
||||||
|
|
||||||
@@ -272,6 +286,12 @@ protected:
|
|||||||
DecodeResult decodeGapTo(Audio::DataWindow& output, DecodeOptions options);
|
DecodeResult decodeGapTo(Audio::DataWindow& output, DecodeOptions options);
|
||||||
DecodeResult decodePacketTo(Audio::DataWindow& output, DecodeOptions options, const std::shared_ptr<RtpBuffer::Packet>& p);
|
DecodeResult decodePacketTo(Audio::DataWindow& output, DecodeOptions options, const std::shared_ptr<RtpBuffer::Packet>& p);
|
||||||
DecodeResult decodeEmptyTo(Audio::DataWindow& output, DecodeOptions options);
|
DecodeResult decodeEmptyTo(Audio::DataWindow& output, DecodeOptions options);
|
||||||
|
|
||||||
|
std::optional<std::chrono::steady_clock::time_point> mLastDecodeTimestamp;
|
||||||
|
std::chrono::microseconds mIntervalBetweenDecode = 0us;
|
||||||
|
size_t mDecodeCount = 0;
|
||||||
|
void updateDecodeIntervalStatistics();
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -137,7 +137,6 @@ 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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -342,8 +342,6 @@ void Logger::beginLine(LogLevel level, const char* filename, int linenumber, con
|
|||||||
mFilename = filenamestart;
|
mFilename = filenamestart;
|
||||||
mLine = linenumber;
|
mLine = linenumber;
|
||||||
mSubsystem = subsystem;
|
mSubsystem = subsystem;
|
||||||
|
|
||||||
// mStream << std::setw(8) << ICETimeHelper::timestamp() << " | " << std::setw(8) << ThreadInfo::currentThread() << " | " << std::setw(30) << filenamestart << " | " << std::setw(4) << linenumber << " | " << std::setw(12) << subsystem << " | ";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|||||||
Reference in New Issue
Block a user