PR #25144 opened by Jun Zhao (mypopydev)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25144
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25144.patch

This series makes the native AMR decoders emit samples for 
discontinuous-transmission streams. 

AMR-WB dropped every SID frame (frame type 9) with AVERROR_PATCHWELCOME, 
shortening 
the output by 20 ms per frame. It now decodes the 40-bit SID and follows the TS 
26.173 DTX 
state machine in floating point: interpolate and mute between updates, 
synthesize noise at the 
decoded energy, and continue that noise on later frames that carry no data. 
Duration matches 
the reference decoder, and the noise level stays within 0.4 dB, or 1.5 dB on 
the SID frame that 
opens a silent period. The high-band gain is a constant approximation; FATE 
checks the frame 
count and the overall level. AMR-NB likewise rejected SID frames and frames 
with no data. It 
now decodes the 39-bit SID per TS 26.073 and TS 26.093, generates comfort 
noise, treats 
an FT=15 frame the same whether or not the quality bit is set, and conceals a 
lost speech frame
per TS 26.091 instead of emitting digital silence. Comfort-noise frames stay 
within 0.5 dB of the 
reference, concealed frames within 0.8 dB. Speech outside a silent period is 
unchanged. 

FATE covers duration, noise level, a SID frame after a loss, and the first 
speech frame after a muted silent period. Also fix #24922


>From 45cd962c0363208caa92f6322306cb98fd980d73 Mon Sep 17 00:00:00 2001
From: Jun Zhao <[email protected]>
Date: Fri, 9 Oct 2026 12:20:37 +0800
Subject: [PATCH 1/8] avcodec/amrwbdata: add the comfort noise ISF codebooks

Add the five noise ISF codebooks and their mean vector from
3GPP TS 26.173 (qisf_ns.tab) for the SID decoder. They match
qisf_ns.tab element by element.

Signed-off-by: Jun Zhao <[email protected]>
---
 libavcodec/amrwbdata.h | 300 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 300 insertions(+)

diff --git a/libavcodec/amrwbdata.h b/libavcodec/amrwbdata.h
index 95c0aaa37b..ba471f08df 100644
--- a/libavcodec/amrwbdata.h
+++ b/libavcodec/amrwbdata.h
@@ -42,6 +42,20 @@
 #define ENERGY_MEAN         30.0              ///< mean innovation energy (dB) 
in all modes
 #define PREEMPH_FAC         0.68              ///< factor used to de-emphasize 
synthesis
 
+/* Comfort noise (DTX) parameters, see 3GPP TS 26.192 */
+#define DTX_HIST_SIZE            8                    ///< frames kept for 
comfort noise parameter averaging
+#define DTX_MAX_EMPTY_THRESH     50                   ///< frames without SID 
update before muting
+#define DTX_ELAPSED_FRAMES_THRESH 30                  ///< frames before a DTX 
hangover is assumed
+#define DTX_HANG_CONST           7                    ///< DTX hangover length 
in frames
+#define ISF_DITH_GAP             (448.0 / 32768.0)    ///< minimum ISF gap 
used by comfort noise dithering
+#define ISF_DITH_FAC_LOW         (256.0 / 32768.0)    ///< lowest ISF 
dithering factor
+#define ISF_DITH_FAC_STEP        (2.0 / 32768.0)      ///< ISF dithering 
factor increment
+#define CN_DITH_GAIN_FAC         (150.0 / 512.0)      ///< energy dithering 
factor
+#define CN_LOG_EN_INIT           (3500.0 / 512.0)     ///< initial comfort 
noise log2 energy
+#define CN_LOG_EN_HIST_INIT      ((3500 >> 3) / 128.0) ///< initial value of 
the energy history
+#define CN_LOG_EN_MUTE_STEP      (1.0 / 8.0)          ///< log2 energy 
decrease per muted frame (-3/8 dB)
+#define CN_NOISE_SEED_INIT       21845                ///< initial seed of the 
comfort noise generator
+
 #define AMRWB_SFR_SIZE      64                ///< samples per subframe at 
12.8 kHz
 #define AMRWB_SFR_SIZE_16k  80                ///< samples per subframe at 16 
kHz
 #define AMRWB_P_DELAY_MAX   231               ///< maximum pitch delay value
@@ -1627,6 +1641,292 @@ static const int16_t isf_init[LP_ORDER] = {
   9216, 10240, 11264, 12288, 13312, 14336, 15360, 3840
 };
 
+/* Comfort noise ISF quantization tables below are taken from 3GPP TS 26.173
+ * (qisf_ns.tab). */
+
+/** Means of ISF vectors for background noise in Q15 */
+
+static const int16_t mean_isf_noise[16] = {
+   478, 1100, 2213, 3267, 4219, 5222, 6198, 7240,
+  8229, 9153,10098,11108,12144,13184,14165, 3803
+};
+
+/**
+ * Indexed tables for retrieval of quantized background noise ISF vectors
+ * in Q15 (split-by-5 VQ: 2+3+3+4+4 entries per set of indices)
+ */
+
+static const int16_t dico1_isf_noise[64][2] = {
+ { -269, -673},
+ { -222, -537},
+ { -233, -430},
+ { -138, -451},
+ { -212, -331},
+ { -192, -241},
+ {  -87, -231},
+ { -191, -128},
+ {  -70, -106},
+ { -164,   -6},
+ {   74, -179},
+ {   27,  -33},
+ { -102,   74},
+ { -162,  115},
+ {  -94,  172},
+ {   -6,  130},
+ { -143,  234},
+ {   14,  218},
+ {  -65,  270},
+ {   88,  182},
+ { -124,  341},
+ {  -44,  381},
+ {   38,  335},
+ {  117,  274},
+ { -112,  454},
+ {   74,  431},
+ {   -5,  488},
+ {  175,  384},
+ {  -83,  561},
+ {  122,  529},
+ {   21,  601},
+ {  229,  481},
+ {  231,  303},
+ {  226,  608},
+ {  300,  372},
+ {  210,  187},
+ {  306,  265},
+ {  328,  473},
+ {  382,  331},
+ {  371,  132},
+ {  139,   58},
+ {  365,   21},
+ {  250,  -82},
+ {  443,  218},
+ {  483,  110},
+ {  426,  415},
+ {  579,  222},
+ {  518,  333},
+ {  573,  448},
+ {  455,  529},
+ {  685,  329},
+ {  332,  580},
+ {  595,  593},
+ {  468,  645},
+ {  762,  517},
+ {  326,  709},
+ {  485,  793},
+ {  130,  684},
+ {  671,  737},
+ {  354,  876},
+ {   88,  806},
+ {  -65,  706},
+ {  -35, 1016},
+ {  266, 1123},
+};
+
+static const int16_t dico2_isf_noise[64][3] = {
+ { -824, -884, -949},
+ { -805, -456, -418},
+ { -442, -438, -541},
+ { -217, -578, -793},
+ { -168, -444, -582},
+ { -287, -492, -274},
+ { -552, -297, -300},
+ { -163, -333, -358},
+ { -370, -232, -232},
+ { -175, -358, -159},
+ { -381,  -21, -357},
+ { -184, -159, -162},
+ {  -53, -191, -280},
+ {   18, -267, -215},
+ { -138,   61, -283},
+ {   71,  -95, -294},
+ {   13, -156, -546},
+ {    0,  -83,  -79},
+ {   44,   97, -316},
+ {  178,  -52, -213},
+ {  222, -261, -422},
+ {  237, -118,  -44},
+ {  141,  145, -132},
+ {  363,   81, -287},
+ {  213,   65,   34},
+ { -107,   94,   -5},
+ {   91,  -29,  126},
+ { -355,   51,  -41},
+ { -219,  -76,  145},
+ {  -63,  100,  244},
+ { -719,   44,   27},
+ { -572, -124,  155},
+ { -423,  133,  315},
+ { -917,   71,  224},
+ { -268,  318,  131},
+ {  -93, -190,  420},
+ {  -97,  122,  491},
+ {  -79,  317,  355},
+ {  130,  100,  325},
+ {   86, -293,  210},
+ {  133,  258,  161},
+ {  176,  -73,  465},
+ {  195,  300,  384},
+ {  348,   22,  221},
+ {  376,  183,  409},
+ {  377,  286,  202},
+ {  242,  213,  659},
+ {  257,  565,  248},
+ {  344,  408,  -76},
+ {  405,  440,  509},
+ {  612,  385,  379},
+ {  536,  607,  216},
+ {  -56,  582,  192},
+ {  100,  517,  567},
+ { -365,  448,  445},
+ {  728,  347,   10},
+ {  505,  357,  759},
+ {  636,  582,  658},
+ {  335,  517,  852},
+ {  378,  809,  572},
+ { -195,  878,  829},
+ {  529,  707,  987},
+ {  918,  726,  392},
+ { 1250,  997, 1063},
+};
+
+static const int16_t dico3_isf_noise[64][3] = {
+ { -805, -838, -774},
+ { -522, -627, -828},
+ { -477, -486, -603},
+ { -295, -481, -634},
+ { -366, -384, -393},
+ { -186, -414, -396},
+ { -237, -394, -106},
+ { -252, -202, -275},
+ {  -61, -177, -442},
+ {  -84, -198, -199},
+ { -179, -125,  -31},
+ {  -72,  -47, -163},
+ { -298, -220,  215},
+ {  -64, -168,  251},
+ { -133,  156,  -59},
+ {  -30,   -2,  127},
+ {   54,   66,  -61},
+ { -233,   21,  251},
+ {  209,  -50,   32},
+ {   33,  194,  136},
+ { -117,  -18,  475},
+ {  202,   46,  309},
+ {  256,  185,   53},
+ {   35,  200,  390},
+ {  200,  263,  242},
+ { -216,  302,  294},
+ {  128,  358,    0},
+ {   19,  431,  287},
+ {  224,  447,  280},
+ {  367,  165,  213},
+ {  397,  314,  319},
+ {  383,  379,   75},
+ {  277,  325,  462},
+ {  394,  505,  334},
+ {  251,   98, -213},
+ {  450,  153,  448},
+ {  565,  226,   76},
+ {  470,  383,  502},
+ {  635,  390,  278},
+ {  237,  135,  620},
+ {  342,  401,  649},
+ {  331,  551,  518},
+ {  130,  418,  592},
+ {  531,  306,  737},
+ {  729,  389,  580},
+ {  497,  557,  699},
+ {  296,  383,  874},
+ {  283,  624,  759},
+ {  126,  622,  476},
+ {  559,  595,  472},
+ {  382,  770,  616},
+ {  719,  613,  745},
+ {  540,  639,  928},
+ {  517,  826,  801},
+ {  684,  811,  604},
+ {  752,  786,  857},
+ {  933,  661,  350},
+ {  694,  450, 1061},
+ {  562,  911, 1051},
+ {  824,  813, 1104},
+ {  758, 1047,  882},
+ { 1140,  917,  889},
+ { 1039, 1246, 1426},
+ { 1483, 1666, 1876},
+};
+
+static const int16_t dico4_isf_noise[32][4] = {
+ { -776, -854, -891, -920},
+ { -552, -610, -663, -741},
+ { -321, -370, -476, -565},
+ {  274, -160, -456,  201},
+ {  265,   67, -160, -306},
+ {   -8, -210,   79,  272},
+ {  163,  236,  307,  308},
+ {  578,  317,   64,  298},
+ {   -9,  197,  342,  620},
+ {  343,  232,  314,  622},
+ {  173,  149,  548,  527},
+ {  356,  370,  481,  376},
+ {  135,  444,  488,  556},
+ {  391,  471,  487,  653},
+ {  228,  424,  576,  835},
+ {  422,  372,  722,  682},
+ {  295,  673,  693,  635},
+ {  539,  596,  590,  449},
+ {  475,  618,  659,  818},
+ {  735,  517,  491,  673},
+ {  602,  346,  257,  877},
+ {  625,  635,  849,  720},
+ {  727,  818,  698,  595},
+ {  653,  481,  690, 1139},
+ {  814,  762,  704,  908},
+ {  507,  747,  898,  936},
+ {  848,  855,  924,  785},
+ {  646, 1037,  882,  795},
+ {  772,  845, 1024, 1151},
+ { 1133,  983,  818,  921},
+ {  940, 1068, 1252, 1302},
+ { 1588, 1767, 1718, 1513},
+};
+
+static const int16_t dico5_isf_noise[32][4] = {
+ { -810, -879, -945, -254},
+ {  248,  184,  671,  128},
+ {  288,  703,  918,   99},
+ {  658,  558,  662,  219},
+ {  552,  585,  910,  208},
+ {  559,  804,  759,  119},
+ {  606,  774,  921, -139},
+ {  782,  761,  748,  208},
+ {  756,  708,  983,   56},
+ {  544,  864, 1010,  152},
+ {  737,  698,  987,  299},
+ {  771,  924,  879,  103},
+ {  536,  785,  961,  405},
+ {  667,  916,  801,  328},
+ {  738,  705,  773,  439},
+ {  823,  871,  992,  355},
+ {  640, 1004, 1052,  369},
+ {  724,  822,  949,  597},
+ {  415,  655,  729,  482},
+ { 1009,  896,  793,  363},
+ {  908,  803,  687,  -25},
+ { 1016,  838, 1011,  189},
+ {  947, 1112,  942,  222},
+ {  914, 1049,  981,  527},
+ {  956,  987, 1011, -120},
+ {  781, 1049, 1121,   92},
+ { 1178, 1053,  884,   47},
+ { 1123, 1059, 1182,  118},
+ {  933,  972, 1277,  357},
+ { 1109,  918, 1101,  503},
+ { 1039, 1286, 1220,  317},
+ { 1351, 1207, 1010,  326},
+};
+
 /** ISF/ISP interpolation coefficients for each subframe */
 static const float isfp_inter[4] = { 0.45, 0.8, 0.96, 1.0 };
 
-- 
2.54.0


>From cd34875c9f224d0d5dd4c9888bc5cb659bc5b4d8 Mon Sep 17 00:00:00 2001
From: Jun Zhao <[email protected]>
Date: Fri, 9 Oct 2026 12:20:37 +0800
Subject: [PATCH 2/8] avcodec/amrwbdec: generate comfort noise for SID frames

A SID frame (frame type 9) returned AVERROR_PATCHWELCOME and was
dropped, so a DTX stream lost 20 ms per SID frame.

Decode the 40-bit SID and follow the TS 26.173 DTX state machine
in floating point: interpolate and mute between updates, then
synthesize noise at the decoded energy. After a silent period has
started, frames with no data keep producing that noise; until
comfort-noise parameters exist, the frame stays silent. Unused
frame types between SID and NO_DATA are treated as NO_DATA.

Duration matches the reference decoder. The noise level stays
within 0.4 dB, or 1.5 dB on the SID frame that starts a silent
period. Speech samples are unchanged. FATE checks the frame count
and the level.

Signed-off-by: Jun Zhao <[email protected]>
---
 libavcodec/amrwbdec.c         | 652 ++++++++++++++++++++++++++++++----
 tests/data/fate/amrwb-dtx.amr | Bin 0 -> 657 bytes
 tests/fate/amrwb.mak          |  13 +-
 3 files changed, 601 insertions(+), 64 deletions(-)
 create mode 100644 tests/data/fate/amrwb-dtx.amr

diff --git a/libavcodec/amrwbdec.c b/libavcodec/amrwbdec.c
index 3557b4709d..6979a91bf4 100644
--- a/libavcodec/amrwbdec.c
+++ b/libavcodec/amrwbdec.c
@@ -32,6 +32,7 @@
 #include "libavutil/lfg.h"
 
 #include "avcodec.h"
+#include "get_bits.h"
 #include "lsp.h"
 #include "celp_filters.h"
 #include "celp_math.h"
@@ -49,6 +50,35 @@
 #include "mips/amrwbdec_mips.h"
 #endif /* ARCH_MIPS */
 
+/**
+ * High band gain of the comfort noise relative to the low band one.
+ *
+ * This is a non-reference approximation for the high band excitation
+ * scaling, not a value ported from the reference decoder: the reference
+ * shapes much less noise into the high band but has no single constant
+ * counterpart to this figure.
+ */
+#define CN_HF_GAIN_FAC  0.25f
+
+/** Received frame types used by the comfort noise (DTX) state machine */
+enum RXFrameType {
+    RX_SPEECH_GOOD = 0,
+    RX_SPEECH_PROBABLY_DEGRADED,
+    RX_SPEECH_LOST,
+    RX_SPEECH_BAD,
+    RX_SID_FIRST,
+    RX_SID_UPDATE,
+    RX_SID_BAD,
+    RX_NO_DATA
+};
+
+/** States of the comfort noise (DTX) state machine */
+enum DTXState {
+    DTX_SPEECH = 0,
+    DTX,
+    DTX_MUTE
+};
+
 typedef struct AMRWBContext {
     AMRWBFrame                             frame; ///< AMRWB parameters 
decoded from bitstream
     enum Mode                        fr_cur_mode; ///< mode index of current 
frame
@@ -96,6 +126,28 @@ typedef struct AMRWBContext {
     CELPFContext                       celpf_ctx; ///< context for filters for 
CELP-based codecs
     CELPMContext                       celpm_ctx; ///< context for fixed point 
math operations
 
+    /* Comfort noise (DTX) state */
+    int   dtx_state;                              ///< current DTX state 
(SPEECH, DTX or DTX_MUTE)
+    int   since_last_sid;                         ///< frames since the last 
SID update
+    float sid_period_inv;                         ///< inverse of the SID 
update period
+    float cn_log_en;                              ///< comfort noise log2 
energy of the last SID
+    float cn_log_en_old;                          ///< comfort noise log2 
energy before the last SID
+    float cn_isf[LP_ORDER];                       ///< comfort noise ISF 
vector of the last SID
+    float cn_isf_old[LP_ORDER];                   ///< comfort noise ISF 
vector before the last SID
+    int16_t cn_seed;                              ///< comfort noise generator 
seed
+    int16_t cn_dither_seed;                       ///< comfort noise dithering 
seed
+    int   cn_dith;                                ///< comfort noise dithering 
flag from the last SID
+    float isf_hist[DTX_HIST_SIZE][LP_ORDER];      ///< ISF history used when a 
SID frame starts DTX
+    float log_en_hist[DTX_HIST_SIZE];             ///< energy history used 
when a SID frame starts DTX
+    int   hist_ptr;                               ///< next position to write 
in the DTX history
+    int   hangover_count;                         ///< remaining DTX hangover 
frames
+    int   dec_ana_elapsed;                        ///< frames since the last 
DTX hangover
+    int   hangover_added;                         ///< a DTX hangover was 
assumed for this frame
+    int   sid_frame;                              ///< the current frame 
carries CN parameters
+    int   valid_data;                             ///< the CN parameters of 
this frame may be used
+    int   data_updated;                           ///< CN parameters have been 
renewed at least once
+    int   vad_hist;                               ///< consecutive frames 
without voice activity
+
 } AMRWBContext;
 
 typedef struct AMRWBChannelsContext {
@@ -134,6 +186,24 @@ static av_cold int amrwb_decode_init(AVCodecContext *avctx)
         for (i = 0; i < 4; i++)
             ctx->prediction_error[i] = MIN_ENERGY;
 
+        /* Comfort noise state, matches the DTX decoder reset */
+        ctx->dtx_state       = DTX_SPEECH;
+        ctx->sid_period_inv  = 0.25f;
+        ctx->cn_log_en       = CN_LOG_EN_INIT;
+        ctx->cn_log_en_old   = CN_LOG_EN_INIT;
+        ctx->cn_seed         = CN_NOISE_SEED_INIT;
+        ctx->cn_dither_seed  = CN_NOISE_SEED_INIT;
+        ctx->hangover_count  = DTX_HANG_CONST;
+        ctx->dec_ana_elapsed = 32767;
+
+        for (i = 0; i < LP_ORDER; i++)
+            ctx->cn_isf[i] = ctx->cn_isf_old[i] = isf_init[i] * (1.0f / (1 << 
15));
+
+        for (i = 0; i < DTX_HIST_SIZE; i++) {
+            memcpy(ctx->isf_hist[i], ctx->cn_isf, LP_ORDER * 
sizeof(*ctx->cn_isf));
+            ctx->log_en_hist[i] = CN_LOG_EN_HIST_INIT;
+        }
+
         ff_acelp_filter_init(&ctx->acelpf_ctx);
         ff_acelp_vectors_init(&ctx->acelpv_ctx);
         ff_celp_filter_init(&ctx->celpf_ctx);
@@ -556,8 +626,8 @@ static void decode_fixed_vector(float *fixed_vector, const 
uint16_t *pulse_hi,
                            ((int) pulse_hi[i] << 11), 4, 1);
         break;
     default:
-        av_unreachable("Everything >= MODE_SID is impossible: MODE_SID is 
patchwelcome,"
-                       "> MODE_SID is invalid");
+        av_unreachable("SID and the unused frame types are handled before this 
"
+                       "function, so only speech modes reach it");
     }
 
     memset(fixed_vector, 0, sizeof(float) * AMRWB_SFR_SIZE);
@@ -1109,6 +1179,460 @@ static void update_sub_state(AMRWBContext *ctx)
             LP_ORDER_16k * sizeof(float));
 }
 
+/**
+ * Comfort noise (DTX) generation, see 3GPP TS 26.192.
+ *
+ * The implementation follows the decoder of the reference codec (3GPP
+ * TS 26.173, as shipped in opencore-amr) with the fixed point arithmetic
+ * replaced by floating point: ISF vectors and log2 energies are kept in
+ * the same units as in the speech path (0..0.5 normalized frequency and
+ * log2 of the excitation energy).
+ */
+
+/**
+ * Generate the next sample of the white noise used as comfort noise
+ * excitation. The seed wraps around like a 16 bit integer.
+ */
+static int16_t noise_gen_amrwb(int16_t *seed)
+{
+    *seed = (int16_t)(uint16_t)((int32_t) *seed * 31821 + 13849);
+
+    return *seed;
+}
+
+/**
+ * Decode the comfort noise parameters of a SID frame.
+ *
+ * The payload is 40 bits, of which 36 are used: five ISF indices
+ * (6, 6, 6, 5 and 5 bits), the log energy index (6 bits), the comfort noise
+ * dithering flag and a flag telling whether this is the first SID frame of a
+ * silent period.
+ *
+ * @param[in]     buf               Pointer to the frame payload (5 bytes)
+ * @param[out]    ind               The five ISF indices
+ * @param[out]    en_idx            Index of the logarithmic frame energy
+ *
+ * @return non zero if this is a SID update frame
+ */
+static int decode_sid_frame(AMRWBContext *ctx, const uint8_t *buf,
+                            int *ind, int *en_idx)
+{
+    GetBitContext gb;
+
+    init_get_bits8(&gb, buf, 5);
+
+    ind[0] = get_bits(&gb, 6);
+    ind[1] = get_bits(&gb, 6);
+    ind[2] = get_bits(&gb, 6);
+    ind[3] = get_bits(&gb, 5);
+    ind[4] = get_bits(&gb, 5);
+    *en_idx     = get_bits(&gb, 6);
+    ctx->cn_dith = get_bits1(&gb);
+
+    return get_bits1(&gb);
+}
+
+/**
+ * Decode the quantized background noise ISF vector of a SID frame.
+ *
+ * @param[in]     ind               The five quantization indices
+ * @param[out]    isf_q             Buffer for isf_q[LP_ORDER]
+ */
+static void decode_isf_noise(const int *ind, float *isf_q)
+{
+    int i;
+
+    isf_q[0] = dico1_isf_noise[ind[0]][0] * (1.0f / (1 << 15));
+    isf_q[1] = dico1_isf_noise[ind[0]][1] * (1.0f / (1 << 15));
+
+    for (i = 0; i < 3; i++) {
+        isf_q[i + 2] = dico2_isf_noise[ind[1]][i] * (1.0f / (1 << 15));
+        isf_q[i + 5] = dico3_isf_noise[ind[2]][i] * (1.0f / (1 << 15));
+    }
+
+    for (i = 0; i < 4; i++) {
+        isf_q[i +  8] = dico4_isf_noise[ind[3]][i] * (1.0f / (1 << 15));
+        isf_q[i + 12] = dico5_isf_noise[ind[4]][i] * (1.0f / (1 << 15));
+    }
+
+    for (i = 0; i < LP_ORDER; i++)
+        isf_q[i] += mean_isf_noise[i] * (1.0f / (1 << 15));
+
+    ff_set_min_dist_lsf(isf_q, MIN_ISF_SPACING, LP_ORDER - 1);
+}
+
+/**
+ * Draw a dithering value in [-1, 1] from the comfort noise generator.
+ *
+ * @param[in,out] ctx               The Context
+ */
+static float cn_dither_noise(AMRWBContext *ctx)
+{
+    return ((noise_gen_amrwb(&ctx->cn_dither_seed) >> 1) +
+            (noise_gen_amrwb(&ctx->cn_dither_seed) >> 1)) * (1.0f / 32768.0f);
+}
+
+/**
+ * Insert comfort noise dithering, used when the background noise is not
+ * stationary.
+ *
+ * @param[in,out] ctx               The Context
+ * @param[in,out] isf               ISF vector of the comfort noise
+ * @param[in,out] log_en            log2 energy of the comfort noise
+ */
+static void cn_dithering(AMRWBContext *ctx, float *isf, float *log_en)
+{
+    float fac;
+    int i;
+
+    /* Insert comfort noise dithering for energy parameter */
+    *log_en = FFMAX(*log_en + cn_dither_noise(ctx) * CN_DITH_GAIN_FAC, 0.0f);
+
+    /* Insert comfort noise dithering for spectral parameters (ISF vector) */
+    fac = ISF_DITH_FAC_LOW;
+    isf[0] = FFMAX(isf[0] + cn_dither_noise(ctx) * fac, MIN_ISF_SPACING);
+
+    for (i = 1; i < LP_ORDER - 1; i++) {
+        float cur;
+        fac += ISF_DITH_FAC_STEP;
+        cur = isf[i] + cn_dither_noise(ctx) * fac;
+        /* Make sure that the ISF spacing remains at least ISF_DITH_GAP */
+        isf[i] = FFMAX(cur, isf[i - 1] + ISF_DITH_GAP);
+    }
+
+    isf[LP_ORDER - 2] = FFMIN(isf[LP_ORDER - 2], 0.5f);
+}
+
+/**
+ * Store the ISF vector and the excitation energy of a speech frame so
+ * that the first SID frame of a silent period can reuse them.
+ *
+ * @param[in,out] ctx               The Context
+ * @param[in]     isf               ISF vector of the decoded frame
+ * @param[in]     exc_energy        Excitation energy of the decoded frame
+ */
+static void dtx_activity_update(AMRWBContext *ctx, const float *isf,
+                                float exc_energy)
+{
+    ctx->hist_ptr = (ctx->hist_ptr + 1) & (DTX_HIST_SIZE - 1);
+    memcpy(ctx->isf_hist[ctx->hist_ptr], isf, LP_ORDER * sizeof(*isf));
+    ctx->log_en_hist[ctx->hist_ptr] = log2f(FFMAX(exc_energy, 1.0f) /
+                                            (4 * AMRWB_SFR_SIZE));
+}
+
+/**
+ * Update the comfort noise state and generate the excitation of the
+ * comfort noise for this frame.
+ *
+ * @param[in,out] ctx               The Context
+ * @param[out]    cn_exc            Excitation for the whole frame (256 
samples)
+ * @param[in]     new_state         DTX state of this frame
+ * @param[in]     ind               ISF indices of the SID frame
+ * @param[in]     en_idx            Energy index of the SID frame
+ */
+static void dtx_dec(AMRWBContext *ctx, float *cn_exc, int new_state,
+                    const int *ind, int en_idx)
+{
+    float *isf = ctx->isf_cur;
+    float log_en, int_fac;
+    int i, j, period;
+
+    /* SID frames that start a silent period reuse the noise parameters
+       of the speech frames preceding them. */
+    if (ctx->hangover_added && ctx->sid_frame) {
+        int ptr = (ctx->hist_ptr + 1) & (DTX_HIST_SIZE - 1);
+
+        memcpy(ctx->isf_hist[ptr], ctx->isf_hist[ctx->hist_ptr],
+               sizeof(ctx->isf_hist[0]));
+        ctx->log_en_hist[ptr] = ctx->log_en_hist[ctx->hist_ptr];
+
+        ctx->cn_log_en = 0.0f;
+        for (i = 0; i < DTX_HIST_SIZE; i++)
+            ctx->cn_log_en += ctx->log_en_hist[i];
+        /* the reference decoder keeps the average in Q9 and adds 2 so that
+           the value stays positive, it is subtracted again when the level
+           of the excitation is computed */
+        ctx->cn_log_en = ctx->cn_log_en / DTX_HIST_SIZE + 2.0f;
+        ctx->cn_log_en = FFMAX(ctx->cn_log_en, 0.0f);
+
+        for (j = 0; j < LP_ORDER; j++) {
+            float acc = 0.0f;
+            for (i = 0; i < DTX_HIST_SIZE; i++)
+                acc += ctx->isf_hist[i][j];
+            ctx->cn_isf[j] = acc / DTX_HIST_SIZE;
+        }
+    }
+
+    if (ctx->sid_frame) {
+        memcpy(ctx->cn_isf_old, ctx->cn_isf, LP_ORDER * sizeof(*ctx->cn_isf));
+        ctx->cn_log_en_old = ctx->cn_log_en;
+
+        if (ctx->valid_data) {
+            period = FFMIN(ctx->since_last_sid, 32);
+            ctx->sid_period_inv = period >= 2 ? 1.0f / period : 0.5f;
+
+            decode_isf_noise(ind, ctx->cn_isf);
+            ctx->cn_log_en = en_idx * (1.0f / 2.625f);
+
+            /* no interpolation at startup or right after speech */
+            if (!ctx->data_updated || ctx->dtx_state == DTX_SPEECH) {
+                memcpy(ctx->cn_isf_old, ctx->cn_isf,
+                       LP_ORDER * sizeof(*ctx->cn_isf));
+                ctx->cn_log_en_old = ctx->cn_log_en;
+            }
+        }
+    }
+
+    /* A frame carrying new parameters ends the interpolation, so the
+       parameters of this very frame are the ones used below. The counter is
+       reset again at the end of this function. */
+    if (ctx->sid_frame && ctx->valid_data)
+        ctx->since_last_sid = 0;
+
+    /* Interpolate the comfort noise parameters since the last update */
+    int_fac = FFMIN(ctx->since_last_sid * ctx->sid_period_inv, 1.0f);
+
+    log_en = int_fac * ctx->cn_log_en + (1.0f - int_fac) * ctx->cn_log_en_old;
+    for (j = 0; j < LP_ORDER; j++)
+        isf[j] = int_fac * ctx->cn_isf[j] + (1.0f - int_fac) * 
ctx->cn_isf_old[j];
+
+    if (ctx->cn_dith)
+        cn_dithering(ctx, isf, &log_en);
+
+    /* Generate the excitation and scale it to the wanted energy */
+    for (i = 0; i < 4 * AMRWB_SFR_SIZE; i++)
+        cn_exc[i] = noise_gen_amrwb(&ctx->cn_seed) >> 4;
+
+    /* the excitation carries the energy of the decoded noise parameters */
+    ff_scale_vector_to_given_sum_of_squares(cn_exc, cn_exc,
+                                            (4 * AMRWB_SFR_SIZE) *
+                                            exp2f(log_en - 2.0f),
+                                            4 * AMRWB_SFR_SIZE);
+
+    if (new_state == DTX_MUTE) {
+        /* Mute the comfort noise, nothing has been updated for a while */
+        period = ctx->since_last_sid;
+        if (period > 32)
+            period = 32;
+        if (period <= 0)
+            period = 8;
+        ctx->sid_period_inv = 1.0f / period;
+        ctx->since_last_sid = 0;
+        ctx->cn_log_en_old  = ctx->cn_log_en;
+        ctx->cn_log_en     -= CN_LOG_EN_MUTE_STEP;
+    }
+
+    if (ctx->sid_frame &&
+        (ctx->valid_data || (!ctx->valid_data && ctx->hangover_added))) {
+        ctx->since_last_sid = 0;
+        ctx->data_updated   = 1;
+    }
+}
+
+/**
+ * Update the comfort noise state machine for a received frame.
+ *
+ * @param[in,out] ctx               The Context
+ * @param[in]     frame_type        Type of the received frame
+ *
+ * @return the DTX state to decode this frame with
+ */
+static int dtx_handler(AMRWBContext *ctx, int frame_type)
+{
+    int new_state, enc_state;
+
+    if (frame_type == RX_SID_FIRST  || frame_type == RX_SID_UPDATE ||
+        frame_type == RX_SID_BAD    ||
+        ((ctx->dtx_state == DTX || ctx->dtx_state == DTX_MUTE) &&
+         (frame_type == RX_NO_DATA    || frame_type == RX_SPEECH_BAD ||
+          frame_type == RX_SPEECH_LOST))) {
+        new_state = DTX;
+
+        /* stay muted for these input types */
+        if (ctx->dtx_state == DTX_MUTE &&
+            (frame_type == RX_SID_BAD   || frame_type == RX_SID_FIRST  ||
+             frame_type == RX_SPEECH_LOST || frame_type == RX_NO_DATA))
+            new_state = DTX_MUTE;
+
+        ctx->since_last_sid = FFMIN(ctx->since_last_sid + 1, 32767);
+
+        /* SID updates are handled separately to avoid muting a late one */
+        if (frame_type != RX_SID_UPDATE &&
+            ctx->since_last_sid > DTX_MAX_EMPTY_THRESH)
+            new_state = DTX_MUTE;
+    } else {
+        new_state = DTX_SPEECH;
+        ctx->since_last_sid = 0;
+    }
+
+    if (!ctx->data_updated && frame_type == RX_SID_UPDATE)
+        ctx->dec_ana_elapsed = 0;
+
+    ctx->dec_ana_elapsed = FFMIN(ctx->dec_ana_elapsed + 1, 32767);
+    ctx->hangover_added  = 0;
+
+    if (frame_type == RX_SID_FIRST || frame_type == RX_SID_UPDATE ||
+        frame_type == RX_SID_BAD   ||
+        (frame_type == RX_NO_DATA && (ctx->dtx_state != DTX_SPEECH ||
+                                      ctx->vad_hist >= DTX_HANG_CONST)))
+        enc_state = DTX;
+    else
+        enc_state = DTX_SPEECH;
+
+    if (enc_state == DTX_SPEECH) {
+        ctx->hangover_count = DTX_HANG_CONST;
+    } else if (ctx->dec_ana_elapsed > DTX_ELAPSED_FRAMES_THRESH) {
+        ctx->hangover_added  = 1;
+        ctx->dec_ana_elapsed = 0;
+        ctx->hangover_count  = 0;
+    } else if (!ctx->hangover_count) {
+        ctx->dec_ana_elapsed = 0;
+    } else {
+        ctx->hangover_count--;
+    }
+
+    if (new_state != DTX_SPEECH) {
+        ctx->sid_frame  = 0;
+        ctx->valid_data = 0;
+
+        if (frame_type == RX_SID_FIRST) {
+            ctx->sid_frame = 1;
+        } else if (frame_type == RX_SID_UPDATE) {
+            ctx->sid_frame  = 1;
+            ctx->valid_data = 1;
+        } else if (frame_type == RX_SID_BAD) {
+            ctx->sid_frame      = 1;
+            ctx->hangover_added = 0;
+        }
+    }
+
+    return new_state;
+}
+
+/**
+ * Reset the speech decoder state, it is not used during a silent period.
+ */
+static void cn_reset_speech_state(AMRWBContext *ctx)
+{
+    memset(ctx->excitation_buf, 0, sizeof(ctx->excitation_buf));
+    memset(ctx->isf_q_past, 0, sizeof(ctx->isf_q_past));
+
+    ctx->first_frame      = 1;
+    ctx->prev_tr_gain     = 0.0f;
+    ctx->prev_ir_filter_nr = 0;
+    ctx->tilt_coef        = 0.0f;
+}
+
+/**
+ * Post-process one synthesized subframe, generate the high frequency band
+ * and store the result at 16 kHz.
+ *
+ * @param[in,out] ctx               The Context
+ * @param[out]    sub_buf           Output samples of the subframe at 16 kHz
+ * @param[in]     synth_exc         Excitation the subframe was built from
+ * @param[in]     sub               Subframe index
+ * @param[in]     hb_idx            High-band gain index (23k85 mode only)
+ * @param[in]     vad               Voice activity flag of the frame
+ * @param[in]     hf_gain           Scaling of the high band noise
+ */
+static void subframe_output(AMRWBContext *ctx, float *sub_buf,
+                            const float *synth_exc, int sub, uint16_t hb_idx,
+                            uint8_t vad, float hf_gain)
+{
+    float hb_exc[AMRWB_SFR_SIZE_16k];
+    float hb_samples[AMRWB_SFR_SIZE_16k];
+    float hb_gain;
+    int i;
+
+    /* Synthesis speech post-processing */
+    de_emphasis(&ctx->samples_up[UPS_MEM_SIZE], &ctx->samples_az[LP_ORDER],
+                PREEMPH_FAC, ctx->demph_mem);
+
+    
ctx->acelpf_ctx.acelp_apply_order_2_transfer_function(&ctx->samples_up[UPS_MEM_SIZE],
+                                                          
&ctx->samples_up[UPS_MEM_SIZE], hpf_zeros, hpf_31_poles,
+                                                          hpf_31_gain, 
ctx->hpf_31_mem, AMRWB_SFR_SIZE);
+
+    upsample_5_4(sub_buf, &ctx->samples_up[UPS_FIR_SIZE],
+                 AMRWB_SFR_SIZE_16k, &ctx->celpm_ctx);
+
+    /* High frequency band (6.4 - 7.0 kHz) generation part */
+    ctx->acelpf_ctx.acelp_apply_order_2_transfer_function(hb_samples,
+                                                          
&ctx->samples_up[UPS_MEM_SIZE], hpf_zeros, hpf_400_poles,
+                                                          hpf_400_gain, 
ctx->hpf_400_mem, AMRWB_SFR_SIZE);
+
+    hb_gain = find_hb_gain(ctx, hb_samples, hb_idx, vad);
+
+    scaled_hb_excitation(ctx, hb_exc, synth_exc, hb_gain * hf_gain);
+
+    hb_synthesis(ctx, sub, &ctx->samples_hb[LP_ORDER_16k],
+                 hb_exc, ctx->isf_cur, ctx->isf_past_final);
+
+    /* High-band post-processing filters */
+    hb_fir_filter(hb_samples, bpf_6_7_coef, ctx->bpf_6_7_mem,
+                  &ctx->samples_hb[LP_ORDER_16k]);
+
+    if (ctx->fr_cur_mode == MODE_23k85)
+        hb_fir_filter(hb_samples, lpf_7_coef, ctx->lpf_7_mem,
+                      hb_samples);
+
+    /* Add the low and high frequency bands */
+    for (i = 0; i < AMRWB_SFR_SIZE_16k; i++)
+        sub_buf[i] = (sub_buf[i] + hb_samples[i]) * (1.0f / (1 << 15));
+
+    /* Update buffers and history */
+    update_sub_state(ctx);
+}
+
+/**
+ * Decode one frame of comfort noise into the output buffer.
+ *
+ * @param[in,out] ctx               The Context
+ * @param[in]     new_state         DTX state of this frame
+ * @param[in]     ind               ISF indices of the SID frame
+ * @param[in]     en_idx            Energy index of the SID frame
+ * @param[out]    buf_out           Output buffer for one frame at 16 kHz
+ */
+static void cn_frame(AMRWBContext *ctx, int new_state, const int *ind,
+                     int en_idx, float *buf_out)
+{
+    float cn_exc[4 * AMRWB_SFR_SIZE];
+    float synth_exc[AMRWB_SFR_SIZE];
+    int sub;
+
+    dtx_dec(ctx, cn_exc, new_state, ind, en_idx);
+
+    ff_set_min_dist_lsf(ctx->isf_cur, MIN_ISF_SPACING, LP_ORDER - 1);
+
+    ctx->isf_cur[LP_ORDER - 1] *= 2.0;
+    ff_acelp_lsf2lspd(ctx->isp[3], ctx->isf_cur, LP_ORDER);
+    ff_amrwb_lsp2lpc(ctx->isp[3], ctx->lp_coef[0], LP_ORDER);
+    for (sub = 1; sub < 4; sub++)
+        memcpy(ctx->lp_coef[sub], ctx->lp_coef[0], sizeof(ctx->lp_coef[0]));
+
+    /* the reference decoder uses one set of CN parameters per frame and
+       no adaptive codebook contribution */
+    ctx->pitch_gain[0] = 0.0f;
+
+    for (sub = 0; sub < 4; sub++) {
+        float *sub_buf = buf_out + sub * AMRWB_SFR_SIZE_16k;
+
+        memcpy(ctx->fixed_vector, &cn_exc[sub * AMRWB_SFR_SIZE],
+               AMRWB_SFR_SIZE * sizeof(*ctx->fixed_vector));
+
+        synthesis(ctx, ctx->lp_coef[sub], synth_exc, 1.0f,
+                  ctx->fixed_vector, &ctx->samples_az[LP_ORDER]);
+
+        subframe_output(ctx, sub_buf, synth_exc, sub, 0, !ctx->vad_hist,
+                        CN_HF_GAIN_FAC);
+    }
+
+    memcpy(ctx->isp_sub4_past, ctx->isp[3], LP_ORDER * sizeof(ctx->isp[3][0]));
+    memcpy(ctx->isf_past_final, ctx->isf_cur, LP_ORDER * 
sizeof(*ctx->isf_cur));
+
+    cn_reset_speech_state(ctx);
+    ctx->dtx_state = new_state;
+}
+
 static int amrwb_decode_frame(AVCodecContext *avctx, AVFrame *frame,
                               int *got_frame_ptr, AVPacket *avpkt)
 {
@@ -1125,16 +1649,16 @@ static int amrwb_decode_frame(AVCodecContext *avctx, 
AVFrame *frame,
     for (int ch = 0; ch < avctx->ch_layout.nb_channels; ch++) {
         AMRWBContext *ctx  = &s->ch[ch];
         AMRWBFrame   *cf   = &ctx->frame;
-        int expected_fr_size, header_size;
+        int expected_fr_size, header_size, rx_type, new_state;
         float spare_vector[AMRWB_SFR_SIZE];      // extra stack space to hold 
result from anti-sparseness processing
         float fixed_gain_factor;                 // fixed gain correction 
factor (gamma)
         float *synth_fixed_vector;               // pointer to the fixed 
vector that synthesis should use
         float synth_fixed_gain;                  // the fixed gain that 
synthesis should use
         float voice_fac, stab_fac;               // parameters used for gain 
smoothing
         float synth_exc[AMRWB_SFR_SIZE];         // post-processed excitation 
for synthesis
-        float hb_exc[AMRWB_SFR_SIZE_16k];        // excitation for the high 
frequency band
-        float hb_samples[AMRWB_SFR_SIZE_16k];    // filtered high-band samples 
from synthesis
-        float hb_gain;
+        int sid_ind[5] = { 0 };                  // ISF indices of a SID frame
+        int sid_en_idx = 0;                      // energy index of a SID frame
+        float frame_exc_energy = 0.0f;           // excitation energy of this 
frame
         float *buf_out = (float *)frame->extended_data[ch];
 
         header_size      = decode_mime_header(ctx, buf);
@@ -1143,32 +1667,56 @@ static int amrwb_decode_frame(AVCodecContext *avctx, 
AVFrame *frame,
         if (!ctx->fr_quality)
             av_log(avctx, AV_LOG_ERROR, "Encountered a bad or corrupted 
frame\n");
 
-        if (ctx->fr_cur_mode == NO_DATA || !ctx->fr_quality) {
-            /* The specification suggests a "random signal" and
-               "a muting technique" to "gradually decrease the output level". 
*/
-            av_samples_set_silence(&frame->extended_data[ch], 0, 
frame->nb_samples, 1, AV_SAMPLE_FMT_FLT);
-            buf += expected_fr_size;
-            buf_size -= expected_fr_size;
-            continue;
-        }
-        if (ctx->fr_cur_mode > MODE_SID) {
-            av_log(avctx, AV_LOG_ERROR,
-                   "Invalid mode %d\n", ctx->fr_cur_mode);
-            return AVERROR_INVALIDDATA;
-        }
-
         if (buf_size < expected_fr_size) {
             av_log(avctx, AV_LOG_ERROR,
                    "Frame too small (%d bytes). Truncated file?\n", buf_size);
             *got_frame_ptr = 0;
             return AVERROR_INVALIDDATA;
         }
-
-        if (ctx->fr_cur_mode == MODE_SID) { /* Comfort noise frame */
-            avpriv_request_sample(avctx, "SID mode");
-            return AVERROR_PATCHWELCOME;
+        /* Classify the received frame for the comfort noise state machine */
+        if (ctx->fr_cur_mode == MODE_SID) {
+            if (ctx->fr_quality) {
+                if (decode_sid_frame(ctx, buf + 1, sid_ind, &sid_en_idx))
+                    rx_type = RX_SID_UPDATE;
+                else
+                    rx_type = RX_SID_FIRST;
+            } else {
+                rx_type = RX_SID_BAD;
+            }
+        } else if (ctx->fr_cur_mode == NO_DATA ||
+                   (ctx->fr_cur_mode > MODE_SID && ctx->fr_cur_mode < 
SP_LOST)) {
+            /* the reference decoder replaces the unused frame types by
+               frames carrying no data */
+            rx_type = RX_NO_DATA;
+        } else if (ctx->fr_cur_mode == SP_LOST) {
+            rx_type = RX_SPEECH_LOST;
+        } else if (!ctx->fr_quality) {
+            rx_type = RX_SPEECH_BAD;
+        } else {
+            rx_type = RX_SPEECH_GOOD;
+        }
+        new_state = dtx_handler(ctx, rx_type);
+        if (new_state != DTX_SPEECH) {
+            /* Comfort noise is generated for SID frames and, once a silent
+               period has started, for the frames carrying no data at all */
+            cn_frame(ctx, new_state, sid_ind, sid_en_idx,
+                     (float *)frame->extended_data[ch]);
+            buf += expected_fr_size;
+            buf_size -= expected_fr_size;
+            continue;
+        }
+        if (ctx->fr_cur_mode > MODE_SID || !ctx->fr_quality) {
+            /* The specification suggests a "random signal" and
+               "a muting technique" to "gradually decrease the output level".
+               Outside of a silent period no comfort noise parameters are
+               available, so the frames carrying no payload at all are output
+               as silence. They still produce a full frame of samples, keeping
+               the frame count and the timing of the decoded stream correct. */
+            av_samples_set_silence(&frame->extended_data[ch], 0, 
frame->nb_samples, 1, AV_SAMPLE_FMT_FLT);
+            buf += expected_fr_size;
+            buf_size -= expected_fr_size;
+            continue;
         }
-
         ff_amr_bit_reorder((uint16_t *) &ctx->frame, sizeof(AMRWBFrame),
                            buf + header_size, 
amr_bit_orderings_by_mode[ctx->fr_cur_mode]);
 
@@ -1234,6 +1782,11 @@ static int amrwb_decode_frame(AVCodecContext *avctx, 
AVFrame *frame,
                 ctx->excitation[i] = truncf(ctx->excitation[i]);
             }
 
+            /* the excitation energy is the comfort noise reference for the
+               silent periods following this frame */
+            frame_exc_energy += ctx->celpm_ctx.dot_productf(ctx->excitation,
+                                                            ctx->excitation,
+                                                            AMRWB_SFR_SIZE);
             /* Post-processing of excitation elements */
             synth_fixed_gain = noise_enhancer(ctx->fixed_gain[0], 
&ctx->prev_tr_gain,
                                               voice_fac, stab_fac);
@@ -1246,50 +1799,23 @@ static int amrwb_decode_frame(AVCodecContext *avctx, 
AVFrame *frame,
             synthesis(ctx, ctx->lp_coef[sub], synth_exc, synth_fixed_gain,
                       synth_fixed_vector, &ctx->samples_az[LP_ORDER]);
 
-            /* Synthesis speech post-processing */
-            de_emphasis(&ctx->samples_up[UPS_MEM_SIZE],
-                        &ctx->samples_az[LP_ORDER], PREEMPH_FAC, 
ctx->demph_mem);
-
-            
ctx->acelpf_ctx.acelp_apply_order_2_transfer_function(&ctx->samples_up[UPS_MEM_SIZE],
-                                                                  
&ctx->samples_up[UPS_MEM_SIZE], hpf_zeros, hpf_31_poles,
-                                                                  hpf_31_gain, 
ctx->hpf_31_mem, AMRWB_SFR_SIZE);
-
-            upsample_5_4(sub_buf, &ctx->samples_up[UPS_FIR_SIZE],
-                         AMRWB_SFR_SIZE_16k, &ctx->celpm_ctx);
-
-            /* High frequency band (6.4 - 7.0 kHz) generation part */
-            ctx->acelpf_ctx.acelp_apply_order_2_transfer_function(hb_samples,
-                                                                  
&ctx->samples_up[UPS_MEM_SIZE], hpf_zeros, hpf_400_poles,
-                                                                  
hpf_400_gain, ctx->hpf_400_mem, AMRWB_SFR_SIZE);
-
-            hb_gain = find_hb_gain(ctx, hb_samples,
-                                   cur_subframe->hb_gain, cf->vad);
-
-            scaled_hb_excitation(ctx, hb_exc, synth_exc, hb_gain);
-
-            hb_synthesis(ctx, sub, &ctx->samples_hb[LP_ORDER_16k],
-                         hb_exc, ctx->isf_cur, ctx->isf_past_final);
-
-            /* High-band post-processing filters */
-            hb_fir_filter(hb_samples, bpf_6_7_coef, ctx->bpf_6_7_mem,
-                          &ctx->samples_hb[LP_ORDER_16k]);
-
-            if (ctx->fr_cur_mode == MODE_23k85)
-                hb_fir_filter(hb_samples, lpf_7_coef, ctx->lpf_7_mem,
-                              hb_samples);
-
-            /* Add the low and high frequency bands */
-            for (i = 0; i < AMRWB_SFR_SIZE_16k; i++)
-                sub_buf[i] = (sub_buf[i] + hb_samples[i]) * (1.0f / (1 << 15));
-
-            /* Update buffers and history */
-            update_sub_state(ctx);
+            subframe_output(ctx, sub_buf, synth_exc, sub,
+                            cur_subframe->hb_gain, cf->vad, 1.0f);
         }
 
         /* update state for next frame */
         memcpy(ctx->isp_sub4_past, ctx->isp[3], LP_ORDER * 
sizeof(ctx->isp[3][0]));
         memcpy(ctx->isf_past_final, ctx->isf_cur, LP_ORDER * sizeof(float));
 
+        /* keep the noise parameters of this speech frame for the next SID */
+        ctx->isf_cur[LP_ORDER - 1] *= 0.5f;
+        dtx_activity_update(ctx, ctx->isf_cur, frame_exc_energy);
+        ctx->isf_cur[LP_ORDER - 1] *= 2.0f;
+        if (ctx->fr_quality) {
+            ctx->vad_hist = cf->vad ? 0 : FFMIN(ctx->vad_hist + 1, 32767);
+        } else if (ctx->vad_hist > 0)
+            ctx->vad_hist = FFMIN(ctx->vad_hist + 1, 32767);
+        ctx->dtx_state = new_state;
         buf += expected_fr_size;
         buf_size -= expected_fr_size;
     }
diff --git a/tests/data/fate/amrwb-dtx.amr b/tests/data/fate/amrwb-dtx.amr
new file mode 100644
index 
0000000000000000000000000000000000000000..7be555a0ba9232fc3d4289dac0d94f303808a6f0
GIT binary patch
literal 657
zcmV;C0&e{yAwf-2EmuMcMBu^11V=%Q;#*^iW>Ml3Ub}5N%@`V=Y^+U&=I_mkXZ`T@
z_hYeef~Yf0N=QRZ<Zss8gPjT#F%7uf2t>m8r*=19Tk^flws6<X6(hf0=4nG09`B{@
zzzMk(s5FKY1qG=#_OH^`gIL%z{g&dcZ}T_x=@@lpq@YCU3??p4!Zs(~O3Z~NbSCWY
zn3aW=WZ!0Z6de=a@sml%UZ0b%`Mf13`T9y67~$KkpWGs}>*u!iQQ6o;*aA3y^b!m8
zy~zGTz~^L-|K3SCfs^zN{%=#iRsLhMDfmKD-NJBnzQM$2C^~_x9Y{G2+-(P|#6g09
zM1d3xF-W|pp?%x`Yd?(w2akO>WD|VD+_}r8nCXn_qkuhts~FK{Z)!+l`HMXX6TsSC
z)*Mz4yM(G%fJDJU(0GH*!J-e>-LCb`W~aaXllg_Amkb*58ty_43kH4E8V>AM-YsAy
zTw%N?^#;DEC(6ewQW{X4b&y1Z6Uv@}P+s(U-pjn_95)7kxv;japy}P|BP&a*buQ`w
zzk7}O0OH+lFjF{ly;$p<X1rGo!1{_M5@y&$fdGKRAISIQd%UGcRqeUA|JssF7?S^!
z?k!0&pWygS(AC``(!+)r1#(L}*J&<+S)3m=VpY`xx~SK1M8PxZJ)Y#%bGoa!C74&s
zHt*KF_@JJsr>qwPQH~5o9<>}plM~8-*O>*-8?=E?A3X2J=5^yDIUhBfAVh&(rPf4d
z*~8knYP`1nl}6m%lYVyip_+JUPk0D*-aN@QQy+k}^!9y;70E`yr#VWkaQ`@+_FMxu
rZTL(8000FLOaK4?1rSUC000FLe0+R-e0+R-e0+R-e0+R-e0+R-$%IIq

literal 0
HcmV?d00001

diff --git a/tests/fate/amrwb.mak b/tests/fate/amrwb.mak
index aec1c9d859..287924e1d3 100644
--- a/tests/fate/amrwb.mak
+++ b/tests/fate/amrwb.mak
@@ -46,6 +46,17 @@ fate-amrwb-remux: CMD = transcode mov 
$(TARGET_SAMPLES)/amrwb/seed-23k85.awb amr
 
 $(FATE_AMRWB): CMP = stddev
 
+# Check that SID frames are decoded to comfort noise instead of being dropped,
+# so that a stream using DTX keeps its duration (see ticket #24921).
+FATE_AMRWB_DTX-$(call ALLYES, AMR_DEMUXER AMRWB_DECODER ARESAMPLE_FILTER 
ASTATS_FILTER NULL_MUXER FILE_PROTOCOL) += fate-amrwb-dtx-duration 
fate-amrwb-dtx-cn
+fate-amrwb-dtx-duration: CMD = ffmpeg -i 
$(TARGET_PATH)/tests/data/fate/amrwb-dtx.amr -af 
"aresample=osf=flt,astats=metadata=0:reset=0" -c:a pcm_f32le -f null -
+fate-amrwb-dtx-duration: CMP = grep
+fate-amrwb-dtx-duration: REF = Number of samples: 10560
+fate-amrwb-dtx-cn: CMD = ffmpeg -i 
$(TARGET_PATH)/tests/data/fate/amrwb-dtx.amr -af 
"atrim=start_sample=4160:end_sample=10560,aresample=osf=flt,astats=metadata=0:reset=0"
 -c:a pcm_f32le -f null -
+fate-amrwb-dtx-cn: CMP = grep
+fate-amrwb-dtx-cn: REF = RMS level dB: -1[89]\.
+
 FATE_SAMPLES_FFMPEG-$(call PCM, MOV, AMRWB, ARESAMPLE_FILTER) += $(FATE_AMRWB)
+FATE_FFMPEG += $(FATE_AMRWB_DTX-yes)
 FATE_SAMPLES_FFMPEG += $(FATE_AMRWB_REMUX-yes)
-fate-amrwb: $(FATE_AMRWB) $(FATE_AMRWB_REMUX-yes)
+fate-amrwb: $(FATE_AMRWB) $(FATE_AMRWB_REMUX-yes) $(FATE_AMRWB_DTX-yes)
-- 
2.54.0


>From c4832f97a090dbd72a9fa7ab56125970ff3174c3 Mon Sep 17 00:00:00 2001
From: Jun Zhao <[email protected]>
Date: Fri, 9 Oct 2026 12:20:37 +0800
Subject: [PATCH 3/8] avcodec/amrnbdata: add the comfort noise tables

Add the TS 26.092 comfort-noise tables used by the following
commit: initial prediction residuals and LSP vector, LSF deviation
scales, the mode-dependent level adjustment, and the DTX and
excitation constants. They match 3GPP TS 26.073 element by element.

Signed-off-by: Jun Zhao <[email protected]>
---
 libavcodec/amrnbdata.h | 38 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 38 insertions(+)

diff --git a/libavcodec/amrnbdata.h b/libavcodec/amrnbdata.h
index 20b7a6452f..f38efb476a 100644
--- a/libavcodec/amrnbdata.h
+++ b/libavcodec/amrnbdata.h
@@ -1663,4 +1663,42 @@ static const float highpass_zeros[2] = { -2.0, 1.0 };
 static const float highpass_poles[2] = { -1.933105469, 0.935913085 };
 static const float highpass_gain     = 0.939819335;
 
+/* Comfort noise (DTX) tables and constants, see 3GPP TS 26.092 */
+
+#define DTX_HIST_SIZE            8
+#define DTX_MAX_EMPTY_THRESH     50
+#define DTX_HANG_CONST           7
+#define DTX_ELAPSED_FRAMES_THRESH 30
+#define CN_NB_PULSE             10
+#define CN_PN_SEED_INIT         0x70816958u
+/** Initial LSP vector of the comfort noise decoder */
+static const float cn_lsp_init[LP_FILTER_ORDER] = {
+    0.915527f, 0.793457f, 0.640869f, 0.457764f, 0.244141f, 0.000000f, 
-0.244141f, -0.457764f, -0.640869f, -0.793457f
+};
+
+/**
+ * Initial prediction residual vectors of the comfort noise LSF decoder,
+ * indexed by the first SID parameter, see 3GPP TS 26.073
+ */
+static const int16_t past_rq_init[8][LP_FILTER_ORDER] = {
+    {  -258,  -318,  -439,  -634,  -656,  -773,  -711,  -502,  -268,  -193},
+    {    -2,   125,   122,   -39,    -9,   105,   129,   283,   372,   575},
+    {  -277,  -324,  -197,  -487,  -445,  -362,  -292,   -27,   177,   543},
+    {   342,   517,   516,   130,    27,  -104,  -120,  -140,   -74,   -56},
+    {  -564,  -943, -1520,  -965,  -814,  -526,  -322,    -2,   159,   657},
+    {  -312,  -284,  -386,  -597,  -493,  -526,  -418,  -229,   105,   449},
+    {  -557,  -870, -1075,  -919,  -950,  -752,  -709,  -316,    62,   486},
+    {  -314,  -191,  -203,  -330,  -160,  -103,   -51,   131,   338,   515},
+};
+
+/** Scale factors applied to the deviation from the mean LSF vector */
+static const float lsf_hist_mean_scale[LP_FILTER_ORDER] = {
+    0.610352f, 0.610352f, 0.610352f, 0.610352f, 0.610352f, 0.549316f, 
0.500000f, 0.250000f, 0.000000f, 0.000000f
+};
+
+/** Speech mode dependent adjustment of the comfort noise level */
+static const float dtx_log_en_adjust[9] = {
+    -0.499512f, -0.428711f, -0.357422f, -0.286133f, -0.214844f, -0.143555f, 
-0.072266f, 0.000000f, 0.000000f
+};
+
 #endif /* AVCODEC_AMRNBDATA_H */
-- 
2.54.0


>From a3bf6ad070d8afb0126869c4fedbe0393811f40a Mon Sep 17 00:00:00 2001
From: Jun Zhao <[email protected]>
Date: Fri, 9 Oct 2026 14:07:50 +0800
Subject: [PATCH 4/8] avcodec/amrnbdec: generate comfort noise for SID frames

SID frames returned AVERROR_PATCHWELCOME and NO_DATA frames were
rejected, so a DTX stream decoded short.

Decode the 39-bit SID and follow TS 26.073 in floating point.
Interpolate and mute between updates, and synthesize a sparse
excitation at the decoded energy, normalized by the prediction
gain. After a silent period has started, NO_DATA keeps producing
that noise (TS 26.093 clause 5.2.3). Unused frame types between
SID and NO_DATA are treated as NO_DATA.

Reset the memories required by TS 26.092 clause 6.2 during comfort
noise, and limit the gains of the first speech frame after an
unusable frame (TS 26.091 clause 7.2.2).

Duration matches the reference decoder. The noise level stays
within 0.5 dB on each silent frame and 0.1 dB on average. Speech
not preceded by silence is unchanged. FATE checks the duration,
the noise level, speech resuming after silence, and a SID frame
that follows a lost frame.

Signed-off-by: Jun Zhao <[email protected]>
---
 libavcodec/amrnbdec.c                         | 738 +++++++++++++++++-
 tests/data/fate/amrnb-nodata-cases.amr        | Bin 0 -> 1569 bytes
 tests/data/fate/amrnb-sid-after-nodata.amr    | Bin 0 -> 439 bytes
 tests/data/fate/amrnb-sid-update.amr          | Bin 0 -> 675 bytes
 tests/data/fate/amrnb-state-resume-update.amr | Bin 0 -> 675 bytes
 tests/fate/amrnb.mak                          |  41 +
 6 files changed, 769 insertions(+), 10 deletions(-)
 create mode 100644 tests/data/fate/amrnb-nodata-cases.amr
 create mode 100644 tests/data/fate/amrnb-sid-after-nodata.amr
 create mode 100644 tests/data/fate/amrnb-sid-update.amr
 create mode 100644 tests/data/fate/amrnb-state-resume-update.amr

diff --git a/libavcodec/amrnbdec.c b/libavcodec/amrnbdec.c
index b8e340684b..60ae300d91 100644
--- a/libavcodec/amrnbdec.c
+++ b/libavcodec/amrnbdec.c
@@ -52,6 +52,7 @@
 #include "acelp_filters.h"
 #include "acelp_vectors.h"
 #include "acelp_pitch_delay.h"
+#include "get_bits.h"
 #include "lsp.h"
 #include "amr.h"
 #include "codec_internal.h"
@@ -97,6 +98,31 @@
 /** Adaptive gain control factor used in post-filter */
 #define AMR_AGC_ALPHA      0.9
 
+/**
+ * Scaling of the comfort noise excitation level, the pulses of the fixed
+ * codebook of the reference have an amplitude of 4096 in a Q15 domain
+ */
+#define CN_LEVEL_SCALE      (1.0f / 8.0f)
+
+/** Received frame types used by the comfort noise (DTX) state machine */
+enum RXFrameType {
+    RX_SPEECH_GOOD = 0,
+    RX_SPEECH_DEGRADED,
+    RX_ONSET,
+    RX_SPEECH_BAD,
+    RX_SID_FIRST,
+    RX_SID_UPDATE,
+    RX_SID_BAD,
+    RX_NO_DATA
+};
+
+/** States of the comfort noise (DTX) state machine */
+enum DTXState {
+    DTX_SPEECH = 0,
+    DTX,
+    DTX_MUTE
+};
+
 typedef struct AMRContext {
     AMRNBFrame                        frame; ///< decoded AMR parameters (lsf 
coefficients, codebook indexes, etc)
     uint8_t             bad_frame_indicator; ///< bad frame ? 1 : 0
@@ -143,6 +169,31 @@ typedef struct AMRContext {
     CELPFContext                       celpf_ctx; ///< context for filters for 
CELP-based codecs
     CELPMContext                       celpm_ctx; ///< context for fixed point 
math operations
 
+    /* Comfort noise (DTX) state */
+    int         dtx_state;                            ///< current DTX state 
(SPEECH, DTX or DTX_MUTE)
+    int         since_last_sid;                       ///< frames since the 
last SID update
+    float       sid_period_inv;                       ///< inverse of the SID 
update period
+    float       cn_log_en;                            ///< comfort noise log2 
energy of the last SID
+    float       cn_log_en_old;                        ///< comfort noise log2 
energy before the last SID
+    float       cn_lsp[LP_FILTER_ORDER];              ///< comfort noise LSP 
vector of the last SID
+    float       cn_lsp_old[LP_FILTER_ORDER];          ///< comfort noise LSP 
vector before the last SID
+    uint32_t    cn_seed;                              ///< comfort noise 
generator shift register
+    float       lsf_hist[DTX_HIST_SIZE][LP_FILTER_ORDER];      ///< LSF 
history used when a SID frame starts DTX
+    float       lsf_hist_mean[DTX_HIST_SIZE][LP_FILTER_ORDER]; ///< deviation 
from the mean of the LSF history
+    float       log_en_hist[DTX_HIST_SIZE];           ///< energy history used 
when a SID frame starts DTX
+    int         lsf_hist_ptr;                         ///< next position to 
write in the LSF history
+    int         log_en_hist_ptr;                      ///< next position to 
write in the energy history
+    float       log_en_adjust;                        ///< speech mode 
dependent level adjustment
+    float       log_pg_mean;                          ///< smoothed logarithm 
of the prediction gain
+    int         hangover_count;                       ///< remaining DTX 
hangover frames
+    int         dec_ana_elapsed;                      ///< frames since the 
last DTX hangover
+    int         hangover_added;                       ///< a DTX hangover was 
assumed for this frame
+    int         sid_frame;                            ///< the current frame 
carries CN parameters
+    int         valid_data;                           ///< the CN parameters 
of this frame may be used
+    int         data_updated;                         ///< CN parameters have 
been renewed at least once
+    int         prev_bf;                              ///< the previous frame 
could not be used
+    float       prev_pitch_gain;                      ///< pitch gain of the 
last usable frame
+    float       prev_fixed_gain;                      ///< fixed gain of the 
last usable frame
 } AMRContext;
 
 typedef struct AMRChannelsContext {
@@ -164,7 +215,7 @@ static void weighted_vector_sumd(double *out, const double 
*in_a,
 static av_cold int amrnb_decode_init(AVCodecContext *avctx)
 {
     AMRChannelsContext *s = avctx->priv_data;
-    int i;
+    int i, j;
 
     if (avctx->ch_layout.nb_channels > 2) {
         avpriv_report_missing_feature(avctx, ">2 channel AMR");
@@ -192,6 +243,28 @@ static av_cold int amrnb_decode_init(AVCodecContext *avctx)
         for (i = 0; i < 4; i++)
             p->prediction_error[i] = MIN_ENERGY;
 
+        /* Comfort noise state, matches the DTX decoder reset */
+        p->dtx_state       = DTX;
+        p->sid_period_inv  = 0.25f;
+        p->cn_log_en       = 3500.0f / 2048.0f;
+        p->cn_log_en_old   = p->cn_log_en;
+        p->cn_seed         = CN_PN_SEED_INIT;
+        p->hangover_count  = DTX_HANG_CONST;
+        p->dec_ana_elapsed = 32767;
+        p->prev_bf         = 0;
+        p->prev_pitch_gain = 0.0f;
+        p->prev_fixed_gain = 0.0f;
+
+        for (i = 0; i < LP_FILTER_ORDER; i++)
+            p->cn_lsp[i] = p->cn_lsp_old[i] = cn_lsp_init[i];
+
+        for (i = 0; i < DTX_HIST_SIZE; i++) {
+            for (j = 0; j < LP_FILTER_ORDER; j++)
+                p->lsf_hist[i][j] = lsf_3_mean[j] * (1.0 / 8000.0);
+            memset(p->lsf_hist_mean[i], 0, sizeof(p->lsf_hist_mean[i]));
+            p->log_en_hist[i] = p->cn_log_en;
+        }
+
         ff_acelp_filter_init(&p->acelpf_ctx);
         ff_acelp_vectors_init(&p->acelpv_ctx);
         ff_celp_filter_init(&p->celpf_ctx);
@@ -954,6 +1027,558 @@ static void postfilter(AMRContext *p, float *lpc, float 
*buf_out)
 
 /// @}
 
+/**
+ * Comfort noise (DTX) generation, see 3GPP TS 26.092.
+ *
+ * As for AMR-WB the implementation follows the decoder of the reference
+ * codec (3GPP TS 26.073) with the fixed point arithmetic replaced by
+ * floating point: LSF vectors are kept in the same unit as in the speech
+ * path (normalized frequency) and energies as log2 values.
+ */
+
+
+/**
+ * Draw a value from the pseudo noise generator, see 3GPP TS 26.073.
+ *
+ * @param[in,out] shift_reg        State of the shift register
+ * @param[in]     no_bits          Number of bits to draw
+ */
+static int pseudonoise(uint32_t *shift_reg, int no_bits)
+{
+    int noise_bits = 0, i;
+
+    for (i = 0; i < no_bits; i++) {
+        int Sn = (*shift_reg & 1) ^ ((*shift_reg >> 28) & 1);
+
+        noise_bits = (noise_bits << 1) | (*shift_reg & 1);
+        *shift_reg >>= 1;
+        if (Sn)
+            *shift_reg |= 0x40000000u;
+    }
+
+    return noise_bits;
+}
+
+/**
+ * Build the comfort noise excitation, a vector of sparse pulses.
+ *
+ * @param[in,out] seed             State of the pseudo noise generator
+ * @param[out]    cod              Excitation for one subframe
+ */
+static void build_cn_code(uint32_t *seed, float *cod)
+{
+    int i, k;
+
+    memset(cod, 0, AMR_SUBFRAME_SIZE * sizeof(*cod));
+
+    for (k = 0; k < CN_NB_PULSE; k++) {
+        i = pseudonoise(seed, 2) * 10 + k;
+        cod[i] = pseudonoise(seed, 1) > 0 ? 1.0f : -1.0f;
+    }
+}
+
+/**
+ * Decode the comfort noise parameters of a SID frame.
+ *
+ * The payload carries 39 bits: five parameters (3, 8, 9, 9 and 6 bits),
+ * a flag telling whether this is the first SID frame of a silent period
+ * and the speech mode used before the silence.
+ *
+ * @param[out]    ind              The five SID parameters
+ * @param[out]    mode             Speech mode used before the silence
+ *
+ * @return non zero if this is a SID update frame
+ */
+static int decode_sid_frame(const uint8_t *buf, int *ind, int *mode)
+{
+    GetBitContext gb;
+    int sid_type;
+
+    init_get_bits8(&gb, buf, 5);
+
+    ind[0] = get_bits(&gb, 3);
+    ind[1] = get_bits(&gb, 8);
+    ind[2] = get_bits(&gb, 9);
+    ind[3] = get_bits(&gb, 9);
+    ind[4] = get_bits(&gb, 6);
+    sid_type = get_bits1(&gb);
+
+    /* the three bits of the speech mode indication are interleaved */
+    *mode = get_bits(&gb, 3);
+    *mode = ((*mode & 4) >> 2) | (*mode & 2) | ((*mode & 1) << 2);
+
+    return sid_type;
+}
+
+/**
+ * Decode the LSF vector of a SID frame, the comfort noise quantizer does
+ * not use the prediction of the speech modes.
+ *
+ * @param[in]     ind              The five SID parameters
+ * @param[out]    lsf_q            Decoded LSF vector
+ */
+static void decode_lsf_noise(const int *ind, float *lsf_q)
+{
+    int i;
+
+    for (i = 0; i < 3; i++)
+        lsf_q[i] = lsf_3_1[ind[1]][i] * (LSF_R_FAC / 8000.0);
+
+    for (i = 0; i < 3; i++)
+        lsf_q[i + 3] = lsf_3_2[ind[2]][i] * (LSF_R_FAC / 8000.0);
+
+    for (i = 0; i < 4; i++)
+        lsf_q[i + 6] = lsf_3_3[ind[3]][i] * (LSF_R_FAC / 8000.0);
+
+    /* the first parameter selects the initial prediction residual */
+    for (i = 0; i < LP_FILTER_ORDER; i++) {
+        lsf_q[i] += past_rq_init[ind[0]][i] * (LSF_R_FAC / 8000.0);
+        lsf_q[i] += lsf_3_mean[i] * (1.0 / 8000.0);
+    }
+
+    ff_set_min_dist_lsf(lsf_q, MIN_LSF_SPACING, LP_FILTER_ORDER);
+}
+
+/**
+ * Store the LSF vector and the energy of a speech frame so that the first
+ * SID frame of a silent period can reuse them.
+ *
+ * @param[in,out] p                The Context
+ * @param[in]     synth            Synthesis of the decoded frame
+ */
+static void dtx_activity_update(AMRContext *p, const float *synth)
+{
+    float energy = 0.0f;
+    int i;
+
+    p->lsf_hist_ptr = (p->lsf_hist_ptr + 1) % DTX_HIST_SIZE;
+    memcpy(p->lsf_hist[p->lsf_hist_ptr], p->lsf_q[3], sizeof(p->lsf_q[3]));
+
+    p->log_en_hist_ptr = (p->log_en_hist_ptr + 1) % DTX_HIST_SIZE;
+
+    for (i = 0; i < AMR_BLOCK_SIZE; i++)
+        energy += synth[i] * synth[i];
+
+    /* The reference accumulates the energy with L_mac(), which shifts every
+       product left by one, so after dividing by 320 what is left is the
+       logarithm of the mean square of the frame. The history keeps one more
+       fractional bit than the energy carried by a SID frame. */
+    p->log_en_hist[p->log_en_hist_ptr] =
+        0.5f * log2f(FFMAX(energy, 1.0f) / AMR_BLOCK_SIZE);
+}
+
+/**
+ * Compute the reflection coefficients of an LP filter with the backward
+ * Levinson recursion, they give the prediction gain of the filter.
+ *
+ * @param[in]     lpc              LP coefficients of the filter
+ * @param[out]    refl             Reflection coefficients
+ */
+static void lpc2refl(const float *lpc, float *refl)
+{
+    float a[LP_FILTER_ORDER + 1], b[LP_FILTER_ORDER + 1];
+    int i, m;
+
+    for (i = 1; i <= LP_FILTER_ORDER; i++)
+        a[i] = lpc[i - 1];
+
+    for (m = LP_FILTER_ORDER; m >= 1; m--) {
+        float k = a[m];
+
+        refl[m - 1] = k;
+        for (i = 1; i < m; i++)
+            b[i] = (a[i] - k * a[m - i]) / (1.0f - k * k);
+        for (i = 1; i < m; i++)
+            a[i] = b[i];
+    }
+}
+
+/**
+ * Update the comfort noise state machine for a received frame.
+ *
+ * @param[in,out] p                The Context
+ * @param[in]     frame_type       Type of the received frame
+ *
+ * @return the DTX state to decode this frame with
+ */
+static int dtx_handler(AMRContext *p, int frame_type)
+{
+    int new_state, enc_state;
+
+    if (frame_type == RX_SID_FIRST || frame_type == RX_SID_UPDATE ||
+        frame_type == RX_SID_BAD   ||
+        ((p->dtx_state == DTX || p->dtx_state == DTX_MUTE) &&
+         (frame_type == RX_NO_DATA    || frame_type == RX_SPEECH_BAD ||
+          frame_type == RX_ONSET))) {
+        new_state = DTX;
+
+        /* stay muted for these input types */
+        if (p->dtx_state == DTX_MUTE &&
+            (frame_type == RX_SID_BAD   || frame_type == RX_SID_FIRST ||
+             frame_type == RX_ONSET     || frame_type == RX_NO_DATA))
+            new_state = DTX_MUTE;
+
+        p->since_last_sid = FFMIN(p->since_last_sid + 1, 32767);
+
+        /* SID updates are handled separately to avoid muting a late one */
+        if (frame_type != RX_SID_UPDATE &&
+            p->since_last_sid > DTX_MAX_EMPTY_THRESH)
+            new_state = DTX_MUTE;
+    } else {
+        new_state = DTX_SPEECH;
+        p->since_last_sid = 0;
+    }
+
+    if (!p->data_updated && frame_type == RX_SID_UPDATE)
+        p->dec_ana_elapsed = 0;
+
+    p->dec_ana_elapsed = FFMIN(p->dec_ana_elapsed + 1, 32767);
+    p->hangover_added  = 0;
+
+    if (frame_type == RX_SID_FIRST || frame_type == RX_SID_UPDATE ||
+        frame_type == RX_SID_BAD   || frame_type == RX_ONSET   ||
+        frame_type == RX_NO_DATA)
+        enc_state = DTX;
+    else
+        enc_state = DTX_SPEECH;
+
+    /* dtx_dec.c: a RX_NO_DATA frame may be a speech packet that was lost on
+       the way, so the assumed encoder state is SPEECH in that case. */
+    if (frame_type == RX_NO_DATA && new_state == DTX_SPEECH)
+        enc_state = DTX_SPEECH;
+
+    if (enc_state == DTX_SPEECH) {
+        p->hangover_count = DTX_HANG_CONST;
+    } else if (p->dec_ana_elapsed > DTX_ELAPSED_FRAMES_THRESH) {
+        p->hangover_added  = 1;
+        p->dec_ana_elapsed = 0;
+        p->hangover_count  = 0;
+    } else if (!p->hangover_count) {
+        p->dec_ana_elapsed = 0;
+    } else {
+        p->hangover_count--;
+    }
+
+    if (new_state != DTX_SPEECH) {
+        p->sid_frame  = 0;
+        p->valid_data = 0;
+
+        if (frame_type == RX_SID_FIRST) {
+            p->sid_frame = 1;
+        } else if (frame_type == RX_SID_UPDATE) {
+            p->sid_frame  = 1;
+            p->valid_data = 1;
+        } else if (frame_type == RX_SID_BAD) {
+            p->sid_frame      = 1;
+            p->hangover_added = 0;
+        }
+    }
+
+    return new_state;
+}
+
+/**
+ * Compute the logarithm of the prediction gain of an LP filter, it is
+ * used to normalize the level of the comfort noise.
+ *
+ * @param[in]     lpc              LP coefficients of the filter
+ *
+ * @return log2 of the amplitude gained by filtering, negated
+ */
+static float pred_gain_log(const float *lpc)
+{
+    float refl[LP_FILTER_ORDER], pred_err = 1.0f;
+    int i;
+
+    lpc2refl(lpc, refl);
+    for (i = 0; i < LP_FILTER_ORDER; i++)
+        pred_err *= 1.0f - refl[i] * refl[i];
+
+    /* the prediction gain is applied to the amplitude of the excitation,
+       so only half of it is subtracted from the logarithm of the level */
+    return -0.5f * log2f(FFMAX(pred_err, 1e-6f));
+}
+
+/**
+ * Reuse the noise parameters of the speech frames preceding a silent
+ * period, they are the average of the LSF and energy history.
+ *
+ * @param[in,out] p                The Context
+ * @param[in]     mode             Speech mode used before the silence
+ */
+static void cn_reuse_speech_history(AMRContext *p, int mode)
+{
+    float lsf[LP_FILTER_ORDER];
+    double lsp[LP_FILTER_ORDER];
+    int i, j, ptr;
+
+    ptr = (p->lsf_hist_ptr + 1) % DTX_HIST_SIZE;
+    memcpy(p->lsf_hist[ptr], p->lsf_hist[p->lsf_hist_ptr],
+           sizeof(p->lsf_hist[0]));
+    ptr = (p->log_en_hist_ptr + 1) % DTX_HIST_SIZE;
+    p->log_en_hist[ptr] = p->log_en_hist[p->log_en_hist_ptr];
+
+    p->log_en_adjust = dtx_log_en_adjust[mode];
+
+    memset(lsf, 0, sizeof(lsf));
+    p->cn_log_en = 0.0f;
+    for (i = 0; i < DTX_HIST_SIZE; i++) {
+        p->cn_log_en += p->log_en_hist[i];
+        for (j = 0; j < LP_FILTER_ORDER; j++)
+            lsf[j] += p->lsf_hist[i][j];
+    }
+    p->cn_log_en /= DTX_HIST_SIZE;
+    for (j = 0; j < LP_FILTER_ORDER; j++)
+        lsf[j] /= DTX_HIST_SIZE;
+
+    /* make the energy independent of the speech coder mode */
+    p->cn_log_en -= p->log_en_adjust;
+
+    /* store the deviation from the mean of the LSF history, it is
+       used to make the comfort noise spectrum vary like the one of
+       the background noise */
+    for (j = 0; j < LP_FILTER_ORDER; j++) {
+        float mean = 0.0f;
+
+        for (i = 0; i < DTX_HIST_SIZE; i++)
+            mean += p->lsf_hist[i][j];
+        mean /= DTX_HIST_SIZE;
+
+        for (i = 0; i < DTX_HIST_SIZE; i++) {
+            float dev = (p->lsf_hist[i][j] - mean) *
+                        lsf_hist_mean_scale[j];
+            float sign = dev < 0.0f ? -1.0f : 1.0f;
+
+            dev = fabsf(dev);
+            /* soft and hard limits on the deviation */
+            if (dev > 655.0f / 32768.0f)
+                dev = 655.0f / 32768.0f +
+                      (dev - 655.0f / 32768.0f) * 0.25f;
+            dev = FFMIN(dev, 1310.0f / 32768.0f);
+            p->lsf_hist_mean[i][j] = dev * sign;
+        }
+    }
+
+    ff_set_min_dist_lsf(lsf, MIN_LSF_SPACING, LP_FILTER_ORDER);
+    ff_acelp_lsf2lspd(lsp, lsf, LP_FILTER_ORDER);
+    for (i = 0; i < LP_FILTER_ORDER; i++)
+        p->cn_lsp[i] = lsp[i];
+}
+
+/**
+ * Update the comfort noise parameters with the ones carried by a SID
+ * frame and reset the state the speech decoder starts from.
+ *
+ * @param[in,out] p                The Context
+ * @param[in]     ind              SID parameters of this frame
+ */
+static void cn_update_from_sid(AMRContext *p, const int *ind)
+{
+    float lsf[LP_FILTER_ORDER];
+    double lsp[LP_FILTER_ORDER];
+    float pred_init;
+    int i, period;
+
+    memcpy(p->cn_lsp_old, p->cn_lsp, sizeof(p->cn_lsp));
+    p->cn_log_en_old = p->cn_log_en;
+
+    if (p->valid_data) {
+        period = p->since_last_sid;
+        p->since_last_sid = 0;
+        period = FFMIN(period, 32);
+        p->sid_period_inv = period >= 2 ? 1.0f / period : 0.5f;
+
+        decode_lsf_noise(ind, lsf);
+        ff_acelp_lsf2lspd(lsp, lsf, LP_FILTER_ORDER);
+        for (i = 0; i < LP_FILTER_ORDER; i++)
+            p->cn_lsp[i] = lsp[i];
+
+        /* index 0 is reserved for silence */
+        p->cn_log_en = ind[4] ? (ind[4] / 4.0f) - 2.5f : -16.0f;
+
+        if (!p->data_updated || p->dtx_state == DTX_SPEECH) {
+            memcpy(p->cn_lsp_old, p->cn_lsp, sizeof(p->cn_lsp));
+            p->cn_log_en_old = p->cn_log_en;
+        }
+    }
+
+    /* Initialize the gain predictor using this SID frame's energy. */
+    pred_init = FFMIN(p->cn_log_en - 9000.0f / 1024.0f, 0.0f);
+    pred_init = FFMAX(pred_init, MIN_ENERGY);
+    for (i = 0; i < 4; i++)
+        p->prediction_error[i] = pred_init;
+}
+
+/**
+ * Synthesize one frame of comfort noise into the output buffer.
+ *
+ * @param[in,out] p                The Context
+ * @param[in]     lpc_variab       LP coefficients of the dithered filter
+ * @param[in]     level            Amplitude of the excitation
+ * @param[in]     new_state        DTX state of this frame
+ * @param[out]    buf_out          Synthesis of the frame, before filtering
+ */
+static void cn_synthesize(AMRContext *p, const float *lpc_variab, float level,
+                          int new_state, float *buf_out)
+{
+    float ex[AMR_SUBFRAME_SIZE];
+    float synth_mem[LP_FILTER_ORDER + AMR_BLOCK_SIZE];
+    int i, sub;
+
+    /* The synthesis filter needs the previous samples in front of the
+       output. The reference decoder resets its state with the MRDTX mode,
+       which keeps the synthesis memory, so the comfort noise of a silent
+       period starts from the memory of the speech frames before it. */
+    memcpy(synth_mem, p->samples_in, LP_FILTER_ORDER * sizeof(*synth_mem));
+
+    /* 3GPP TS 26.092 clause 6.2: while comfort noise is generated the
+       adaptive codebook gain is zero and the memory of the adaptive codebook
+       is set to zero, so that the speech decoder starts from a known state
+       when the speech activity begins again. The predictor memory of the LSF
+       quantizer is reset the same way. */
+    memset(p->excitation_buf, 0, (PITCH_DELAY_MAX + LP_FILTER_ORDER + 1) *
+           sizeof(*p->excitation_buf));
+    memset(p->prev_lsf_r, 0, sizeof(p->prev_lsf_r));
+
+    /* 3GPP TS 26.073 Decoder_amr_reset() sets the pitch sharpening factor to
+       SHARPMIN and the pitch lag to 40 whenever a silent period is entered
+       (the reference resets these unconditionally, including for MRDTX). */
+    p->beta          = 0.0f;
+    p->pitch_lag_int = 40;
+
+    /* The reference decoder resets the error concealment states with every
+       frame of a silent period, so the gains of the speech frame starting
+       the next speech burst are limited to zero as well (3GPP TS 26.091
+       clause 7.2.2). It also marks the frame as unusable when the comfort
+       noise had been muted. */
+    p->prev_pitch_gain = 0.0f;
+    p->prev_fixed_gain = 0.0f;
+    p->prev_bf         = new_state == DTX_MUTE;
+
+    for (sub = 0; sub < 4; sub++) {
+        build_cn_code(&p->cn_seed, ex);
+        for (i = 0; i < AMR_SUBFRAME_SIZE; i++)
+            ex[i] *= level;
+
+        p->celpf_ctx.celp_lp_synthesis_filterf(
+            &synth_mem[LP_FILTER_ORDER + sub * AMR_SUBFRAME_SIZE], lpc_variab, 
ex,
+            AMR_SUBFRAME_SIZE, LP_FILTER_ORDER);
+    }
+
+    memcpy(buf_out, &synth_mem[LP_FILTER_ORDER], AMR_BLOCK_SIZE * 
sizeof(*buf_out));
+
+    /* keep the synthesis memory so that the next speech frame starts from it 
*/
+    memcpy(p->samples_in, &synth_mem[AMR_BLOCK_SIZE], LP_FILTER_ORDER * 
sizeof(*p->samples_in));
+}
+
+/**
+ * Decode one frame of comfort noise into the output buffer.
+ *
+ * @param[in,out] p                The Context
+ * @param[in]     new_state        DTX state of this frame
+ * @param[in]     ind              SID parameters of this frame
+ * @param[in]     mode             Speech mode used before the silence
+ * @param[out]    buf_out          Synthesis of the frame, before filtering
+ */
+static void cn_frame(AMRContext *p, int new_state, const int *ind, int mode,
+                     float *buf_out)
+{
+    float lsf[LP_FILTER_ORDER], lsf_variab[LP_FILTER_ORDER];
+    double lsp[LP_FILTER_ORDER], lsp_variab[LP_FILTER_ORDER];
+    float lpc[LP_FILTER_ORDER], lpc_variab[LP_FILTER_ORDER];
+    float variab_fac, log_en, int_fac, level, log_pg;
+    int i, sub, period, variab_index;
+
+    /* A SID frame starting a silent period reuses the noise parameters
+       of the speech frames preceding it */
+    if (p->hangover_added && p->sid_frame)
+        cn_reuse_speech_history(p, mode);
+
+    /* A SID frame carries new comfort noise parameters */
+    if (p->sid_frame)
+        cn_update_from_sid(p, ind);
+
+    /* The level adjustment of the speech mode is low pass filtered so that
+       it does not jump when the mode changes */
+    p->log_en_adjust = 0.9f * p->log_en_adjust +
+                       0.1f * dtx_log_en_adjust[mode];
+
+    /* Interpolate the comfort noise parameters since the last update */
+    int_fac = FFMIN((1 + p->since_last_sid) * p->sid_period_inv, 1.0f);
+
+    log_en = int_fac * p->cn_log_en + (1.0f - int_fac) * p->cn_log_en_old;
+    for (i = 0; i < LP_FILTER_ORDER; i++)
+        lsp[i] = int_fac * p->cn_lsp[i] + (1.0f - int_fac) * p->cn_lsp_old[i];
+
+    /* convert to LSF to apply the variability of the background noise */
+    for (i = 0; i < LP_FILTER_ORDER; i++)
+        lsf[i] = acosf(av_clipd(lsp[i], -1.0, 1.0)) / (2.0f * M_PI);
+
+    variab_fac = 1.0f - (p->log_pg_mean - 0.6f) * 0.3f;
+    variab_fac = av_clipf(variab_fac, 0.0f, 1.0f);
+    variab_index = pseudonoise(&p->cn_seed, 3);
+    variab_index = FFMIN(variab_index, DTX_HIST_SIZE - 1);
+
+    memcpy(lsf_variab, lsf, sizeof(lsf_variab));
+    for (i = 0; i < LP_FILTER_ORDER; i++)
+        lsf_variab[i] += variab_fac * p->lsf_hist_mean[variab_index][i];
+
+    ff_set_min_dist_lsf(lsf, MIN_LSF_SPACING, LP_FILTER_ORDER);
+    ff_set_min_dist_lsf(lsf_variab, MIN_LSF_SPACING, LP_FILTER_ORDER);
+
+    ff_acelp_lsf2lspd(lsp, lsf, LP_FILTER_ORDER);
+    ff_acelp_lsf2lspd(lsp_variab, lsf_variab, LP_FILTER_ORDER);
+
+    /* TS 26.073 publishes the comfort noise LSF to the speech decoder on
+       every comfort noise frame (dtx_dec.c:548) and converts it there so
+       that the first speech frame after the silent period interpolates from
+       it (dec_amr.c:329-330). */
+    memcpy(p->lsf_q[3], lsf, sizeof(p->lsf_q[3]));
+    memcpy(p->prev_lsp_sub4, lsp, sizeof(p->prev_lsp_sub4));
+    p->acelpv_ctx.weighted_vector_sumf(p->lsf_avg, p->lsf_avg, lsf,
+                                       0.84, 0.16, LP_FILTER_ORDER);
+
+    ff_acelp_lspd2lpc(lsp, lpc, LP_FILTER_ORDER / 2);
+    ff_acelp_lspd2lpc(lsp_variab, lpc_variab, LP_FILTER_ORDER / 2);
+
+    /* the filter without variability is used to normalize the level and
+       by the postfilter */
+    for (sub = 0; sub < 4; sub++)
+        memcpy(p->lpc[sub], lpc, sizeof(*lpc) * LP_FILTER_ORDER);
+
+    /* level normalization with the prediction gain of the filter, the
+       reference decoder normalizes with the gain of this very frame, its
+       average is only used for the variability of the spectrum */
+    log_pg = pred_gain_log(lpc);
+    p->log_pg_mean = 0.9f * p->log_pg_mean + 0.1f * log_pg;
+
+    level = exp2f(log_en + 4.0f - log_pg + p->log_en_adjust) *
+            CN_LEVEL_SCALE;
+
+    cn_synthesize(p, lpc_variab, level, new_state, buf_out);
+
+    if (new_state == DTX_MUTE) {
+        /* mute the comfort noise, nothing has been updated for a while */
+        period = p->since_last_sid;
+        if (period > 32)
+            period = 32;
+        if (period <= 0)
+            period = 8;
+        p->sid_period_inv = 1.0f / period;
+        p->since_last_sid  = 0;
+        p->cn_log_en_old   = p->cn_log_en;
+        p->cn_log_en      -= 0.125f;
+    }
+
+    if (p->sid_frame &&
+        (p->valid_data || (!p->valid_data && p->hangover_added))) {
+        p->since_last_sid = 0;
+        p->data_updated   = 1;
+    }
+
+    p->dtx_state = new_state;
+}
+
 static int amrnb_decode_frame(AVCodecContext *avctx, AVFrame *frame,
                               int *got_frame_ptr, AVPacket *avpkt)
 {
@@ -976,21 +1601,95 @@ static int amrnb_decode_frame(AVCodecContext *avctx, 
AVFrame *frame,
         float synth_fixed_gain;                  // the fixed gain that 
synthesis should use
         const float *synth_fixed_vector;         // pointer to the fixed 
vector that synthesis should use
         float *buf_out = (float *)frame->extended_data[ch];
-        int channel_size;
+        /* Frames carrying no data only consist of the table of contents, so
+           a packet may hold no more than one byte per channel. */
+        if (buf_size < 1)
+            return AVERROR_INVALIDDATA;
+
+        int mode = buf[0] >> 3 & 0x0F;
+        int channel_size, rx_type, new_state;
+        int sid_ind[5] = { 0 };
+        int sid_mode = 0;
+        float synth[AMR_BLOCK_SIZE];             // synthesis before 
post-filtering
         int i, subframe;
 
-        p->cur_frame_mode = unpack_bitstream(p, buf, buf_size);
-        if (p->cur_frame_mode == NO_DATA) {
-            av_log(avctx, AV_LOG_ERROR, "Corrupt bitstream\n");
+        /* Frames carrying no data only consist of the table of contents, and
+           so do the frame types between the SID one and NO_DATA, which are
+           reserved: the reference decoder treats them like NO_DATA. */
+        if (mode == NO_DATA || (mode > MODE_DTX && mode < NO_DATA))
+            channel_size = 1;
+        else
+            channel_size = frame_sizes_nb[mode] + 1; // +7 for rounding and +8 
for TOC
+
+        if (channel_size > buf_size) {
+            av_log(avctx, AV_LOG_ERROR, "Frame too small (%d bytes)\n",
+                   buf_size);
             return AVERROR_INVALIDDATA;
         }
-        if (p->cur_frame_mode == MODE_DTX) {
-            avpriv_report_missing_feature(avctx, "dtx mode");
-            av_log(avctx, AV_LOG_INFO, "Note: libopencore_amrnb supports 
dtx\n");
-            return AVERROR_PATCHWELCOME;
+
+        p->bad_frame_indicator = (buf[0] & 0x4) != 0x4; // quality bit
+
+        /* Classify the received frame for the comfort noise state machine */
+        if (mode == NO_DATA || (mode > MODE_DTX && mode < NO_DATA)) {
+            rx_type = RX_NO_DATA;
+        } else if (mode == MODE_DTX) {
+            int sid_update = decode_sid_frame(buf + 1, sid_ind, &sid_mode);
+
+            /* the comfort noise is scaled with the speech mode of the SID
+               frame, it is also needed by the frames carrying no data */
+            p->cur_frame_mode = sid_mode;
+
+            if (p->bad_frame_indicator)
+                rx_type = RX_SID_BAD;
+            else if (sid_update)
+                rx_type = RX_SID_UPDATE;
+            else
+                rx_type = RX_SID_FIRST;
+        } else if (p->bad_frame_indicator) {
+            rx_type = RX_SPEECH_BAD;
+        } else {
+            rx_type = RX_SPEECH_GOOD;
+        }
+
+        if (rx_type == RX_SPEECH_GOOD || rx_type == RX_SPEECH_BAD) {
+            p->cur_frame_mode = unpack_bitstream(p, buf, buf_size);
+            if (p->cur_frame_mode == NO_DATA) {
+                av_log(avctx, AV_LOG_ERROR, "Corrupt bitstream\n");
+                return AVERROR_INVALIDDATA;
+            }
+        }
+
+        new_state = dtx_handler(p, rx_type);
+
+        if (new_state != DTX_SPEECH) {
+            /* Comfort noise is generated for SID frames and, once a silent
+               period has started, for the frames carrying no data at all */
+            cn_frame(p, new_state, sid_ind, p->cur_frame_mode, buf_out);
+
+            p->acelpf_ctx.acelp_apply_order_2_transfer_function(
+                buf_out, buf_out, highpass_zeros, highpass_poles,
+                highpass_gain * AMR_SAMPLE_SCALE, p->high_pass_mem,
+                AMR_BLOCK_SIZE);
+
+            buf += channel_size;
+            buf_size -= channel_size;
+            continue;
+        }
+
+        p->dtx_state = new_state;
+
+        if (p->bad_frame_indicator) {
+            /* Outside of a silent period no comfort noise parameters are
+               available, so the frame is output as silence. It still
+               produces a full frame of samples, keeping the frame count and
+               the timing of the decoded stream correct. */
+            av_samples_set_silence(&frame->extended_data[ch], 0, 
frame->nb_samples, 1, AV_SAMPLE_FMT_FLT);
+            p->prev_bf = 1;
+            buf += channel_size;
+            buf_size -= channel_size;
+            continue;
         }
 
-        channel_size = frame_sizes_nb[p->cur_frame_mode] + 1; // +7 for 
rounding and +8 for TOC
         if (p->cur_frame_mode == MODE_12k2) {
             lsf2lsp_5(p);
         } else
@@ -1032,6 +1731,16 @@ static int amrnb_decode_frame(AVCodecContext *avctx, 
AVFrame *frame,
                                       p->prediction_error,
                                       energy_mean[p->cur_frame_mode], 
energy_pred_fac);
 
+            /* 3GPP TS 26.091 clause 7.2.2: the gains of the first usable frame
+               after an unusable one are limited to the ones of the last usable
+               frame, so that the speech does not start at full level */
+            if (p->prev_bf) {
+                p->pitch_gain[4] = FFMIN(p->pitch_gain[4], p->prev_pitch_gain);
+                p->fixed_gain[4] = FFMIN(p->fixed_gain[4], p->prev_fixed_gain);
+            }
+            p->prev_pitch_gain = p->pitch_gain[4];
+            p->prev_fixed_gain = p->fixed_gain[4];
+
             // The excitation feedback is calculated without any processing 
such
             // as fixed gain smoothing. This isn't mentioned in the 
specification.
             for (i = 0; i < AMR_SUBFRAME_SIZE; i++)
@@ -1064,6 +1773,10 @@ static int amrnb_decode_frame(AVCodecContext *avctx, 
AVFrame *frame,
                 synthesis(p, p->lpc[subframe], synth_fixed_gain,
                           synth_fixed_vector, &p->samples_in[LP_FILTER_ORDER], 
1);
 
+            memcpy(synth + subframe * AMR_SUBFRAME_SIZE,
+                   &p->samples_in[LP_FILTER_ORDER],
+                   AMR_SUBFRAME_SIZE * sizeof(*synth));
+
             postfilter(p, p->lpc[subframe], buf_out + subframe * 
AMR_SUBFRAME_SIZE);
 
             // update buffers and history
@@ -1077,6 +1790,11 @@ static int amrnb_decode_frame(AVCodecContext *avctx, 
AVFrame *frame,
                                                             highpass_gain * 
AMR_SAMPLE_SCALE,
                                                             p->high_pass_mem, 
AMR_BLOCK_SIZE);
 
+        p->prev_bf = 0;
+
+        /* keep the noise parameters of this speech frame for the next SID */
+        dtx_activity_update(p, synth);
+
         /* Update averaged lsf vector (used for fixed gain smoothing).
          *
          * Note that lsf_avg should not incorporate the current frame's LSFs
diff --git a/tests/data/fate/amrnb-nodata-cases.amr 
b/tests/data/fate/amrnb-nodata-cases.amr
new file mode 100644
index 
0000000000000000000000000000000000000000..2f118ab4546256a93e2a179f14be5651e8ab9e1a
GIT binary patch
literal 1569
zcmY#Zbo344VzGH@;Qwjw&-jMlUTrMXoYd-nw&|ZZdF|dKmWf);HE)luv$@$ZX93H#
zXBzjPyx6!!BjnjXmQZ1J`!D(m`aE)WMl92~l=mNT54g_ZFkOLVn(_Poztijwc%DD(
z!!k|t#hWkPin9+HpP#^D^XyaK-{<yG7VBmmVeu1w>vQDbgn0`c7TU1%&0V$r=&4kW
zlpFR_SUx{f`}eoM|Hq3Q(*l+sS|`tZ?lqH2Dhm!_`7BUxe>RE#wb;_jYgi^Syy1E2
zxS!#1(&+^(pFi~T-#x{bAv8nx7fYYO<<6JO>ZfOTcdTRie5btb%|iV{5rM36EH?#D
zzq@;mkMCO0t7|~d)!9utq?!9M)k&g;<+I@F9dE1WO;Ko@c8g_Vah1)bqm^gg)^hA%
zc`kU`rt@u{V|!ny0Lw(D`pQZ9yAl%5FWklQ*{Nm6+}$;64m<}2&$OKSGjFHMsk|}F
z-ox_aLr&(?lLvn9-q<mLWx)ZVbw~3jxTGle{9&=l`Lyn76T`=AN^A;1j?l`l{RIv`
zb%Ktu*u;NYclS_gomJbb7$8Sz-rK$}i3`_s*s<7%d|I>bP^+E7n;Kwj#V6&xJskSe
z%u)UTOJDrDwO0=+<~(|y4D>?Ir+rTkHySTLI`at2=lJz@|2qX=w@-Wol!>2y@9g=h
z|Jmkzy~i>!KJLuX+Kr!!UzsgpnfPJ3+|Nd~c2DEOXGRl(lSIX6LKsa5pc27nG$9;5
F0|0}S9>@Ry

literal 0
HcmV?d00001

diff --git a/tests/data/fate/amrnb-sid-after-nodata.amr 
b/tests/data/fate/amrnb-sid-after-nodata.amr
new file mode 100644
index 
0000000000000000000000000000000000000000..6bc7e49f5a1f25c48f89c0e98b714957d5430896
GIT binary patch
literal 439
zcmV;o0Z9HMAwf-23M@qs1>n#d+40R%@Cwfb{>L(7bL1=`9CP4-+@sQv1hin|F}Alc
zErO~nAQpMxAyY&=U0yNb<#P2bv^@H9EJ+a0;1F5<9>=)kbt;!00JfdIb}UG8dEi0Y
zRx-qdX%S3!`9_#sm<B9JZ_(fY8&-d=;ow803HlMOjA!sHNh(?30NI{@=M7_{Bn@n&
zEbt;0EI^~f;AZ@jtk+=+W!JB4BO@una4bkiHQ=x?oOYs3HJu<BMi|na@pvpDD#PI*
z4jHYMI^eKd&AKGD(ZD<`KyshLvKqcO7ja3*UcN`0tm0H4EFru#;g&i4<PzLZ#KOr<
zIF`-oR4hbtHQ+%mqiXW_)yg<Z4<gh3v9v5A5!<9>8T(99gS`?CZ!qv);XODkAV=@u
z1W&#vPJ^C#s%f192U=Frd_+0%^JxHle0+R-d@Lq6?zF%zk6I6tR6qD{6DM@<#CR-d
z!Jc`oEUQ>yhE6a@%)Z%>VA}L7bc@pQNX7&3a`y?cmQDOD@>7AfEL?AM;W59OoH;y0
hDzk?Ahs2r!0xVMy+2BG7WSXs^)F2|4`EYzEm+)m9&MyD}

literal 0
HcmV?d00001

diff --git a/tests/data/fate/amrnb-sid-update.amr 
b/tests/data/fate/amrnb-sid-update.amr
new file mode 100644
index 
0000000000000000000000000000000000000000..ad5c9174d7f12b354bf0c229f271132a4ef1ccfd
GIT binary patch
literal 675
zcmV;U0$lwgAwf-23IxyUcQ7~KgmyAoK57IvJf|Z&lDEJbDzsb#HyEn|IopteXNypJ
z1Qk4Y12>|S_OC6<mIOCEXf-?TcgSxOGFk*T>QDnaCUt`hu5|JQWjy;ef6lrMeohXY
z1U5VeG8>DAQUNrqh6GqV3$+S<`a;tW9##ZcJk|AGi?SU0P8GTY&pa>lIoyGSfQMt^
z1UBkF1v5)IGk*h}2m~@7;-y!^wMgT3;64OXXSCC6rziReqb}|Q&ln?>8^1RSDJT2r
z1pgNA!K4GYT^dg-gakKd9Pt~P7E*+=%nAe~i$I1`rnzplA)(d;HYj(~R)&>IIzrR%
z1U50t#dk)v8Bg7IdIV|(UP2m^svOQ$Am#+mnCtUbi)DQ9_F=>XNj!dYJ<x*M1-#-m
z1Tq-d#D^lqi*S&D>;x4km(f|)Kxh3N7RUtu>cRtEha+T>N=%#t&pg@jJ;THI>!we*
z1pk5Whh2md*6;^0W&~xJE%Rv4tHZ-lYD@%bC`B_Jk>lT9AESaqIq~yp0DOFWL^<*E
zZV-HYe0&5v<uFh3z4>2&`$~ocmO>_f`GzmZ$=2TD1d%F(eC@@qJlTkxpaj<DDBtAh
zv9u^u=Kcg#6DL3PFCZ@r9zHSzmI@=k#79uq2tb!01eP=JkNuWEz)8=;OazuG=<M{1
zA)CT8&yWN>=JJmH=RaCFuA0OIP801+#KDlBp+KQL1dg4muf@t`2xQnllmzqUCjR}8
zkND_pHE;y@D#^_AjW!x$c2iUY^ALYO&0-Jh7Nygy1d#yj4Cz6?0OMlIpak>qj}P6-
z4rmgXE&2qG5Yvh2rhk`cONg!n^W1lS?4d8hSWyLD1lbYG?%mD~4%SlY)&!9%em;`I
JDQ)p(LKy#-Gf)5k

literal 0
HcmV?d00001

diff --git a/tests/data/fate/amrnb-state-resume-update.amr 
b/tests/data/fate/amrnb-state-resume-update.amr
new file mode 100644
index 
0000000000000000000000000000000000000000..b4ad4d39eeece8e7903e5bba896105a87eb0945a
GIT binary patch
literal 675
zcmV;U0$lwgAwf-23IxyUcQ7~KgmyAoK57IvJf|Z&lDEJbDzsb#HyEn|IopteXNypJ
z1Qk4Y12>|S_OC6<mIOCEXf-?TcgSxOGFk*T>QDnaCUt`hu5|JQWjy;ef6lrMeohXY
z1U5VeG8>DAQUNrqh6GqV3$+S<`a;tW9##ZcJk|AGi?SU0P8GTY&pa>lIoyGSfQMt^
z1UBkF1v5)IGk*h}2m~@7;-y!^wMgT3;64OXXSCC6rziReqb}|Q&ln?>8^1RSDJT2r
z1pgNA!K4GYT^dg-gakKd9Pt~P7E*+=%nAe~i$I1`rnzplA)(d;HYj(~R)&>IIzrR%
z1U50t#dk)v8Bg7IdIV|(UP2m^svOQ$Am#+mnCtUbi)DQ9_F=>XNj!dYJ<x*M1-#-m
z1Tq-d#D^lqi*S&D>;x4km(f|)Kxh3N7RUtu>cRtEha+T>N=%#t&pg@jJ;THI>!we*
z1pk5Whh2md*6;^0W&~xJE%Rv4tHZ-lYD@%bC`B_Jk>lT9AESaqIq~yp0DOFW1U%(1
zPx8I_Ux52ch6I*ECV%;cFUZN(-r@w2DuaCO#jZTrh@7AV*5)YR<mj=qC{*VD1XU9!
zKlCpkFAN?&G6a?iBfrE)P}c}Rmmma|Gw+Z6mOsEr&%;avmMQ4$^ot>z!ZXj11U%;Q
zj{WC9S~#wn!~{+g?M%eMke;DHp*#eRovN?J%4G;-*gup6^X4Z0{g03M=xjA`1o$e+
z%=3*l8e(=+R0Q)7e?QG)59=1C)2sxM0PGCuLB9awV#}Ze^YD)k-O3JV5|}Oe1db5X
ziRq?)muO3ft_1VkcYf@lFTz++1zrT%5zFq~&JGUNQtH+Okt%*ZlENu%@nu37L^<*E
JZV-HYe0(gDGf)5k

literal 0
HcmV?d00001

diff --git a/tests/fate/amrnb.mak b/tests/fate/amrnb.mak
index 007174de0a..083bd178bb 100644
--- a/tests/fate/amrnb.mak
+++ b/tests/fate/amrnb.mak
@@ -33,6 +33,47 @@ fate-amrnb-12k2: REF = $(SAMPLES)/amrnb/12.2k.pcm
 FATE_AMRNB_REMUX-$(call REMUX, AMR, AMR_PARSER ARESAMPLE_FILTER) += 
fate-amrnb-remux
 fate-amrnb-remux: CMD = transcode amr $(TARGET_SAMPLES)/amrnb/10.2k.amr amr 
"-c copy" "-c copy -t 1"
 
+# Check the first speech frame after SID_UPDATE and a NO_DATA interval.
+FATE_AMRNB_DTX-$(call ALLYES, AMR_DEMUXER AMRNB_DECODER ARESAMPLE_FILTER 
ASTATS_FILTER NULL_MUXER FILE_PROTOCOL) += fate-amrnb-sid-update
+fate-amrnb-sid-update: CMD = ffmpeg -i 
$(TARGET_PATH)/tests/data/fate/amrnb-sid-update.amr -af 
"atrim=start_sample=6240:end_sample=6400,aresample=osf=flt,astats=metadata=0:reset=0"
 -c:a pcm_f32le -f null -
+fate-amrnb-sid-update: CMP = grep
+fate-amrnb-sid-update: REF = RMS level dB: -22\.
+
+# Check a SID_UPDATE while already in DTX.
+FATE_AMRNB_DTX-$(call ALLYES, AMR_DEMUXER AMRNB_DECODER ARESAMPLE_FILTER 
ASTATS_FILTER NULL_MUXER FILE_PROTOCOL) += fate-amrnb-sid-update-during-dtx
+fate-amrnb-sid-update-during-dtx: CMD = ffmpeg -i 
$(TARGET_PATH)/tests/data/fate/amrnb-sid-update.amr -af 
"atrim=start_sample=5440:end_sample=5600,aresample=osf=flt,astats=metadata=0:reset=0"
 -c:a pcm_f32le -f null -
+fate-amrnb-sid-update-during-dtx: CMP = grep
+fate-amrnb-sid-update-during-dtx: REF = RMS level dB: -58\.
+
+# Check SID_UPDATE parameters are adopted immediately after speech resumes.
+FATE_AMRNB_DTX-$(call ALLYES, AMR_DEMUXER AMRNB_DECODER ARESAMPLE_FILTER 
ASTATS_FILTER NULL_MUXER FILE_PROTOCOL) += fate-amrnb-state-resume-update
+fate-amrnb-state-resume-update: CMD = ffmpeg -i 
$(TARGET_PATH)/tests/data/fate/amrnb-state-resume-update.amr -af 
"atrim=start_sample=8640:end_sample=8800,aresample=osf=flt,astats=metadata=0:reset=0"
 -c:a pcm_f32le -f null -
+fate-amrnb-state-resume-update: CMP = grep
+fate-amrnb-state-resume-update: REF = RMS level dB: -29\.
+
+# Check NO_DATA frames in speech state ignore Q and reserved FT.
+FATE_AMRNB_DTX-$(call ALLYES, AMR_DEMUXER AMRNB_DECODER ARESAMPLE_FILTER 
ASTATS_FILTER NULL_MUXER FILE_PROTOCOL) += fate-amrnb-nodata-ft15-q1 
fate-amrnb-nodata-ft15-q0 fate-amrnb-nodata-reserved-q1
+fate-amrnb-nodata-ft15-q1: CMD = ffmpeg -i 
$(TARGET_PATH)/tests/data/fate/amrnb-nodata-cases.amr -af 
"atrim=start_sample=3200:end_sample=3360,aresample=osf=flt,astats=metadata=0:reset=0"
 -c:a pcm_f32le -f null -
+fate-amrnb-nodata-ft15-q1: CMP = grep
+fate-amrnb-nodata-ft15-q1: REF = RMS level dB: -[23][019]\.
+fate-amrnb-nodata-ft15-q0: CMD = ffmpeg -i 
$(TARGET_PATH)/tests/data/fate/amrnb-nodata-cases.amr -af 
"atrim=start_sample=9760:end_sample=9920,aresample=osf=flt,astats=metadata=0:reset=0"
 -c:a pcm_f32le -f null -
+fate-amrnb-nodata-ft15-q0: CMP = grep
+fate-amrnb-nodata-ft15-q0: REF = RMS level dB: -28\.
+fate-amrnb-nodata-reserved-q1: CMD = ffmpeg -i 
$(TARGET_PATH)/tests/data/fate/amrnb-nodata-cases.amr -af 
"atrim=start_sample=16320:end_sample=16480,aresample=osf=flt,astats=metadata=0:reset=0"
 -c:a pcm_f32le -f null -
+fate-amrnb-nodata-reserved-q1: CMP = grep
+fate-amrnb-nodata-reserved-q1: REF = RMS level dB: -29\.
+
+# A SID frame arriving after a lost frame must still update the comfort
+# noise: the lost frame is assumed to be speech, so the noise parameters
+# are taken over from it instead of collapsing to silence.
+FATE_AMRNB_DTX-$(call ALLYES, AMR_DEMUXER AMRNB_DECODER ARESAMPLE_FILTER 
ASTATS_FILTER NULL_MUXER FILE_PROTOCOL) += fate-amrnb-sid-after-nodata
+fate-amrnb-sid-after-nodata: CMD = ffmpeg -i 
$(TARGET_PATH)/tests/data/fate/amrnb-sid-after-nodata.amr -af 
"atrim=start_sample=2720:end_sample=2880,aresample=osf=flt,astats=metadata=0:reset=0"
 -c:a pcm_f32le -f null -
+fate-amrnb-sid-after-nodata: CMP = grep
+fate-amrnb-sid-after-nodata: REF = RMS level dB: -2[01]\.
+
+fate-amrnb: $(FATE_AMRNB_DTX-yes)
+FATE_FFMPEG += $(FATE_AMRNB_DTX-yes)
+
 $(FATE_AMRNB): CMP = stddev
 
 FATE_SAMPLES_FFMPEG-$(call PCM, AMR, AMRNB, ARESAMPLE_FILTER) += $(FATE_AMRNB)
-- 
2.54.0


>From a6331c537bda24246b56a8c8e9f79cdb570d3053 Mon Sep 17 00:00:00 2001
From: Jun Zhao <[email protected]>
Date: Sat, 10 Oct 2026 15:49:53 +0800
Subject: [PATCH 5/8] avcodec/amrnbdec: handle no-data frames independently of
 the quality bit

The Q bit of an FT=15 frame chose between digital silence and
decoding with stale parameters. TS 26.093 clause 5.2.3 treats
SPEECH_BAD and NO_DATA the same in speech mode, so both now go
through the speech decoder. Concealment follows in the next commits.

Signed-off-by: Jun Zhao <[email protected]>
---
 libavcodec/amrnbdec.c                     |  12 ------------
 tests/data/fate/amrnb-nodata-qswap-q0.amr | Bin 0 -> 1569 bytes
 tests/data/fate/amrnb-nodata-qswap-q1.amr | Bin 0 -> 1569 bytes
 tests/fate/amrnb.mak                      |   8 ++++++++
 4 files changed, 8 insertions(+), 12 deletions(-)
 create mode 100644 tests/data/fate/amrnb-nodata-qswap-q0.amr
 create mode 100644 tests/data/fate/amrnb-nodata-qswap-q1.amr

diff --git a/libavcodec/amrnbdec.c b/libavcodec/amrnbdec.c
index 60ae300d91..033e4445b7 100644
--- a/libavcodec/amrnbdec.c
+++ b/libavcodec/amrnbdec.c
@@ -1678,18 +1678,6 @@ static int amrnb_decode_frame(AVCodecContext *avctx, 
AVFrame *frame,
 
         p->dtx_state = new_state;
 
-        if (p->bad_frame_indicator) {
-            /* Outside of a silent period no comfort noise parameters are
-               available, so the frame is output as silence. It still
-               produces a full frame of samples, keeping the frame count and
-               the timing of the decoded stream correct. */
-            av_samples_set_silence(&frame->extended_data[ch], 0, 
frame->nb_samples, 1, AV_SAMPLE_FMT_FLT);
-            p->prev_bf = 1;
-            buf += channel_size;
-            buf_size -= channel_size;
-            continue;
-        }
-
         if (p->cur_frame_mode == MODE_12k2) {
             lsf2lsp_5(p);
         } else
diff --git a/tests/data/fate/amrnb-nodata-qswap-q0.amr 
b/tests/data/fate/amrnb-nodata-qswap-q0.amr
new file mode 100644
index 
0000000000000000000000000000000000000000..72078fa779334aec6f146edeaf3065a6a3428828
GIT binary patch
literal 1569
zcmY#Zbo344VzGH@;Qwjw&-jMlUTrMXoYd-nw&|ZZdF|dKmWf);HE)luv$@$ZX93H#
zXBzjPyx6!!BjnjXmQZ1J`!D(m`aE)WMl92~l=mNT54g_ZFkOLVn(_Poztijwc%DD(
z!!k|t#hWkPin9+HpP#^D^XyaK-{<yG7VBmmVeu1w>vQDbgn0`c7TU1%&0V$r=&4kW
zlpFR_SUx{f`}eoM|Hq3Q(*l+sS|`tZ?lqH2Dhm!_`7BUxe>RE#wb;_jYgi^Syy1E2
zxS!#1(&+^(pFi~T-#x{bAv8nx7fYYO<<6JO>ZfOTcdTRie5btb%|iV{5rM36EH?#D
zzq@;mkMCO0t7|~d)!9utq?!9M)k&g)<+I@F9dE1WO;Ko@c8g_Vah1)bqm^gg)^hA%
zc`kU`rt@u{V|!ny0Lw(D`pQZ9yAl%5FWklQ*{Nm6+}$;64m<}2&$OKSGjFHMsk|}F
z-ox_aLr&(?lLvn9-q<mLWx)ZVbw~3jxTGle{9&=l`Lyn76T`=AN^A;1j?l`l{RIv`
zb%Ktu*u;NYclS_gomJbb7$8Sz-rK$}i3`_s*s<7%d|I>bP^+E7n;Kwj#V6&xJskSe
z%u)UTOJDrDwO0=+<~(|yT*LA?=hMEYhZ~KTADwxG<#YUcyZ@bnuiGa+0?NcszjyZh
z)c<UAzTRV*7$0}$Xzj+&#jng3u}u81T<&KhTf3+6;WMKNVKgC(CIo1S;4@kx96kd8
DZ#f>s

literal 0
HcmV?d00001

diff --git a/tests/data/fate/amrnb-nodata-qswap-q1.amr 
b/tests/data/fate/amrnb-nodata-qswap-q1.amr
new file mode 100644
index 
0000000000000000000000000000000000000000..2f118ab4546256a93e2a179f14be5651e8ab9e1a
GIT binary patch
literal 1569
zcmY#Zbo344VzGH@;Qwjw&-jMlUTrMXoYd-nw&|ZZdF|dKmWf);HE)luv$@$ZX93H#
zXBzjPyx6!!BjnjXmQZ1J`!D(m`aE)WMl92~l=mNT54g_ZFkOLVn(_Poztijwc%DD(
z!!k|t#hWkPin9+HpP#^D^XyaK-{<yG7VBmmVeu1w>vQDbgn0`c7TU1%&0V$r=&4kW
zlpFR_SUx{f`}eoM|Hq3Q(*l+sS|`tZ?lqH2Dhm!_`7BUxe>RE#wb;_jYgi^Syy1E2
zxS!#1(&+^(pFi~T-#x{bAv8nx7fYYO<<6JO>ZfOTcdTRie5btb%|iV{5rM36EH?#D
zzq@;mkMCO0t7|~d)!9utq?!9M)k&g;<+I@F9dE1WO;Ko@c8g_Vah1)bqm^gg)^hA%
zc`kU`rt@u{V|!ny0Lw(D`pQZ9yAl%5FWklQ*{Nm6+}$;64m<}2&$OKSGjFHMsk|}F
z-ox_aLr&(?lLvn9-q<mLWx)ZVbw~3jxTGle{9&=l`Lyn76T`=AN^A;1j?l`l{RIv`
zb%Ktu*u;NYclS_gomJbb7$8Sz-rK$}i3`_s*s<7%d|I>bP^+E7n;Kwj#V6&xJskSe
z%u)UTOJDrDwO0=+<~(|y4D>?Ir+rTkHySTLI`at2=lJz@|2qX=w@-Wol!>2y@9g=h
z|Jmkzy~i>!KJLuX+Kr!!UzsgpnfPJ3+|Nd~c2DEOXGRl(lSIX6LKsa5pc27nG$9;5
F0|0}S9>@Ry

literal 0
HcmV?d00001

diff --git a/tests/fate/amrnb.mak b/tests/fate/amrnb.mak
index 083bd178bb..ad4547cf90 100644
--- a/tests/fate/amrnb.mak
+++ b/tests/fate/amrnb.mak
@@ -71,6 +71,14 @@ fate-amrnb-sid-after-nodata: CMD = ffmpeg -i 
$(TARGET_PATH)/tests/data/fate/amrn
 fate-amrnb-sid-after-nodata: CMP = grep
 fate-amrnb-sid-after-nodata: REF = RMS level dB: -2[01]\.
 
+# FT15 must be handled identically for Q=0 and Q=1.
+FATE_AMRNB_DTX-$(call ALLYES, AMR_DEMUXER AMRNB_DECODER ARESAMPLE_FILTER 
ASTATS_FILTER NULL_MUXER FILE_PROTOCOL) += fate-amrnb-nodata-q0 
fate-amrnb-nodata-q1
+fate-amrnb-nodata-q0: CMD = ffmpeg -i 
$(TARGET_PATH)/tests/data/fate/amrnb-nodata-qswap-q0.amr -af 
"atrim=start_sample=3200:end_sample=3360,aresample=osf=flt,astats=metadata=0:reset=0"
 -c:a pcm_f32le -f null -
+fate-amrnb-nodata-q0: CMP = grep
+fate-amrnb-nodata-q0: REF = RMS level dB: -2[0-9]\.
+fate-amrnb-nodata-q1: CMD = ffmpeg -i 
$(TARGET_PATH)/tests/data/fate/amrnb-nodata-qswap-q1.amr -af 
"atrim=start_sample=3200:end_sample=3360,aresample=osf=flt,astats=metadata=0:reset=0"
 -c:a pcm_f32le -f null -
+fate-amrnb-nodata-q1: CMP = grep
+fate-amrnb-nodata-q1: REF = RMS level dB: -2[0-9]\.
 fate-amrnb: $(FATE_AMRNB_DTX-yes)
 FATE_FFMPEG += $(FATE_AMRNB_DTX-yes)
 
-- 
2.54.0


>From f16c5224c9918d8cc24057b765aa03410b7aa9df Mon Sep 17 00:00:00 2001
From: Jun Zhao <[email protected]>
Date: Sat, 10 Oct 2026 15:54:50 +0800
Subject: [PATCH 6/8] avcodec/amrnbdata: add the error concealment tables

Add the window build_CN_param() in 3GPP TS 26.073 uses as the
pseudo-random source when substituting a lost speech frame. It
matches the reference element by element. The following commit
uses it.

Signed-off-by: Jun Zhao <[email protected]>
---
 libavcodec/amrnbdata.h | 32 ++++++++++++++++++++++++++++++++
 1 file changed, 32 insertions(+)

diff --git a/libavcodec/amrnbdata.h b/libavcodec/amrnbdata.h
index f38efb476a..fb91a2057c 100644
--- a/libavcodec/amrnbdata.h
+++ b/libavcodec/amrnbdata.h
@@ -1701,4 +1701,36 @@ static const float dtx_log_en_adjust[9] = {
     -0.499512f, -0.428711f, -0.357422f, -0.286133f, -0.214844f, -0.143555f, 
-0.072266f, 0.000000f, 0.000000f
 };
 
+/*
+ * Hamming_cos window for LPC analysis, from 3GPP TS 26.073 (window.tab).
+ * It is used as the pseudo-random source for parameter substitution in
+ * lost AMR-NB frames.
+ */
+static const int16_t window_200_40[240] = {
+      2621,   2623,   2629,   2638,   2651,   2668,   2689,   2713,   2741,   
2772,
+      2808,   2847,   2890,   2936,   2986,   3040,   3097,   3158,   3223,   
3291,
+      3363,   3438,   3517,   3599,   3685,   3774,   3867,   3963,   4063,   
4166,
+      4272,   4382,   4495,   4611,   4731,   4853,   4979,   5108,   5240,   
5376,
+      5514,   5655,   5800,   5947,   6097,   6250,   6406,   6565,   6726,   
6890,
+      7057,   7227,   7399,   7573,   7750,   7930,   8112,   8296,   8483,   
8672,
+      8863,   9057,   9252,   9450,   9650,   9852,  10055,  10261,  10468,  
10677,
+     10888,  11101,  11315,  11531,  11748,  11967,  12187,  12409,  12632,  
12856,
+     13082,  13308,  13536,  13764,  13994,  14225,  14456,  14688,  14921,  
15155,
+     15389,  15624,  15859,  16095,  16331,  16568,  16805,  17042,  17279,  
17516,
+     17754,  17991,  18228,  18465,  18702,  18939,  19175,  19411,  19647,  
19882,
+     20117,  20350,  20584,  20816,  21048,  21279,  21509,  21738,  21967,  
22194,
+     22420,  22644,  22868,  23090,  23311,  23531,  23749,  23965,  24181,  
24394,
+     24606,  24816,  25024,  25231,  25435,  25638,  25839,  26037,  26234,  
26428,
+     26621,  26811,  26999,  27184,  27368,  27548,  27727,  27903,  28076,  
28247,
+     28415,  28581,  28743,  28903,  29061,  29215,  29367,  29515,  29661,  
29804,
+     29944,  30081,  30214,  30345,  30472,  30597,  30718,  30836,  30950,  
31062,
+     31170,  31274,  31376,  31474,  31568,  31659,  31747,  31831,  31911,  
31988,
+     32062,  32132,  32198,  32261,  32320,  32376,  32428,  32476,  32521,  
32561,
+     32599,  32632,  32662,  32688,  32711,  32729,  32744,  32755,  32763,  
32767,
+     32767,  32741,  32665,  32537,  32359,  32129,  31850,  31521,  31143,  
30716,
+     30242,  29720,  29151,  28538,  27879,  27177,  26433,  25647,  24821,  
23957,
+     23055,  22117,  21145,  20139,  19102,  18036,  16941,  15820,  14674,  
13505,
+     12315,  11106,   9879,   8637,   7381,   6114,   4838,   3554,   2264,    
971
+};
+
 #endif /* AVCODEC_AMRNBDATA_H */
-- 
2.54.0


>From c14fb50be9e9ec5285a9f059fd51fb44ef512dbb Mon Sep 17 00:00:00 2001
From: Jun Zhao <[email protected]>
Date: Sat, 10 Oct 2026 16:13:16 +0800
Subject: [PATCH 7/8] avcodec/amrnbdec: conceal lost speech frames

A lost or corrupt speech frame was emitted as digital silence.
TS 26.093 clause 5.2.3 requires the substitution and muting
described in TS 26.091.

Follow TS 26.073 in floating point: a bad-frame state machine,
pseudo-random replacement of a NO_DATA frame, LSF concealment
towards the mean, median gain attenuation, and gradual pitch-lag
degradation. The NO_DATA FATE levels are tightened to the reference.

Concealed frames stay within 0.8 dB of the reference. Speech
outside a silent period is unchanged.

Signed-off-by: Jun Zhao <[email protected]>
---
 libavcodec/amrnbdec.c | 418 +++++++++++++++++++++++++++++++++++++-----
 tests/fate/amrnb.mak  |   5 +-
 2 files changed, 378 insertions(+), 45 deletions(-)

diff --git a/libavcodec/amrnbdec.c b/libavcodec/amrnbdec.c
index 033e4445b7..8a935500b1 100644
--- a/libavcodec/amrnbdec.c
+++ b/libavcodec/amrnbdec.c
@@ -81,6 +81,19 @@
 #define MIN_LSF_SPACING    (50.0488 / 8000.0) ///< Ensures stability of LPC 
filter
 #define PITCH_LAG_MIN_MODE_12k2          18   ///< Lower bound on decoded lag 
search in 12.2kbit/s mode
 
+/**
+ * Weights of the past LSFs and of their mean when concealing a lost frame,
+ * ALPHA and ONE_ALPHA of 3GPP TS 26.073. The 12.2 kbit/s mode uses a
+ * different pair than all the other modes do:
+ *
+ *   D_plsf_3()  d_plsf_3.c:51-52  29491 /  3277 (0.90 / 0.10)
+ *   D_plsf_5()  d_plsf_5.c:49-50  31128 /  1639 (0.95 / 0.05)
+ */
+#define LSF_ALPHA        (29491.0f / 32768.0f)
+#define LSF_ONE_ALPHA    ( 3277.0f / 32768.0f)
+#define LSF_ALPHA_5      (31128.0f / 32768.0f)
+#define LSF_ONE_ALPHA_5  ( 1639.0f / 32768.0f)
+
 /** Initial energy in dB. Also used for bad frames (unimplemented). */
 #define MIN_ENERGY -14.0
 
@@ -192,8 +205,12 @@ typedef struct AMRContext {
     int         valid_data;                           ///< the CN parameters 
of this frame may be used
     int         data_updated;                         ///< CN parameters have 
been renewed at least once
     int         prev_bf;                              ///< the previous frame 
could not be used
-    float       prev_pitch_gain;                      ///< pitch gain of the 
last usable frame
-    float       prev_fixed_gain;                      ///< fixed gain of the 
last usable frame
+
+    /* Bad frame handling and error concealment state, 3GPP TS 26.091 */
+    int         bfh_state;                            ///< bad frame handling 
state (0..6)
+    uint32_t    no_data_seed;                         ///< pseudo-random 
source for lost frames
+    float       ec_gain_pbuf[5], ec_gain_past_pit, ec_gain_prev_pit;
+    float       ec_gain_gbuf[5], ec_gain_past_code, ec_gain_prev_code;
 } AMRContext;
 
 typedef struct AMRChannelsContext {
@@ -252,8 +269,26 @@ static av_cold int amrnb_decode_init(AVCodecContext *avctx)
         p->hangover_count  = DTX_HANG_CONST;
         p->dec_ana_elapsed = 32767;
         p->prev_bf         = 0;
-        p->prev_pitch_gain = 0.0f;
-        p->prev_fixed_gain = 0.0f;
+
+        /* Bad frame handling and error concealment state, matches the
+           Decoder_amr_reset() / ec_gain_*_reset() of 3GPP TS 26.073. */
+        p->bfh_state    = 0;
+        p->no_data_seed = 21845;
+        /* The pitch gains follow ec_gain_pitch_reset(). For the code gain,
+           the reference sets gbuf = 1 and prev_gc = 1 in a Q1 domain
+           (see ec_gain_code_reset(); d_gain_c.c:105 marks gain_code as Q1), 
i.e.
+           0.5 in the absolute domain. FFmpeg's fixed_gain[] absolute domain
+           could not be established and an experiment showed that 1.0 and
+           0.5 give frame-identical output because the recovery clamp never
+           binds, so 1.0 is kept deliberately. */
+        for (i = 0; i < 5; i++) {
+            p->ec_gain_pbuf[i] = 1640.0f / 16384.0f;
+            p->ec_gain_gbuf[i] = 1.0f;
+        }
+        p->ec_gain_past_pit  = 0.0f;
+        p->ec_gain_prev_pit  = 1.0f;
+        p->ec_gain_past_code = 0.0f;
+        p->ec_gain_prev_code = 1.0f;
 
         for (i = 0; i < LP_FILTER_ORDER; i++)
             p->cn_lsp[i] = p->cn_lsp_old[i] = cn_lsp_init[i];
@@ -376,14 +411,44 @@ static void lsf2lsp_for_mode12k2(AMRContext *p, double 
lsp[LP_FILTER_ORDER],
  * Decode a set of 5 split-matrix quantized lsf indexes into 2 lsp vectors.
  *
  * @param p                 pointer to the AMRContext
+ * @param bfi               the frame is unusable, conceal the LSF vector
  */
-static void lsf2lsp_5(AMRContext *p)
+static void lsf2lsp_5(AMRContext *p, int bfi)
 {
     const uint16_t *lsf_param = p->frame.lsf;
     float lsf_no_r[LP_FILTER_ORDER]; // LSFs without the residual vector
     const int16_t *lsf_quantizer[5];
+    float lsf_q[LP_FILTER_ORDER];
     int i;
 
+    if (bfi) {
+        /* Use the past LSFs slightly shifted towards their mean, and update
+           the prediction residual, see D_plsf_5() in 3GPP TS 26.073
+           (d_plsf_5.c:88-115). The LSFs are kept in Hertz while the
+           residual prev_lsf_r is in the codebook domain (LSF_R_FAC). */
+        for (i = 0; i < LP_FILTER_ORDER; i++)
+            lsf_q[i] = (LSF_ALPHA_5 * p->lsf_q[3][i] * 8000.0f +
+                        LSF_ONE_ALPHA_5 * lsf_5_mean[i]) * (1.0f / 8000.0f);
+
+        for (i = 0; i < LP_FILTER_ORDER; i++)
+            p->prev_lsf_r[i] = av_clip_int16(lrintf(
+                (lsf_q[i] * 8000.0f -
+                 (lsf_5_mean[i] + PRED_FAC_MODE_12k2 * p->prev_lsf_r[i] *
+                  LSF_R_FAC)) / LSF_R_FAC));
+
+        ff_set_min_dist_lsf(lsf_q, MIN_LSF_SPACING, LP_FILTER_ORDER);
+        interpolate_lsf(&p->acelpv_ctx, p->lsf_q, lsf_q);
+        ff_acelp_lsf2lspd(p->lsp[3], lsf_q, LP_FILTER_ORDER);
+
+        /* the first and second LSP vectors are concealed identically */
+        memcpy(p->lsp[1], p->lsp[3], sizeof(p->lsp[1]));
+        weighted_vector_sumd(p->lsp[0], p->prev_lsp_sub4, p->lsp[1], 0.5, 0.5,
+                             LP_FILTER_ORDER);
+        weighted_vector_sumd(p->lsp[2], p->lsp[1], p->lsp[3], 0.5, 0.5,
+                             LP_FILTER_ORDER);
+        return;
+    }
+
     lsf_quantizer[0] = lsf_5_1[lsf_param[0]];
     lsf_quantizer[1] = lsf_5_2[lsf_param[1]];
     lsf_quantizer[2] = lsf_5_3[lsf_param[2] >> 1];
@@ -405,15 +470,44 @@ static void lsf2lsp_5(AMRContext *p)
  * Decode a set of 3 split-matrix quantized lsf indexes into an lsp vector.
  *
  * @param p                 pointer to the AMRContext
+ * @param bfi               the frame is unusable, conceal the LSF vector
  */
-static void lsf2lsp_3(AMRContext *p)
+static void lsf2lsp_3(AMRContext *p, int bfi)
 {
     const uint16_t *lsf_param = p->frame.lsf;
-    int16_t lsf_r[LP_FILTER_ORDER]; // residual LSF vector
     float lsf_q[LP_FILTER_ORDER]; // quantified LSF vector
-    const int16_t *lsf_quantizer;
     int i, j;
 
+    if (bfi) {
+        /* Use the past LSFs slightly shifted towards their mean, and update
+           the prediction residual, see D_plsf_3() in 3GPP TS 26.073
+           (d_plsf_3.c:84-115). The LSFs are kept in Hertz while the residual
+           prev_lsf_r is in the codebook domain (LSF_R_FAC). */
+        for (i = 0; i < LP_FILTER_ORDER; i++)
+            lsf_q[i] = (LSF_ALPHA * p->lsf_q[3][i] * 8000.0f +
+                        LSF_ONE_ALPHA * lsf_3_mean[i]) * (1.0f / 8000.0f);
+
+        for (i = 0; i < LP_FILTER_ORDER; i++)
+            p->prev_lsf_r[i] = av_clip_int16(lrintf(
+                (lsf_q[i] * 8000.0f -
+                 (lsf_3_mean[i] + pred_fac[i] * p->prev_lsf_r[i] *
+                  LSF_R_FAC)) / LSF_R_FAC));
+
+        ff_set_min_dist_lsf(lsf_q, MIN_LSF_SPACING, LP_FILTER_ORDER);
+        interpolate_lsf(&p->acelpv_ctx, p->lsf_q, lsf_q);
+        ff_acelp_lsf2lspd(p->lsp[3], lsf_q, LP_FILTER_ORDER);
+
+        // interpolate LSP vectors at subframes 1, 2 and 3
+        for (i = 1; i <= 3; i++)
+            for (j = 0; j < LP_FILTER_ORDER; j++)
+                p->lsp[i-1][j] = p->prev_lsp_sub4[j] +
+                    (p->lsp[3][j] - p->prev_lsp_sub4[j]) * 0.25 * i;
+        return;
+    }
+
+    int16_t lsf_r[LP_FILTER_ORDER]; // residual LSF vector
+    const int16_t *lsf_quantizer;
+
     lsf_quantizer = (p->cur_frame_mode == MODE_7k95 ? lsf_3_1_MODE_7k95 : 
lsf_3_1)[lsf_param[0]];
     memcpy(lsf_r, lsf_quantizer, 3 * sizeof(*lsf_r));
 
@@ -472,12 +566,22 @@ static void decode_pitch_lag_1_6(int *lag_int, int 
*lag_frac, int pitch_index,
 
 static void decode_pitch_vector(AMRContext *p,
                                 const AMRNBSubframe *amr_subframe,
-                                const int subframe)
+                                const int subframe, const int bfi)
 {
     int pitch_lag_int, pitch_lag_frac;
     enum Mode mode = p->cur_frame_mode;
 
-    if (p->cur_frame_mode == MODE_12k2) {
+    if (bfi) {
+        /* Graceful pitch degradation for a lost frame, see Decoder_amr() in
+           3GPP TS 26.073 (dec_amr.c:527-548 for the delta-coded modes and
+           dec_amr.c:559-568 for 12.2kbit/s). The integer lag keeps growing
+           until it reaches its maximum instead of being decoded. */
+        if (mode != MODE_12k2)
+            pitch_lag_int = FFMIN(p->pitch_lag_int + 1, PITCH_DELAY_MAX);
+        else
+            pitch_lag_int = p->pitch_lag_int;
+        pitch_lag_frac = 0;
+    } else if (p->cur_frame_mode == MODE_12k2) {
         decode_pitch_lag_1_6(&pitch_lag_int, &pitch_lag_frac,
                              amr_subframe->p_lag, p->pitch_lag_int,
                              subframe);
@@ -738,6 +842,149 @@ static void decode_gains(AMRContext *p, const 
AMRNBSubframe *amr_subframe,
 /// @}
 
 
+/// @name AMR error concealment functions
+/// @{
+
+/**
+ * Median of the last five gain values, equivalent to gmed_n() in
+ * 3GPP TS 26.073 (gmed_n.c:57-84).
+ */
+static float ec_gain_median(const float *buf)
+{
+    float tmp[5];
+    int i, j;
+
+    memcpy(tmp, buf, sizeof(tmp));
+
+    for (i = 1; i < 5; i++) {
+        float v = tmp[i];
+
+        for (j = i - 1; j >= 0 && tmp[j] > v; j--)
+            tmp[j + 1] = tmp[j];
+        tmp[j + 1] = v;
+    }
+
+    return tmp[2];
+}
+
+/**
+ * Conceal the pitch gain of a lost subframe, ec_gain_pitch() in
+ * 3GPP TS 26.073 (ec_gains.c:306-330).
+ *
+ * @param p     the context
+ * @param state state of the bad frame handling state machine
+ */
+static void ec_gain_pitch(AMRContext *p, int state)
+{
+    /* pdown[] from 3GPP TS 26.073, kept as exact fractions of 32768 */
+    static const float pdown[7] = {
+        32767.0f / 32768.0f, 32112.0f / 32768.0f, 32112.0f / 32768.0f,
+        26214.0f / 32768.0f,  9830.0f / 32768.0f,  6553.0f / 32768.0f,
+         6553.0f / 32768.0f,
+    };
+    float gain = ec_gain_median(p->ec_gain_pbuf);
+
+    if (gain > p->ec_gain_past_pit)
+        gain = p->ec_gain_past_pit;
+    p->pitch_gain[4] = gain * pdown[state];
+}
+
+/**
+ * Update the pitch gain concealment state, ec_gain_pitch_update() in
+ * 3GPP TS 26.073 (ec_gains.c:343-379).
+ *
+ * @param p     the context
+ * @param bfi   the current frame is unusable
+ */
+static void ec_gain_pitch_update(AMRContext *p, int bfi)
+{
+    int i;
+
+    if (!bfi) {
+        /* Limit the gain of the first usable subframe after an unusable
+           frame to the last usable gain (3GPP TS 26.091 clause 7.2.2). */
+        if (p->prev_bf && p->pitch_gain[4] > p->ec_gain_prev_pit)
+            p->pitch_gain[4] = p->ec_gain_prev_pit;
+        p->ec_gain_prev_pit = p->pitch_gain[4];
+    }
+
+    p->ec_gain_past_pit = FFMIN(p->pitch_gain[4], 1.0f);
+
+    for (i = 1; i < 5; i++)
+        p->ec_gain_pbuf[i - 1] = p->ec_gain_pbuf[i];
+    p->ec_gain_pbuf[4] = p->ec_gain_past_pit;
+}
+
+/**
+ * Advance the gain predictor with the average of the four past energies,
+ * gc_pred_average_limited() followed by gc_pred_update(), as ec_gain_code()
+ * does in 3GPP TS 26.073 when the energies of the frame are not available.
+ *
+ * @param p the context
+ */
+static void ec_gain_code_predictor_update(AMRContext *p)
+{
+    float avg = 0.0f;
+    int i;
+
+    for (i = 0; i < 4; i++)
+        avg += p->prediction_error[i];
+    avg = FFMAX(avg * 0.25f, MIN_ENERGY);
+
+    memmove(&p->prediction_error[0], &p->prediction_error[1],
+            3 * sizeof(p->prediction_error[0]));
+    p->prediction_error[3] = avg;
+}
+
+/**
+ * Conceal the codebook gain of a lost subframe, ec_gain_code() in
+ * 3GPP TS 26.073 (ec_gains.c:137-171).
+ *
+ * @param p     the context
+ * @param state state of the bad frame handling state machine
+ */
+static void ec_gain_code(AMRContext *p, int state)
+{
+    /* cdown[] from 3GPP TS 26.073, kept as exact fractions of 32768 */
+    static const float cdown[7] = {
+        32767.0f / 32768.0f, 32112.0f / 32768.0f, 32112.0f / 32768.0f,
+        32112.0f / 32768.0f, 32112.0f / 32768.0f, 32112.0f / 32768.0f,
+        22937.0f / 32768.0f,
+    };
+    float gain = ec_gain_median(p->ec_gain_gbuf);
+
+    if (gain > p->ec_gain_past_code)
+        gain = p->ec_gain_past_code;
+    p->fixed_gain[4] = gain * cdown[state];
+}
+
+/**
+ * Update the codebook gain concealment state, ec_gain_code_update() in
+ * 3GPP TS 26.073 (ec_gains.c:184-219).
+ *
+ * @param p     the context
+ * @param bfi   the current frame is unusable
+ */
+static void ec_gain_code_update(AMRContext *p, int bfi)
+{
+    int i;
+
+    if (!bfi) {
+        if (p->prev_bf && p->fixed_gain[4] > p->ec_gain_prev_code)
+            p->fixed_gain[4] = p->ec_gain_prev_code;
+        p->ec_gain_prev_code = p->fixed_gain[4];
+    }
+
+    p->ec_gain_past_code = p->fixed_gain[4];
+
+    for (i = 1; i < 5; i++)
+        p->ec_gain_gbuf[i - 1] = p->ec_gain_gbuf[i];
+    p->ec_gain_gbuf[4] = p->fixed_gain[4];
+}
+
+/// @}
+
+
 /// @name AMR preprocessing functions
 /// @{
 
@@ -1402,7 +1649,13 @@ static void cn_update_from_sid(AMRContext *p, const int 
*ind)
         }
     }
 
-    /* Initialize the gain predictor using this SID frame's energy. */
+    /* Initialize the gain predictor using this SID frame's energy.
+
+       Note: this decoder's log2-vs-dB gain-predictor modelling (pred_init)
+       leaves a residual of about 1.8 dB versus the reference on the first
+       subframe after DTX. That is a modelling difference, not a unit error;
+       the reference's ma_pred_init (dtx_dec.c:446) corresponds to TS 26.092
+       clause 6.2's "predictor memories are initialized". */
     pred_init = FFMIN(p->cn_log_en - 9000.0f / 1024.0f, 0.0f);
     pred_init = FFMAX(pred_init, MIN_ENERGY);
     for (i = 0; i < 4; i++)
@@ -1446,14 +1699,26 @@ static void cn_synthesize(AMRContext *p, const float 
*lpc_variab, float level,
     p->beta          = 0.0f;
     p->pitch_lag_int = 40;
 
-    /* The reference decoder resets the error concealment states with every
-       frame of a silent period, so the gains of the speech frame starting
-       the next speech burst are limited to zero as well (3GPP TS 26.091
-       clause 7.2.2). It also marks the frame as unusable when the comfort
-       noise had been muted. */
-    p->prev_pitch_gain = 0.0f;
-    p->prev_fixed_gain = 0.0f;
-    p->prev_bf         = new_state == DTX_MUTE;
+    /* The reference decoder resets the bad frame handling and error
+       concealment states with every frame of a silent period
+       (Decoder_amr_reset() and ec_gain_*_reset() in 3GPP TS 26.073), so the
+       gains of the speech frame starting the next speech burst are limited
+       accordingly (3GPP TS 26.091 clause 7.2.2). It also marks the frame as
+       unusable when the comfort noise had been muted. */
+    p->bfh_state    = 0;
+    p->no_data_seed = 21845;
+    /* See the note on the code gain in the decoder reset: the reference's
+       Q1 gbuf/prev_gc = 1 corresponds to 0.5 in the absolute domain, but
+       1.0 is kept deliberately (the recovery clamp never binds). */
+    for (i = 0; i < 5; i++) {
+        p->ec_gain_pbuf[i] = 1640.0f / 16384.0f;
+        p->ec_gain_gbuf[i] = 1.0f;
+    }
+    p->ec_gain_past_pit  = 0.0f;
+    p->ec_gain_prev_pit  = 1.0f;
+    p->ec_gain_past_code = 0.0f;
+    p->ec_gain_prev_code = 1.0f;
+    p->prev_bf           = new_state == DTX_MUTE;
 
     for (sub = 0; sub < 4; sub++) {
         build_cn_code(&p->cn_seed, ex);
@@ -1579,6 +1844,39 @@ static void cn_frame(AMRContext *p, int new_state, const 
int *ind, int mode,
     p->dtx_state = new_state;
 }
 
+/**
+ * Fill the frame parameters of a lost speech frame with pseudo-random
+ * values, see build_CN_param() in 3GPP TS 26.073 (b_cn_cod.c:145-162).
+ * Only the fields the concealment does not itself overwrite are actually
+ * consumed (notably the fixed codebook pulses). The field order follows
+ * the AMRNBFrame order table, not the reference's bitstream parameter
+ * order, so this is deliberately not bit-exact with the reference -- the
+ * decoder is float and not bit-exact either (see the file header).
+ *
+ * @param p     the context
+ * @param seed  state of the pseudo-random generator
+ * @param mode  speech mode of the lost frame
+ */
+static void build_bad_frame_params(AMRContext *p, uint32_t *seed, enum Mode 
mode)
+{
+    const uint8_t *ord = amr_unpacking_bitmaps_per_mode[mode];
+    const int16_t *win;
+
+    /* reference LCG: seed = (seed * 31821 + 13849) mod 2^16 */
+    *seed = (*seed * 31821U + 13849U) & 0xFFFFU;
+    win   = &window_200_40[*seed & 0x7F];
+
+    while (*ord) {
+        int field_size = *ord++;
+        int field      = *ord++;            /* byte offset into AMRNBFrame */
+
+        ((uint16_t *)&p->frame)[field >> 1] =
+            *win++ & ((1 << field_size) - 1);
+
+        ord += field_size;                  /* skip the bit-position list */
+    }
+}
+
 static int amrnb_decode_frame(AVCodecContext *avctx, AVFrame *frame,
                               int *got_frame_ptr, AVPacket *avpkt)
 {
@@ -1607,7 +1905,8 @@ static int amrnb_decode_frame(AVCodecContext *avctx, 
AVFrame *frame,
             return AVERROR_INVALIDDATA;
 
         int mode = buf[0] >> 3 & 0x0F;
-        int channel_size, rx_type, new_state;
+        int channel_size, rx_type, new_state, bfi;
+        int prev_dtx_state = p->dtx_state;       // DTX state before this frame
         int sid_ind[5] = { 0 };
         int sid_mode = 0;
         float synth[AMR_BLOCK_SIZE];             // synthesis before 
post-filtering
@@ -1676,12 +1975,41 @@ static int amrnb_decode_frame(AVCodecContext *avctx, 
AVFrame *frame,
             continue;
         }
 
+        /* A speech frame that is either flagged as bad or carries no data
+           at all cannot be decoded: its parameters are concealed instead
+           (3GPP TS 26.091 clause 7). */
+        bfi = rx_type == RX_SPEECH_BAD || rx_type == RX_NO_DATA;
+
+        /* Bad frame handling state machine, 3GPP TS 26.073
+           (dec_amr.c:357-398). The state saturates at 6 and, once it has
+           reached 6, only decays to 5 on a good frame. */
+        if (bfi)
+            p->bfh_state = FFMIN(p->bfh_state + 1, 6);
+        else
+            p->bfh_state = p->bfh_state == 6 ? 5 : 0;
+
+        /* The first speech frame after a comfort noise period is seeded with
+           the DTX state of the frame before it: prev_dtx_state was saved
+           above, before this frame could overwrite dtx_state. */
+        if (prev_dtx_state == DTX) {
+            p->bfh_state = 5;
+            p->prev_bf   = 0;
+        } else if (prev_dtx_state == DTX_MUTE) {
+            p->bfh_state = 5;
+            p->prev_bf   = 1;
+        }
+
+        /* A frame carrying no data has no parameters of its own, fill them
+           with pseudo-random values (3GPP TS 26.073 build_CN_param()). */
+        if (rx_type == RX_NO_DATA)
+            build_bad_frame_params(p, &p->no_data_seed, p->cur_frame_mode);
+
         p->dtx_state = new_state;
 
         if (p->cur_frame_mode == MODE_12k2) {
-            lsf2lsp_5(p);
+            lsf2lsp_5(p, bfi);
         } else
-            lsf2lsp_3(p);
+            lsf2lsp_3(p, bfi);
 
         for (i = 0; i < 4; i++)
             ff_acelp_lspd2lpc(p->lsp[i], p->lpc[i], 5);
@@ -1689,7 +2017,7 @@ static int amrnb_decode_frame(AVCodecContext *avctx, 
AVFrame *frame,
         for (subframe = 0; subframe < 4; subframe++) {
             const AMRNBSubframe *amr_subframe = &p->frame.subframe[subframe];
 
-            decode_pitch_vector(p, amr_subframe, subframe);
+            decode_pitch_vector(p, amr_subframe, subframe, bfi);
 
             decode_fixed_sparse(&fixed_sparse, amr_subframe->pulses,
                                 p->cur_frame_mode, subframe);
@@ -1698,8 +2026,16 @@ static int amrnb_decode_frame(AVCodecContext *avctx, 
AVFrame *frame,
             // (section 6.1.2), but the fixed vector calculation uses
             // pitch sharpening based on the on the pitch gain (section 6.1.3).
             // So the correct order is: pitch gain, pitch sharpening, fixed 
gain.
-            decode_gains(p, amr_subframe, p->cur_frame_mode, subframe,
-                         &fixed_gain_factor);
+            if (bfi)
+                ec_gain_pitch(p, p->bfh_state);
+            else
+                decode_gains(p, amr_subframe, p->cur_frame_mode, subframe,
+                             &fixed_gain_factor);
+
+            /* Keep the concealment history and limit the gain of the first
+               usable subframe after an unusable one (3GPP TS 26.073
+               ec_gain_pitch_update(), 3GPP TS 26.091 clause 7.2.2). */
+            ec_gain_pitch_update(p, bfi);
 
             pitch_sharpening(p, subframe, p->cur_frame_mode, &fixed_sparse);
 
@@ -1710,24 +2046,20 @@ static int amrnb_decode_frame(AVCodecContext *avctx, 
AVFrame *frame,
             ff_set_fixed_vector(p->fixed_vector, &fixed_sparse, 1.0,
                                 AMR_SUBFRAME_SIZE);
 
-            p->fixed_gain[4] =
-                ff_amr_set_fixed_gain(fixed_gain_factor,
-                                      
p->celpm_ctx.dot_productf(p->fixed_vector,
-                                                                
p->fixed_vector,
-                                                                
AMR_SUBFRAME_SIZE) /
-                                      AMR_SUBFRAME_SIZE,
-                                      p->prediction_error,
-                                      energy_mean[p->cur_frame_mode], 
energy_pred_fac);
-
-            /* 3GPP TS 26.091 clause 7.2.2: the gains of the first usable frame
-               after an unusable one are limited to the ones of the last usable
-               frame, so that the speech does not start at full level */
-            if (p->prev_bf) {
-                p->pitch_gain[4] = FFMIN(p->pitch_gain[4], p->prev_pitch_gain);
-                p->fixed_gain[4] = FFMIN(p->fixed_gain[4], p->prev_fixed_gain);
+            if (bfi) {
+                ec_gain_code(p, p->bfh_state);
+                ec_gain_code_predictor_update(p);
+            } else {
+                p->fixed_gain[4] =
+                    ff_amr_set_fixed_gain(fixed_gain_factor,
+                                          
p->celpm_ctx.dot_productf(p->fixed_vector,
+                                                                    
p->fixed_vector,
+                                                                    
AMR_SUBFRAME_SIZE) /
+                                          AMR_SUBFRAME_SIZE,
+                                          p->prediction_error,
+                                          energy_mean[p->cur_frame_mode], 
energy_pred_fac);
             }
-            p->prev_pitch_gain = p->pitch_gain[4];
-            p->prev_fixed_gain = p->fixed_gain[4];
+            ec_gain_code_update(p, bfi);
 
             // The excitation feedback is calculated without any processing 
such
             // as fixed gain smoothing. This isn't mentioned in the 
specification.
@@ -1778,7 +2110,7 @@ static int amrnb_decode_frame(AVCodecContext *avctx, 
AVFrame *frame,
                                                             highpass_gain * 
AMR_SAMPLE_SCALE,
                                                             p->high_pass_mem, 
AMR_BLOCK_SIZE);
 
-        p->prev_bf = 0;
+        p->prev_bf = bfi;
 
         /* keep the noise parameters of this speech frame for the next SID */
         dtx_activity_update(p, synth);
diff --git a/tests/fate/amrnb.mak b/tests/fate/amrnb.mak
index ad4547cf90..35d25c60df 100644
--- a/tests/fate/amrnb.mak
+++ b/tests/fate/amrnb.mak
@@ -75,10 +75,11 @@ fate-amrnb-sid-after-nodata: REF = RMS level dB: -2[01]\.
 FATE_AMRNB_DTX-$(call ALLYES, AMR_DEMUXER AMRNB_DECODER ARESAMPLE_FILTER 
ASTATS_FILTER NULL_MUXER FILE_PROTOCOL) += fate-amrnb-nodata-q0 
fate-amrnb-nodata-q1
 fate-amrnb-nodata-q0: CMD = ffmpeg -i 
$(TARGET_PATH)/tests/data/fate/amrnb-nodata-qswap-q0.amr -af 
"atrim=start_sample=3200:end_sample=3360,aresample=osf=flt,astats=metadata=0:reset=0"
 -c:a pcm_f32le -f null -
 fate-amrnb-nodata-q0: CMP = grep
-fate-amrnb-nodata-q0: REF = RMS level dB: -2[0-9]\.
+fate-amrnb-nodata-q0: REF = RMS level dB: -[23][019]\.
 fate-amrnb-nodata-q1: CMD = ffmpeg -i 
$(TARGET_PATH)/tests/data/fate/amrnb-nodata-qswap-q1.amr -af 
"atrim=start_sample=3200:end_sample=3360,aresample=osf=flt,astats=metadata=0:reset=0"
 -c:a pcm_f32le -f null -
 fate-amrnb-nodata-q1: CMP = grep
-fate-amrnb-nodata-q1: REF = RMS level dB: -2[0-9]\.
+fate-amrnb-nodata-q1: REF = RMS level dB: -[23][019]\.
+
 fate-amrnb: $(FATE_AMRNB_DTX-yes)
 FATE_FFMPEG += $(FATE_AMRNB_DTX-yes)
 
-- 
2.54.0


>From 1b269e7f21cc05d10e0d1da5909179bdef96d1bf Mon Sep 17 00:00:00 2001
From: Jun Zhao <[email protected]>
Date: Sat, 10 Oct 2026 16:36:22 +0800
Subject: [PATCH 8/8] tests/fate/amrnb: cover resumption after a muted silent
 period

The first speech frame after silence resumes from the comfort-noise
state, including after that noise has been muted. Check that frame
against the reference decoder.

Signed-off-by: Jun Zhao <[email protected]>
---
 tests/data/fate/amrnb-dtx-mute-resume.amr | Bin 0 -> 722 bytes
 tests/fate/amrnb.mak                      |   7 +++++++
 2 files changed, 7 insertions(+)
 create mode 100644 tests/data/fate/amrnb-dtx-mute-resume.amr

diff --git a/tests/data/fate/amrnb-dtx-mute-resume.amr 
b/tests/data/fate/amrnb-dtx-mute-resume.amr
new file mode 100644
index 
0000000000000000000000000000000000000000..223099f7baaf522ca0ff96da050ce88d8e1fcc9a
GIT binary patch
literal 722
zcmbu7`%6;+9K{!WlOdvWR!E4=EK@5jM^bUy<}kyu*K!adUNsG+<paa8rqdpCl5S3H
zW;t<DO0i}_!qC#WhoqY>GG|7!Fz2T3w#QZ2QnUL%bbdR0emUQBzR^)B=Q)9Z*p6KE
ze(<BU>t}@^W5)>m!o-hbmbrP5#^2{-1{E?Z>gWQ?V(gCvN8h{H%*DUJ4LiVGO{66T
zMKY2=78pIOKXYk@%xa^7jP8<b%9xsTvn~T<aSrF|qo`+Ey15yUMXz|%&dfTlda8#D
zVOWNKq`6bUQXn9fzwR}R7m^FUGFt&(jQP7Mk@y_EV`borA2Z=i$-DC_uVeyF5ilq6
zLsoXZoKk|6<P2I$87BS8H+BK;mV{WxgNvw74*vl$WvXGRThLZ?PNV|-`|TR1B2jzw
z^7b5<4K@C@P6h{CILj8|xEe-_HtwdUkR6_S!h;u+`2QimLyc<eZf7e^DEkJ|iaW?>
zLzM}uCab|7YD5~al9YNemjY6@psJ;GAUDsf7yz98KrJ8SD>ZhaUl~te!rCJDIgj3W
z4?Z`q*fyqFAM6t~LzjlCGL$x_7DP900K)UCZ1+eW%iaJQVW_XxBxNlBA<ifus8?mx
z7S@~;E@u;l+KGu*^At)E1CSkF<&gFvgXZKDbEEksn2zgDPT8XYF{*kqJrc#6w_oD}
z&hrl0banI|L%SYuw3o5f5V}$?ohJy|HaB5@ycN)~HV0BP$7HDP!z-4PpM%uh6X~xD
zs86TsOyJWjF%4Q0_98rz=)mpRkWXi~dz!dE3F7Q*uMFs(POnWMh;veNdNj)=3ZsD!
z#Sm+GKkMx&R~CqEt_iszBI8q88kvaW7@kZ@U0PT>c%2C9L9qR|zNDEcxxhwkeEH5+
VbvY}7N_^~0qqVw!TM%bG;UC2JZN&fp

literal 0
HcmV?d00001

diff --git a/tests/fate/amrnb.mak b/tests/fate/amrnb.mak
index 35d25c60df..686d26ea32 100644
--- a/tests/fate/amrnb.mak
+++ b/tests/fate/amrnb.mak
@@ -80,6 +80,13 @@ fate-amrnb-nodata-q1: CMD = ffmpeg -i 
$(TARGET_PATH)/tests/data/fate/amrnb-nodat
 fate-amrnb-nodata-q1: CMP = grep
 fate-amrnb-nodata-q1: REF = RMS level dB: -[23][019]\.
 
+# Check the first speech frame after a muted silent period: once the comfort
+# noise has been muted the frame must resume at the reference decoder's level.
+FATE_AMRNB_DTX-$(call ALLYES, AMR_DEMUXER AMRNB_DECODER ARESAMPLE_FILTER 
ASTATS_FILTER NULL_MUXER FILE_PROTOCOL) += fate-amrnb-dtx-mute-resume
+fate-amrnb-dtx-mute-resume: CMD = ffmpeg -i 
$(TARGET_PATH)/tests/data/fate/amrnb-dtx-mute-resume.amr -af 
"atrim=start_sample=12960:end_sample=13120,aresample=osf=flt,astats=metadata=0:reset=0"
 -c:a pcm_f32le -f null -
+fate-amrnb-dtx-mute-resume: CMP = grep
+fate-amrnb-dtx-mute-resume: REF = RMS level dB: -37\.
+
 fate-amrnb: $(FATE_AMRNB_DTX-yes)
 FATE_FFMPEG += $(FATE_AMRNB_DTX-yes)
 
-- 
2.54.0

_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to