This is an automated email from the git hooks/post-receive script. Git pushed a commit to branch master in repository ffmpeg.
commit 4984c18829206c63a7c17b1995aee68e65f52f1b Author: Jun Zhao <[email protected]> AuthorDate: Fri Oct 9 12:20:37 2026 +0800 Commit: Jun Zhao <[email protected]> CommitDate: Sun Oct 18 22:26:08 2026 +0000 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. Speech samples are unchanged. FATE checks the frame count and the level. Signed-off-by: Jun Zhao <[email protected]> --- Makefile | 2 +- libavcodec/amrwbdec.c | 666 +++++++++++++++++++++++++++++++++++++++++++++----- tests/Makefile | 4 + tests/amrgen.c | 298 ++++++++++++++++++++++ tests/fate/amrwb.mak | 18 +- 5 files changed, 923 insertions(+), 65 deletions(-) diff --git a/Makefile b/Makefile index 93dc74f721..9eaeb27b7a 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,7 @@ vpath %.ptx $(SRC_PATH) vpath %.metal $(SRC_PATH) vpath %/fate_config.sh.template $(SRC_PATH) -TESTTOOLS = audiogen videogen rotozoom tiny_psnr tiny_ssim base64 audiomatch +TESTTOOLS = audiogen videogen rotozoom tiny_psnr tiny_ssim base64 audiomatch amrgen HOSTPROGS := $(TESTTOOLS:%=tests/%) doc/print_options ALLFFLIBS = \ diff --git a/libavcodec/amrwbdec.c b/libavcodec/amrwbdec.c index 3557b4709d..f4daee37df 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,474 @@ 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 reference decoder in 3GPP TS 26.173, + * with the fixed-point arithmetic replaced by floating point. ISF vectors + * and log2 energies stay in the same units as the speech path (0..0.5 + * normalized frequency and log2 of the excitation energy). The tables are + * the noise ISF codebooks from that specification. + */ + +/** + * 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; + int dith, sid_update; + + 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); + + /* TS 26.173 reads the dither flag only on a valid SID_UPDATE. A + SID_FIRST frame carries the bit but must not change the flag used + for later frames that have no data. */ + dith = get_bits1(&gb); + sid_update = get_bits1(&gb); + if (sid_update) + ctx->cn_dith = dith; + + return sid_update; +} + +/** + * 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; + + /* Reset_decoder(st, 0) clears the phase-dispersion memory, including + the previous fixed gain and the pitch-gain history. */ + memset(ctx->pitch_gain, 0, sizeof(ctx->pitch_gain)); + memset(ctx->fixed_gain, 0, sizeof(ctx->fixed_gain)); + ctx->prev_sparse_fixed_gain = 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 +1663,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 +1681,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 +1796,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 +1813,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/Makefile b/tests/Makefile index 97e9d39339..35db2edb3b 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -65,6 +65,10 @@ tests/data/streamgroups/%: TAG = COPY tests/data/streamgroups/%: $(SRC_PATH)/tests/streamgroups/% | tests/data/streamgroups $(M)cp $< $@ +tests/data/fate/%.amr: TAG = GEN +tests/data/fate/%.amr: tests/amrgen$(HOSTEXESUF) | tests/data/fate + $(M)./$< $@ + RUNNING_FATE := $(filter check fate%,$(filter-out fate-rsync,$(MAKECMDGOALS))) # Check sanity of dependencies when running FATE tests. diff --git a/tests/amrgen.c b/tests/amrgen.c new file mode 100644 index 0000000000..855a85aaa6 --- /dev/null +++ b/tests/amrgen.c @@ -0,0 +1,298 @@ +/* + * Generate the AMR streams used by the DTX FATE tests. + * + * The storage format is a header plus one table-of-contents byte and the + * payload of each frame (RFC 4867). These files are built from that layout + * here, the same way audiogen builds its wave files, so the tests do not + * need media from the fate-suite. + * + * Copyright (c) 2026 Jun Zhao + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <errno.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +/* Narrowband speech is one repeated 4.75 kbit/s frame: pitch lag 40 on the + first subframe (coded index 61) and gain index 3. Comfort-noise energy is + the 6-bit index carried by a SID_UPDATE. A silent period mutes once more + than 50 frames have passed with no SID update. */ + +enum { + NB_FRAME_SAMPLES = 160, + WB_FRAME_SAMPLES = 320, +}; + +static void die(const char *path) +{ + fprintf(stderr, "amrgen: %s: %s\n", path, strerror(errno)); + exit(1); +} + +static void put_msb(uint8_t *data, int *pos, int n, unsigned value) +{ + int i; + + for (i = n - 1; i >= 0; i--) { + int bit = (*pos)++; + + if (value & (1u << i)) + data[bit >> 3] |= 1u << (7 - (bit & 7)); + } +} + +static void set_lsb_field(uint8_t *data, const uint8_t *bits, int n, + unsigned value) +{ + int i; + + for (i = 0; i < n; i++) { + if (value & (1u << (n - 1 - i))) + data[bits[i] >> 3] |= 1u << (bits[i] & 7); + } +} + +static void write_nb_speech(FILE *f) +{ + /* Bit positions are those consumed by order_MODE_4k75[], MSB first. */ + static const uint8_t lag0[] = { 23, 22, 21, 20, 19, 18, 43, 42 }; + static const uint8_t gain0[] = { 54, 55, 40, 41, 24, 25, 26, 27 }; + static const uint8_t gain2[] = { 44, 45, 46, 47, 36, 37, 38, 39 }; + uint8_t frame[13] = { 0x04 }; + + set_lsb_field(frame + 1, lag0, 8, 61); + set_lsb_field(frame + 1, gain0, 8, 3); + set_lsb_field(frame + 1, gain2, 8, 3); + if (fwrite(frame, 1, sizeof(frame), f) != sizeof(frame)) + die("write"); +} + +/* energy is the 6-bit index. update selects SID_UPDATE over SID_FIRST. */ +static void write_nb_sid(FILE *f, int energy, int update) +{ + uint8_t frame[6] = { 0x44 }; + int pos = 0; + + put_msb(frame + 1, &pos, 3, 0); + put_msb(frame + 1, &pos, 8, 0); + put_msb(frame + 1, &pos, 9, 0); + put_msb(frame + 1, &pos, 9, 0); + put_msb(frame + 1, &pos, 6, energy); + put_msb(frame + 1, &pos, 1, update); + put_msb(frame + 1, &pos, 3, 0); + if (fwrite(frame, 1, sizeof(frame), f) != sizeof(frame)) + die("write"); +} + +static void write_nodata(FILE *f, int q) +{ + uint8_t toc = q ? 0x7c : 0x78; + + if (fwrite(&toc, 1, 1, f) != 1) + die("write"); +} + +/* Frame types 9..14 are reserved and decoded as NO_DATA. */ +static void write_reserved(FILE *f) +{ + uint8_t toc = 0x4c; + + if (fwrite(&toc, 1, 1, f) != 1) + die("write"); +} + +static void write_wb_sid(FILE *f, int energy) +{ + uint8_t frame[6] = { 0x4c }; + int pos = 0; + int i; + + for (i = 0; i < 3; i++) + put_msb(frame + 1, &pos, 6, 0); + put_msb(frame + 1, &pos, 5, 0); + put_msb(frame + 1, &pos, 5, 0); + put_msb(frame + 1, &pos, 6, energy); + put_msb(frame + 1, &pos, 1, 0); /* dither */ + put_msb(frame + 1, &pos, 1, 1); /* SID_UPDATE */ + if (fwrite(frame, 1, sizeof(frame), f) != sizeof(frame)) + die("write"); +} + +static FILE *open_out(const char *path, const char *header) +{ + FILE *f = fopen(path, "wb"); + + if (!f) + die(path); + if (fwrite(header, 1, strlen(header), f) != strlen(header)) + die(path); + return f; +} + +/* 8 speech, SID, 4 NO_DATA, SID, 4 NO_DATA, 8 speech. + Frame 13 is the second SID. Frame 18 is the first speech after it. */ +static void gen_sid_update(const char *path) +{ + FILE *f = open_out(path, "#!AMR\n"); + int i; + + for (i = 0; i < 8; i++) + write_nb_speech(f); + write_nb_sid(f, 16, 1); + for (i = 0; i < 4; i++) + write_nodata(f, 1); + write_nb_sid(f, 16, 1); + for (i = 0; i < 4; i++) + write_nodata(f, 1); + for (i = 0; i < 8; i++) + write_nb_speech(f); + if (fclose(f)) + die(path); +} + +/* 8 speech, quiet SID, 4 NO_DATA, loud SID, 4 NO_DATA, 4 speech. + Frame 18 is the first speech frame after the loud update. */ +static void gen_state_resume(const char *path) +{ + FILE *f = open_out(path, "#!AMR\n"); + int i; + + for (i = 0; i < 8; i++) + write_nb_speech(f); + write_nb_sid(f, 8, 1); + for (i = 0; i < 4; i++) + write_nodata(f, 1); + write_nb_sid(f, 32, 1); + for (i = 0; i < 4; i++) + write_nodata(f, 1); + for (i = 0; i < 4; i++) + write_nb_speech(f); + if (fclose(f)) + die(path); +} + +/* Speech, then one NO_DATA or reserved frame, repeated. + q1_at_first selects the Q bit of the first NO_DATA frame only. + Frame 8 is that NO_DATA, frame 17 is a Q=0 NO_DATA, frame 26 is reserved. */ +static void gen_nodata(const char *path, int q1_at_first) +{ + FILE *f = open_out(path, "#!AMR\n"); + int i; + + for (i = 0; i < 8; i++) + write_nb_speech(f); + write_nodata(f, q1_at_first); + for (i = 0; i < 8; i++) + write_nb_speech(f); + write_nodata(f, 0); + for (i = 0; i < 8; i++) + write_nb_speech(f); + write_reserved(f); + for (i = 0; i < 8; i++) + write_nb_speech(f); + if (fclose(f)) + die(path); +} + +/* 8 speech, one lost frame, SID_UPDATE, 4 NO_DATA, 4 speech. + Frame 10 is the first comfort-noise frame after that SID. */ +static void gen_sid_after_nodata(const char *path) +{ + FILE *f = open_out(path, "#!AMR\n"); + int i; + + for (i = 0; i < 8; i++) + write_nb_speech(f); + write_nodata(f, 1); + write_nb_sid(f, 20, 1); + for (i = 0; i < 4; i++) + write_nodata(f, 1); + for (i = 0; i < 4; i++) + write_nb_speech(f); + if (fclose(f)) + die(path); +} + +/* 11 speech, one SID, 51 NO_DATA (past the 50-frame mute threshold), 2 speech. + Eleven speech frames (44 subframes) take fixed_gain_smooth() past its + 40-subframe hangover before silence begins. Frame 63 is the first speech + frame after the muted period. 65 frames * 160 samples = 10400. */ +static void gen_mute_resume(const char *path) +{ + FILE *f = open_out(path, "#!AMR\n"); + int i; + + for (i = 0; i < 11; i++) + write_nb_speech(f); + write_nb_sid(f, 15, 1); + for (i = 0; i < 51; i++) + write_nodata(f, 1); + for (i = 0; i < 2; i++) + write_nb_speech(f); + if (fclose(f)) + die(path); +} + +/* One SID_UPDATE and 10 NO_DATA frames. 11 * 320 = 3520 samples. */ +static void gen_wb(const char *path) +{ + FILE *f = open_out(path, "#!AMR-WB\n"); + int i; + + write_wb_sid(f, 20); + for (i = 0; i < 10; i++) + write_nodata(f, 1); + if (fclose(f)) + die(path); +} + +int main(int argc, char **argv) +{ + const char *base; + + if (argc != 2) { + fprintf(stderr, "usage: amrgen <output.amr>\n"); + return 1; + } + + base = strrchr(argv[1], '/'); + base = base ? base + 1 : argv[1]; + + if (!strcmp(base, "amrnb-sid-update.amr")) + gen_sid_update(argv[1]); + else if (!strcmp(base, "amrnb-state-resume-update.amr")) + gen_state_resume(argv[1]); + else if (!strcmp(base, "amrnb-nodata-cases.amr")) + gen_nodata(argv[1], 1); + else if (!strcmp(base, "amrnb-nodata-qswap-q0.amr")) + gen_nodata(argv[1], 0); + else if (!strcmp(base, "amrnb-sid-after-nodata.amr")) + gen_sid_after_nodata(argv[1]); + else if (!strcmp(base, "amrnb-dtx-mute-resume.amr")) + gen_mute_resume(argv[1]); + else if (!strcmp(base, "amrwb-dtx.amr")) + gen_wb(argv[1]); + else { + fprintf(stderr, "amrgen: unknown stream '%s'\n", base); + return 1; + } + return 0; +} diff --git a/tests/fate/amrwb.mak b/tests/fate/amrwb.mak index aec1c9d859..3ffb934998 100644 --- a/tests/fate/amrwb.mak +++ b/tests/fate/amrwb.mak @@ -46,6 +46,22 @@ 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 AMR_PARSER AMRWB_DECODER ARESAMPLE_FILTER \ + ASTATS_FILTER PCM_F32LE_ENCODER NULL_MUXER FILE_PROTOCOL) += fate-amrwb-dtx-duration +FATE_AMRWB_DTX-$(call ALLYES, AMR_DEMUXER AMR_PARSER AMRWB_DECODER ARESAMPLE_FILTER \ + ASTATS_FILTER ATRIM_FILTER PCM_F32LE_ENCODER NULL_MUXER \ + FILE_PROTOCOL) += fate-amrwb-dtx-cn +fate-amrwb-dtx-duration fate-amrwb-dtx-cn: tests/data/fate/amrwb-dtx.amr +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: 3520 +fate-amrwb-dtx-cn: CMD = ffmpeg -i $(TARGET_PATH)/tests/data/fate/amrwb-dtx.amr -af "atrim=start_sample=320:end_sample=3520,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: -19\. + 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) -- To stop receiving notification emails like this one, please contact [email protected]. _______________________________________________ ffmpeg-cvslog mailing list -- [email protected] To unsubscribe send an email to [email protected]
