PR #23748 opened by Jun Zhao (mypopydev)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23748
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23748.patch

These two commits improve DTS profile detection for MP4 files carrying a ddts 
box. The demuxer change (mov) parses
  the DTS profile and channel layout directly from the ddts box, and the 
decoder change (dca) makes the codec trust the
   bitstream-derived profile once parsing succeeds, ensuring the correct 
profile and channel configuration are used
  during decoding.


>From 0b8766484207686637625207fb1dcca66a7376a2 Mon Sep 17 00:00:00 2001
From: Jun Zhao <[email protected]>
Date: Thu, 9 Jul 2026 07:39:49 +0800
Subject: [PATCH 1/2] avformat/mov: read DTS profile and channel layout from
 ddts box

Parse the full DTSSpecificBox per ETSI TS 102 114: map StreamConstruction
(Table E-2) to codecpar->profile and ChannelLayout (Table E-5) to ch_layout.
This gives ffprobe correct metadata for DTS-in-MP4, including CENC-encrypted
tracks where sample data is unreadable.

Signed-off-by: Jun Zhao <[email protected]>
---
 libavformat/mov.c | 95 ++++++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 85 insertions(+), 10 deletions(-)

diff --git a/libavformat/mov.c b/libavformat/mov.c
index 4154038d9e..fccbbb3287 100644
--- a/libavformat/mov.c
+++ b/libavformat/mov.c
@@ -1166,6 +1166,75 @@ static int mov_read_dec3(MOVContext *c, AVIOContext *pb, 
MOVAtom atom)
     return 0;
 }
 
+/* Maps the DTS `StreamConstruction` field (ETSI TS 102 114 Table E-2) to the
+ * coarse ffmpeg DTS profile enum. The mapping mirrors the precedence used by
+ * the DTS parser (libavcodec/dca_parser.c): XLL => lossless (HD_MA), otherwise
+ * XXCH/XBR => HD_HRA, X96 => 96/24, XCH => ES, core-only => DTS, LBR => 
Express.
+ * Index 0 (undefined) and any value outside 1..21 stay AV_PROFILE_UNKNOWN so 
the
+ * parser can derive the profile from the bitstream instead.
+ * This is the same "container declares a profile-only field; set 
codecpar->profile
+ * directly" pattern used by the ARIB caption demuxers (matroskadec.c, 
mpegts.c). */
+static int mov_dts_stream_construction_to_profile(unsigned int sc)
+{
+    static const int profile[22] = {
+        [ 1] = AV_PROFILE_DTS,            /* core only                         
*/
+        [ 2] = AV_PROFILE_DTS_ES,         /* core + XCH (DTS-ES)               
*/
+        [ 3] = AV_PROFILE_DTS_96_24,      /* core + X96                        
*/
+        [ 4] = AV_PROFILE_DTS_96_24,      /* core + XCH + X96                  
*/
+        [ 5] = AV_PROFILE_DTS_HD_HRA,     /* core + XXCH                       
*/
+        [ 6] = AV_PROFILE_DTS_HD_HRA,     /* core + XXCH + X96                 
*/
+        [ 7] = AV_PROFILE_DTS_HD_HRA,     /* core + XCH + XXCH                 
*/
+        [ 8] = AV_PROFILE_DTS_HD_HRA,     /* core + X96 + XXCH                 
*/
+        [ 9] = AV_PROFILE_DTS_HD_HRA,     /* core + XXCH + X96                 
*/
+        [10] = AV_PROFILE_DTS_HD_HRA,     /* core + XXCH + X96                 
*/
+        [11] = AV_PROFILE_DTS_HD_HRA,     /* core + XCH + XXCH + X96           
*/
+        [12] = AV_PROFILE_DTS_HD_HRA,     /* core + X96 + XXCH                 
*/
+        [13] = AV_PROFILE_DTS_HD_MA,      /* core + XXCH + X96 + XLL           
*/
+        [14] = AV_PROFILE_DTS_HD_MA,      /* core + XXCH + XLL                 
*/
+        [15] = AV_PROFILE_DTS_HD_MA,      /* core + XCH + XLL                  
*/
+        [16] = AV_PROFILE_DTS_HD_MA,      /* core + X96 + XLL                  
*/
+        [17] = AV_PROFILE_DTS_HD_MA,      /* XXCH + XLL, no core (dtsl)        
*/
+        [18] = AV_PROFILE_DTS_EXPRESS,    /* LBR (DTS Express)                 
*/
+        [19] = AV_PROFILE_DTS_HD_HRA,     /* XXCH, core in extension substream 
*/
+        [20] = AV_PROFILE_DTS_HD_MA,      /* XXCH + XLL, core in ext substream 
*/
+        [21] = AV_PROFILE_DTS_HD_MA,      /* XLL, core in ext substream 
(DTS:X)*/
+    };
+    if (!sc || sc > 21)
+        return AV_PROFILE_UNKNOWN;
+    return profile[sc];
+}
+
+/* Maps the 16-bit DTS `ChannelLayout` bitmask (ETSI TS 102 114 Table E-5) to 
an
+ * ffmpeg channel mask. Pair bits follow Table 7-10 / DCA_SPEAKER_PAIR_*;
+ * LwRw (0x0400) and LssRss (0x0800) must not collapse onto SIDE like LsRs. */
+static uint64_t mov_dts_channel_layout_to_mask(unsigned int code)
+{
+    static const struct { unsigned int dts; uint64_t av; } map[] = {
+        { 0x0001, AV_CH_FRONT_CENTER },
+        { 0x0002, AV_CH_FRONT_LEFT  | AV_CH_FRONT_RIGHT },
+        { 0x0004, AV_CH_SIDE_LEFT   | AV_CH_SIDE_RIGHT },
+        { 0x0008, AV_CH_LOW_FREQUENCY },
+        { 0x0010, AV_CH_BACK_CENTER },
+        { 0x0020, AV_CH_TOP_FRONT_LEFT  | AV_CH_TOP_FRONT_RIGHT },
+        { 0x0040, AV_CH_BACK_LEFT   | AV_CH_BACK_RIGHT },
+        { 0x0080, AV_CH_TOP_FRONT_CENTER },
+        { 0x0100, AV_CH_TOP_CENTER },
+        { 0x0200, AV_CH_FRONT_LEFT_OF_CENTER | AV_CH_FRONT_RIGHT_OF_CENTER },
+        { 0x0400, AV_CH_WIDE_LEFT   | AV_CH_WIDE_RIGHT },
+        { 0x0800, AV_CH_SIDE_SURROUND_LEFT | AV_CH_SIDE_SURROUND_RIGHT },
+        { 0x1000, AV_CH_LOW_FREQUENCY_2 },
+        { 0x2000, AV_CH_TOP_SIDE_LEFT    | AV_CH_TOP_SIDE_RIGHT },
+        { 0x4000, AV_CH_TOP_BACK_CENTER },
+        { 0x8000, AV_CH_TOP_BACK_LEFT    | AV_CH_TOP_BACK_RIGHT },
+    };
+    uint64_t mask = 0;
+    int i;
+    for (i = 0; i < FF_ARRAY_ELEMS(map); i++)
+        if (code & map[i].dts)
+            mask |= map[i].av;
+    return mask;
+}
+
 static int mov_read_ddts(MOVContext *c, AVIOContext *pb, MOVAtom atom)
 {
 #define DDTS_SIZE 20
@@ -1173,6 +1242,7 @@ static int mov_read_ddts(MOVContext *c, AVIOContext *pb, 
MOVAtom atom)
     AVStream *st = NULL;
     uint32_t frame_duration_code = 0;
     uint32_t channel_layout_code = 0;
+    unsigned int stream_construction;
     GetBitContext gb;
     int ret;
 
@@ -1195,7 +1265,12 @@ static int mov_read_ddts(MOVContext *c, AVIOContext *pb, 
MOVAtom atom)
     st->codecpar->bit_rate = get_bits_long(&gb, 32);
     st->codecpar->bits_per_coded_sample = get_bits(&gb, 8);
     frame_duration_code = get_bits(&gb, 2);
-    skip_bits(&gb, 30); /* various fields */
+    stream_construction = get_bits(&gb, 5); /* Table E-2 - profile source */
+    skip_bits(&gb, 1);  /* CoreLFEPresent */
+    skip_bits(&gb, 6);  /* CoreLayout */
+    skip_bits(&gb, 14); /* CoreSize */
+    skip_bits(&gb, 1);  /* StereoDownmix */
+    skip_bits(&gb, 3);  /* RepresentationType */
     channel_layout_code = get_bits(&gb, 16);
 
     st->codecpar->frame_size =
@@ -1204,17 +1279,17 @@ static int mov_read_ddts(MOVContext *c, AVIOContext 
*pb, MOVAtom atom)
             (frame_duration_code == 2) ? 2048 :
             (frame_duration_code == 3) ? 4096 : 0;
 
-    if (channel_layout_code > 0xff) {
-        av_log(c->fc, AV_LOG_WARNING, "Unsupported DTS audio channel 
layout\n");
-    }
+    /* Always publish StreamConstruction as codecpar->profile for ffprobe and
+     * for encrypted tracks where the parser cannot read frame headers. When
+     * samples are plaintext the DTS parser overwrites avctx->profile from the
+     * bitstream; on parse failure it keeps this demuxer-provided value. */
+    st->codecpar->profile = 
mov_dts_stream_construction_to_profile(stream_construction);
+
     av_channel_layout_uninit(&st->codecpar->ch_layout);
     av_channel_layout_from_mask(&st->codecpar->ch_layout,
-            ((channel_layout_code & 0x1) ? AV_CH_FRONT_CENTER : 0) |
-            ((channel_layout_code & 0x2) ? AV_CH_FRONT_LEFT : 0) |
-            ((channel_layout_code & 0x2) ? AV_CH_FRONT_RIGHT : 0) |
-            ((channel_layout_code & 0x4) ? AV_CH_SIDE_LEFT : 0) |
-            ((channel_layout_code & 0x4) ? AV_CH_SIDE_RIGHT : 0) |
-            ((channel_layout_code & 0x8) ? AV_CH_LOW_FREQUENCY : 0));
+                                
mov_dts_channel_layout_to_mask(channel_layout_code));
+    if (!st->codecpar->ch_layout.nb_channels)
+        av_channel_layout_default(&st->codecpar->ch_layout, 2);
 
     return 0;
 }
-- 
2.52.0


>From 7a455a87efefcfcb2bc15f0fe445095ba0d4f859 Mon Sep 17 00:00:00 2001
From: Jun Zhao <[email protected]>
Date: Thu, 9 Jul 2026 07:39:49 +0800
Subject: [PATCH 2/2] avcodec/dca: prefer bitstream-derived DTS profile when
 parsing succeeds

Remove the parser trust-guard that skipped profile derivation when the
demuxer had already set avctx->profile. The parser now overwrites profile
from frame headers on success and keeps the demuxer-provided value when
parse fails (e.g. encrypted samples). Warn once when container and
bitstream profiles disagree.

Signed-off-by: Jun Zhao <[email protected]>
---
 libavcodec/dca_parser.c | 30 ++++++++++++++++++++++++++++--
 1 file changed, 28 insertions(+), 2 deletions(-)

diff --git a/libavcodec/dca_parser.c b/libavcodec/dca_parser.c
index b7e2add698..c6cbb76f18 100644
--- a/libavcodec/dca_parser.c
+++ b/libavcodec/dca_parser.c
@@ -39,6 +39,7 @@ typedef struct DCAParseContext {
     unsigned int startpos;
     DCAExssParser exss;
     unsigned int sr_code;
+    int profile_mismatch_warned;
 } DCAParseContext;
 
 #define IS_CORE_MARKER(state) \
@@ -270,9 +271,10 @@ static int dca_parse_params(DCAParseContext *pc1, const 
uint8_t *buf,
 
     *duration = h.npcmblocks * DCA_PCMBLOCK_SAMPLES;
     *sample_rate = ff_dca_sample_rates[h.sr_code];
-    if (*profile != AV_PROFILE_UNKNOWN)
-        return 0;
 
+    /* Prefer a bitstream-derived profile when the frame header parses.
+     * On failure (e.g. encrypted samples) the caller keeps *profile as set
+     * by the demuxer via codecpar (see mov_read_ddts). */
     *profile = AV_PROFILE_DTS;
     if (h.ext_audio_present) {
         switch (h.ext_audio_type) {
@@ -305,6 +307,26 @@ static int dca_parse_params(DCAParseContext *pc1, const 
uint8_t *buf,
     return 0;
 }
 
+static void dca_warn_profile_mismatch(AVCodecContext *avctx, int 
container_profile,
+                                      int bitstream_profile, int *warned)
+{
+    const char *container_name, *bitstream_name;
+
+    if (*warned || container_profile == AV_PROFILE_UNKNOWN ||
+        bitstream_profile == AV_PROFILE_UNKNOWN ||
+        container_profile == bitstream_profile)
+        return;
+
+    container_name  = avcodec_profile_name(AV_CODEC_ID_DTS, container_profile);
+    bitstream_name  = avcodec_profile_name(AV_CODEC_ID_DTS, bitstream_profile);
+    av_log(avctx, AV_LOG_WARNING,
+           "DTS profile %s from container disagrees with bitstream profile %s; 
"
+           "using bitstream value\n",
+           container_name ? container_name : "unknown",
+           bitstream_name ? bitstream_name : "unknown");
+    *warned = 1;
+}
+
 static int dca_parse(AVCodecParserContext *s, AVCodecContext *avctx,
                      const uint8_t **poutbuf, int *poutbuf_size,
                      const uint8_t *buf, int buf_size)
@@ -312,6 +334,7 @@ static int dca_parse(AVCodecParserContext *s, 
AVCodecContext *avctx,
     DCAParseContext *pc1 = s->priv_data;
     ParseContext *pc = &pc1->pc;
     int next, duration, sample_rate;
+    int container_profile;
 
     if (s->flags & PARSER_FLAG_COMPLETE_FRAMES) {
         next = buf_size;
@@ -333,7 +356,10 @@ static int dca_parse(AVCodecParserContext *s, 
AVCodecContext *avctx,
     }
 
     /* read the duration and sample rate from the frame header */
+    container_profile = avctx->profile;
     if (!dca_parse_params(pc1, buf, buf_size, &duration, &sample_rate, 
&avctx->profile)) {
+        dca_warn_profile_mismatch(avctx, container_profile, avctx->profile,
+                                  &pc1->profile_mismatch_warned);
         if (!avctx->sample_rate)
             avctx->sample_rate = sample_rate;
         s->duration = av_rescale(duration, avctx->sample_rate, sample_rate);
-- 
2.52.0

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

Reply via email to