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


From 251bdffffc6e8f7c6eb8fc1a3a5caaa21c8379b6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= <[email protected]>
Date: Tue, 28 Jul 2026 01:41:36 +0200
Subject: [PATCH 1/2] avcodec/dstdec: fix decoding of uncompressed frames

The DSD payload of an uncompressed frame was copied packed into the
output buffer, but the in-place DSD to PCM conversion expects the DSD
bytes in every 4th byte, in the place of the float sample they produce.

This was always broken, but I guess, the uncompressed DST is something
that exists only on paper.
---
 libavcodec/dstdec.c | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/libavcodec/dstdec.c b/libavcodec/dstdec.c
index d747670141..1db742d193 100644
--- a/libavcodec/dstdec.c
+++ b/libavcodec/dstdec.c
@@ -268,10 +268,17 @@ static int decode_frame(AVCodecContext *avctx, AVFrame 
*frame,
         return ret;
 
     if (!get_bits1(gb)) {
+        unsigned total = frame->nb_samples * channels;
+        unsigned n = FFMIN(avpkt->size - 1, total);
         skip_bits1(gb);
         if (get_bits(gb, 6))
             return AVERROR_INVALIDDATA;
-        memcpy(frame->data[0], avpkt->data + 1, FFMIN(avpkt->size - 1, 
frame->nb_samples * channels));
+        // DSD bytes are stored in every 4th byte, as expected by the
+        // in-place DSD to PCM conversion. Pad short frames with silence.
+        for (i = 0; i < n; i++)
+            dsd[i * 4] = avpkt->data[1 + i];
+        for (; i < total; i++)
+            dsd[i * 4] = 0x69;
         goto dsd;
     }
 
-- 
2.52.0


From 7648095dfea798830bf8019c44061bb1d90570c0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= <[email protected]>
Date: Tue, 28 Jul 2026 01:42:02 +0200
Subject: [PATCH 2/2] avcodec/dstdec: add raw_dsd option to output the DSD
 bitstream

When enabled, output the losslessly decompressed DSD bitstream as
unsigned 8-bit samples (one byte per 8 DSD bits, MSB first, channels
interleaved, at the usual DSD byte rate) instead of converting to float
PCM. This allows bit-exact pass-through of DST compressed audio, e.g.
as DSD-over-PCM (DoP) to DSD capable audio devices.

By default the conversion to PCM is done as before.
---
 doc/decoders.texi    | 18 +++++++++++++++
 libavcodec/dstdec.c  | 52 +++++++++++++++++++++++++++++++++-----------
 libavcodec/version.h |  2 +-
 3 files changed, 58 insertions(+), 14 deletions(-)

diff --git a/doc/decoders.texi b/doc/decoders.texi
index c8177c15e7..ca65e37759 100644
--- a/doc/decoders.texi
+++ b/doc/decoders.texi
@@ -282,6 +282,24 @@ Loud sounds are fully compressed.  Soft sounds are 
enhanced.
 
 @end table
 
+@section dst
+
+DST (Direct Stream Transfer) audio decoder.
+
+Lossless DSD (Direct Stream Digital) compression, used in DSDIFF files and
+on Super Audio CD.
+
+@subsection DST Decoder Options
+
+@table @option
+
+@item -raw_dsd @var{boolean}
+Output the losslessly decompressed raw DSD bitstream as unsigned 8-bit
+samples (one byte per 8 DSD bits, most significant bit first, channels
+interleaved) instead of converting to PCM.
+
+@end table
+
 @section flac
 
 FLAC audio decoder.
diff --git a/libavcodec/dstdec.c b/libavcodec/dstdec.c
index 1db742d193..5e7af0d71f 100644
--- a/libavcodec/dstdec.c
+++ b/libavcodec/dstdec.c
@@ -27,6 +27,7 @@
 
 #include "libavutil/intreadwrite.h"
 #include "libavutil/mem_internal.h"
+#include "libavutil/opt.h"
 #include "libavutil/reverse.h"
 #include "codec_internal.h"
 #include "decode.h"
@@ -67,6 +68,7 @@ typedef struct Table {
 
 typedef struct DSTContext {
     AVClass *class;
+    int raw_dsd;
 
     GetBitContext gb;
     ArithCoder ac;
@@ -96,7 +98,7 @@ static av_cold int decode_init(AVCodecContext *avctx)
         return AVERROR_PATCHWELCOME;
     }
 
-    avctx->sample_fmt = AV_SAMPLE_FMT_FLT;
+    avctx->sample_fmt = s->raw_dsd ? AV_SAMPLE_FMT_U8 : AV_SAMPLE_FMT_FLT;
 
     for (i = 0; i < avctx->ch_layout.nb_channels; i++)
         memset(s->dsdctx[i].buf, 0x69, sizeof(s->dsdctx[i].buf));
@@ -248,6 +250,7 @@ static int decode_frame(AVCodecContext *avctx, AVFrame 
*frame,
     unsigned i, ch, same_map, dst_x_bit;
     unsigned half_prob[DST_MAX_CHANNELS];
     const int channels = avctx->ch_layout.nb_channels;
+    const int bps = avctx->sample_fmt == AV_SAMPLE_FMT_U8 ? 1 : 4;
     DSTContext *s = avctx->priv_data;
     GetBitContext *gb = &s->gb;
     ArithCoder *ac = &s->ac;
@@ -273,12 +276,17 @@ static int decode_frame(AVCodecContext *avctx, AVFrame 
*frame,
         skip_bits1(gb);
         if (get_bits(gb, 6))
             return AVERROR_INVALIDDATA;
-        // DSD bytes are stored in every 4th byte, as expected by the
-        // in-place DSD to PCM conversion. Pad short frames with silence.
-        for (i = 0; i < n; i++)
-            dsd[i * 4] = avpkt->data[1 + i];
-        for (; i < total; i++)
-            dsd[i * 4] = 0x69;
+        if (bps == 1) {
+            memcpy(dsd, avpkt->data + 1, n);
+            memset(dsd + n, 0x69, total - n);
+        } else {
+            // DSD bytes are stored in every 4th byte, as expected by the
+            // in-place DSD to PCM conversion. Pad short frames with silence.
+            for (i = 0; i < n; i++)
+                dsd[i * 4] = avpkt->data[1 + i];
+            for (; i < total; i++)
+                dsd[i * 4] = 0x69;
+        }
         goto dsd;
     }
 
@@ -343,7 +351,7 @@ static int decode_frame(AVCodecContext *avctx, AVFrame 
*frame,
         return ret;
 
     memset(s->status, 0xAA, sizeof(s->status));
-    memset(dsd, 0, frame->nb_samples * 4 * channels);
+    memset(dsd, 0, frame->nb_samples * bps * channels);
 
     ac_get(ac, gb, prob_dst_x_bit(s->fsets.coeff[0][0]), &dst_x_bit);
 
@@ -371,7 +379,7 @@ static int decode_frame(AVCodecContext *avctx, AVFrame 
*frame,
 
             ac_get(ac, gb, prob, &residual);
             v = ((predict >> 15) ^ residual) & 1;
-            dsd[((i >> 3) * channels + ch) << 2] |= v << (7 - (i & 0x7 ));
+            dsd[((i >> 3) * channels + ch) * bps] |= v << (7 - (i & 0x7 ));
 
             AV_WL64A(status + 8, (AV_RL64A(status + 8) << 1) | 
((AV_RL64A(status) >> 63) & 1));
             AV_WL64A(status, (AV_RL64A(status) << 1) | v);
@@ -379,10 +387,12 @@ static int decode_frame(AVCodecContext *avctx, AVFrame 
*frame,
     }
 
 dsd:
-    for (i = 0; i < channels; i++) {
-        ff_dsd2pcm_translate(&s->dsdctx[i], frame->nb_samples, 0,
-                             frame->data[0] + i * 4,
-                             channels * 4, pcm + i, channels);
+    if (avctx->sample_fmt == AV_SAMPLE_FMT_FLT) {
+        for (i = 0; i < channels; i++) {
+            ff_dsd2pcm_translate(&s->dsdctx[i], frame->nb_samples, 0,
+                                 frame->data[0] + i * 4,
+                                 channels * 4, pcm + i, channels);
+        }
     }
 
     *got_frame_ptr = 1;
@@ -390,11 +400,27 @@ dsd:
     return avpkt->size;
 }
 
+#define OFFSET(x) offsetof(DSTContext, x)
+#define AD AV_OPT_FLAG_AUDIO_PARAM | AV_OPT_FLAG_DECODING_PARAM
+
+static const AVOption options[] = {
+    { "raw_dsd", "output raw DSD bitstream instead of decoding to PCM",
+      OFFSET(raw_dsd), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, AD },
+    { NULL }
+};
+
+static const AVClass dst_decoder_class = {
+    .class_name = "DST decoder",
+    .option     = options,
+    .version    = LIBAVUTIL_VERSION_INT,
+};
+
 const FFCodec ff_dst_decoder = {
     .p.name         = "dst",
     CODEC_LONG_NAME("DST (Digital Stream Transfer)"),
     .p.type         = AVMEDIA_TYPE_AUDIO,
     .p.id           = AV_CODEC_ID_DST,
+    .p.priv_class   = &dst_decoder_class,
     .priv_data_size = sizeof(DSTContext),
     .init           = decode_init,
     FF_CODEC_DECODE_CB(decode_frame),
diff --git a/libavcodec/version.h b/libavcodec/version.h
index 39dbec0208..b633612341 100644
--- a/libavcodec/version.h
+++ b/libavcodec/version.h
@@ -30,7 +30,7 @@
 #include "version_major.h"
 
 #define LIBAVCODEC_VERSION_MINOR   6
-#define LIBAVCODEC_VERSION_MICRO 101
+#define LIBAVCODEC_VERSION_MICRO 102
 
 #define LIBAVCODEC_VERSION_INT  AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \
                                                LIBAVCODEC_VERSION_MINOR, \
-- 
2.52.0

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

Reply via email to