Commit 4f924efab5 for ffmpeg
commit 4f924efab554e3b85ef2c141140ffba0ed3bbcc2
Author: Patrick Domack <patrickdk@patrickdk.com>
Date: Sun Nov 1 04:56:41 2026 -0500
avcodec/g723_1dec: saturate the synthesis filter like the reference
The ITU reference runs the LPC synthesis filter in 32 bits and
saturates the sum after every product (DS2-Anywhere SPEC.md section
4.7). FFmpeg uses ff_celp_lp_synthesis_filter(), which sums with
wraparound and clips once at the end. The two agree until a partial
sum overflows. On loud signals with large LPC coefficients that
happens, and FFmpeg's output can then be off by the whole 16-bit
range. This has been so since the decoder was added in 2011.
Give the decoder its own synthesis filter that does what the reference
does: the input times 8192, a saturating multiply-subtract for each
coefficient in the reference's order, a saturating shift by 2, then
rounding to 16 bits. ff_celp_lp_synthesis_filter() stays as it is for
the other decoders that use it.
I checked the cause on three short cuts from loud streams. A Python
model of the reference's filter reproduces the reference decoder
sample for sample, a model of FFmpeg's filter reproduces FFmpeg, and
the reference's sum saturates 17 to 50 times in each cut.
The test set for this and the next two commits is 371 streams of
random G.723.1 frames, 822343 frames in all: the 35 random streams of
the earlier commits, 238 with loud gains or many lost frames, 90 that
start or lose frames in unusual ways, and a few short cuts. Over them,
the frames that differ from the reference drop from 61 to 0 with the
postfilter off and from 263 to 145 with it on.
saturation.tco holds 48 frames in 961 bytes, loud enough that the
synthesis filter and the formant postfilter saturate, with a
reflection coefficient that rounds differently at frame 20. Before the
G.723.1 fixes, 25 of its 48 frames differ from the reference.
Frame 42 of saturation.tco is a loud 5.3 kbit/s frame. With the
postfilter off and every G.723.1 fix but this one, sample 224 is 32767
where the reference gives 25516, the frame differs by up to 65535, and
6 frames differ.
fate-g723_1-dec-12 decodes saturation.tco with the postfilter off. Its
ref matches the DS2-Anywhere G.723.1 decoder frame for frame. It fails
with this fix or the Annex A fix left out. fate-g723_1-dec-1 to -8
pass unchanged.
Upload the sample to fate-suite as g723_1/saturation.tco.
Signed-off-by: Patrick Domack <patrickdk@patrickdk.com>
diff --git a/libavcodec/g723_1dec.c b/libavcodec/g723_1dec.c
index 845af6a628..b230b6f1c2 100644
--- a/libavcodec/g723_1dec.c
+++ b/libavcodec/g723_1dec.c
@@ -32,7 +32,6 @@
#define BITSTREAM_READER_LE
#include "acelp_vectors.h"
#include "avcodec.h"
-#include "celp_filters.h"
#include "codec_internal.h"
#include "decode.h"
#include "get_bits.h"
@@ -931,6 +930,20 @@ static void generate_noise(G723_1_ChannelContext *p)
PITCH_MAX * sizeof(*p->excitation));
}
+static void lp_synthesis_filter(int16_t *out, const int16_t *lpc,
+ const int16_t *in)
+{
+ for (int n = 0; n < SUBFRAME_LEN; n++) {
+ int sum = in[n] * (1 << 13);
+
+ for (int i = 0; i < LPC_ORDER; i++)
+ sum = av_sat_dsub32(sum, lpc[i] * out[n - 1 - i]);
+
+ sum = av_clipl_int32((int64_t)sum * 4);
+ out[n] = av_sat_add32(sum, 1 << 15) >> 16;
+ }
+}
+
static int g723_1_decode_frame(AVCodecContext *avctx, AVFrame *frame,
int *got_frame_ptr, AVPacket *avpkt)
{
@@ -1087,9 +1100,7 @@ static int g723_1_decode_frame(AVCodecContext *avctx, AVFrame *frame,
memcpy(p->audio, p->synth_mem, LPC_ORDER * sizeof(*p->audio));
for (i = LPC_ORDER, j = 0; j < SUBFRAMES; i += SUBFRAME_LEN, j++)
- ff_celp_lp_synthesis_filter(p->audio + i, &lpc[j * LPC_ORDER],
- audio + i, SUBFRAME_LEN, LPC_ORDER,
- 0, 1, 1 << 12);
+ lp_synthesis_filter(p->audio + i, &lpc[j * LPC_ORDER], audio + i);
memcpy(p->synth_mem, p->audio + FRAME_LEN, LPC_ORDER * sizeof(*p->audio));
if (s->postfilter) {
diff --git a/tests/fate/voice.mak b/tests/fate/voice.mak
index d5c09c2daf..e71e95690c 100644
--- a/tests/fate/voice.mak
+++ b/tests/fate/voice.mak
@@ -53,6 +53,9 @@ fate-g723_1-dec-9: CMD = framecrc -postfilter 1 -i $(TARGET_SAMPLES)/g723_1/cng-
FATE_G723_1 += fate-g723_1-dec-10
fate-g723_1-dec-10: CMD = framecrc -postfilter 0 -i $(TARGET_SAMPLES)/g723_1/cng-erasures.tco -af aresample
+FATE_G723_1 += fate-g723_1-dec-12
+fate-g723_1-dec-12: CMD = framecrc -postfilter 0 -i $(TARGET_SAMPLES)/g723_1/saturation.tco -af aresample
+
FATE_G723_1 += fate-g723_1-dec-13
fate-g723_1-dec-13: CMD = framecrc -postfilter 1 -i $(TARGET_SAMPLES)/g723_1/lost-start.tco -af aresample
diff --git a/tests/ref/fate/g723_1-dec-12 b/tests/ref/fate/g723_1-dec-12
new file mode 100644
index 0000000000..0c24561525
--- /dev/null
+++ b/tests/ref/fate/g723_1-dec-12
@@ -0,0 +1,53 @@
+#tb 0: 1/8000
+#media_type 0: audio
+#codec_id 0: pcm_s16le
+#sample_rate 0: 8000
+#channel_layout_name 0: mono
+0, 0, 0, 240, 480, 0x01b2e49d
+0, 240, 240, 240, 480, 0xdc83f49b
+0, 480, 480, 240, 480, 0xdf12efa2
+0, 720, 720, 240, 480, 0x1b4e050f
+0, 960, 960, 240, 480, 0x3939f6c5
+0, 1200, 1200, 240, 480, 0xd66def25
+0, 1440, 1440, 240, 480, 0xdfdbe8ed
+0, 1680, 1680, 240, 480, 0x5d60f777
+0, 1920, 1920, 240, 480, 0xbcc9ed22
+0, 2160, 2160, 240, 480, 0x0590ef0a
+0, 2400, 2400, 240, 480, 0x8ef0e897
+0, 2640, 2640, 240, 480, 0x0913d866
+0, 2880, 2880, 240, 480, 0xb51cded3
+0, 3120, 3120, 240, 480, 0x4e20e45a
+0, 3360, 3360, 240, 480, 0xa2afebcf
+0, 3600, 3600, 240, 480, 0xcdfef505
+0, 3840, 3840, 240, 480, 0xd99ae7c7
+0, 4080, 4080, 240, 480, 0x9072f588
+0, 4320, 4320, 240, 480, 0x627ae859
+0, 4560, 4560, 240, 480, 0x2a54e445
+0, 4800, 4800, 240, 480, 0xc903f0bd
+0, 5040, 5040, 240, 480, 0xcb6cfcba
+0, 5280, 5280, 240, 480, 0x8257f79c
+0, 5520, 5520, 240, 480, 0xb4ade802
+0, 5760, 5760, 240, 480, 0x0242f960
+0, 6000, 6000, 240, 480, 0xd6f9ede7
+0, 6240, 6240, 240, 480, 0x87bee16b
+0, 6480, 6480, 240, 480, 0x0326eeb0
+0, 6720, 6720, 240, 480, 0x8debf274
+0, 6960, 6960, 240, 480, 0x350ef634
+0, 7200, 7200, 240, 480, 0x7bd20d93
+0, 7440, 7440, 240, 480, 0x010cd501
+0, 7680, 7680, 240, 480, 0x5fe1d1a3
+0, 7920, 7920, 240, 480, 0x148f1e5d
+0, 8160, 8160, 240, 480, 0x0d0c1340
+0, 8400, 8400, 240, 480, 0xf495db89
+0, 8640, 8640, 240, 480, 0x985ddd43
+0, 8880, 8880, 240, 480, 0xbbb9e18d
+0, 9120, 9120, 240, 480, 0xe11dde0a
+0, 9360, 9360, 240, 480, 0x87cef472
+0, 9600, 9600, 240, 480, 0x4c0fe75b
+0, 9840, 9840, 240, 480, 0x46d5e56f
+0, 10080, 10080, 240, 480, 0xdce2f488
+0, 10320, 10320, 240, 480, 0x4eddf50d
+0, 10560, 10560, 240, 480, 0x19a2f38d
+0, 10800, 10800, 240, 480, 0x4e29ee4d
+0, 11040, 11040, 240, 480, 0xd66ee964
+0, 11280, 11280, 240, 480, 0xc9aeefb1