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]

Reply via email to