- take the RTP packet duration from the payload (Opus ptime > 20 ms)

AudioReceiver assumed every packet of a variable-size codec lasts
frameTime(), i.e. the local 20 ms ptime. A 40 ms Opus packet then looked
like 20 ms of audio followed by a 20 ms gap: decodePacketTo() emitted
silence before every packet and the jitter buffer dropped about half of
them (335 of 605 decoded on a 20/40 ms call), so vq-core scored clean
audio at MOS ~1.3. Codec::packetTime() now returns the duration of a
payload; Opus reads it with opus_packet_get_nb_samples(), fixed-frame
codecs keep the old formula. With it the same call decodes 603 of 605.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LzU7iFc93X5jFATRKaoKiZ
This commit is contained in:
Dmytro Bogovych
2026-10-09 11:01:20 +02:00
co-authored by Claude Opus 5.5
parent 03c58c4572
commit f0a880c64d
4 changed files with 27 additions and 5 deletions
+7
View File
@@ -434,6 +434,13 @@ Codec::Info OpusCodec::info()
}; };
} }
int OpusCodec::packetTime(std::span<const uint8_t> payload)
{
int samples = payload.empty() ? 0
: opus_packet_get_nb_samples(payload.data(), (opus_int32)payload.size(), mSamplerate);
return samples > 0 ? samples * 1000 / mSamplerate : frameTime();
}
Codec::EncodeResult OpusCodec::encode(std::span<const uint8_t> input, std::span<uint8_t> output) Codec::EncodeResult OpusCodec::encode(std::span<const uint8_t> input, std::span<uint8_t> output)
{ {
// opus_encode() takes the frame size in samples per channel and the output // opus_encode() takes the frame size in samples per channel and the output
+2
View File
@@ -69,6 +69,8 @@ protected:
int mDecoderChannels = 0; int mDecoderChannels = 0;
public: public:
int packetTime(std::span<const uint8_t> payload) override;
struct Params struct Params
{ {
bool mUseDtx = false, mUseInbandFec = false, mStereo = false; bool mUseDtx = false, mUseInbandFec = false, mStereo = false;
+6 -5
View File
@@ -520,10 +520,8 @@ Codec* AudioReceiver::add(const std::shared_ptr<jrtplib::RTPPacket>& p)
if (!codec) if (!codec)
time_length = 10; time_length = 10;
else if (!codec->rtpLength())
time_length = codec->frameTime();
else else
time_length = lround(double(payloadLength) / codec->rtpLength() * codec->frameTime()); time_length = codec->packetTime(std::span{p->GetPayloadData(), (size_t)payloadLength});
if (codec) if (codec)
samplerate = codec->rtpClockRate(); // RTP timestamp units, not PCM rate (G.722) samplerate = codec->rtpClockRate(); // RTP timestamp units, not PCM rate (G.722)
@@ -794,8 +792,11 @@ AudioReceiver::DecodeResult AudioReceiver::decodePacketTo(Audio::DataWindow& out
mFrameCount = mCodec->rtpLength() ? rtp.GetPayloadLength() / mCodec->rtpLength() : 1; mFrameCount = mCodec->rtpLength() ? rtp.GetPayloadLength() / mCodec->rtpLength() : 1;
int frameLength = mCodec->rtpLength() ? mCodec->rtpLength() : (int)rtp.GetPayloadLength(); int frameLength = mCodec->rtpLength() ? mCodec->rtpLength() : (int)rtp.GetPayloadLength();
// Save last packet time length // Save last packet time length. Taken from the payload, not mFrameCount *
mLastPacketTimeLength = mFrameCount * mCodec->frameTime(); // frameTime(): an Opus packet of 40 or 60 ms is one "frame" here, and counting
// it as frameTime() (20 ms) made the gap check above emit silence before every
// packet and pushed the jitter buffer into discarding about half of them.
mLastPacketTimeLength = mCodec->packetTime(std::span{rtp.GetPayloadData(), payload_length});
// Decode // Decode
for (int i = 0; i < mFrameCount && !mCodecSettings.mSkipDecode; i++) for (int i = 0; i < mFrameCount && !mCodecSettings.mSkipDecode; i++)
+12
View File
@@ -6,6 +6,7 @@
#ifndef __MT_CODEC_H #ifndef __MT_CODEC_H
#define __MT_CODEC_H #define __MT_CODEC_H
#include <cmath>
#include <map> #include <map>
#include <span> #include <span>
@@ -73,6 +74,17 @@ public:
Audio::Format getAudioFormat() { return Audio::Format(this->info().mSamplerate, this->info().mChannels); } Audio::Format getAudioFormat() { return Audio::Format(this->info().mSamplerate, this->info().mChannels); }
// Audio duration of one RTP payload, in milliseconds. The default suits fixed-size
// frames (rtpLength() bytes per frameTime()); a codec whose packets carry a variable
// amount of audio (Opus: 2.5-120 ms, whatever the sender's ptime) reads it from the
// payload instead, as frameTime() is only the local ptime.
virtual int packetTime(std::span<const uint8_t> payload)
{
if (!rtpLength())
return frameTime();
return static_cast<int>(std::lround(double(payload.size()) / rtpLength() * frameTime()));
}
// Returns size of encoded data (RTP) in bytes // Returns size of encoded data (RTP) in bytes
struct EncodeResult struct EncodeResult
{ {