[PR] avcodec/(e)ac3enc: drain the buffered samples at the end of encoding (PR #23651)

James Almer via ffmpeg-devel <[email protected]>
Newsgroups gmane.comp.video.ffmpeg.devel
Message-ID <178277856132.59.18140391453784745293@29965ddac10e>
PR #23651 opened by James Almer (jamrial)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23651
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23651.patch

When encoding a stream with an amount of samples multiple of a block, the last the last 256 samples would be lost as the encoders were not marked as AV_CODEC_CAP_DELAY.

This can be easily reproduced with:

`ffmpeg -f lavfi -i sine -ac 2 -af atrim=start_sample=0:end_sample=4608 -c:a eac3 -f framecrc -`


>From 378faf5bac08d2f856996e8d7a320e2bdaf0ddc7 Mon Sep 17 00:00:00 2001
From: James Almer <[email protected]>
Date: Mon, 29 Jun 2026 19:24:39 -0300
Subject: [PATCH] avcodec/ac3enc: drain the buffered samples at the end of
 encoding

When encoding a stream with an amount of samples multiple of a block, the last
the last 256 samples would be lost as the encoders were not marked as
AV_CODEC_CAP_DELAY.

This can be easily reproduced with:

ffmpeg -f lavfi -i sine -ac 2 -af atrim=start_sample=0:end_sample=4608 -c:a eac3 -f framecrc -

Signed-off-by: James Almer <[email protected]>
---
 libavcodec/ac3enc.c               | 34 ++++++++++++++++++++++++++-----
 libavcodec/ac3enc.h               |  6 +++++-
 libavcodec/ac3enc_fixed.c         |  3 ++-
 libavcodec/ac3enc_float.c         |  3 ++-
 libavcodec/ac3enc_template.c      | 29 ++++++++++++++++++++++----
 libavcodec/eac3enc.c              |  3 ++-
 tests/fate/ac3.mak                | 18 +++++++---------
 tests/ref/fate/ac3-fixed-encode-2 |  1 +
 tests/ref/fate/autorotate         |  2 +-
 tests/ref/fate/shortest           |  1 +
 tests/ref/fate/unknown_layout-ac3 |  2 +-
 tests/ref/lavf/rm                 |  2 +-
 12 files changed, 77 insertions(+), 27 deletions(-)

diff --git a/libavcodec/ac3enc.c b/libavcodec/ac3enc.c
index 5a1a3ab63a..9172348712 100644
--- a/libavcodec/ac3enc.c
+++ b/libavcodec/ac3enc.c
@@ -1982,8 +1982,18 @@ int ff_ac3_encode_frame(AVCodecContext *avctx, AVPacket *avpkt,
                         const AVFrame *frame, int *got_packet_ptr)
 {
     AC3EncodeContext *const s = avctx->priv_data;
+    int discard_padding;
     int ret;
 
+    /* add current frame to queue */
+    if (frame) {
+        if ((ret = ff_af_queue_add(&s->afq, frame)) < 0)
+            return ret;
+    } else {
+        if (!s->afq.remaining_samples || (!s->afq.frame_alloc && !s->afq.frame_count))
+            return 0;
+    }
+
     if (s->options.allow_per_frame_metadata) {
         ret = ac3_validate_metadata(s);
         if (ret)
@@ -1993,7 +2003,7 @@ int ff_ac3_encode_frame(AVCodecContext *avctx, AVPacket *avpkt,
     if (s->bit_alloc.sr_code == 1 || s->eac3)
         ac3_adjust_frame_size(s);
 
-    s->encode_frame(s, frame->extended_data);
+    s->encode_frame(s, frame);
 
     ac3_apply_rematrixing(s);
 
@@ -2014,8 +2024,17 @@ int ff_ac3_encode_frame(AVCodecContext *avctx, AVPacket *avpkt,
         return ret;
     ac3_output_frame(s, avpkt->data);
 
-    if (frame->pts != AV_NOPTS_VALUE)
-        avpkt->pts = frame->pts - ff_samples_to_time_base(avctx, avctx->initial_padding);
+    ff_af_queue_remove(&s->afq, avctx->frame_size, &avpkt->pts,
+                       &avpkt->duration);
+
+    discard_padding = avctx->frame_size - ff_samples_from_time_base(avctx, avpkt->duration);
+    if (discard_padding > 0) {
+        uint8_t *side_data =
+            av_packet_new_side_data(avpkt, AV_PKT_DATA_SKIP_SAMPLES, 10);
+        if (!side_data)
+            return AVERROR(ENOMEM);
+        AV_WL32(side_data + 4, discard_padding);
+    }
 
     *got_packet_ptr = 1;
     return 0;
@@ -2155,8 +2174,10 @@ av_cold int ff_ac3_encode_close(AVCodecContext *avctx)
 {
     AC3EncodeContext *s = avctx->priv_data;
 
-    for (int ch = 0; ch < s->channels; ch++)
+    for (int ch = 0; ch < s->channels; ch++) {
         av_freep(&s->planar_samples[ch]);
+        av_freep(&s->input_samples[ch]);
+    }
     av_freep(&s->bap_buffer);
     av_freep(&s->bap1_buffer);
     av_freep(&s->mdct_coef_buffer);
@@ -2416,7 +2437,8 @@ static av_cold int allocate_buffers(AC3EncodeContext *s)
 
     for (int ch = 0; ch < s->channels; ch++) {
         s->planar_samples[ch] = av_mallocz(AC3_BLOCK_SIZE * sampletype_size);
-        if (!s->planar_samples[ch])
+        s->input_samples[ch] = av_mallocz(AC3_BLOCK_SIZE * sampletype_size * s->num_blocks);
+        if (!s->planar_samples[ch] || !s->input_samples[ch])
             return AVERROR(ENOMEM);
     }
 
@@ -2516,6 +2538,8 @@ av_cold int ff_ac3_encode_init(AVCodecContext *avctx)
 
     dprint_options(s);
 
+    ff_af_queue_init(avctx, &s->afq);
+
     ff_thread_once(&init_static_once, exponent_init);
 
     return 0;
diff --git a/libavcodec/ac3enc.h b/libavcodec/ac3enc.h
index 5e98ad188b..3e92af17f0 100644
--- a/libavcodec/ac3enc.h
+++ b/libavcodec/ac3enc.h
@@ -37,6 +37,7 @@
 #include "ac3.h"
 #include "ac3defs.h"
 #include "ac3dsp.h"
+#include "audio_frame_queue.h"
 #include "avcodec.h"
 #include "codec_internal.h"
 #include "mathops.h"
@@ -233,6 +234,7 @@ typedef struct AC3EncodeContext {
     int exponent_bits;                      ///< number of bits used for exponents
 
     uint8_t *planar_samples[AC3_MAX_CHANNELS - 1];
+    uint8_t *input_samples[AC3_MAX_CHANNELS - 1];
     uint8_t *bap_buffer;
     uint8_t *bap1_buffer;
     CoefType *mdct_coef_buffer;
@@ -252,8 +254,10 @@ typedef struct AC3EncodeContext {
     uint8_t *ref_bap     [AC3_MAX_CHANNELS][AC3_MAX_BLOCKS]; ///< bit allocation pointers (bap)
     int ref_bap_set;                                         ///< indicates if ref_bap pointers have been set
 
+    AudioFrameQueue afq;
+
     /** fixed vs. float function pointers */
-    void (*encode_frame)(struct AC3EncodeContext *s, uint8_t * const *samples);
+    void (*encode_frame)(struct AC3EncodeContext *s, const AVFrame *frame);
 
     /* AC-3 vs. E-AC-3 function pointers */
     void (*output_frame_header)(struct AC3EncodeContext *s, struct PutBitContext *pb);
diff --git a/libavcodec/ac3enc_fixed.c b/libavcodec/ac3enc_fixed.c
index 42530b0ea1..876bb6bbc6 100644
--- a/libavcodec/ac3enc_fixed.c
+++ b/libavcodec/ac3enc_fixed.c
@@ -114,7 +114,8 @@ const FFCodec ff_ac3_fixed_encoder = {
     CODEC_LONG_NAME("ATSC A/52A (AC-3)"),
     .p.type          = AVMEDIA_TYPE_AUDIO,
     .p.id            = AV_CODEC_ID_AC3,
-    .p.capabilities  = AV_CODEC_CAP_DR1 | AV_CODEC_CAP_ENCODER_REORDERED_OPAQUE,
+    .p.capabilities  = AV_CODEC_CAP_DR1 | AV_CODEC_CAP_DELAY |
+                       AV_CODEC_CAP_SMALL_LAST_FRAME | AV_CODEC_CAP_ENCODER_REORDERED_OPAQUE,
     .priv_data_size  = sizeof(AC3EncodeContext),
     .init            = ac3_fixed_encode_init,
     FF_CODEC_ENCODE_CB(ff_ac3_encode_frame),
diff --git a/libavcodec/ac3enc_float.c b/libavcodec/ac3enc_float.c
index 0ae7ddd7eb..974c38ac25 100644
--- a/libavcodec/ac3enc_float.c
+++ b/libavcodec/ac3enc_float.c
@@ -116,7 +116,8 @@ const FFCodec ff_ac3_encoder = {
     CODEC_LONG_NAME("ATSC A/52A (AC-3)"),
     .p.type          = AVMEDIA_TYPE_AUDIO,
     .p.id            = AV_CODEC_ID_AC3,
-    .p.capabilities  = AV_CODEC_CAP_DR1 | AV_CODEC_CAP_ENCODER_REORDERED_OPAQUE,
+    .p.capabilities  = AV_CODEC_CAP_DR1 | AV_CODEC_CAP_DELAY |
+                       AV_CODEC_CAP_SMALL_LAST_FRAME | AV_CODEC_CAP_ENCODER_REORDERED_OPAQUE,
     .priv_data_size  = sizeof(AC3EncodeContext),
     .init            = ff_ac3_float_encode_init,
     FF_CODEC_ENCODE_CB(ff_ac3_encode_frame),
diff --git a/libavcodec/ac3enc_template.c b/libavcodec/ac3enc_template.c
index 049666fdca..6d662c74da 100644
--- a/libavcodec/ac3enc_template.c
+++ b/libavcodec/ac3enc_template.c
@@ -49,9 +49,10 @@
  * This applies the KBD window and normalizes the input to reduce precision
  * loss due to fixed-point calculations.
  */
-static void apply_mdct(AC3EncodeContext *s, uint8_t * const *samples)
+static void apply_mdct(AC3EncodeContext *s)
 {
     av_assert1(s->num_blocks > 0);
+    uint8_t * const *samples = s->input_samples;
 
     for (int ch = 0; ch < s->channels; ch++) {
         const SampleType *input_samples0 = (const SampleType*)s->planar_samples[ch];
@@ -346,10 +347,30 @@ static void compute_rematrixing_strategy(AC3EncodeContext *s)
     }
 }
 
-
-static void encode_frame(AC3EncodeContext *s, uint8_t * const *samples)
+/*
+ * Copy input samples.
+ */
+static void copy_input_samples(AC3EncodeContext *s, const AVFrame *frame)
 {
-    apply_mdct(s, samples);
+    int ch;
+    int end = frame ? frame->nb_samples : 0;
+
+    for (ch = 0; ch < s->channels; ch++) {
+        /* copy new samples and zero any remaining samples */
+        if (frame) {
+            memcpy(s->input_samples[ch],
+                   frame->extended_data[ch],
+                   frame->nb_samples * sizeof(SampleType));
+        }
+        memset(&s->input_samples[ch][end], 0,
+               (s->avctx->frame_size - end) * sizeof(SampleType));
+    }
+}
+
+static void encode_frame(AC3EncodeContext *s, const AVFrame *frame)
+{
+    copy_input_samples(s, frame);
+    apply_mdct(s);
 
     s->cpl_on = s->cpl_enabled;
     ff_ac3_compute_coupling_strategy(s);
diff --git a/libavcodec/eac3enc.c b/libavcodec/eac3enc.c
index 10b1ab337c..7a678b518c 100644
--- a/libavcodec/eac3enc.c
+++ b/libavcodec/eac3enc.c
@@ -270,7 +270,8 @@ const FFCodec ff_eac3_encoder = {
     CODEC_LONG_NAME("ATSC A/52 E-AC-3"),
     .p.type          = AVMEDIA_TYPE_AUDIO,
     .p.id            = AV_CODEC_ID_EAC3,
-    .p.capabilities  = AV_CODEC_CAP_DR1 | AV_CODEC_CAP_ENCODER_REORDERED_OPAQUE,
+    .p.capabilities  = AV_CODEC_CAP_DR1 | AV_CODEC_CAP_DELAY |
+                       AV_CODEC_CAP_SMALL_LAST_FRAME | AV_CODEC_CAP_ENCODER_REORDERED_OPAQUE,
     .priv_data_size  = sizeof(AC3EncodeContext),
     .init            = eac3_encode_init,
     FF_CODEC_ENCODE_CB(ff_ac3_encode_frame),
diff --git a/tests/fate/ac3.mak b/tests/fate/ac3.mak
index 9f655c3ff4..c0b10c68fb 100644
--- a/tests/fate/ac3.mak
+++ b/tests/fate/ac3.mak
@@ -68,18 +68,14 @@ $(FATE_AC3) $(FATE_EAC3): CMP = oneoff
 FATE_AC3-$(call  PCM, AC3,  AC3 AC3_FIXED, PCM_S16LE_MUXER ARESAMPLE_FILTER)  += $(FATE_AC3)
 FATE_EAC3-$(call PCM, EAC3, EAC3,          PCM_S16LE_MUXER ARESAMPLE_FILTER) += $(FATE_EAC3)
 
-FATE_AC3-$(call ENCDEC, AC3, AC3, WAV_MUXER WAV_DEMUXER ARESAMPLE_FILTER PCM_S16LE_ENCODER PIPE_PROTOCOL) += fate-ac3-encode
-fate-ac3-encode: CMD = enc_dec_pcm ac3 wav s16le $(subst $(SAMPLES),$(TARGET_SAMPLES),$(REF)) -c:a ac3 -b:a 128k
-fate-ac3-encode: CMP_SHIFT = -1024
-fate-ac3-encode: CMP_TARGET = 404.53
-fate-ac3-encode: SIZE_TOLERANCE = 488
+FATE_AC3-$(call ENCDEC, AC3, MP4 MOV, WAV_MUXER WAV_DEMUXER ARESAMPLE_FILTER PCM_S16LE_ENCODER PIPE_PROTOCOL) += fate-ac3-encode
+fate-ac3-encode: CMD = enc_dec_pcm mp4 wav s16le $(subst $(SAMPLES),$(TARGET_SAMPLES),$(REF)) -c:a ac3 -b:a 128k
+fate-ac3-encode: CMP_TARGET = 410.25
 
 
-FATE_EAC3-$(call ENCDEC, EAC3, EAC3, WAV_MUXER WAV_DEMUXER ARESAMPLE_FILTER PCM_S16LE_ENCODER PIPE_PROTOCOL) += fate-eac3-encode
-fate-eac3-encode: CMD = enc_dec_pcm eac3 wav s16le $(subst $(SAMPLES),$(TARGET_SAMPLES),$(REF)) -c:a eac3 -b:a 128k
-fate-eac3-encode: CMP_SHIFT = -1024
-fate-eac3-encode: CMP_TARGET = 516.94
-fate-eac3-encode: SIZE_TOLERANCE = 488
+FATE_EAC3-$(call ENCDEC, EAC3, MP4 MOV, WAV_MUXER WAV_DEMUXER ARESAMPLE_FILTER PCM_S16LE_ENCODER PIPE_PROTOCOL) += fate-eac3-encode
+fate-eac3-encode: CMD = enc_dec_pcm mp4 wav s16le $(subst $(SAMPLES),$(TARGET_SAMPLES),$(REF)) -c:a eac3 -b:a 128k
+fate-eac3-encode: CMP_TARGET = 520.69
 
 fate-ac3-encode fate-eac3-encode: CMP = stddev
 fate-ac3-encode fate-eac3-encode: REF = $(SAMPLES)/audio-reference/luckynight_2ch_44kHz_s16.wav
@@ -89,7 +85,7 @@ fate-ac3-fixed-encode: tests/data/asynth-44100-2.wav
 fate-ac3-fixed-encode: SRC = $(TARGET_PATH)/tests/data/asynth-44100-2.wav
 fate-ac3-fixed-encode: CMD = md5 -i $(SRC) -c ac3_fixed -ab 128k -f ac3 -flags +bitexact -af aresample
 fate-ac3-fixed-encode: CMP = oneline
-fate-ac3-fixed-encode: REF = e9d78bca187b4bbafc4512bcea8efd3e
+fate-ac3-fixed-encode: REF = 024170ce0147cbb9d6eda34b30b05935
 
 # This tests that the LFE does not get lost when converting the input 7.1
 # to a channel layout supported by the encoder.
diff --git a/tests/ref/fate/ac3-fixed-encode-2 b/tests/ref/fate/ac3-fixed-encode-2
index 8e945b6637..468441cd7e 100644
--- a/tests/ref/fate/ac3-fixed-encode-2
+++ b/tests/ref/fate/ac3-fixed-encode-2
@@ -11,3 +11,4 @@
 0,       7424,       7424,     1536,     1114, 0xfbcc27ad
 0,       8960,       8960,     1536,     1114, 0xe7ed3321
 0,      10496,      10496,     1536,     1114, 0xa1823473
+0,      12032,      12032,      256,     1116, 0x7cc628e3, S=1, Skip Samples,       10, 0x00190005
diff --git a/tests/ref/fate/autorotate b/tests/ref/fate/autorotate
index 1166bb63fc..34870592c8 100644
--- a/tests/ref/fate/autorotate
+++ b/tests/ref/fate/autorotate
@@ -1,4 +1,4 @@
-bb03affe94dfbe39c8cad8eb69dc367c *tests/data/fate/autorotate.mov
+54cea6414b534ed2ee8c37c00988dabd *tests/data/fate/autorotate.mov
 197366 tests/data/fate/autorotate.mov
 #extradata 0:       34, 0x9d7d073f
 #tb 0: 1/15360
diff --git a/tests/ref/fate/shortest b/tests/ref/fate/shortest
index 3592b3f1f0..68d1038b71 100644
--- a/tests/ref/fate/shortest
+++ b/tests/ref/fate/shortest
@@ -115,3 +115,4 @@
 0,         48,         48,        1,    28143, 0x1df268c5, S=1, Quality stats,        8, 0x050000a1
 1,      85760,      85760,     1536,      418, 0xae06ca91
 0,         49,         49,        1,    10073, 0xedb9f031, F=0x0, S=1, Quality stats,        8, 0x050400a2
+1,      87296,      87296,      256,      418, 0x0a0bdddc, S=1, Skip Samples,       10, 0x00190005
diff --git a/tests/ref/fate/unknown_layout-ac3 b/tests/ref/fate/unknown_layout-ac3
index a694c52899..071541f2db 100644
--- a/tests/ref/fate/unknown_layout-ac3
+++ b/tests/ref/fate/unknown_layout-ac3
@@ -1 +1 @@
-ff7e25844b3cb6abb571ef7e226cbafa
+15c6d7256fa03f0fdbf6e0e2376448c0
diff --git a/tests/ref/lavf/rm b/tests/ref/lavf/rm
index 62251380cf..cec7ec0725 100644
--- a/tests/ref/lavf/rm
+++ b/tests/ref/lavf/rm
@@ -1,2 +1,2 @@
-a7b0ac6e5131bbf662a07ccc82ab8618 *tests/data/lavf/lavf.rm
+4ef2bdea6c0fbf4f71726e5a9e359415 *tests/data/lavf/lavf.rm
 346424 tests/data/lavf/lavf.rm
-- 
2.52.0

_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.