- 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:
Dmytro Bogovych
2026-09-21 18:03:58 +02:00
parent c8d9bad845
commit 03c58c4572
4 changed files with 25 additions and 13 deletions
+12 -7
View File
@@ -526,7 +526,7 @@ Codec* AudioReceiver::add(const std::shared_ptr<jrtplib::RTPPacket>& p)
time_length = lround(double(payloadLength) / codec->rtpLength() * codec->frameTime());
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
@@ -724,7 +724,7 @@ AudioReceiver::DecodeResult AudioReceiver::decodePacketTo(Audio::DataWindow& out
if (mLastPacketTimestamp && mLastPacketTimeLength && mCodec)
{
int units = rtp.GetTimestamp() - *mLastPacketTimestamp;
int milliseconds = units / (mCodec->samplerate() / 1000);
int milliseconds = units / (mCodec->rtpClockRate() / 1000);
if (milliseconds > mLastPacketTimeLength)
{
auto silenceLength = std::chrono::milliseconds(milliseconds - mLastPacketTimeLength);
@@ -1157,11 +1157,16 @@ AudioReceiver::MediaInfo AudioReceiver::infoFor(jrtplib::RTPPacket& p)
if (codec->rtpLength() != 0)
{
int frameCount = static_cast<int>(p.GetPayloadLength() / codec->rtpLength());
if (p.GetPayloadType() == 9 /*G729A silence*/ && p.GetPayloadLength() % codec->rtpLength())
frameCount++;
packetTime = std::chrono::milliseconds(frameCount * codec->frameTime());
// A G.729 (PT 18) SID frame is shorter than a voice frame but stands for one; other
// codecs scale with the payload (G.722 at 10 ms ptime carries half a 160-byte frame).
if (p.GetPayloadType() == 18 && p.GetPayloadLength() % codec->rtpLength())
{
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))
{