PR #24680 opened by AYOUB NABIL BOUBAGRAT (ayoubnabil)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24680
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24680.patch

add a parser that assembles ac-4 sync frames from fragmented input. it handles 
0xac40, 0xac41 and extended frame sizes, then returns the raw frame without the 
sync header or crc.

fate covers fragmentation, resynchronization and allocation failure. i also 
checked 54 frames from a real mpeg-ts stream. their sizes and hashes match the 
raw ac-4 demuxer.

this patch does not parse the toc or decode audio.

framing follows [etsi ts 103 190-1 v1.4.1, annex 
g](https://www.etsi.org/deliver/etsi_ts/103100_103199/10319001/01.04.01_60/ts_10319001v010401p.pdf)



>From beff10f23050e6dd84b60629b1c0216f54f2cc91 Mon Sep 17 00:00:00 2001
From: Ayoub Nabil Boubagrat <[email protected]>
Date: Thu, 24 Sep 2026 21:37:58 +0200
Subject: [PATCH] avcodec/ac4: add sync-frame parser

Signed-off-by: Ayoub Nabil Boubagrat 
<[email protected]>
---
 libavcodec/Makefile           |   2 +
 libavcodec/ac4_parser.c       | 178 ++++++++++++++++++++++++++++
 libavcodec/parsers.c          |   1 +
 libavcodec/tests/ac4_parser.c | 217 ++++++++++++++++++++++++++++++++++
 tests/fate/libavcodec.mak     |   4 +
 tests/ref/fate/ac4-parser     |  24 ++++
 6 files changed, 426 insertions(+)
 create mode 100644 libavcodec/ac4_parser.c
 create mode 100644 libavcodec/tests/ac4_parser.c
 create mode 100644 tests/ref/fate/ac4-parser

diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index e2c60e9051..dab2952669 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -1251,6 +1251,7 @@ OBJS-$(CONFIG_AAC_LATM_PARSER)         += latm_parser.o
 OBJS-$(CONFIG_AAC_PARSER)              += aac_parser.o aac_ac3_parser.o
 OBJS-$(CONFIG_AC3_PARSER)              += aac_ac3_parser.o ac3tab.o \
                                           ac3_channel_layout_tab.o
+OBJS-$(CONFIG_AC4_PARSER)              += ac4_parser.o
 OBJS-$(CONFIG_ADX_PARSER)              += adx_parser.o
 OBJS-$(CONFIG_AHX_PARSER)              += ahx_parser.o
 OBJS-$(CONFIG_AMR_PARSER)              += amr_parser.o
@@ -1385,6 +1386,7 @@ TESTPROGS = avcodec                                       
              \
             mathops                                                    \
 
 TESTPROGS-$(CONFIG_AC3DSP)                += ac3
+TESTPROGS-$(CONFIG_AC4_PARSER)            += ac4_parser
 TESTPROGS-$(CONFIG_APV_DECODER)           += apv
 TESTPROGS-$(CONFIG_AV1_VAAPI_ENCODER)     += av1_levels
 TESTPROGS-$(CONFIG_CABAC)                 += cabac
diff --git a/libavcodec/ac4_parser.c b/libavcodec/ac4_parser.c
new file mode 100644
index 0000000000..0d35f72b20
--- /dev/null
+++ b/libavcodec/ac4_parser.c
@@ -0,0 +1,178 @@
+/*
+ * AC-4 sync frame parser
+ *
+ * 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 <string.h>
+
+#include "libavutil/common.h"
+#include "libavutil/mem.h"
+
+#include "parser_internal.h"
+
+typedef struct AC4ParseContext {
+    uint8_t header[7];
+    int header_pos;
+    uint8_t *frame;
+    unsigned int frame_alloc_size;
+    int frame_pos;
+    int frame_size;
+    int header_size;
+    int payload_size;
+} AC4ParseContext;
+
+static int ac4_parse(AVCodecParserContext *s, AVCodecContext *avctx,
+                     const uint8_t **poutbuf, int *poutbuf_size,
+                     const uint8_t *buf, int buf_size)
+{
+    AC4ParseContext *pc = s->priv_data;
+    int i = 0;
+    int header_start = -1;
+
+    *poutbuf = NULL;
+    *poutbuf_size = 0;
+
+    /* container packets may contain raw ac-4 frames without a sync header. */
+    if (s->flags & PARSER_FLAG_COMPLETE_FRAMES) {
+        *poutbuf = buf;
+        *poutbuf_size = buf_size;
+        return buf_size;
+    }
+
+    if (!buf_size) {
+        av_freep(&pc->frame);
+        pc->frame_alloc_size = 0;
+        pc->frame_size = pc->frame_pos = pc->header_pos = 0;
+        return 0;
+    }
+
+    if (!pc->frame_size) {
+        /* release the previous frame, which may have used the extended size. 
*/
+        av_freep(&pc->frame);
+        pc->frame_alloc_size = 0;
+    }
+
+    while (i < buf_size) {
+        if (!pc->frame_size) {
+            int header_size, payload_size;
+            uint8_t byte = buf[i++];
+
+            if (!pc->header_pos) {
+                if (byte == 0xac) {
+                    pc->header[0] = byte;
+                    pc->header_pos = 1;
+                    header_start = i - 1;
+                }
+                continue;
+            }
+            if (pc->header_pos == 1) {
+                if (byte == 0x40 || byte == 0x41) {
+                    pc->header[1] = byte;
+                    pc->header_pos = 2;
+                } else if (byte == 0xac) {
+                    /* this byte may start the next sync word. */
+                    header_start = i - 1;
+                } else {
+                    pc->header_pos = 0;
+                }
+                continue;
+            }
+
+            pc->header[pc->header_pos++] = byte;
+            if (pc->header_pos < 4)
+                continue;
+
+            payload_size = (pc->header[2] << 8) | pc->header[3];
+            header_size = payload_size == 0xffff ? 7 : 4;
+            if (pc->header_pos < header_size)
+                continue;
+            if (header_size == 7)
+                payload_size = (pc->header[4] << 16) |
+                               (pc->header[5] << 8) | pc->header[6];
+            if (!payload_size) {
+                pc->header_pos = 0;
+                continue;
+            }
+
+            /* the 24-bit size field bounds the complete frame to about 16 
mebibytes. */
+            pc->frame_size = header_size + payload_size +
+                             (pc->header[1] == 0x41 ? 2 : 0);
+            pc->header_size = header_size;
+            pc->payload_size = payload_size;
+            if (header_start >= 0 && pc->frame_size <= buf_size - 
header_start) {
+                /* return the raw frame, without the sync header or crc. */
+                *poutbuf = buf + header_start + header_size;
+                *poutbuf_size = payload_size;
+                i = header_start + pc->frame_size;
+                pc->frame_size = pc->header_pos = 0;
+                return i;
+            }
+            pc->frame = av_fast_realloc(pc->frame, &pc->frame_alloc_size,
+                                        header_size + 
AV_INPUT_BUFFER_PADDING_SIZE);
+            if (!pc->frame) {
+                pc->frame_size = pc->header_pos = 0;
+                /* look for another sync word in the remaining input. */
+                continue;
+            }
+            memcpy(pc->frame, pc->header, header_size);
+            memset(pc->frame + header_size, 0, AV_INPUT_BUFFER_PADDING_SIZE);
+            pc->frame_pos = header_size;
+            pc->header_pos = 0;
+        }
+
+        if (pc->frame_size) {
+            int count = FFMIN(buf_size - i, pc->frame_size - pc->frame_pos);
+            uint8_t *frame = av_fast_realloc(pc->frame, &pc->frame_alloc_size,
+                                             pc->frame_pos + count +
+                                             AV_INPUT_BUFFER_PADDING_SIZE);
+            if (!frame) {
+                av_freep(&pc->frame);
+                pc->frame_size = pc->frame_pos = 0;
+                pc->frame_alloc_size = 0;
+                /* the current frame is lost, but a later one may fit. */
+                continue;
+            }
+            pc->frame = frame;
+            memcpy(pc->frame + pc->frame_pos, buf + i, count);
+            pc->frame_pos += count;
+            memset(pc->frame + pc->frame_pos, 0, AV_INPUT_BUFFER_PADDING_SIZE);
+            i += count;
+            if (pc->frame_pos == pc->frame_size) {
+                *poutbuf = pc->frame + pc->header_size;
+                *poutbuf_size = pc->payload_size;
+                pc->frame_size = pc->frame_pos = 0;
+                return i;
+            }
+        }
+    }
+
+    return i;
+}
+
+static void ac4_parse_close(AVCodecParserContext *s)
+{
+    AC4ParseContext *pc = s->priv_data;
+    av_freep(&pc->frame);
+}
+
+const FFCodecParser ff_ac4_parser = {
+    PARSER_CODEC_LIST(AV_CODEC_ID_AC4),
+    .priv_data_size = sizeof(AC4ParseContext),
+    .parse          = ac4_parse,
+    .close          = ac4_parse_close,
+};
diff --git a/libavcodec/parsers.c b/libavcodec/parsers.c
index b54442a0ea..89b4c8f4ea 100644
--- a/libavcodec/parsers.c
+++ b/libavcodec/parsers.c
@@ -24,6 +24,7 @@
 extern const FFCodecParser ff_aac_parser;
 extern const FFCodecParser ff_aac_latm_parser;
 extern const FFCodecParser ff_ac3_parser;
+extern const FFCodecParser ff_ac4_parser;
 extern const FFCodecParser ff_adx_parser;
 extern const FFCodecParser ff_ahx_parser;
 extern const FFCodecParser ff_amr_parser;
diff --git a/libavcodec/tests/ac4_parser.c b/libavcodec/tests/ac4_parser.c
new file mode 100644
index 0000000000..6a9ae9092e
--- /dev/null
+++ b/libavcodec/tests/ac4_parser.c
@@ -0,0 +1,217 @@
+/*
+ * AC-4 parser test
+ *
+ * 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 <inttypes.h>
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "libavcodec/avcodec.h"
+#include "libavutil/adler32.h"
+#include "libavutil/mem.h"
+
+static int parse(const char *name, const uint8_t *data, int size,
+                 int chunk_size, int complete, int max_alloc)
+{
+    AVCodecParserContext *parser = av_parser_init(AV_CODEC_ID_AC4);
+    AVCodecContext *avctx = avcodec_alloc_context3(NULL);
+    int offset = 0;
+    int ret = 0;
+
+    if (!parser || !avctx) {
+        ret = 1;
+        goto end;
+    }
+    avctx->codec_id = AV_CODEC_ID_AC4;
+    if (complete)
+        parser->flags |= PARSER_FLAG_COMPLETE_FRAMES;
+    if (max_alloc)
+        av_max_alloc(max_alloc);
+
+    while (offset < size) {
+        uint8_t input[64 + AV_INPUT_BUFFER_PADDING_SIZE] = { 0 };
+        uint8_t *output;
+        int output_size;
+        int chunk = FFMIN(size - offset, chunk_size);
+        int consumed;
+
+        memcpy(input, data + offset, chunk);
+        consumed = av_parser_parse2(parser, avctx, &output, &output_size,
+                                    input, chunk, AV_NOPTS_VALUE,
+                                    AV_NOPTS_VALUE, -1);
+        if (consumed < 0 || consumed > chunk || (!consumed && !output_size)) {
+            ret = 1;
+            goto end;
+        }
+        offset += consumed;
+        if (output_size) {
+            printf("%s %d", name, output_size);
+            for (int i = 0; i < output_size; i++)
+                printf(" %02x", output[i]);
+            putchar('\n');
+        }
+    }
+
+    {
+        uint8_t *output;
+        int output_size;
+
+        av_parser_parse2(parser, avctx, &output, &output_size, NULL, 0,
+                         AV_NOPTS_VALUE, AV_NOPTS_VALUE, -1);
+        printf("%s flush %d\n", name, output_size);
+    }
+
+end:
+    if (max_alloc)
+        av_max_alloc(INT_MAX);
+    av_parser_close(parser);
+    avcodec_free_context(&avctx);
+    return ret;
+}
+
+static int parse_large_complete(void)
+{
+    const int payload_size = 1 << 20;
+    const int frame_size = payload_size + 7;
+    AVCodecParserContext *parser = av_parser_init(AV_CODEC_ID_AC4);
+    AVCodecContext *avctx = avcodec_alloc_context3(NULL);
+    uint8_t *frame = av_malloc(frame_size + AV_INPUT_BUFFER_PADDING_SIZE);
+    AVAdler checksum = 1;
+    int offset = 0;
+    int frames = 0;
+    int ret = 0;
+
+    if (!parser || !avctx || !frame) {
+        ret = 1;
+        goto end;
+    }
+    avctx->codec_id = AV_CODEC_ID_AC4;
+    frame[0] = 0xac;
+    frame[1] = 0x40;
+    frame[2] = frame[3] = 0xff;
+    frame[4] = 0x10;
+    frame[5] = frame[6] = 0;
+    for (int i = 0; i < payload_size; i++)
+        frame[i + 7] = i * 17 + 3;
+    frame[1007] = 0xac;
+    frame[1008] = 0x40;
+
+    while (offset < frame_size) {
+        uint8_t input[4096 + AV_INPUT_BUFFER_PADDING_SIZE] = { 0 };
+        uint8_t *output;
+        int output_size;
+        int chunk = FFMIN(frame_size - offset, 4096);
+        int consumed;
+
+        memcpy(input, frame + offset, chunk);
+        consumed = av_parser_parse2(parser, avctx, &output, &output_size,
+                                    input, chunk, AV_NOPTS_VALUE,
+                                    AV_NOPTS_VALUE, -1);
+        if (consumed < 0 || consumed > chunk || (!consumed && !output_size)) {
+            ret = 1;
+            goto end;
+        }
+        offset += consumed;
+        if (output_size) {
+            frames++;
+            checksum = av_adler32_update(checksum, output, output_size);
+            printf("large-complete %d %d %08"PRIx32"\n", frames,
+                   output_size, checksum);
+        }
+    }
+
+    {
+        uint8_t *output;
+        int output_size;
+
+        av_parser_parse2(parser, avctx, &output, &output_size, NULL, 0,
+                         AV_NOPTS_VALUE, AV_NOPTS_VALUE, -1);
+        printf("large-complete flush %d\n", output_size);
+    }
+
+end:
+    av_free(frame);
+    av_parser_close(parser);
+    avcodec_free_context(&avctx);
+    return ret;
+}
+
+int main(void)
+{
+    uint8_t oom[7 + 5000 + 5] = { 0 };
+    uint8_t oom_same_chunk[62 + 4 + 40 + 5] = { 0 };
+    static const uint8_t consecutive[] = {
+        0xac, 0x40, 0x00, 0x03, 0x11, 0x22, 0x33,
+        0xac, 0x41, 0x00, 0x02, 0x44, 0x55, 0x12, 0x34,
+    };
+    static const uint8_t fragmented[] = {
+        0x00, 0xac, 0xac, 0x40, 0xff, 0xff, 0x00, 0x00, 0x03,
+        0xac, 0x40, 0x7f, 0xac, 0x40, 0x00, 0x01, 0x66,
+    };
+    static const uint8_t crc_fragmented[] = {
+        0xac, 0x41, 0x00, 0x02, 0x44, 0x55, 0x12, 0x34,
+        0xac, 0x40, 0x00, 0x01, 0x77,
+    };
+    static const uint8_t invalid[] = {
+        0xac, 0x40, 0x00, 0x00, 0xac, 0x40, 0x00, 0x01, 0x77,
+    };
+    static const uint8_t overlap[] = {
+        0xac, 0xac, 0x40, 0x00, 0x01, 0x77,
+    };
+    static const uint8_t truncated[] = {
+        0xac, 0x40, 0x00, 0x04, 0x11,
+    };
+    static const uint8_t raw[] = { 0x11, 0x22, 0x33 };
+    static const uint8_t large[] = {
+        0xac, 0x40, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xac, 0x40, 0x00, 0x01, 0x77,
+    };
+
+    oom[0] = 0xac;
+    oom[1] = 0x40;
+    oom[2] = 0x20;
+    oom[7 + 5000] = 0xac;
+    oom[7 + 5001] = 0x40;
+    oom[7 + 5003] = 0x01;
+    oom[7 + 5004] = 0x77;
+
+    oom_same_chunk[62] = 0xac;
+    oom_same_chunk[63] = 0x40;
+    oom_same_chunk[65] = 40;
+    oom_same_chunk[106] = 0xac;
+    oom_same_chunk[107] = 0x40;
+    oom_same_chunk[109] = 1;
+    oom_same_chunk[110] = 0x77;
+
+    return parse("consecutive", consecutive, sizeof(consecutive), 64, 0, 0) ||
+           parse("fragmented", fragmented, sizeof(fragmented), 1, 0, 0) ||
+           parse("crc-fragmented", crc_fragmented,
+                 sizeof(crc_fragmented), 1, 0, 0) ||
+           parse("invalid", invalid, sizeof(invalid), 64, 0, 0) ||
+           parse("overlap", overlap, sizeof(overlap), 64, 0, 0) ||
+           parse("truncated", truncated, sizeof(truncated), 1, 0, 0) ||
+           parse("raw", raw, sizeof(raw), 64, 1, 0) ||
+           parse_large_complete() ||
+           parse("large", large, sizeof(large), 7, 0, 4096) ||
+           parse("oom-control", oom, sizeof(oom), 64, 0, 8192) ||
+           parse("oom", oom, sizeof(oom), 64, 0, 4096) ||
+           parse("oom-same-chunk", oom_same_chunk,
+                 sizeof(oom_same_chunk), 64, 0, 100);
+}
diff --git a/tests/fate/libavcodec.mak b/tests/fate/libavcodec.mak
index f6858c4921..0e2c2fa8a5 100644
--- a/tests/fate/libavcodec.mak
+++ b/tests/fate/libavcodec.mak
@@ -2,6 +2,10 @@ FATE_LIBAVCODEC-$(CONFIG_AC3DSP) += fate-ac3-bit-allocation
 fate-ac3-bit-allocation: libavcodec/tests/ac3$(EXESUF)
 fate-ac3-bit-allocation: CMD = run libavcodec/tests/ac3$(EXESUF)
 
+FATE_LIBAVCODEC-$(CONFIG_AC4_PARSER) += fate-ac4-parser
+fate-ac4-parser: libavcodec/tests/ac4_parser$(EXESUF)
+fate-ac4-parser: CMD = run libavcodec/tests/ac4_parser$(EXESUF)
+
 FATE_LIBAVCODEC-$(CONFIG_AV1_VAAPI_ENCODER) += fate-av1-levels
 fate-av1-levels: libavcodec/tests/av1_levels$(EXESUF)
 fate-av1-levels: CMD = run libavcodec/tests/av1_levels$(EXESUF)
diff --git a/tests/ref/fate/ac4-parser b/tests/ref/fate/ac4-parser
new file mode 100644
index 0000000000..90a46b08d1
--- /dev/null
+++ b/tests/ref/fate/ac4-parser
@@ -0,0 +1,24 @@
+consecutive 3 11 22 33
+consecutive 2 44 55
+consecutive flush 0
+fragmented 3 ac 40 7f
+fragmented 1 66
+fragmented flush 0
+crc-fragmented 2 44 55
+crc-fragmented 1 77
+crc-fragmented flush 0
+invalid 1 77
+invalid flush 0
+overlap 1 77
+overlap flush 0
+truncated flush 0
+raw 3 11 22 33
+raw flush 0
+large-complete 1 1048576 12ed778e
+large-complete flush 0
+large flush 0
+oom-control flush 0
+oom 1 77
+oom flush 0
+oom-same-chunk 1 77
+oom-same-chunk flush 0
-- 
2.52.0

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

Reply via email to