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>
The previous src/libs/g722 was spandsp's G.722 offered only under GPL-2 or
LGPL-2.1. That blocks shipping rtphone statically linked in a closed binary
(the freeware VQ for Asterisk engine) without a relink offer. Asterisk's
codecs/g722 is the same code: Steve Underwood placed his contributions in the
public domain there, and the CMU 1993 code under it is "completely
unrestricted" for any use. src/libs/g722/README.md records the provenance and
both statements.
It also fixes the decoded level. Asterisk's copy halves the encoder input and
doubles the decoder output, so streams from Asterisk and ffmpeg decode at
their original level. The old copy decoded them 6 dB too quiet (ITU-convention
streams from pjmedia 12 dB), and its encoder clipped input above -6 dBFS.
Local changes to Asterisk's files:
- the decoder saturates its output instead of casting. Asterisk's cast wraps:
259 samples in 11 s of speech peaking at -0.2 dBFS came out with the
opposite sign;
- g722.h includes <stdint.h> and maps __inline__ for MSVC (the removed
g722_inttypes.h used to).
Dropped: g722_bitstream.*, g722codec.c and three helper headers, none used.
The API is unchanged (g722_encode_init / g722_decode, same options).
Measured in vq-core (16 simultaneous replayed calls, NISQA speech):
- G.711 control calls score identically;
- normal-level G.722: SilentCall -0.09..-0.14, DeadAir-01 -0.06..-0.09, MOS
unchanged;
- loud G.722 with a clipped source now shows AmpClipping 0.02-0.03 and MOS
3.89/4.28 instead of 4.45: the codec overshoots on the clipped peaks at the
correct level, which the old -6 dB hid;
- results are identical across runs (the old decoder varied by up to 0.30 MOS).
G.722 still loses about 44% of its packets in AudioReceiver because
G722Codec::info() reports an 8 kHz sample rate for 16 kHz audio. That is fixed
separately.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Lets a host inject e.g. NullInputDevice before start() so the default
platform microphone is not constructed, avoiding the RECORD_AUDIO
permission on Android while leaving the real speaker output in place.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>