diff --git a/DOCS/interface-changes/audio-spdif-dsd.txt b/DOCS/interface-changes/audio-spdif-dsd.txt
new file mode 100644
index 0000000000000..4b1a90d72b268
--- /dev/null
+++ b/DOCS/interface-changes/audio-spdif-dsd.txt
@@ -0,0 +1 @@
+add `dsd` choice to `--audio-spdif` for DSD-over-PCM (DoP) passthrough
diff --git a/DOCS/man/options.rst b/DOCS/man/options.rst
index 2a359d4692d5b..c74b192f83ef8 100644
--- a/DOCS/man/options.rst
+++ b/DOCS/man/options.rst
@@ -2121,11 +2121,16 @@ Audio
List of codecs for which compressed audio passthrough should be used. This
works for both classic S/PDIF and HDMI.
- Possible codecs are ``ac3``, ``dts``, ``dts-hd``, ``eac3``, ``truehd``.
- Multiple codecs can be specified by separating them with ``,``. ``dts``
- refers to low bitrate DTS core, while ``dts-hd`` refers to DTS MA (receiver
- and OS support varies). If both ``dts`` and ``dts-hd`` are specified, it
- behaves equivalent to specifying ``dts-hd`` only.
+ Possible codecs are ``ac3``, ``dts``, ``dts-hd``, ``eac3``, ``truehd``,
+ ``dsd``. Multiple codecs can be specified by separating them with ``,``.
+ ``dts`` refers to low bitrate DTS core, while ``dts-hd`` refers to DTS MA
+ (receiver and OS support varies). If both ``dts`` and ``dts-hd`` are
+ specified, it behaves equivalent to specifying ``dts-hd`` only.
+
+ ``dsd`` enables bit-perfect passthrough of DSD audio, requires an audio
+ output with exclusive device access (currently ``wasapi``) and a DAC that
+ accepts DoP at the resulting PCM rate (176.4 kHz for DSD64, 352.8 kHz for
+ DSD128, and so on).
In earlier mpv versions you could use ``--ad`` to force the spdif wrapper.
This does not work anymore.
diff --git a/audio/aframe.c b/audio/aframe.c
index fbba7a3b10445..3c93f289610b6 100644
--- a/audio/aframe.c
+++ b/audio/aframe.c
@@ -32,7 +32,8 @@ struct mp_aframe {
AVFrame *av_frame;
// We support channel layouts different from AVFrame channel masks
struct mp_chmap chmap;
- // We support spdif formats, which are allocated as AV_SAMPLE_FMT_S16.
+ // We support bitstream formats, which are allocated as fake integer PCM
+ // of matching sample size.
int format;
double pts;
double speed;
@@ -306,9 +307,10 @@ bool mp_aframe_set_format(struct mp_aframe *frame, int format)
return false;
enum AVSampleFormat av_format = af_to_avformat(format);
if (av_format == AV_SAMPLE_FMT_NONE && format) {
- if (!af_fmt_is_spdif(format))
+ if (af_fmt_is_pcm(format))
return false;
- av_format = AV_SAMPLE_FMT_S16;
+ av_format = af_fmt_to_bytes(format) == 4 ? AV_SAMPLE_FMT_S32
+ : AV_SAMPLE_FMT_S16;
}
frame->format = format;
frame->av_frame->format = av_format;
@@ -488,7 +490,7 @@ double mp_aframe_duration(struct mp_aframe *f)
// Clip the given frame to the given timestamp range. Adjusts the frame size
// and timestamp.
-// Refuses to change spdif frames.
+// Refuses to change bitstream (spdif/DoP) frames.
void mp_aframe_clip_timestamps(struct mp_aframe *f, double start, double end)
{
double f_end = mp_aframe_end_pts(f);
@@ -500,7 +502,7 @@ void mp_aframe_clip_timestamps(struct mp_aframe *f, double start, double end)
if (f->pts >= end) {
f->av_frame->nb_samples = 0;
} else {
- if (af_fmt_is_spdif(mp_aframe_get_format(f)))
+ if (!af_fmt_is_pcm(mp_aframe_get_format(f)))
return;
int new = (end - f->pts) * rate;
f->av_frame->nb_samples = MPCLAMP(new, 0, f->av_frame->nb_samples);
@@ -513,7 +515,7 @@ void mp_aframe_clip_timestamps(struct mp_aframe *f, double start, double end)
f->av_frame->nb_samples = 0;
f->pts = f_end;
} else {
- if (af_fmt_is_spdif(mp_aframe_get_format(f)))
+ if (!af_fmt_is_pcm(mp_aframe_get_format(f)))
return;
int skip = (start - f->pts) * rate;
skip = MPCLAMP(skip, 0, f->av_frame->nb_samples);
diff --git a/audio/decode/ad_dsd.c b/audio/decode/ad_dsd.c
new file mode 100644
index 0000000000000..721e2678a025b
--- /dev/null
+++ b/audio/decode/ad_dsd.c
@@ -0,0 +1,383 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv 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.
+ *
+ * mpv 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 mpv. If not, see .
+ */
+
+#include
+#include
+#include
+
+#include
+#include
+
+#include "audio/aframe.h"
+#include "audio/chmap.h"
+#include "audio/chmap_avchannel.h"
+#include "audio/format.h"
+#include "common/av_common.h"
+#include "common/codecs.h"
+#include "common/common.h"
+#include "common/msg.h"
+#include "demux/packet.h"
+#include "demux/stheader.h"
+#include "filters/f_decoder_wrapper.h"
+#include "filters/filter_internal.h"
+#include "misc/bstr.h"
+#include "osdep/compiler.h"
+
+// Each output sample carries one marker byte and 16 DSD bits per channel:
+// (marker << 24) | (dsd0 << 16) | (dsd1 << 8), with the oldest DSD bit in
+// the MSB and the marker alternating between 0x05 and 0xfa every sample.
+// See
+
+struct priv {
+ bool planar;
+ int rate; // output PCM rate (DSD byte rate / 2)
+ struct mp_chmap chmap;
+ struct mp_aframe *fmt;
+ struct mp_aframe_pool *pool;
+ uint8_t bits[256]; // maps input byte to MSB-first DSD bits
+ bool marker;
+ bool have_hold;
+ uint8_t hold[MP_NUM_CHANNELS]; // odd trailing byte carried between packets
+ bool warned_align;
+
+ // DST decompression (lossless DST -> DSD)
+ AVCodecContext *avctx;
+ AVPacket *avpkt;
+ AVFrame *avframe;
+
+ struct mp_decoder public;
+};
+
+static uint8_t reverse_bits8(uint8_t v)
+{
+#if __has_builtin(__builtin_bitreverse8)
+ return __builtin_bitreverse8(v);
+#else
+ v = (v & 0xf0) >> 4 | (v & 0x0f) << 4;
+ v = (v & 0xcc) >> 2 | (v & 0x33) << 2;
+ v = (v & 0xaa) >> 1 | (v & 0x55) << 1;
+ return v;
+#endif
+}
+
+// Per-channel input byte stream: the held byte (if any), then packet data.
+static inline uint8_t in_byte(struct priv *p, const uint8_t *plane, int step,
+ int c, int i)
+{
+ if (p->have_hold) {
+ if (i == 0)
+ return p->hold[c];
+ i -= 1;
+ }
+ return p->bits[plane[(size_t)i * step]];
+}
+
+// Convert per_ch DSD bytes per channel to DoP and output the result.
+// planes[c] points at the first byte of channel c, consecutive bytes of one
+// channel are step bytes apart.
+static void emit_frame(struct mp_filter *da, const uint8_t *const *planes,
+ int step, int per_ch, double pts)
+{
+ struct priv *p = da->priv;
+ int nch = p->chmap.num;
+ int total = per_ch + (p->have_hold ? 1 : 0);
+ int frames = total / 2;
+
+ if (!frames) {
+ if (per_ch) {
+ for (int c = 0; c < nch; c++)
+ p->hold[c] = in_byte(p, planes[c], step, c, total - 1);
+ p->have_hold = true;
+ }
+ mp_filter_internal_mark_progress(da);
+ return;
+ }
+
+ if (pts != MP_NOPTS_VALUE && p->have_hold)
+ pts -= 0.5 / p->rate;
+
+ struct mp_aframe *out = mp_aframe_new_ref(p->fmt);
+ if (mp_aframe_pool_allocate(p->pool, out, frames) < 0)
+ goto fail;
+
+ uint8_t **data = mp_aframe_get_data_rw(out);
+ if (!data)
+ goto fail;
+ uint32_t *dst = (uint32_t *)data[0];
+
+ for (int f = 0; f < frames; f++) {
+ uint32_t marker = p->marker ? 0xfa : 0x05;
+ p->marker = !p->marker;
+ for (int c = 0; c < nch; c++) {
+ uint32_t b0 = in_byte(p, planes[c], step, c, f * 2);
+ uint32_t b1 = in_byte(p, planes[c], step, c, f * 2 + 1);
+ *dst++ = marker << 24 | b0 << 16 | b1 << 8;
+ }
+ }
+
+ bool hold = total & 1;
+ if (hold) {
+ uint8_t tmp[MP_NUM_CHANNELS];
+ for (int c = 0; c < nch; c++)
+ tmp[c] = in_byte(p, planes[c], step, c, total - 1);
+ memcpy(p->hold, tmp, nch);
+ }
+ p->have_hold = hold;
+
+ mp_aframe_set_pts(out, pts);
+ mp_pin_in_write(da->ppins[1], MAKE_FRAME(MP_FRAME_AUDIO, out));
+ return;
+
+fail:
+ talloc_free(out);
+ mp_filter_internal_mark_failed(da);
+}
+
+static void process_dst(struct mp_filter *da, struct demux_packet *mpkt)
+{
+ struct priv *p = da->priv;
+ int nch = p->chmap.num;
+
+ mp_set_av_packet(p->avpkt, mpkt, NULL);
+ int ret = avcodec_send_packet(p->avctx, p->avpkt);
+ if (ret >= 0)
+ ret = avcodec_receive_frame(p->avctx, p->avframe);
+ if (ret < 0) {
+ MP_WARN(da, "Error decoding DST frame.\n");
+ mp_filter_internal_mark_progress(da);
+ return;
+ }
+
+ if (p->avframe->format != AV_SAMPLE_FMT_U8 ||
+ p->avframe->ch_layout.nb_channels != nch)
+ {
+ MP_ERR(da, "Unexpected DST decoder output.\n");
+ av_frame_unref(p->avframe);
+ mp_filter_internal_mark_failed(da);
+ return;
+ }
+
+ const uint8_t *planes[MP_NUM_CHANNELS];
+ for (int c = 0; c < nch; c++)
+ planes[c] = p->avframe->data[0] + c;
+ emit_frame(da, planes, nch, p->avframe->nb_samples, mpkt->pts);
+
+ av_frame_unref(p->avframe);
+}
+
+static void process(struct mp_filter *da)
+{
+ struct priv *p = da->priv;
+
+ if (!mp_pin_can_transfer_data(da->ppins[1], da->ppins[0]))
+ return;
+
+ struct mp_frame inframe = mp_pin_out_read(da->ppins[0]);
+ if (inframe.type == MP_FRAME_EOF) {
+ // A held byte is half a DoP frame; nothing useful can be output.
+ p->have_hold = false;
+ mp_pin_in_write(da->ppins[1], inframe);
+ return;
+ } else if (inframe.type != MP_FRAME_PACKET) {
+ if (inframe.type) {
+ MP_ERR(da, "unknown frame type\n");
+ mp_filter_internal_mark_failed(da);
+ }
+ return;
+ }
+
+ struct demux_packet *mpkt = inframe.data;
+
+ if (p->avctx) {
+ process_dst(da, mpkt);
+ } else {
+ int nch = p->chmap.num;
+ int per_ch = (int)MPMIN(mpkt->len / nch, INT_MAX / 2);
+
+ if (mpkt->len % nch && !p->warned_align) {
+ MP_WARN(da, "Packet not aligned to channel count.\n");
+ p->warned_align = true;
+ }
+
+ const uint8_t *planes[MP_NUM_CHANNELS];
+ for (int c = 0; c < nch; c++)
+ planes[c] = mpkt->buffer + (p->planar ? c * (size_t)per_ch : c);
+ emit_frame(da, planes, p->planar ? 1 : nch, per_ch, mpkt->pts);
+ }
+
+ talloc_free(mpkt);
+}
+
+static void reset(struct mp_filter *da)
+{
+ struct priv *p = da->priv;
+
+ p->have_hold = false;
+ p->marker = false;
+ if (p->avctx)
+ avcodec_flush_buffers(p->avctx);
+}
+
+static void destroy(struct mp_filter *da)
+{
+ struct priv *p = da->priv;
+
+ avcodec_free_context(&p->avctx);
+ mp_free_av_packet(&p->avpkt);
+ av_frame_free(&p->avframe);
+}
+
+static const struct {
+ const char *codec;
+ bool lsbf, planar, dst;
+} dsd_codecs[] = {
+ {"dsd_lsbf", true, false, false},
+ {"dsd_lsbf_planar", true, true, false},
+ {"dsd_msbf", false, false, false},
+ {"dsd_msbf_planar", false, true, false},
+ {"dst", false, false, true},
+};
+
+// codec is the libavcodec name of the source audio codec.
+// pref is the ","-separated list from --audio-spdif; the "dsd" entry enables
+// DoP pass-through for all raw DSD and DST codecs.
+struct mp_decoder_list *select_dsd_codec(const char *codec, const char *pref)
+{
+ struct mp_decoder_list *list = talloc_zero(NULL, struct mp_decoder_list);
+
+ if (!codec)
+ return list;
+
+ bool found = false;
+ for (int n = 0; n < MP_ARRAY_SIZE(dsd_codecs); n++)
+ found |= strcmp(dsd_codecs[n].codec, codec) == 0;
+ if (!found)
+ return list;
+
+ bstr sel = bstr0(pref);
+ while (sel.len) {
+ bstr entry;
+ bstr_split_tok(sel, ",", &entry, &sel);
+ if (bstr_equals0(entry, "dsd")) {
+ mp_add_decoder(list, codec, "dop",
+ "DSD to DSD-over-PCM (DoP) pass-through decoder");
+ break;
+ }
+ }
+ return list;
+}
+
+static const struct mp_filter_info ad_dsd_filter = {
+ .name = "ad_dsd",
+ .priv_size = sizeof(struct priv),
+ .process = process,
+ .reset = reset,
+ .destroy = destroy,
+};
+
+static bool init_dst_decoder(struct mp_filter *da)
+{
+ struct priv *p = da->priv;
+
+ const AVCodec *dec = avcodec_find_decoder(AV_CODEC_ID_DST);
+ if (!dec)
+ return false;
+
+ p->avctx = avcodec_alloc_context3(dec);
+ p->avpkt = av_packet_alloc();
+ p->avframe = av_frame_alloc();
+ if (!p->avctx || !p->avpkt || !p->avframe)
+ return false;
+
+ p->avctx->sample_rate = p->rate * 2;
+ mp_chmap_to_av_layout(&p->avctx->ch_layout, &p->chmap);
+
+ if (av_opt_set_int(p->avctx, "raw_dsd", 1, AV_OPT_SEARCH_CHILDREN) < 0) {
+ MP_ERR(da, "FFmpeg DST decoder does not support raw DSD output.\n");
+ return false;
+ }
+
+ if (avcodec_open2(p->avctx, dec, NULL) < 0 ||
+ p->avctx->sample_fmt != AV_SAMPLE_FMT_U8)
+ return false;
+
+ return true;
+}
+
+static struct mp_decoder *create(struct mp_filter *parent,
+ struct mp_codec_params *codec,
+ const char *decoder)
+{
+ struct mp_filter *da = mp_filter_create(parent, &ad_dsd_filter);
+ if (!da)
+ return NULL;
+
+ mp_filter_add_pin(da, MP_PIN_IN, "in");
+ mp_filter_add_pin(da, MP_PIN_OUT, "out");
+
+ da->log = mp_log_new(da, parent->log, NULL);
+
+ struct priv *p = da->priv;
+ p->public.f = da;
+
+ bool lsbf = false, dst = false, found = false;
+ for (int n = 0; n < MP_ARRAY_SIZE(dsd_codecs); n++) {
+ if (codec->codec && strcmp(dsd_codecs[n].codec, codec->codec) == 0) {
+ lsbf = dsd_codecs[n].lsbf;
+ p->planar = dsd_codecs[n].planar;
+ dst = dsd_codecs[n].dst;
+ found = true;
+ break;
+ }
+ }
+
+ // codec->samplerate is the DSD byte rate. DSD64 (2.8224 MHz) is the
+ // lowest rate DoP is defined for.
+ if (!found || codec->samplerate < 352800 || codec->samplerate % 2 ||
+ !mp_chmap_is_valid(&codec->channels))
+ {
+ MP_ERR(da, "Codec parameters unsuitable for DoP.\n");
+ goto fail;
+ }
+
+ p->rate = codec->samplerate / 2;
+ p->chmap = codec->channels;
+
+ if (dst && !init_dst_decoder(da))
+ goto fail;
+
+ for (int n = 0; n < 256; n++)
+ p->bits[n] = lsbf ? reverse_bits8(n) : n;
+
+ p->fmt = mp_aframe_create();
+ talloc_steal(p, p->fmt);
+ mp_aframe_set_chmap(p->fmt, &p->chmap);
+ mp_aframe_set_format(p->fmt, AF_FORMAT_S_DOP);
+ mp_aframe_set_rate(p->fmt, p->rate);
+
+ p->pool = mp_aframe_pool_create(p);
+
+ return &p->public;
+
+fail:
+ talloc_free(da);
+ return NULL;
+}
+
+const struct mp_decoder_fns ad_dsd = {
+ .create = create,
+};
diff --git a/audio/format.c b/audio/format.c
index 4441456eded9c..efa22c8c35e42 100644
--- a/audio/format.c
+++ b/audio/format.c
@@ -33,6 +33,8 @@ int af_fmt_to_bytes(int format)
case AF_FORMAT_FLOAT: return 4;
case AF_FORMAT_DOUBLE: return 8;
}
+ if (format == AF_FORMAT_S_DOP)
+ return 4;
if (af_fmt_is_spdif(format))
return 2;
return 0;
@@ -53,9 +55,10 @@ bool af_fmt_is_float(int format)
// true for both unsigned and signed ints
bool af_fmt_is_int(int format)
{
- return format && !af_fmt_is_spdif(format) && !af_fmt_is_float(format);
+ return format && af_fmt_is_pcm(format) && !af_fmt_is_float(format);
}
+// true for IEC61937 wrapped formats only (not other bitstream formats)
bool af_fmt_is_spdif(int format)
{
return af_format_sample_alignment(format) > 1;
@@ -63,7 +66,8 @@ bool af_fmt_is_spdif(int format)
bool af_fmt_is_pcm(int format)
{
- return af_fmt_is_valid(format) && !af_fmt_is_spdif(format);
+ return af_fmt_is_valid(format) && !af_fmt_is_spdif(format) &&
+ format != AF_FORMAT_S_DOP;
}
static const int planar_formats[][2] = {
@@ -135,6 +139,7 @@ const char *af_fmt_to_str(int format)
case AF_FORMAT_S_EAC3: return "spdif-eac3";
case AF_FORMAT_S_MP3: return "spdif-mp3";
case AF_FORMAT_S_TRUEHD: return "spdif-truehd";
+ case AF_FORMAT_S_DOP: return "dop";
}
return "??";
}
diff --git a/audio/format.h b/audio/format.h
index bdd4744ae3aea..ddd6af1660277 100644
--- a/audio/format.h
+++ b/audio/format.h
@@ -48,6 +48,10 @@ enum af_format {
AF_FORMAT_S_MP3,
AF_FORMAT_S_TRUEHD,
+ // DSD-over-PCM (DoP): 16 DSD bits and a marker byte in the 24 MSBs of
+ // 32-bit samples.
+ AF_FORMAT_S_DOP,
+
AF_FORMAT_COUNT
};
diff --git a/audio/out/ao_wasapi_utils.c b/audio/out/ao_wasapi_utils.c
index a1e116b6c66bc..f17809c757ead 100644
--- a/audio/out/ao_wasapi_utils.c
+++ b/audio/out/ao_wasapi_utils.c
@@ -145,6 +145,8 @@ static const struct wasapi_sample_fmt wasapi_formats[] = {
{AF_FORMAT_S_TRUEHD, 16, 16, &KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_MLP},
{AF_FORMAT_S_EAC3, 16, 16, &KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL_PLUS},
{AF_FORMAT_S_DTSHD, 16, 16, &KSDATAFORMAT_SUBTYPE_IEC61937_DTS_HD},
+ // DSD-over-PCM
+ {AF_FORMAT_S_DOP, 32, 24, &KSDATAFORMAT_SUBTYPE_PCM},
{0},
};
@@ -328,8 +330,9 @@ static bool set_ao_format(struct ao *ao, WAVEFORMATEX *wf,
}
// Do not touch the ao for passthrough, just assume that we set WAVEFORMATEX
- // correctly.
- if (af_fmt_is_pcm(format.mp_format)) {
+ // correctly. Checked on both sides, since the DoP wire format is plain
+ // 24-in-32 PCM and reverse maps to a PCM sample format.
+ if (af_fmt_is_pcm(format.mp_format) && af_fmt_is_pcm(ao->format)) {
struct mp_chmap channels;
if (!chmap_from_waveformat(&channels, wf)) {
MP_ERR(ao, "Unable to construct channel map from WAVEFORMAT %s\n",
@@ -474,6 +477,10 @@ static bool find_formats_exclusive(struct ao *ao, WAVEFORMATEXTENSIBLE *wformat)
if (try_format_exclusive(ao, wformat))
return true;
+ // DoP must be played back bit-exactly at the requested rate.
+ if (ao->format == AF_FORMAT_S_DOP)
+ return false;
+
if (af_fmt_is_spdif(ao->format)) {
if (ao->format != AF_FORMAT_S_AC3) {
// If the requested format failed and it is passthrough, but not
@@ -576,7 +583,7 @@ static bool find_formats(struct ao *ao)
WAVEFORMATEXTENSIBLE wformat;
set_waveformat(&wformat, &wasapi_format, ao->samplerate, &channels);
- if (state->opt_exclusive || af_fmt_is_spdif(ao->format)) {
+ if (state->opt_exclusive || !af_fmt_is_pcm(ao->format)) {
share_mode = AUDCLNT_SHAREMODE_EXCLUSIVE;
if(!find_formats_exclusive(ao, &wformat))
return false;
diff --git a/filters/f_decoder_wrapper.c b/filters/f_decoder_wrapper.c
index 2137b4b0d2152..8418eef5970f3 100644
--- a/filters/f_decoder_wrapper.c
+++ b/filters/f_decoder_wrapper.c
@@ -261,7 +261,7 @@ static int decoder_list_help(struct mp_log *log, const m_option_t *opt,
return M_OPT_EXIT;
}
if (strcmp(opt->name, "audio-spdif") == 0) {
- mp_info(log, "Choices: ac3,dts-hd,dts (and possibly more)\n");
+ mp_info(log, "Choices: ac3,dts-hd,dts,dsd (and possibly more)\n");
return M_OPT_EXIT;
}
return 1;
@@ -453,6 +453,14 @@ static bool reinit_decoder(struct priv *p)
list = spdif;
} else {
talloc_free(spdif);
+ struct mp_decoder_list *dsd =
+ select_dsd_codec(p->codec->codec, p->opts->audio_spdif);
+ if (dsd->num_entries) {
+ driver = &ad_dsd;
+ list = dsd;
+ } else {
+ talloc_free(dsd);
+ }
}
}
}
diff --git a/filters/f_decoder_wrapper.h b/filters/f_decoder_wrapper.h
index a352dc0c4dd88..92718e26ab29f 100644
--- a/filters/f_decoder_wrapper.h
+++ b/filters/f_decoder_wrapper.h
@@ -106,6 +106,7 @@ struct mp_decoder_fns {
extern const struct mp_decoder_fns vd_lavc;
extern const struct mp_decoder_fns ad_lavc;
extern const struct mp_decoder_fns ad_spdif;
+extern const struct mp_decoder_fns ad_dsd;
// Convenience wrapper for lavc based decoders. Treat lavc_state as private;
// init to all-0 on init and resets.
@@ -119,3 +120,6 @@ void lavc_process(struct mp_filter *f, struct lavc_state *state,
// ad_spdif.c
struct mp_decoder_list *select_spdif_codec(const char *codec, const char *pref);
+
+// ad_dsd.c
+struct mp_decoder_list *select_dsd_codec(const char *codec, const char *pref);
diff --git a/meson.build b/meson.build
index 80bd39d7ec3bf..ec432b8e75fe7 100644
--- a/meson.build
+++ b/meson.build
@@ -60,6 +60,7 @@ sources = files(
'audio/chmap.c',
'audio/chmap_avchannel.c',
'audio/chmap_sel.c',
+ 'audio/decode/ad_dsd.c',
'audio/decode/ad_lavc.c',
'audio/decode/ad_spdif.c',
'audio/filter/af_drop.c',
diff --git a/player/audio.c b/player/audio.c
index b3e975dd2eda3..7dc19158c9340 100644
--- a/player/audio.c
+++ b/player/audio.c
@@ -416,7 +416,7 @@ static int reinit_audio_filters_and_output(struct MPContext *mpctx)
mp_aframe_get_chmap(out_fmt, &out_channels);
int ao_flags = 0;
- bool spdif_fallback = af_fmt_is_spdif(out_format) &&
+ bool spdif_fallback = !af_fmt_is_pcm(out_format) &&
ao_c->spdif_passthrough;
if (opts->ao_null_fallback && !spdif_fallback)
@@ -454,7 +454,7 @@ static int reinit_audio_filters_and_output(struct MPContext *mpctx)
ao_get_format(mpctx->ao, &ao_rate, &ao_format, &ao_channels);
// Verify passthrough format was not changed.
- if (mpctx->ao && af_fmt_is_spdif(out_format)) {
+ if (mpctx->ao && !af_fmt_is_pcm(out_format)) {
if (out_rate != ao_rate || out_format != ao_format ||
!mp_chmap_equals(&out_channels, &ao_channels))
{