Commit 73d3b57be5 for ffmpeg
commit 73d3b57be52173bfebce3c9947129056733112c3
Author: Patrick Domack <patrickdk@patrickdk.com>
Date: Sun Nov 1 04:56:38 2026 -0500
avcodec/g723_1dec: follow Annex A when the first SID frame is lost
An untransmitted frame right after an active frame means the SID frame
that should have started the silence was lost. G.723.1 Annex A then
rebuilds the comfort noise level from the energy of the last good
active frame. FFmpeg's decoder has handled this case differently from
the ITU reference decoder since comfort noise was added in 04fc5c6bde
(2012), and n9.0.2 and master 81cf906e75 still do.
estimate_sid_gain() quantizes that energy to a 6-bit SID gain index.
It departs from the reference in three places:
- It multiplies by 273 and shifts by 16. The reference uses mul32x16
with 273, a shift by 15, so FFmpeg compares half the energy with the
segment bounds.
- In segments 0 and 1 it steps by 1 and 2 instead of 2 and 4, and its
index comes out 16 too low.
- Its final rounding compares the wrong distances, so it always rounds
up when the energy lies above the candidate.
The caller then stores the index as the gain. The reference decodes
it, as the SID frame path already does with sid_gain_to_lsp_index().
The reference also takes the comfort noise LSPs from the last good
active frame. FFmpeg keeps sid_lsp from the last SID frame, or the
initial DC vector when there was none.
Quantize with MULL2(), which gives the reference's mul32x16 result
here because nothing in this calculation saturates. Step through the
three segments by 2, 4 and 8 with their index offsets 0, 16 and 32,
round to the nearer index and decode it. Copy prev_lsp into sid_lsp at
the end of every good active frame.
The reference used here is the G.723.1 decoder of DS2-Anywhere,
vendor/dss-codec/src/codec/g723_1.rs in
https://github.com/Guillain-RDCDE/DS2-Anywhere. It matches the ITU
reference decoder on the 11 ITU test vectors and on 17 more cases
decoded with the ITU reference. One of those, a stream of random bits,
has 283 first-SID-lost frames. Sections 4.4.4, 4.10.2, 4.10.4 and
4.10.5 of its docs/g723-1/SPEC.md describe each step.
Its g723-mix.g723pk fixture holds 900 packets of every frame type.
Before this change, 520 of its frames differed from the reference with
the postfilter on and 529 with it off. Now none do. Over 35 random
streams of 3000 frames each, the frames that differ drop from 100308
to 10169 with the postfilter on and from 99885 to 9656 with it off.
The following commits fix the rest.
Frame 16 of cng-erasures.tco, a stream that a later commit adds to FATE,
is an untransmitted frame after a 6.3 kbit/s frame. With the postfilter
on and every G.723.1 fix but this one, its first sample is -3881 where
the reference gives -3458, the frame differs by up to 7448, and 79 of
the 97 frames differ.
fate-g723_1-dec-1 to -8 pass unchanged.
Signed-off-by: Patrick Domack <patrickdk@patrickdk.com>
diff --git a/libavcodec/g723_1dec.c b/libavcodec/g723_1dec.c
index 8406655e44..2b433e4552 100644
--- a/libavcodec/g723_1dec.c
+++ b/libavcodec/g723_1dec.c
@@ -761,7 +761,7 @@ static int estimate_sid_gain(G723_1_ChannelContext *p)
t = (p->sid_gain < 0) ? -1 : 0;
}else
t = p->sid_gain >> -shift;
- x = av_clipl_int32(t * (int64_t)cng_filt[0] >> 16);
+ x = MULL2(t, cng_filt[0]);
if (x >= cng_bseg[2])
return 0x3F;
@@ -773,14 +773,14 @@ static int estimate_sid_gain(G723_1_ChannelContext *p)
shift = 3;
seg = (x >= cng_bseg[0]);
}
- seg2 = FFMIN(seg, 3);
+ seg2 = FFMIN(seg + 1, 3);
val = 1 << shift;
val_add = val >> 1;
for (i = 0; i < shift; i++) {
t = seg * 32 + (val << seg2);
t *= t;
- if (x >= t)
+ if (x >= 2 * t)
val += val_add;
else
val -= val_add;
@@ -788,18 +788,18 @@ static int estimate_sid_gain(G723_1_ChannelContext *p)
}
t = seg * 32 + (val << seg2);
- y = t * t - x;
+ y = 2 * t * t - x;
if (y <= 0) {
t = seg * 32 + (val + 1 << seg2);
- t = t * t - x;
+ t = x - 2 * t * t;
val = (seg2 - 1) * 16 + val;
- if (t >= y)
+ if (y <= t)
val++;
} else {
t = seg * 32 + (val - 1 << seg2);
- t = t * t - x;
+ t = x - 2 * t * t;
val = (seg2 - 1) * 16 + val;
- if (t >= y)
+ if (y >= t)
val--;
}
@@ -1037,6 +1037,7 @@ static int g723_1_decode_frame(AVCodecContext *avctx, AVFrame *frame,
/* Save the excitation for the next frame */
memcpy(p->prev_excitation, p->excitation + FRAME_LEN,
PITCH_MAX * sizeof(*p->excitation));
+ memcpy(p->sid_lsp, p->prev_lsp, LPC_ORDER * sizeof(*p->sid_lsp));
} else {
p->interp_gain = (p->interp_gain * 3 + 2) >> 2;
if (p->erased_frames == 3) {
@@ -1065,7 +1066,7 @@ static int g723_1_decode_frame(AVCodecContext *avctx, AVFrame *frame,
p->sid_gain = sid_gain_to_lsp_index(p->subframe[0].amp_index);
ff_g723_1_inverse_quant(p->sid_lsp, p->prev_lsp, p->lsp_index, 0);
} else if (p->past_frame_type == ACTIVE_FRAME) {
- p->sid_gain = estimate_sid_gain(p);
+ p->sid_gain = sid_gain_to_lsp_index(estimate_sid_gain(p));
}
if (p->past_frame_type == ACTIVE_FRAME)