PR #23714 opened by Kacper Michajłow (kasper93)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23714
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23714.patch

Fixes playback of:
https://samples.mplayerhq.hu/DVD-Audio/ats.AOB
https://samples.mplayerhq.hu/DVD-Audio/incorrect_audio_stream_cut.AOB

Based on information from https://dvd-audio.sourceforge.io/spec/aob.shtml et 
al. 


From 17efed9e790137d9f717c7d1b89d75c58a6e72b4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= <[email protected]>
Date: Sat, 4 Jul 2026 19:58:45 +0200
Subject: [PATCH 1/3] avcodec: add PCM DVD-Audio decoder
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Decodes the LPCM variant found in DVD-Audio AOB streams. Unlike
DVD-Video LPCM it supports up to 192 kHz sample rates and splits
channels in two channel groups, which may use different quantization.
Samples are grouped in sets of 2 samples over all channels, with the
second channel group's data stored first within each set, as 16-bit
big-endian most significant parts followed by the remaining bits.

See: <https://dvd-audio.sourceforge.io/spec/aob.shtml>
Signed-off-by: Kacper Michajłow <[email protected]>
---
 Changelog               |   1 +
 doc/APIchanges          |   3 +
 libavcodec/Makefile     |   1 +
 libavcodec/allcodecs.c  |   1 +
 libavcodec/codec_desc.c |   7 +
 libavcodec/codec_id.h   |   1 +
 libavcodec/pcm-dvda.c   | 306 ++++++++++++++++++++++++++++++++++++++++
 libavcodec/version.c    |   2 +-
 libavcodec/version.h    |   2 +-
 9 files changed, 322 insertions(+), 2 deletions(-)
 create mode 100644 libavcodec/pcm-dvda.c

diff --git a/Changelog b/Changelog
index 1f5c521435..d1d230df55 100644
--- a/Changelog
+++ b/Changelog
@@ -4,6 +4,7 @@ releases are sorted from youngest to oldest.
 version <next>:
 - extensively improved AAC encoder
 - APV Vulkan encoder
+- DVD-Audio LPCM decoder and demuxing support
 
 
 version 9.0:
diff --git a/doc/APIchanges b/doc/APIchanges
index 02481f31ac..7a962f38bb 100644
--- a/doc/APIchanges
+++ b/doc/APIchanges
@@ -2,6 +2,9 @@ The last version increases of all libraries were on 2026-06-23.
 
 API changes, most recent first:
 
+2026-07-04 - xxxxxxxxxx - lavc 63.4.100 - codec_id.h
+  Add AV_CODEC_ID_PCM_DVDA.
+
 2026-06-23 - 61693f6c35f - lavu 60.34.100 - hwcontext_vulkan.h
   Switch AVVkFrame.access to VkAccessFlagBits2.
 
diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index b0e9e4f17b..c05d14bde2 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -895,6 +895,7 @@ OBJS-$(CONFIG_PCM_BLURAY_ENCODER)         += pcm-blurayenc.o
 OBJS-$(CONFIG_PCM_BLURAY_DECODER)         += pcm-bluray.o
 OBJS-$(CONFIG_PCM_DVD_DECODER)            += pcm-dvd.o
 OBJS-$(CONFIG_PCM_DVD_ENCODER)            += pcm-dvdenc.o
+OBJS-$(CONFIG_PCM_DVDA_DECODER)           += pcm-dvda.o
 OBJS-$(CONFIG_PCM_F16LE_DECODER)          += pcm.o
 OBJS-$(CONFIG_PCM_F24LE_DECODER)          += pcm.o
 OBJS-$(CONFIG_PCM_F32BE_DECODER)          += pcm.o
diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c
index e1668f1e80..2d7496a20d 100644
--- a/libavcodec/allcodecs.c
+++ b/libavcodec/allcodecs.c
@@ -560,6 +560,7 @@ extern const FFCodec ff_pcm_bluray_encoder;
 extern const FFCodec ff_pcm_bluray_decoder;
 extern const FFCodec ff_pcm_dvd_encoder;
 extern const FFCodec ff_pcm_dvd_decoder;
+extern const FFCodec ff_pcm_dvda_decoder;
 extern const FFCodec ff_pcm_f16le_decoder;
 extern const FFCodec ff_pcm_f24le_decoder;
 extern const FFCodec ff_pcm_f32be_encoder;
diff --git a/libavcodec/codec_desc.c b/libavcodec/codec_desc.c
index 81c095bea7..ab4d4ef587 100644
--- a/libavcodec/codec_desc.c
+++ b/libavcodec/codec_desc.c
@@ -2246,6 +2246,13 @@ static const AVCodecDescriptor codec_descriptors[] = {
         .long_name = NULL_IF_CONFIG_SMALL("PCM SGA"),
         .props     = AV_CODEC_PROP_INTRA_ONLY | AV_CODEC_PROP_LOSSLESS,
     },
+    {
+        .id        = AV_CODEC_ID_PCM_DVDA,
+        .type      = AVMEDIA_TYPE_AUDIO,
+        .name      = "pcm_dvda",
+        .long_name = NULL_IF_CONFIG_SMALL("PCM signed 16|20|24-bit big-endian 
for DVD-Audio media"),
+        .props     = AV_CODEC_PROP_INTRA_ONLY | AV_CODEC_PROP_LOSSLESS,
+    },
 
     /* various ADPCM codecs */
     {
diff --git a/libavcodec/codec_id.h b/libavcodec/codec_id.h
index 668a068efe..7a47b1d110 100644
--- a/libavcodec/codec_id.h
+++ b/libavcodec/codec_id.h
@@ -364,6 +364,7 @@ enum AVCodecID {
     AV_CODEC_ID_PCM_F24LE,
     AV_CODEC_ID_PCM_VIDC,
     AV_CODEC_ID_PCM_SGA,
+    AV_CODEC_ID_PCM_DVDA,
 
     /* various ADPCM codecs */
     AV_CODEC_ID_ADPCM_IMA_QT = 0x11000,
diff --git a/libavcodec/pcm-dvda.c b/libavcodec/pcm-dvda.c
new file mode 100644
index 0000000000..228e826010
--- /dev/null
+++ b/libavcodec/pcm-dvda.c
@@ -0,0 +1,306 @@
+/*
+ * LPCM codec for PCM formats found in DVD-Audio streams
+ * Copyright (c) 2026 Kacper Michajłow
+ *
+ * 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
+ */
+
+/**
+ * @file
+ * LPCM codec for PCM formats found in DVD-Audio (AOB) streams.
+ *
+ * Samples are grouped in sets of 2 samples over all channels. Channels are
+ * split in up to two channel groups, which may use different quantization.
+ * Within each set the second channel group's data comes first. A group's
+ * data consists of big-endian 16-bit most significant sample parts, followed
+ * by the remaining 4 or 8 bits of all samples for 20 or 24-bit quantization.
+ *
+ * Packets begin with the LPCM private stream header (following the substream
+ * ID), which carries the audio format and the header length.
+ */
+
+#include "libavutil/channel_layout.h"
+#include "libavutil/intreadwrite.h"
+
+#include "avcodec.h"
+#include "bytestream.h"
+#include "codec_internal.h"
+#include "decode.h"
+
+#define HEADER_SIZE 11
+
+typedef struct PCMDVDAContext {
+    uint32_t last_header;    // Cached header to avoid reparsing
+    int block_size;          // Size of a set of 2 samples over all channels
+    int channels;
+    int group_channels[2];   // Channels in group 1 / group 2 (0 = unused)
+    int group_bits[2];       // Quantization of group 1 / group 2
+    uint8_t group_map[2][6]; // Stream channel -> native layout position
+} PCMDVDAContext;
+
+#define CH_F   AV_CH_FRONT_CENTER
+#define CH_L   AV_CH_FRONT_LEFT
+#define CH_R   AV_CH_FRONT_RIGHT
+#define CH_LF  AV_CH_LOW_FREQUENCY
+#define CH_S   AV_CH_BACK_CENTER
+#define CH_LS  AV_CH_BACK_LEFT
+#define CH_RS  AV_CH_BACK_RIGHT
+
+static const struct {
+    uint8_t group1_channels;
+    uint8_t group2_channels;
+    uint64_t channels[6];   // in stream order, group 1 first
+} channel_assignments[21] = {
+    [ 0] = { 1, 0, { CH_F } },
+    [ 1] = { 2, 0, { CH_L, CH_R } },
+    [ 2] = { 2, 1, { CH_L, CH_R, CH_S } },
+    [ 3] = { 2, 2, { CH_L, CH_R, CH_LS, CH_RS } },
+    [ 4] = { 2, 1, { CH_L, CH_R, CH_LF } },
+    [ 5] = { 2, 2, { CH_L, CH_R, CH_LF, CH_S } },
+    [ 6] = { 2, 3, { CH_L, CH_R, CH_LF, CH_LS, CH_RS } },
+    [ 7] = { 2, 1, { CH_L, CH_R, CH_F } },
+    [ 8] = { 2, 2, { CH_L, CH_R, CH_F, CH_S } },
+    [ 9] = { 2, 3, { CH_L, CH_R, CH_F, CH_LS, CH_RS } },
+    [10] = { 2, 2, { CH_L, CH_R, CH_F, CH_LF } },
+    [11] = { 2, 3, { CH_L, CH_R, CH_F, CH_LF, CH_S } },
+    [12] = { 2, 4, { CH_L, CH_R, CH_F, CH_LF, CH_LS, CH_RS } },
+    [13] = { 3, 1, { CH_L, CH_R, CH_F, CH_S } },
+    [14] = { 3, 2, { CH_L, CH_R, CH_F, CH_LS, CH_RS } },
+    [15] = { 3, 1, { CH_L, CH_R, CH_F, CH_LF } },
+    [16] = { 3, 2, { CH_L, CH_R, CH_F, CH_LF, CH_S } },
+    [17] = { 3, 3, { CH_L, CH_R, CH_F, CH_LF, CH_LS, CH_RS } },
+    [18] = { 4, 1, { CH_L, CH_R, CH_LS, CH_RS, CH_LF } },
+    [19] = { 4, 1, { CH_L, CH_R, CH_LS, CH_RS, CH_F } },
+    [20] = { 4, 2, { CH_L, CH_R, CH_LS, CH_RS, CH_F, CH_LF } },
+};
+
+static av_cold int pcm_dvda_decode_init(AVCodecContext *avctx)
+{
+    PCMDVDAContext *s = avctx->priv_data;
+
+    /* Invalid header to force parsing of the first header */
+    s->last_header = -1;
+
+    return 0;
+}
+
+static int pcm_dvda_parse_header(AVCodecContext *avctx, const uint8_t *header)
+{
+    PCMDVDAContext *s = avctx->priv_data;
+    /*
+     * header[0]  continuity counter
+     * header[1]  header size (2 bytes, size following this field)
+     * header[3]  byte pointer to the start of the first audio frame (2 bytes)
+     * header[5]  unknown, observed 0x10 for stereo and 0x00 for surround
+     * header[6]  quantization, group 1 (4) / group 2 (4)
+     * header[7]  sample rate, group 1 (4) / group 2 (4)
+     * header[8]  unknown
+     * header[9]  channel group assignment
+     * header[10] unknown
+     */
+    uint32_t header_int = header[6] | header[7] << 8 | header[9] << 16;
+    int assignment = header[9];
+    int bits[2], rate[2];
+    uint64_t mask = 0;
+
+    /* early exit if the header didn't change */
+    if (s->last_header == header_int)
+        return 0;
+    s->last_header = -1;
+
+    if (avctx->debug & FF_DEBUG_PICT_INFO)
+        av_log(avctx, AV_LOG_DEBUG, "pcm_dvda_parse_header: header = 
%02x%02x%02x\n",
+               header[6], header[7], header[9]);
+
+    if (assignment > 20) {
+        av_log(avctx, AV_LOG_ERROR, "invalid channel group assignment %d\n",
+               assignment);
+        return AVERROR_INVALIDDATA;
+    }
+
+    s->group_channels[0] = channel_assignments[assignment].group1_channels;
+    s->group_channels[1] = channel_assignments[assignment].group2_channels;
+
+    for (int i = 0; i < 2; i++) {
+        int quant = i ? header[6] & 0xf : header[6] >> 4;
+        int freq  = i ? header[7] & 0xf : header[7] >> 4;
+
+        /* 0xf marks an absent channel group */
+        if (i && (quant == 0xf || freq == 0xf))
+            s->group_channels[1] = 0;
+        if (!s->group_channels[i]) {
+            bits[i] = rate[i] = 0;
+            continue;
+        }
+
+        if (quant > 2 || (freq & 7) > 2) {
+            av_log(avctx, AV_LOG_ERROR,
+                   "invalid group %d quantization %#x or sample rate %#x\n",
+                   i + 1, quant, freq);
+            return AVERROR_INVALIDDATA;
+        }
+        bits[i] = 16 + 4 * quant;
+        rate[i] = (freq & 8 ? 44100 : 48000) << (freq & 7);
+
+        if (bits[i] == 20) {
+            avpriv_request_sample(avctx, "20-bit group %d quantization", i + 
1);
+            return AVERROR_PATCHWELCOME;
+        }
+    }
+
+    if (s->group_channels[1] && rate[1] != rate[0]) {
+        avpriv_request_sample(avctx, "Mixed group sample rates (%d, %d)",
+                              rate[0], rate[1]);
+        return AVERROR_PATCHWELCOME;
+    }
+
+    s->group_bits[0] = bits[0];
+    s->group_bits[1] = bits[1];
+    s->channels      = s->group_channels[0] + s->group_channels[1];
+    s->block_size    = 2 * (s->group_channels[0] * bits[0] +
+                            s->group_channels[1] * bits[1]) / 8;
+
+    /* map the stream channel order to the native layout order */
+    for (int i = 0; i < s->channels; i++)
+        mask |= channel_assignments[assignment].channels[i];
+    for (int i = 0; i < s->channels; i++) {
+        uint64_t ch = channel_assignments[assignment].channels[i];
+        int pos = av_popcount64(mask & (ch - 1));
+        if (i < s->group_channels[0])
+            s->group_map[0][i] = pos;
+        else
+            s->group_map[1][i - s->group_channels[0]] = pos;
+    }
+
+    avctx->sample_fmt = FFMAX(bits[0], bits[1]) == 16 ? AV_SAMPLE_FMT_S16
+                                                      : AV_SAMPLE_FMT_S32;
+    avctx->bits_per_raw_sample = FFMAX(bits[0], bits[1]);
+    avctx->sample_rate = rate[0];
+    av_channel_layout_uninit(&avctx->ch_layout);
+    av_channel_layout_from_mask(&avctx->ch_layout, mask);
+    avctx->bit_rate = (int64_t)s->block_size * rate[0] * 8 / 2;
+
+    if (avctx->debug & FF_DEBUG_PICT_INFO)
+        ff_dlog(avctx,
+                "pcm_dvda_parse_header: %d channels, %d+%d bits per sample, "
+                "%d Hz, %"PRId64" bit/s\n",
+                s->channels, bits[0], bits[1], avctx->sample_rate,
+                avctx->bit_rate);
+
+    s->last_header = header_int;
+
+    return 0;
+}
+
+static void pcm_dvda_decode_samples(AVCodecContext *avctx, GetByteContext *gb,
+                                    void *dst, int blocks)
+{
+    PCMDVDAContext *s = avctx->priv_data;
+    int16_t *dst16    = dst;
+    int32_t *dst32    = dst;
+
+    while (blocks--) {
+        /* the second channel group's data comes first in each set */
+        for (int g = 1; g >= 0; g--) {
+            const int ch          = s->group_channels[g];
+            const int bits        = s->group_bits[g];
+            const uint8_t *map    = s->group_map[g];
+
+            if (!ch)
+                continue;
+
+            for (int n = 0; n < 2; n++) {
+                for (int j = 0; j < ch; j++) {
+                    unsigned v = bytestream2_get_be16u(gb);
+                    if (avctx->sample_fmt == AV_SAMPLE_FMT_S16)
+                        dst16[n * s->channels + map[j]] = v;
+                    else
+                        dst32[n * s->channels + map[j]] = v << 16;
+                }
+            }
+            if (bits == 24) {
+                for (int n = 0; n < 2; n++)
+                    for (int j = 0; j < ch; j++)
+                        dst32[n * s->channels + map[j]] |=
+                            bytestream2_get_byteu(gb) << 8;
+            }
+        }
+        dst16 += 2 * s->channels;
+        dst32 += 2 * s->channels;
+    }
+}
+
+static int pcm_dvda_decode_frame(AVCodecContext *avctx, AVFrame *frame,
+                                 int *got_frame_ptr, AVPacket *avpkt)
+{
+    PCMDVDAContext *s  = avctx->priv_data;
+    int buf_size       = avpkt->size;
+    GetByteContext gb;
+    int header_size;
+    int retval;
+    int blocks;
+
+    if (buf_size < HEADER_SIZE) {
+        av_log(avctx, AV_LOG_ERROR, "PCM packet too small\n");
+        return AVERROR_INVALIDDATA;
+    }
+
+    /* skip the header including its padding */
+    header_size = 3 + AV_RB16(avpkt->data + 1);
+    if (header_size < HEADER_SIZE || header_size > buf_size) {
+        av_log(avctx, AV_LOG_ERROR, "invalid PCM header size %d\n",
+               header_size);
+        return AVERROR_INVALIDDATA;
+    }
+
+    if ((retval = pcm_dvda_parse_header(avctx, avpkt->data)))
+        return retval;
+
+    buf_size -= header_size;
+    blocks    = buf_size / s->block_size;
+    if (buf_size % s->block_size)
+        av_log(avctx, AV_LOG_DEBUG, "ignoring %d leftover bytes\n",
+               buf_size % s->block_size);
+    if (!blocks) {
+        *got_frame_ptr = 0;
+        return avpkt->size;
+    }
+
+    /* get output buffer */
+    frame->nb_samples = blocks * 2;
+    if ((retval = ff_get_buffer(avctx, frame, 0)) < 0)
+        return retval;
+
+    bytestream2_init(&gb, avpkt->data + header_size, blocks * s->block_size);
+    pcm_dvda_decode_samples(avctx, &gb, frame->data[0], blocks);
+
+    *got_frame_ptr = 1;
+
+    return avpkt->size;
+}
+
+const FFCodec ff_pcm_dvda_decoder = {
+    .p.name         = "pcm_dvda",
+    CODEC_LONG_NAME("PCM signed 16|20|24-bit big-endian for DVD-Audio media"),
+    .p.type         = AVMEDIA_TYPE_AUDIO,
+    .p.id           = AV_CODEC_ID_PCM_DVDA,
+    .priv_data_size = sizeof(PCMDVDAContext),
+    .init           = pcm_dvda_decode_init,
+    FF_CODEC_DECODE_CB(pcm_dvda_decode_frame),
+    .p.capabilities = AV_CODEC_CAP_CHANNEL_CONF |
+                      AV_CODEC_CAP_DR1,
+};
diff --git a/libavcodec/version.c b/libavcodec/version.c
index 3243ca2df7..7a87d01c90 100644
--- a/libavcodec/version.c
+++ b/libavcodec/version.c
@@ -32,7 +32,7 @@ const char av_codec_ffversion[] = "FFmpeg version " 
FFMPEG_VERSION;
 unsigned avcodec_version(void)
 {
     static_assert(AV_CODEC_ID_JPEGXS       ==   272 &&
-                  AV_CODEC_ID_PCM_SGA      == 65572 &&
+                  AV_CODEC_ID_PCM_DVDA     == 65573 &&
                   AV_CODEC_ID_ADPCM_SANYO  == 69685 &&
                   AV_CODEC_ID_CBD2_DPCM    == 81928 &&
                   AV_CODEC_ID_G728         == 86123 &&
diff --git a/libavcodec/version.h b/libavcodec/version.h
index 43794ea588..06631ffa8c 100644
--- a/libavcodec/version.h
+++ b/libavcodec/version.h
@@ -29,7 +29,7 @@
 
 #include "version_major.h"
 
-#define LIBAVCODEC_VERSION_MINOR   3
+#define LIBAVCODEC_VERSION_MINOR   4
 #define LIBAVCODEC_VERSION_MICRO 100
 
 #define LIBAVCODEC_VERSION_INT  AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \
-- 
2.52.0


From 86d6aba241c4846ac840709fa703d32c23739e75 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= <[email protected]>
Date: Sat, 4 Jul 2026 20:00:59 +0200
Subject: [PATCH 2/3] avformat/mpeg: fix demuxing of DVD-Audio LPCM
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Substream 0xa0 packets without the DVD-Video dynamic range control
marker byte were assumed to be MLP, while in DVD-Audio AOBs substream
0xa0 always carries LPCM (MLP uses 0xa1), so hi-res LPCM streams were
misdetected and decoded as garbage. Classify them as PCM_DVDA and keep
the private stream header in the packet for the decoder to parse, as
its length is variable and it carries the audio format.

Signed-off-by: Kacper Michajłow <[email protected]>
---
 libavformat/mpeg.c    | 68 +++++++++++++++++++++++++++++++------------
 libavformat/version.h |  2 +-
 2 files changed, 50 insertions(+), 20 deletions(-)

diff --git a/libavformat/mpeg.c b/libavformat/mpeg.c
index 29abe329b9..43bbf0ef9f 100644
--- a/libavformat/mpeg.c
+++ b/libavformat/mpeg.c
@@ -488,6 +488,7 @@ static int mpegps_read_packet(AVFormatContext *s,
     FFStream *sti;
     int len, startcode, i, es_type, ret;
     int pcm_dvd = 0;
+    int pcm_dvda = 0;
     int request_probe= 0;
     enum AVCodecID codec_id = AV_CODEC_ID_NONE;
     enum AVMediaType type;
@@ -503,19 +504,33 @@ redo:
             goto skip;
 
         if (!m->raw_ac3) {
-            /* audio: skip header */
-            avio_skip(s->pb, 3);
-            len -= 3;
-            if (startcode >= 0xb0 && startcode <= 0xbf) {
-                /* MLP/TrueHD audio has a 4-byte header */
-                avio_r8(s->pb);
-                len--;
-            } else if (startcode >= 0xa0 && startcode <= 0xaf) {
-                ret = ffio_ensure_seekback(s->pb, 3);
+            if (startcode >= 0xa0 && startcode <= 0xaf) {
+                uint8_t header[6];
+
+                /* Classify the LPCM/MLP substream here; its header is
+                 * skipped at "found" below, once the stream codec is known. */
+                if (len < 6)
+                    goto skip;
+                ret = ffio_ensure_seekback(s->pb, 6);
                 if (ret < 0)
                     return ret;
-                pcm_dvd = (avio_rb24(s->pb) & 0xFF) == 0x80;
-                avio_skip(s->pb, -3);
+                if (avio_read(s->pb, header, 6) != 6)
+                    return AVERROR_INVALIDDATA;
+                avio_seek(s->pb, -6, SEEK_CUR);
+                /* DVD-Video LPCM has the dynamic range control byte here, 
while
+                 * DVD-Audio LPCM and MLP do not. Only substream 0xa0 carries
+                 * LPCM in DVD-Audio AOBs, MLP uses substream 0xa1. */
+                pcm_dvd = header[5] == 0x80;
+                pcm_dvda = startcode == 0xa0 && !pcm_dvd;
+            } else {
+                /* audio: skip header */
+                avio_skip(s->pb, 3);
+                len -= 3;
+                if (startcode >= 0xb0 && startcode <= 0xbf) {
+                    /* MLP/TrueHD audio has a 4-byte header */
+                    avio_r8(s->pb);
+                    len--;
+                }
             }
         }
     }
@@ -602,7 +617,9 @@ redo:
         codec_id = AV_CODEC_ID_DTS;
     } else if (startcode >= 0xa0 && startcode <= 0xaf) {
         type     = AVMEDIA_TYPE_AUDIO;
-        if (!pcm_dvd) {
+        if (pcm_dvda) {
+            codec_id = AV_CODEC_ID_PCM_DVDA;
+        } else if (!pcm_dvd) {
             codec_id = AV_CODEC_ID_MLP;
         } else {
             codec_id = AV_CODEC_ID_PCM_DVD;
@@ -648,13 +665,26 @@ skip:
 found:
     if (st->discard >= AVDISCARD_ALL)
         goto skip;
-    if (startcode >= 0xa0 && startcode <= 0xaf) {
-      if (st->codecpar->codec_id == AV_CODEC_ID_MLP) {
-            if (len < 6)
-                goto skip;
-            avio_skip(s->pb, 6);
-            len -=6;
-      }
+    if (startcode >= 0xa0 && startcode <= 0xaf && !m->raw_ac3) {
+        int header_len = 0;
+        /* Skip the substream headers of codecs whose decoders do not expect
+         * them. AV_CODEC_ID_PCM_DVDA parses the header from the packet. */
+        if (st->codecpar->codec_id == AV_CODEC_ID_MLP) {
+            /* 3-byte substream header + 6-byte MLP header */
+            header_len = 9;
+        } else if (st->codecpar->codec_id == AV_CODEC_ID_PCM_DVD) {
+            header_len = 3;
+        }
+        if (len <= header_len)
+            goto skip;
+        avio_skip(s->pb, header_len);
+        len -= header_len;
+    } else if (startcode >= 0xa0 && startcode <= 0xaf &&
+               st->codecpar->codec_id == AV_CODEC_ID_MLP) {
+        if (len < 6)
+            goto skip;
+        avio_skip(s->pb, 6);
+        len -= 6;
     }
     ret = av_get_packet(s->pb, pkt, len);
 
diff --git a/libavformat/version.h b/libavformat/version.h
index 7ff1483912..ee91990360 100644
--- a/libavformat/version.h
+++ b/libavformat/version.h
@@ -32,7 +32,7 @@
 #include "version_major.h"
 
 #define LIBAVFORMAT_VERSION_MINOR   3
-#define LIBAVFORMAT_VERSION_MICRO 100
+#define LIBAVFORMAT_VERSION_MICRO 101
 
 #define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \
                                                LIBAVFORMAT_VERSION_MINOR, \
-- 
2.52.0


From 99aaad94c096c122bf2e6191c468456e91a7594d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= <[email protected]>
Date: Mon, 6 Jul 2026 04:47:11 +0200
Subject: [PATCH 3/3] tests/fate/pcm: add fate-pcm_dvda test
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Input is cut down version of https://samples.mplayerhq.hu/DVD-Audio/ats.AOB

Signed-off-by: Kacper Michajłow <[email protected]>
---
 tests/fate/pcm.mak      |  3 +++
 tests/ref/fate/pcm_dvda | 29 +++++++++++++++++++++++++++++
 2 files changed, 32 insertions(+)
 create mode 100644 tests/ref/fate/pcm_dvda

diff --git a/tests/fate/pcm.mak b/tests/fate/pcm.mak
index 7f1e650507..6046244491 100644
--- a/tests/fate/pcm.mak
+++ b/tests/fate/pcm.mak
@@ -4,6 +4,9 @@ fate-iff-pcm: CMD = md5 -i $(TARGET_SAMPLES)/iff/Bells -f s16le 
-af aresample
 FATE_SAMPLES_PCM-$(call FRAMECRC, MPEGPS, PCM_DVD, ARESAMPLE_FILTER) += 
fate-pcm_dvd
 fate-pcm_dvd: CMD = framecrc -i 
$(TARGET_SAMPLES)/pcm-dvd/coolitnow-partial.vob -vn -af aresample
 
+FATE_SAMPLES_PCM-$(call FRAMECRC, MPEGPS, PCM_DVDA, PCM_S24LE_ENCODER) += 
fate-pcm_dvda
+fate-pcm_dvda: CMD = framecrc -i 
$(TARGET_SAMPLES)/pcm-dvda/pcm_dvda-96k24bit.aob -c:a pcm_s24le
+
 FATE_SAMPLES_PCM-$(call FRAMECRC, EA, PCM_S16LE_PLANAR, ARESAMPLE_FILTER) += 
fate-pcm-planar
 fate-pcm-planar: CMD = framecrc -i $(TARGET_SAMPLES)/ea-mad/xeasport.mad -vn 
-af aresample
 
diff --git a/tests/ref/fate/pcm_dvda b/tests/ref/fate/pcm_dvda
new file mode 100644
index 0000000000..577d756cd2
--- /dev/null
+++ b/tests/ref/fate/pcm_dvda
@@ -0,0 +1,29 @@
+#tb 0: 1/96000
+#media_type 0: audio
+#codec_id 0: pcm_s24le
+#sample_rate 0: 96000
+#channel_layout_name 0: stereo
+0,          0,          0,      330,     1980, 0x08f4bdcd
+0,        400,        400,      334,     2004, 0x79bad502
+0,        720,        720,      334,     2004, 0x3a23a455
+0,       1040,       1040,      334,     2004, 0x4037dedb
+0,       1360,       1360,      334,     2004, 0x105fe126
+0,       1680,       1680,      334,     2004, 0x50315b3f
+0,       2000,       2000,      334,     2004, 0xeb801735
+0,       2400,       2400,      334,     2004, 0x07c430d0
+0,       2720,       2720,      334,     2004, 0xcbbf2986
+0,       3040,       3040,      334,     2004, 0x318b334a
+0,       3360,       3360,      334,     2004, 0x251947cd
+0,       3680,       3680,      334,     2004, 0xe8bda1a7
+0,       4080,       4080,      334,     2004, 0xf0b38ab9
+0,       4400,       4400,      334,     2004, 0x1b3854de
+0,       4720,       4720,      334,     2004, 0x77674434
+0,       5040,       5040,      334,     2004, 0x9b3241f0
+0,       5360,       5360,      334,     2004, 0xd760d1c0
+0,       5680,       5680,      334,     2004, 0x8184f1a8
+0,       6080,       6080,      334,     2004, 0x49f8954b
+0,       6400,       6400,      334,     2004, 0x8e5970c1
+0,       6720,       6720,      334,     2004, 0x36fed206
+0,       7040,       7040,      334,     2004, 0xf0735b0b
+0,       7360,       7360,      334,     2004, 0x4cd51cf9
+0,       7680,       7680,      334,     2004, 0x2d51e686
-- 
2.52.0

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

Reply via email to