- G.722: decode as 16 kHz audio, keep the 8 kHz RTP clock separate
G722Codec::info() reported an 8 kHz sample rate for its 16 kHz output (640 bytes of PCM per 20 ms frame, read as 40 ms at 8 kHz). AudioReceiver therefore fetched packets at half the rate they arrived, and the high-water trim discarded about 44% of every G.722 stream: 109 of 250 packets per 5 s, with the buffer at 360 ms instead of 100 ms. The 8 kHz figure is right for one thing: RFC 3551 keeps G.722's RTP clock at 8000. Codec::Info already had mTimestampUnit for that. G.722 now reports mSamplerate 16000 and mTimestampUnit 1/8000, and Codec::rtpClockRate() gives the clock rate. The uses of samplerate() that mean RTP time now use it: - the jitter statistics in AudioReceiver::add; - the timestamp-gap-to-milliseconds conversion in AudioReceiver; - AudioStream's transmit timestamp unit and increment (numerically unchanged for every codec, including G.722). PCM uses stay on samplerate(), so AudioStream now also feeds the G.722 encoder 16 kHz audio instead of 8 kHz. The factory still advertises G722/8000 in SDP, as it must; only its backwards comment is fixed. AudioReceiver::infoFor() also applied a "G729A silence" rule to payload type 9, which is G.722; G.729 is 18. It now applies to PT 18. Other fixed-frame payloads are timed proportionally, as AudioReceiver::add already does, so a 10 ms G.722 packet (half a 160-byte frame) counts as 10 ms, not 20. Measured in vq-core with 16 replayed calls (NISQA speech; G.722 from ffmpeg and pjmedia, G.711 controls of the same speech; 3 runs each, all identical): - G.722 packet drops: 44% -> 0; buffer 100 ms like G.711; no underruns; - Sevana MOS of every G.722 call now equals its G.711 control, normal and loud (with the previous code three calls read 4.45 against 4.25); - DeadAir-01 0.68-0.89 -> 0.01-0.27 (G.711: 0.00-0.14); SilentCall within 0.01 of G.711 for the ffmpeg-encoded calls (was 0.15-0.17 higher); - AmpClipping on loud calls 0.004/0.001, as G.711; the 0.02-0.03 seen after the codec swap alone came from the dropped packets; - G.711 results, packet counts, loss, jitter and audio durations unchanged. The transmit path (AudioStream) builds but was not exercised; vq-core only receives. Needs the vq_net change that computes jitter with rtpClockRate(). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1132,13 +1132,15 @@ G722Codec::~G722Codec()
|
|||||||
|
|
||||||
Codec::Info G722Codec::info()
|
Codec::Info G722Codec::info()
|
||||||
{
|
{
|
||||||
// ToDo: double check the G722 calls - remember RFC has bug about samplerate
|
// G.722 decodes to 16 kHz (640 bytes of PCM per 20 ms frame), but RFC 3551 keeps its RTP
|
||||||
|
// clock at 8 kHz for historical reasons: the timestamp unit is 1/8000, not 1/samplerate.
|
||||||
return {.mName = G722_MIME_NAME,
|
return {.mName = G722_MIME_NAME,
|
||||||
.mSamplerate = 8000,
|
.mSamplerate = 16000,
|
||||||
.mChannels = 1,
|
.mChannels = 1,
|
||||||
.mPcmLength = 640,
|
.mPcmLength = 640,
|
||||||
.mFrameTime = 20,
|
.mFrameTime = 20,
|
||||||
.mRtpLength = 160};
|
.mRtpLength = 160,
|
||||||
|
.mTimestampUnit = 1.0f / 8000};
|
||||||
}
|
}
|
||||||
|
|
||||||
Codec::EncodeResult G722Codec::encode(std::span<const uint8_t> input, std::span<uint8_t> output)
|
Codec::EncodeResult G722Codec::encode(std::span<const uint8_t> input, std::span<uint8_t> output)
|
||||||
@@ -1186,7 +1188,8 @@ const char* G722Codec::G722Factory::name()
|
|||||||
|
|
||||||
int G722Codec::G722Factory::samplerate()
|
int G722Codec::G722Factory::samplerate()
|
||||||
{
|
{
|
||||||
// Although G722 uses 16000 as rate for timestamping RTP frames - in fact it is 8KHz codec. So return 8KHz here.
|
// The rate SDP advertises (rtpmap G722/8000) is the RTP clock, 8 kHz by RFC 3551, although the
|
||||||
|
// audio is 16 kHz; see G722Codec::info().
|
||||||
return 8000;
|
return 8000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -526,7 +526,7 @@ Codec* AudioReceiver::add(const std::shared_ptr<jrtplib::RTPPacket>& p)
|
|||||||
time_length = lround(double(payloadLength) / codec->rtpLength() * codec->frameTime());
|
time_length = lround(double(payloadLength) / codec->rtpLength() * codec->frameTime());
|
||||||
|
|
||||||
if (codec)
|
if (codec)
|
||||||
samplerate = codec->samplerate();
|
samplerate = codec->rtpClockRate(); // RTP timestamp units, not PCM rate (G.722)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process jitter anyway - can we decode payload or not
|
// Process jitter anyway - can we decode payload or not
|
||||||
@@ -724,7 +724,7 @@ AudioReceiver::DecodeResult AudioReceiver::decodePacketTo(Audio::DataWindow& out
|
|||||||
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->rtpClockRate() / 1000);
|
||||||
if (milliseconds > mLastPacketTimeLength)
|
if (milliseconds > mLastPacketTimeLength)
|
||||||
{
|
{
|
||||||
auto silenceLength = std::chrono::milliseconds(milliseconds - mLastPacketTimeLength);
|
auto silenceLength = std::chrono::milliseconds(milliseconds - mLastPacketTimeLength);
|
||||||
@@ -1157,11 +1157,16 @@ AudioReceiver::MediaInfo AudioReceiver::infoFor(jrtplib::RTPPacket& p)
|
|||||||
|
|
||||||
if (codec->rtpLength() != 0)
|
if (codec->rtpLength() != 0)
|
||||||
{
|
{
|
||||||
int frameCount = static_cast<int>(p.GetPayloadLength() / codec->rtpLength());
|
// A G.729 (PT 18) SID frame is shorter than a voice frame but stands for one; other
|
||||||
if (p.GetPayloadType() == 9 /*G729A silence*/ && p.GetPayloadLength() % codec->rtpLength())
|
// codecs scale with the payload (G.722 at 10 ms ptime carries half a 160-byte frame).
|
||||||
frameCount++;
|
if (p.GetPayloadType() == 18 && p.GetPayloadLength() % codec->rtpLength())
|
||||||
|
{
|
||||||
packetTime = std::chrono::milliseconds(frameCount * codec->frameTime());
|
int frameCount = static_cast<int>(p.GetPayloadLength() / codec->rtpLength()) + 1;
|
||||||
|
packetTime = std::chrono::milliseconds(frameCount * codec->frameTime());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
packetTime = std::chrono::milliseconds(
|
||||||
|
lround(double(p.GetPayloadLength()) / codec->rtpLength() * codec->frameTime()));
|
||||||
}
|
}
|
||||||
else if (typeid(*codec) == typeid(OpusCodec))
|
else if (typeid(*codec) == typeid(OpusCodec))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ void AudioStream::setTransmittingCodec(Codec::Factory& factory, int payloadType)
|
|||||||
mTransmittingCodec = factory.create();
|
mTransmittingCodec = factory.create();
|
||||||
mTransmittingPayloadType = payloadType;
|
mTransmittingPayloadType = payloadType;
|
||||||
if (mRtpSession.IsActive())
|
if (mRtpSession.IsActive())
|
||||||
mRtpSession.SetTimestampUnit(1.0 / mTransmittingCodec->samplerate());
|
mRtpSession.SetTimestampUnit(mTransmittingCodec->timestampUnit());
|
||||||
}
|
}
|
||||||
|
|
||||||
PCodec AudioStream::transmittingCodec()
|
PCodec AudioStream::transmittingCodec()
|
||||||
@@ -241,7 +241,7 @@ void AudioStream::addData(const void* buffer, int bytes)
|
|||||||
ICELogMedia(<< "Sending RTP packet pt = " << mTransmittingPayloadType
|
ICELogMedia(<< "Sending RTP packet pt = " << mTransmittingPayloadType
|
||||||
<< ", plength = " << (int)mEncodedAudio.size() << " to ");
|
<< ", plength = " << (int)mEncodedAudio.size() << " to ");
|
||||||
mRtpSession.SendPacketEx(mEncodedAudio.data(), mEncodedAudio.size(), mTransmittingPayloadType, false,
|
mRtpSession.SendPacketEx(mEncodedAudio.data(), mEncodedAudio.size(), mTransmittingPayloadType, false,
|
||||||
packetTime * codec->samplerate() / 1000, 0, nullptr, 0);
|
packetTime * codec->rtpClockRate() / 1000, 0, nullptr, 0);
|
||||||
mEncodedAudio.clear();
|
mEncodedAudio.clear();
|
||||||
encodedTime = 0;
|
encodedTime = 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,6 +66,10 @@ public:
|
|||||||
int frameTime() { return info().mFrameTime; }
|
int frameTime() { return info().mFrameTime; }
|
||||||
std::string name() { return info().mName; }
|
std::string name() { return info().mName; }
|
||||||
float timestampUnit() { return info().mTimestampUnit == 0.0f ? 1.0f / info().mSamplerate : info().mTimestampUnit; }
|
float timestampUnit() { return info().mTimestampUnit == 0.0f ? 1.0f / info().mSamplerate : info().mTimestampUnit; }
|
||||||
|
// RTP clock rate in Hz, the unit of RTP timestamps. Equals samplerate() except for codecs
|
||||||
|
// whose RTP clock differs from the decoded audio: G.722 decodes to 16 kHz but its RTP clock
|
||||||
|
// is 8 kHz (RFC 3551). Use it for timestamp arithmetic and jitter, samplerate() for PCM.
|
||||||
|
int rtpClockRate() { return static_cast<int>(1.0f / timestampUnit() + 0.5f); }
|
||||||
|
|
||||||
Audio::Format getAudioFormat() { return Audio::Format(this->info().mSamplerate, this->info().mChannels); }
|
Audio::Format getAudioFormat() { return Audio::Format(this->info().mSamplerate, this->info().mChannels); }
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user