- fixes + correct decode of DTX/CNG periods
This commit is contained in:
@@ -114,4 +114,7 @@
|
|||||||
// In milliseconds
|
// In milliseconds
|
||||||
#define MT_SEVANA_FRAME_TIME 680
|
#define MT_SEVANA_FRAME_TIME 680
|
||||||
|
|
||||||
|
// Number of samples
|
||||||
|
#define MT_MAX_DECODEBUFFER 32768
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ void RtpBuffer::setHigh(int milliseconds)
|
|||||||
mHigh = milliseconds;
|
mHigh = milliseconds;
|
||||||
}
|
}
|
||||||
|
|
||||||
int RtpBuffer::high()
|
int RtpBuffer::high() const
|
||||||
{
|
{
|
||||||
return mHigh;
|
return mHigh;
|
||||||
}
|
}
|
||||||
@@ -89,7 +89,7 @@ void RtpBuffer::setLow(int milliseconds)
|
|||||||
mLow = milliseconds;
|
mLow = milliseconds;
|
||||||
}
|
}
|
||||||
|
|
||||||
int RtpBuffer::low()
|
int RtpBuffer::low() const
|
||||||
{
|
{
|
||||||
return mLow;
|
return mLow;
|
||||||
}
|
}
|
||||||
@@ -99,7 +99,7 @@ void RtpBuffer::setPrebuffer(int milliseconds)
|
|||||||
mPrebuffer = milliseconds;
|
mPrebuffer = milliseconds;
|
||||||
}
|
}
|
||||||
|
|
||||||
int RtpBuffer::prebuffer()
|
int RtpBuffer::prebuffer() const
|
||||||
{
|
{
|
||||||
return mPrebuffer;
|
return mPrebuffer;
|
||||||
}
|
}
|
||||||
@@ -224,7 +224,7 @@ RtpBuffer::FetchResult RtpBuffer::fetch(ResultList& rl)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (mLastSeqno.has_value())
|
if (mLastSeqno) // It means we had previous packet
|
||||||
{
|
{
|
||||||
if (mPacketList.empty())
|
if (mPacketList.empty())
|
||||||
{
|
{
|
||||||
@@ -237,6 +237,7 @@ 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);
|
gap = std::min(gap, 127);
|
||||||
@@ -244,9 +245,16 @@ RtpBuffer::FetchResult RtpBuffer::fetch(ResultList& rl)
|
|||||||
{
|
{
|
||||||
// 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++;
|
||||||
auto currentTimestamp = uint64_t(packet.rtp()->GetReceiveTime().GetDouble() * 1000000);
|
auto currentTimestamp = std::chrono::microseconds(uint64_t(packet.rtp()->GetReceiveTime().GetDouble() * 1000000));
|
||||||
mStat.mPacketLossTimeline.push_back({gap, std::chrono::microseconds(currentTimestamp)});
|
|
||||||
mLastSeqno = *mLastSeqno + 1;
|
if (mStat.mPacketLossTimeline.empty() || (mStat.mPacketLossTimeline.back().mEndSeqno != seqno))
|
||||||
|
mStat.mPacketLossTimeline.push_back({.mStartSeqno = *mLastSeqno,
|
||||||
|
.mEndSeqno = seqno,
|
||||||
|
.mGap = gap,
|
||||||
|
.mTimestamp = currentTimestamp});
|
||||||
|
|
||||||
|
mLastSeqno = *mLastSeqno + 1; // As we deal with the audio gap - return the silence and increase last seqno
|
||||||
|
|
||||||
result = FetchResult::Gap;
|
result = FetchResult::Gap;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -475,235 +483,249 @@ bool AudioReceiver::add(const std::shared_ptr<jrtplib::RTPPacket>& p, Codec** de
|
|||||||
// Queue packet to buffer
|
// Queue packet to buffer
|
||||||
auto packet = mBuffer.add(p, time_length, samplerate).get();
|
auto packet = mBuffer.add(p, time_length, samplerate).get();
|
||||||
|
|
||||||
if (packet)
|
return packet;
|
||||||
{
|
|
||||||
// Check if early decoding configured
|
|
||||||
if (mEarlyDecode && codec)
|
|
||||||
{
|
|
||||||
// Move data to packet buffer
|
|
||||||
size_t available = decode_packet(*codec, *p, mDecodedFrame, sizeof mDecodedFrame);
|
|
||||||
if (available > 0)
|
|
||||||
{
|
|
||||||
packet->pcm().resize(available / 2);
|
|
||||||
memcpy(packet->pcm().data(), mDecodedFrame, available / 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioReceiver::processDecoded(Audio::DataWindow& output, int options)
|
void AudioReceiver::processDecoded(Audio::DataWindow& output, DecodeOptions options)
|
||||||
{
|
{
|
||||||
// Write to audio dump if requested
|
// Write to audio dump if requested
|
||||||
if (mDecodedDump && mDecodedLength)
|
if (mDecodedDump && mDecodedLength)
|
||||||
mDecodedDump->write(mDecodedFrame, mDecodedLength);
|
mDecodedDump->write(mDecodedFrame, mDecodedLength);
|
||||||
|
|
||||||
// Resample to target rate
|
// Resample to target rate
|
||||||
bool resample = !(options & DecodeOptions_DontResample);
|
makeMonoAndResample(options.mResampleToMainRate ? mCodec->samplerate() : 0, mCodec->channels());
|
||||||
makeMonoAndResample(resample ? mCodec->samplerate() : 0,
|
|
||||||
mCodec->channels());
|
|
||||||
|
|
||||||
// Send to output
|
// Send to output
|
||||||
output.add(mResampledFrame, mResampledLength);
|
output.add(mResampledFrame, mResampledLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
AudioReceiver::DecodeResult AudioReceiver::getAudio(Audio::DataWindow& output, int options, int* rate)
|
void AudioReceiver::produceSilence(std::chrono::milliseconds length, Audio::DataWindow& output, DecodeOptions options)
|
||||||
|
{
|
||||||
|
// Fill mDecodeBuffer as much as needed and call processDecoded()
|
||||||
|
// Depending on used codec mono or stereo silence should be produced
|
||||||
|
size_t chunks = length.count() / 10;
|
||||||
|
size_t tail = length.count() % 10;
|
||||||
|
size_t chunk_size = 10 * sizeof(int16_t) * mCodec->samplerate() / 1000 * mCodec->channels();
|
||||||
|
size_t tail_size = tail * sizeof(int16_t) * mCodec->samplerate() / 1000 * mCodec->channels();
|
||||||
|
for (size_t i = 0; i < chunks; i++)
|
||||||
|
{
|
||||||
|
memset(mDecodedFrame, 0, chunk_size);
|
||||||
|
mDecodedLength = chunk_size;
|
||||||
|
processDecoded(output, options);
|
||||||
|
}
|
||||||
|
if (tail)
|
||||||
|
{
|
||||||
|
memset(mDecodedFrame, 0, tail_size);
|
||||||
|
mDecodedLength = tail_size;
|
||||||
|
processDecoded(output, options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioReceiver::produceCNG(std::chrono::milliseconds length, Audio::DataWindow& output, DecodeOptions options)
|
||||||
|
{
|
||||||
|
int frames100ms = length.count() / 100;
|
||||||
|
for (int frameIndex = 0; frameIndex < frames100ms; frameIndex++)
|
||||||
|
{
|
||||||
|
if (options.mSkipDecode)
|
||||||
|
mDecodedLength = 0;
|
||||||
|
else
|
||||||
|
mDecodedLength = mCngDecoder.produce(mCodec->samplerate(), 100, mDecodedFrame, false);
|
||||||
|
|
||||||
|
if (mDecodedLength)
|
||||||
|
processDecoded(output, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not forget about tail!
|
||||||
|
int tail = length.count() % 100;
|
||||||
|
if (tail)
|
||||||
|
{
|
||||||
|
if (options.mSkipDecode)
|
||||||
|
mDecodedLength = 0;
|
||||||
|
else
|
||||||
|
mDecodedLength = mCngDecoder.produce(mCodec->samplerate(), tail, reinterpret_cast<short*>(mDecodedFrame), false);
|
||||||
|
|
||||||
|
if (mDecodedLength)
|
||||||
|
processDecoded(output, options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioReceiver::DecodeResult AudioReceiver::decodeGap(Audio::DataWindow& output, DecodeOptions options)
|
||||||
|
{
|
||||||
|
ICELogDebug(<< "Gap detected.");
|
||||||
|
|
||||||
|
mDecodedLength = mResampledLength = 0;
|
||||||
|
if (mCngPacket && mCodec)
|
||||||
|
{
|
||||||
|
// Synthesize comfort noise. It will be done on AUDIO_SAMPLERATE rate directly to mResampledFrame buffer.
|
||||||
|
// Do not forget to send this noise to analysis
|
||||||
|
mDecodedLength = mCngDecoder.produce(mCodec->samplerate(), mLastPacketTimeLength,
|
||||||
|
reinterpret_cast<short*>(mDecodedFrame), false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
if (mCodec && mFrameCount && !mCodecSettings.mSkipDecode)
|
||||||
|
{
|
||||||
|
// Do PLC to mDecodedFrame/mDecodedLength
|
||||||
|
if (options.mSkipDecode)
|
||||||
|
mDecodedLength = 0;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mDecodedLength = mCodec->plc(mFrameCount, mDecodedFrame, sizeof mDecodedFrame);
|
||||||
|
if (!mDecodedLength)
|
||||||
|
{
|
||||||
|
// PLC is not support or failed
|
||||||
|
// So substitute the silence
|
||||||
|
size_t nr_of_samples = mCodec->frameTime() * mCodec->samplerate() / 1000 * sizeof(short);
|
||||||
|
mDecodedLength = nr_of_samples * sizeof(short);
|
||||||
|
memset(mDecodedFrame, 0, mDecodedLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mDecodedLength)
|
||||||
|
{
|
||||||
|
processDecoded(output, options);
|
||||||
|
return DecodeResult_Ok;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return DecodeResult_Skip;
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioReceiver::DecodeResult AudioReceiver::decodePacket(const RtpBuffer::ResultList& rl, Audio::DataWindow& output, DecodeOptions options, int* rate)
|
||||||
|
{
|
||||||
|
DecodeResult result = DecodeResult_Skip;
|
||||||
|
|
||||||
|
mFailedCount = 0;
|
||||||
|
for (const std::shared_ptr<RtpBuffer::Packet>& p: rl)
|
||||||
|
{
|
||||||
|
assert(p);
|
||||||
|
// Check if we need to emit silence or CNG - previously CNG packet was detected. Emit CNG audio here if needed.
|
||||||
|
if (mLastPacketTimestamp && mLastPacketTimeLength && mCodec)
|
||||||
|
{
|
||||||
|
int units = p->rtp()->GetTimestamp() - *mLastPacketTimestamp;
|
||||||
|
int milliseconds = units / (mCodec->samplerate() / 1000);
|
||||||
|
if (milliseconds > mLastPacketTimeLength)
|
||||||
|
{
|
||||||
|
auto silenceLength = std::chrono::milliseconds(milliseconds - mLastPacketTimeLength);
|
||||||
|
|
||||||
|
if (mCngPacket && options.mFillGapByCNG)
|
||||||
|
produceCNG(silenceLength, output, options);
|
||||||
|
else
|
||||||
|
produceSilence(silenceLength, output, options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mLastPacketTimestamp = p->rtp()->GetTimestamp();
|
||||||
|
|
||||||
|
// Find codec by payload type
|
||||||
|
int ptype = p->rtp()->GetPayloadType();
|
||||||
|
mCodec = mCodecMap[ptype];
|
||||||
|
if (mCodec)
|
||||||
|
{
|
||||||
|
if (rate)
|
||||||
|
*rate = mCodec->samplerate();
|
||||||
|
|
||||||
|
// Check if it is CNG packet
|
||||||
|
if ((ptype == 0 || ptype == 8) && p->rtp()->GetPayloadLength() >= 1 && p->rtp()->GetPayloadLength() <= 6)
|
||||||
|
{
|
||||||
|
if (options.mSkipDecode)
|
||||||
|
mDecodedLength = 0;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mCngPacket = p->rtp();
|
||||||
|
mCngDecoder.decode3389(p->rtp()->GetPayloadData(), p->rtp()->GetPayloadLength());
|
||||||
|
|
||||||
|
// Emit CNG mLastPacketLength milliseconds
|
||||||
|
mDecodedLength = mCngDecoder.produce(mCodec->samplerate(), mLastPacketTimeLength,
|
||||||
|
(short*)mDecodedFrame, true);
|
||||||
|
if (mDecodedLength)
|
||||||
|
processDecoded(output, options);
|
||||||
|
}
|
||||||
|
result = DecodeResult_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 = 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioReceiver::DecodeResult AudioReceiver::decodeNone(Audio::DataWindow& output, DecodeOptions options)
|
||||||
|
{
|
||||||
|
ICELogDebug(<< "No packet available in jitter buffer");
|
||||||
|
mFailedCount++;
|
||||||
|
return DecodeResult_Skip;
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioReceiver::DecodeResult AudioReceiver::getAudio(Audio::DataWindow& output, DecodeOptions options, int* rate)
|
||||||
{
|
{
|
||||||
DecodeResult result = DecodeResult_Skip;
|
DecodeResult result = DecodeResult_Skip;
|
||||||
bool had_decode = false;
|
|
||||||
|
|
||||||
// Get next packet from buffer
|
// Get next packet from buffer
|
||||||
RtpBuffer::ResultList rl;
|
RtpBuffer::ResultList rl;
|
||||||
RtpBuffer::FetchResult fr = mBuffer.fetch(rl);
|
RtpBuffer::FetchResult fr = mBuffer.fetch(rl);
|
||||||
switch (fr)
|
switch (fr)
|
||||||
{
|
{
|
||||||
case RtpBuffer::FetchResult::Gap:
|
case RtpBuffer::FetchResult::Gap: result = decodeGap(output, options); break;
|
||||||
ICELogDebug(<< "Gap detected.");
|
case RtpBuffer::FetchResult::NoPacket: result = decodeNone(output, options); break;
|
||||||
|
case RtpBuffer::FetchResult::RegularPacket: result = decodePacket(rl, output, options, rate); break;
|
||||||
mDecodedLength = mResampledLength = 0;
|
|
||||||
if (mCngPacket && mCodec)
|
|
||||||
{
|
|
||||||
// Synthesize comfort noise. It will be done on AUDIO_SAMPLERATE rate directly to mResampledFrame buffer.
|
|
||||||
// Do not forget to send this noise to analysis
|
|
||||||
mDecodedLength = mCngDecoder.produce(mCodec->samplerate(), mLastPacketTimeLength,
|
|
||||||
reinterpret_cast<short*>(mDecodedFrame), false);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
if (mCodec && mFrameCount && !mCodecSettings.mSkipDecode)
|
|
||||||
{
|
|
||||||
// Do PLC to mDecodedFrame/mDecodedLength
|
|
||||||
if (options & DecodeOptions_SkipDecode)
|
|
||||||
mDecodedLength = 0;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
mDecodedLength = mCodec->plc(mFrameCount, mDecodedFrame, sizeof mDecodedFrame);
|
|
||||||
if (!mDecodedLength)
|
|
||||||
{
|
|
||||||
// PLC is not support or failed
|
|
||||||
// So substitute the silence
|
|
||||||
size_t nr_of_samples = mCodec->frameTime() * mCodec->samplerate() / 1000 * sizeof(short);
|
|
||||||
mDecodedLength = nr_of_samples * sizeof(short);
|
|
||||||
memset(mDecodedFrame, 0, mDecodedLength);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mDecodedLength)
|
|
||||||
{
|
|
||||||
processDecoded(output, options);
|
|
||||||
result = DecodeResult_Ok;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case RtpBuffer::FetchResult::NoPacket:
|
|
||||||
ICELogDebug(<< "No packet available in jitter buffer");
|
|
||||||
mFailedCount++;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case RtpBuffer::FetchResult::RegularPacket:
|
|
||||||
mFailedCount = 0;
|
|
||||||
for (std::shared_ptr<RtpBuffer::Packet>& p: rl)
|
|
||||||
{
|
|
||||||
assert(p);
|
|
||||||
// Check if previously CNG packet was detected. Emit CNG audio here if needed.
|
|
||||||
if (options & DecodeOptions_FillCngGap && mCngPacket && mCodec)
|
|
||||||
{
|
|
||||||
// Fill CNG audio is server mode is present
|
|
||||||
int units = p->rtp()->GetTimestamp() - mCngPacket->GetTimestamp();
|
|
||||||
int milliseconds = units / (mCodec->samplerate() / 1000);
|
|
||||||
if (milliseconds > mLastPacketTimeLength)
|
|
||||||
{
|
|
||||||
int frames100ms = milliseconds / 100;
|
|
||||||
for (int frameIndex = 0; frameIndex < frames100ms; frameIndex++)
|
|
||||||
{
|
|
||||||
if (options & DecodeOptions_SkipDecode)
|
|
||||||
mDecodedLength = 0;
|
|
||||||
else
|
|
||||||
mDecodedLength = mCngDecoder.produce(mCodec->samplerate(), 100,
|
|
||||||
reinterpret_cast<short*>(mDecodedFrame), false);
|
|
||||||
|
|
||||||
if (mDecodedLength)
|
|
||||||
processDecoded(output, options);
|
|
||||||
}
|
|
||||||
// Do not forget about tail!
|
|
||||||
int tail = milliseconds % 100;
|
|
||||||
if (tail)
|
|
||||||
{
|
|
||||||
if (options & DecodeOptions_SkipDecode)
|
|
||||||
mDecodedLength = 0;
|
|
||||||
else
|
|
||||||
mDecodedLength = mCngDecoder.produce(mCodec->samplerate(), tail,
|
|
||||||
reinterpret_cast<short*>(mDecodedFrame), false);
|
|
||||||
|
|
||||||
if (mDecodedLength)
|
|
||||||
processDecoded(output, options);
|
|
||||||
}
|
|
||||||
result = DecodeResult_Ok;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mEarlyDecode)
|
|
||||||
{
|
|
||||||
// ToDo - copy the decoded data to output buffer
|
|
||||||
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Find codec by payload type
|
|
||||||
int ptype = p->rtp()->GetPayloadType();
|
|
||||||
mCodec = mCodecMap[ptype];
|
|
||||||
if (mCodec)
|
|
||||||
{
|
|
||||||
if (rate)
|
|
||||||
*rate = mCodec->samplerate();
|
|
||||||
|
|
||||||
// Check if it is CNG packet
|
|
||||||
if ((ptype == 0 || ptype == 8) && p->rtp()->GetPayloadLength() >= 1 && p->rtp()->GetPayloadLength() <= 6)
|
|
||||||
{
|
|
||||||
if (options & DecodeOptions_SkipDecode)
|
|
||||||
mDecodedLength = 0;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
mCngPacket = p->rtp();
|
|
||||||
mCngDecoder.decode3389(p->rtp()->GetPayloadData(), p->rtp()->GetPayloadLength());
|
|
||||||
// Emit CNG mLastPacketLength milliseconds
|
|
||||||
mDecodedLength = mCngDecoder.produce(mCodec->samplerate(), mLastPacketTimeLength,
|
|
||||||
(short*)mDecodedFrame, true);
|
|
||||||
if (mDecodedLength)
|
|
||||||
processDecoded(output, options);
|
|
||||||
}
|
|
||||||
result = DecodeResult_Ok;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Reset CNG packet
|
|
||||||
mCngPacket.reset();
|
|
||||||
|
|
||||||
// Handle here regular RTP packets
|
|
||||||
// Check if payload length is ok
|
|
||||||
size_t payload_length = p->rtp()->GetPayloadLength();
|
|
||||||
size_t rtp_frame_length = mCodec->rtpLength();
|
|
||||||
|
|
||||||
int tail = rtp_frame_length ? payload_length % rtp_frame_length : 0;
|
|
||||||
|
|
||||||
if (!tail)
|
|
||||||
{
|
|
||||||
// Find number of frames
|
|
||||||
mFrameCount = mCodec->rtpLength() ? p->rtp()->GetPayloadLength() / mCodec->rtpLength() : 1;
|
|
||||||
int frameLength = mCodec->rtpLength() ? mCodec->rtpLength() : (int)p->rtp()->GetPayloadLength();
|
|
||||||
|
|
||||||
// Save last packet time length
|
|
||||||
mLastPacketTimeLength = mFrameCount * mCodec->frameTime();
|
|
||||||
|
|
||||||
// Decode
|
|
||||||
for (int i=0; i<mFrameCount && !mCodecSettings.mSkipDecode; i++)
|
|
||||||
{
|
|
||||||
if (options & DecodeOptions_SkipDecode)
|
|
||||||
mDecodedLength = 0;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Trigger the statistics
|
|
||||||
had_decode = true;
|
|
||||||
|
|
||||||
// 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
|
|
||||||
processStatisticsWithAmrCodec(mCodec.get());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
result = DecodeResult_BadPacket;
|
|
||||||
// ICELogMedia(<< "RTP packet with tail.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
assert(0);
|
assert(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (had_decode)
|
if (result == DecodeResult_Ok)
|
||||||
{
|
{
|
||||||
// mStat.mDecodeRequested++;
|
// Decode statistics
|
||||||
if (mLastDecodeTime == 0.0)
|
if (!mLastDecodeTimestamp)
|
||||||
mLastDecodeTime = now_ms();
|
mLastDecodeTimestamp = std::chrono::steady_clock::now();
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
float t = now_ms();
|
auto t = std::chrono::steady_clock::now();
|
||||||
mStat.mDecodingInterval.process(t - mLastDecodeTime);
|
mStat.mDecodingInterval.process(std::chrono::duration_cast<std::chrono::milliseconds>(t - *mLastDecodeTimestamp).count());
|
||||||
mLastDecodeTime = t;
|
mLastDecodeTimestamp = t;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
@@ -752,7 +774,7 @@ Codec* AudioReceiver::findCodec(int payloadType)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void AudioReceiver::processStatisticsWithAmrCodec(Codec* c)
|
void AudioReceiver::updateAmrCodecStats(Codec* c)
|
||||||
{
|
{
|
||||||
#if !defined(TARGET_ANDROID) && !defined(TARGET_OPENWRT) && !defined(TARGET_WIN) && !defined(TARGET_RPI) && defined(USE_AMR_CODEC)
|
#if !defined(TARGET_ANDROID) && !defined(TARGET_OPENWRT) && !defined(TARGET_WIN) && !defined(TARGET_RPI) && defined(USE_AMR_CODEC)
|
||||||
AmrNbCodec* nb = dynamic_cast<AmrNbCodec*>(c);
|
AmrNbCodec* nb = dynamic_cast<AmrNbCodec*>(c);
|
||||||
@@ -761,8 +783,8 @@ void AudioReceiver::processStatisticsWithAmrCodec(Codec* c)
|
|||||||
if (nb != nullptr)
|
if (nb != nullptr)
|
||||||
mStat.mBitrateSwitchCounter = nb->getSwitchCounter();
|
mStat.mBitrateSwitchCounter = nb->getSwitchCounter();
|
||||||
else
|
else
|
||||||
if (wb != nullptr)
|
if (wb != nullptr)
|
||||||
mStat.mBitrateSwitchCounter = wb->getSwitchCounter();
|
mStat.mBitrateSwitchCounter = wb->getSwitchCounter();
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -819,13 +841,10 @@ int AudioReceiver::samplerateFor(jrtplib::RTPPacket& p)
|
|||||||
// ----------------------- DtmfReceiver -------------------
|
// ----------------------- DtmfReceiver -------------------
|
||||||
DtmfReceiver::DtmfReceiver(Statistics& stat)
|
DtmfReceiver::DtmfReceiver(Statistics& stat)
|
||||||
:Receiver(stat)
|
:Receiver(stat)
|
||||||
{
|
{}
|
||||||
}
|
|
||||||
|
|
||||||
DtmfReceiver::~DtmfReceiver()
|
DtmfReceiver::~DtmfReceiver()
|
||||||
{
|
{}
|
||||||
}
|
|
||||||
|
|
||||||
void DtmfReceiver::add(std::shared_ptr<RTPPacket> /*p*/)
|
void DtmfReceiver::add(std::shared_ptr<RTPPacket> /*p*/)
|
||||||
{
|
{}
|
||||||
}
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#ifndef __MT_AUDIO_RECEIVER_H
|
#ifndef __MT_AUDIO_RECEIVER_H
|
||||||
#define __MT_AUDIO_RECEIVER_H
|
#define __MT_AUDIO_RECEIVER_H
|
||||||
|
|
||||||
|
#include "../engine_config.h"
|
||||||
#include "MT_Stream.h"
|
#include "MT_Stream.h"
|
||||||
#include "MT_CodecList.h"
|
#include "MT_CodecList.h"
|
||||||
#include "MT_CngHelper.h"
|
#include "MT_CngHelper.h"
|
||||||
@@ -61,17 +62,17 @@ public:
|
|||||||
RtpBuffer(Statistics& stat);
|
RtpBuffer(Statistics& stat);
|
||||||
~RtpBuffer();
|
~RtpBuffer();
|
||||||
|
|
||||||
unsigned ssrc();
|
unsigned ssrc() const;
|
||||||
void setSsrc(unsigned ssrc);
|
void setSsrc(unsigned ssrc);
|
||||||
|
|
||||||
void setHigh(int milliseconds);
|
void setHigh(int milliseconds);
|
||||||
int high();
|
int high() const;
|
||||||
|
|
||||||
void setLow(int milliseconds);
|
void setLow(int milliseconds);
|
||||||
int low();
|
int low() const;
|
||||||
|
|
||||||
void setPrebuffer(int milliseconds);
|
void setPrebuffer(int milliseconds);
|
||||||
int prebuffer();
|
int prebuffer() const;
|
||||||
|
|
||||||
int getNumberOfReturnedPackets() const;
|
int getNumberOfReturnedPackets() const;
|
||||||
int getNumberOfAddPackets() const;
|
int getNumberOfAddPackets() const;
|
||||||
@@ -88,12 +89,12 @@ public:
|
|||||||
FetchResult fetch(ResultList& rl);
|
FetchResult fetch(ResultList& rl);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
unsigned mSsrc = 0;
|
unsigned mSsrc = 0;
|
||||||
int mHigh = RTP_BUFFER_HIGH,
|
int mHigh = RTP_BUFFER_HIGH,
|
||||||
mLow = RTP_BUFFER_LOW,
|
mLow = RTP_BUFFER_LOW,
|
||||||
mPrebuffer = RTP_BUFFER_PREBUFFER;
|
mPrebuffer = RTP_BUFFER_PREBUFFER;
|
||||||
int mReturnedCounter = 0,
|
int mReturnedCounter = 0,
|
||||||
mAddCounter = 0;
|
mAddCounter = 0;
|
||||||
|
|
||||||
mutable Mutex mGuard;
|
mutable Mutex mGuard;
|
||||||
typedef std::vector<std::shared_ptr<Packet>> PacketList;
|
typedef std::vector<std::shared_ptr<Packet>> PacketList;
|
||||||
@@ -105,7 +106,7 @@ protected:
|
|||||||
std::optional<uint32_t> mLastSeqno;
|
std::optional<uint32_t> mLastSeqno;
|
||||||
|
|
||||||
// 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.0;
|
float mLastAddTime = 0.0f;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Receiver
|
class Receiver
|
||||||
@@ -125,29 +126,37 @@ public:
|
|||||||
~AudioReceiver();
|
~AudioReceiver();
|
||||||
|
|
||||||
// Update codec settings
|
// Update codec settings
|
||||||
void setCodecSettings(const CodecList::Settings& codecSettings);
|
void setCodecSettings(const CodecList::Settings& codecSettings);
|
||||||
CodecList::Settings& getCodecSettings();
|
CodecList::Settings& getCodecSettings();
|
||||||
|
|
||||||
// Returns false when packet is rejected as illegal. codec parameter will show codec which will be used for decoding.
|
// Returns false when packet is rejected as illegal. codec parameter will show codec which will be used for decoding.
|
||||||
// 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
|
// Returns false when there is no rtp data from jitter
|
||||||
enum DecodeOptions
|
/*enum DecodeOptions
|
||||||
{
|
{
|
||||||
DecodeOptions_ResampleToMainRate = 0,
|
DecodeOptions_ResampleToMainRate = 0,
|
||||||
DecodeOptions_DontResample = 1,
|
DecodeOptions_DontResample = 1,
|
||||||
DecodeOptions_FillCngGap = 2,
|
DecodeOptions_FillCngGap = 2,
|
||||||
DecodeOptions_SkipDecode = 4
|
DecodeOptions_SkipDecode = 4
|
||||||
|
};*/
|
||||||
|
|
||||||
|
struct DecodeOptions
|
||||||
|
{
|
||||||
|
bool mResampleToMainRate = true;
|
||||||
|
bool mFillGapByCNG = false;
|
||||||
|
bool mSkipDecode = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum DecodeResult
|
enum DecodeResult
|
||||||
{
|
{
|
||||||
DecodeResult_Ok,
|
DecodeResult_Ok, // Decoded ok
|
||||||
DecodeResult_Skip,
|
DecodeResult_Skip, // Just no data - emit silence instead
|
||||||
DecodeResult_BadPacket
|
DecodeResult_BadPacket // Error happened during the decode
|
||||||
};
|
};
|
||||||
|
|
||||||
DecodeResult getAudio(Audio::DataWindow& output, int options = DecodeOptions_ResampleToMainRate, int* rate = nullptr);
|
DecodeResult getAudio(Audio::DataWindow& output, DecodeOptions options = {.mResampleToMainRate = true, .mFillGapByCNG = false, .mSkipDecode = false}, int* rate = nullptr);
|
||||||
|
|
||||||
// Looks for codec by payload type
|
// Looks for codec by payload type
|
||||||
Codec* findCodec(int payloadType);
|
Codec* findCodec(int payloadType);
|
||||||
@@ -163,52 +172,57 @@ public:
|
|||||||
int samplerateFor(jrtplib::RTPPacket& p);
|
int samplerateFor(jrtplib::RTPPacket& p);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
RtpBuffer mBuffer;
|
RtpBuffer mBuffer; // Jitter buffer itself
|
||||||
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<jrtplib::RTPPacket> 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
|
||||||
// Decode RTP early, do not wait for speaker callback
|
|
||||||
bool mEarlyDecode = false;
|
|
||||||
|
|
||||||
// Buffer to hold decoded data
|
// Buffer to hold decoded data
|
||||||
char mDecodedFrame[65536];
|
int16_t mDecodedFrame[MT_MAX_DECODEBUFFER];
|
||||||
int mDecodedLength = 0;
|
size_t mDecodedLength = 0;
|
||||||
|
|
||||||
// Buffer to hold data converted to stereo/mono
|
// Buffer to hold data converted to stereo/mono; there is multiplier 2 as it can be stereo audio
|
||||||
char mConvertedFrame[32768];
|
int16_t mConvertedFrame[MT_MAX_DECODEBUFFER * 2];
|
||||||
int mConvertedLength = 0;
|
size_t mConvertedLength = 0;
|
||||||
|
|
||||||
// Buffer to hold data resampled to AUDIO_SAMPLERATE
|
// Buffer to hold data resampled to AUDIO_SAMPLERATE
|
||||||
char mResampledFrame[65536];
|
int16_t mResampledFrame[MT_MAX_DECODEBUFFER];
|
||||||
int mResampledLength = 0;
|
size_t mResampledLength = 0;
|
||||||
|
|
||||||
// Last packet time length
|
// Last packet time length
|
||||||
int mLastPacketTimeLength = 0;
|
int mLastPacketTimeLength = 0;
|
||||||
|
std::optional<uint32_t> mLastPacketTimestamp;
|
||||||
|
|
||||||
int mFailedCount = 0;
|
int mFailedCount = 0;
|
||||||
Audio::Resampler mResampler8, mResampler16,
|
Audio::Resampler mResampler8, mResampler16, mResampler32, mResampler48;
|
||||||
mResampler32, mResampler48;
|
|
||||||
|
|
||||||
Audio::PWavFileWriter mDecodedDump;
|
Audio::PWavFileWriter mDecodedDump;
|
||||||
|
|
||||||
float mLastDecodeTime = 0.0; // Time last call happened to codec->decode()
|
std::optional<std::chrono::steady_clock::time_point> mLastDecodeTimestamp; // Time last call happened to codec->decode()
|
||||||
|
|
||||||
float mIntervalSum = 0.0;
|
float mIntervalSum = 0.0f;
|
||||||
int mIntervalCount = 0;
|
int mIntervalCount = 0;
|
||||||
|
|
||||||
// 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);
|
||||||
|
|
||||||
// Resamples, sends to analysis, writes to dump and queues to output decoded frames from mDecodedFrame
|
// Resamples, sends to analysis, writes to dump and queues to output decoded frames from mDecodedFrame
|
||||||
void processDecoded(Audio::DataWindow& output, int options);
|
void processDecoded(Audio::DataWindow& output, DecodeOptions options);
|
||||||
|
void produceSilence(std::chrono::milliseconds length, Audio::DataWindow& output, DecodeOptions options);
|
||||||
|
void produceCNG(std::chrono::milliseconds length, Audio::DataWindow& output, DecodeOptions options);
|
||||||
|
|
||||||
void processStatisticsWithAmrCodec(Codec* c);
|
// Calculate bitrate switch statistics for AMR codecs
|
||||||
|
void updateAmrCodecStats(Codec* c);
|
||||||
|
|
||||||
|
DecodeResult decodeGap(Audio::DataWindow& output, DecodeOptions options);
|
||||||
|
DecodeResult decodePacket(const RtpBuffer::ResultList& rl, Audio::DataWindow& output, DecodeOptions options, int* rate = nullptr);
|
||||||
|
DecodeResult decodeNone(Audio::DataWindow& output, DecodeOptions options);
|
||||||
};
|
};
|
||||||
|
|
||||||
class DtmfReceiver: public Receiver
|
class DtmfReceiver: public Receiver
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ void SingleAudioStream::copyPcmTo(Audio::DataWindow& output, int needed)
|
|||||||
{
|
{
|
||||||
while (output.filled() < needed)
|
while (output.filled() < needed)
|
||||||
{
|
{
|
||||||
if (mReceiver.getAudio(output) != AudioReceiver::DecodeResult_Ok)
|
if (mReceiver.getAudio(output, {}) != AudioReceiver::DecodeResult_Ok)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,10 @@ protected:
|
|||||||
|
|
||||||
struct PacketLossEvent
|
struct PacketLossEvent
|
||||||
{
|
{
|
||||||
int mGap = 0;
|
// This is extended sequence numbers (not the raw uint16_t seqno)
|
||||||
|
uint32_t mStartSeqno = 0,
|
||||||
|
mEndSeqno = 0;
|
||||||
|
int mGap = 0;
|
||||||
std::chrono::microseconds mTimestamp;
|
std::chrono::microseconds mTimestamp;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user