Commit 867c7150f3 for ffmpeg

commit 867c7150f3e234d5e95806e19291f11618c89209
Author: Jun Zhao <barryjzhao@tencent.com>
Date:   Fri Oct 9 14:07:50 2026 +0800

    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). The quality bit does not
    change that: an FT=15 frame is NO_DATA whether or not it is
    marked bad. Unused frame types between SID and NO_DATA are
    treated as NO_DATA. Outside a silent period, a NO_DATA frame is
    emitted as silence until lost-frame concealment is added.

    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, and speech not preceded by
    silence is unchanged. FATE checks the comfort-noise level, speech
    resuming after silence, a SID frame that follows a lost frame, and
    Q-bit handling on NO_DATA.

    Signed-off-by: Jun Zhao <barryjzhao@tencent.com>

diff --git a/libavcodec/amrnbdec.c b/libavcodec/amrnbdec.c
index b8e340684b..ecd138bf28 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,574 @@ 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;
+
+    /* Decoder_amr_reset(MRDTX) clears the fixed-gain history and the
+       phase-dispersion state. dtx_dec() then restarts smoothing with
+       hangVar already past the 10-subframe threshold and hangCount at 0,
+       so resumed speech does not blend in the gains from before the pause. */
+    memset(p->fixed_gain, 0, sizeof(p->fixed_gain));
+    memset(p->pitch_gain, 0, sizeof(p->pitch_gain));
+    p->diff_count              = 10;
+    p->hang_count              = 0;
+    p->prev_ir_filter_nr       = 0;
+    p->ir_filter_onset         = 0;
+    p->prev_sparse_fixed_gain  = 0.0f;
+
+    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;
+        /* Snapshot the stored SID target, not this frame's interpolated
+           vector, so the next period does not fall back toward the
+           previous SID (dtx_dec.c, DTX_MUTE). */
+        memcpy(p->cn_lsp_old, p->cn_lsp, sizeof(p->cn_lsp));
+        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 +1617,104 @@ 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.
+               Speech_Decode_Frame() then post-filters that synthesis with
+               the same filter as speech. samples_in keeps the unfiltered
+               synthesis memory written by cn_synthesize(). */
+            cn_frame(p, new_state, sid_ind, p->cur_frame_mode, synth);
+            for (subframe = 0; subframe < 4; subframe++) {
+                memcpy(p->samples_in + LP_FILTER_ORDER,
+                       synth + subframe * AMR_SUBFRAME_SIZE,
+                       AMR_SUBFRAME_SIZE * sizeof(float));
+                postfilter(p, p->lpc[subframe],
+                           buf_out + subframe * AMR_SUBFRAME_SIZE);
+            }
+
+            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 (rx_type == RX_NO_DATA) {
+            /* Outside of a silent period a frame carrying no data still
+               produces a full frame of samples, keeping the frame count and
+               the timing of the decoded stream correct. */
+            memset(buf_out, 0, AMR_BLOCK_SIZE * sizeof(*buf_out));
+            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 +1756,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 +1798,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 +1815,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/fate/amrnb.mak b/tests/fate/amrnb.mak
index 007174de0a..7af5b32f14 100644
--- a/tests/fate/amrnb.mak
+++ b/tests/fate/amrnb.mak
@@ -33,6 +33,69 @@ 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"

+AMRNB_DTX_DEPS = AMR_DEMUXER AMR_PARSER AMRNB_DECODER ARESAMPLE_FILTER \
+                 ASTATS_FILTER ATRIM_FILTER AMIX_FILTER PCM_F32LE_ENCODER \
+                 NULL_MUXER FILE_PROTOCOL
+
+# Frame 18 of amrnb-sid-update.amr is the first speech frame after a SID update.
+FATE_AMRNB_DTX-$(call ALLYES, $(AMRNB_DTX_DEPS)) += fate-amrnb-sid-update
+fate-amrnb-sid-update: CMD = ffmpeg -i $(TARGET_PATH)/tests/data/fate/amrnb-sid-update.amr -af "atrim=start_sample=2880:end_sample=3040,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: -56\.
+
+# Frame 13 of amrnb-state-resume-update.amr is a louder SID_UPDATE while
+# comfort noise is already being generated.
+FATE_AMRNB_DTX-$(call ALLYES, $(AMRNB_DTX_DEPS)) += fate-amrnb-sid-update-during-dtx
+fate-amrnb-sid-update-during-dtx: CMD = ffmpeg -i $(TARGET_PATH)/tests/data/fate/amrnb-state-resume-update.amr -af "atrim=start_sample=2080:end_sample=2240,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: -79\.
+
+# Frame 18 of amrnb-state-resume-update.amr follows a louder SID than the
+# update that opened the silent period.
+FATE_AMRNB_DTX-$(call ALLYES, $(AMRNB_DTX_DEPS)) += 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=2880:end_sample=3040,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: -53\.
+
+# NO_DATA in speech state ignores Q. Frame 8 is Q=1, frame 17 is Q=0, and
+# frame 26 is a reserved frame type.
+FATE_AMRNB_DTX-$(call ALLYES, $(AMRNB_DTX_DEPS)) += 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=1280:end_sample=1440,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: -inf
+fate-amrnb-nodata-ft15-q0: CMD = ffmpeg -i $(TARGET_PATH)/tests/data/fate/amrnb-nodata-cases.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-nodata-ft15-q0: CMP = grep
+fate-amrnb-nodata-ft15-q0: REF = RMS level dB: -inf
+fate-amrnb-nodata-reserved-q1: CMD = ffmpeg -i $(TARGET_PATH)/tests/data/fate/amrnb-nodata-cases.amr -af "atrim=start_sample=4160:end_sample=4320,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: -inf
+
+# A SID frame arriving after a lost frame must still update the comfort
+# noise. Frame 10 is the first comfort-noise frame after that SID.
+FATE_AMRNB_DTX-$(call ALLYES, $(AMRNB_DTX_DEPS)) += 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=1600:end_sample=1760,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: -70\.
+
+# FT15 must decode identically for Q=0 and Q=1. The two files differ only
+# in that bit; amrnb-nodata-cases.amr is the Q=1 input. amix keeps the sign
+# of a weight only while normalize is on, so the two streams cancel.
+FATE_AMRNB_DTX-$(call ALLYES, $(AMRNB_DTX_DEPS)) += fate-amrnb-nodata-q-equal
+fate-amrnb-nodata-q-equal: CMD = ffmpeg -i $(TARGET_PATH)/tests/data/fate/amrnb-nodata-qswap-q0.amr -i $(TARGET_PATH)/tests/data/fate/amrnb-nodata-cases.amr -filter_complex "[0:a][1:a]amix=inputs=2:duration=longest:dropout_transition=0:normalize=1:weights=1|-1,aresample=osf=flt,astats=metadata=0:reset=0" -c:a pcm_f32le -f null -
+fate-amrnb-nodata-q-equal: CMP = grep
+fate-amrnb-nodata-q-equal: REF = RMS level dB: -inf
+
+
+fate-amrnb-sid-update: tests/data/fate/amrnb-sid-update.amr
+fate-amrnb-sid-update-during-dtx: tests/data/fate/amrnb-state-resume-update.amr
+fate-amrnb-state-resume-update: tests/data/fate/amrnb-state-resume-update.amr
+fate-amrnb-nodata-ft15-q1 fate-amrnb-nodata-ft15-q0 fate-amrnb-nodata-reserved-q1: tests/data/fate/amrnb-nodata-cases.amr
+fate-amrnb-sid-after-nodata: tests/data/fate/amrnb-sid-after-nodata.amr
+fate-amrnb-nodata-q-equal: tests/data/fate/amrnb-nodata-qswap-q0.amr tests/data/fate/amrnb-nodata-cases.amr
+
+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)