swresample: support AV_SAMPLE_FMT_DSD input

Convert DSD to PCM with the same 96-tap symmetric lowpass filter the
libavcodec DSD decoders use. The implementation is copied from
libavcodec/dsd.c, minus the LSBF table variants, since AV_SAMPLE_FMT_DSD
is defined as MSBF.

The libavcodec copy will be removed in future commits as decoders are
transitioned to SWR.

The conversion is stateful (per-channel filter history), so the audio
conversion functions gain a per-channel state argument.

DSD silence is the 0x69 bit pattern instead of the 0x80 used for PCM.

Only a direct DSD to float conversion function is provided. For format
pairs without a direct conversion function (e.g. DSD to s16 with equal
rates), swr_init() now falls back from the full_convert shortcut to
the generic two-stage path instead of failing, and reports unsupported
conversions with a proper error message instead of ENOMEM.

Signed-off-by: Kacper Michajłow <kasper93@gmail.com>
This commit is contained in:
Kacper Michajłow committed 2026-09-06 00:48:24 +02:00
1 parent 90a20fd15b
commit 72b81d5b64
9 files changed
+239 -12

No files matched your search

+2 -1
View File
@@ -7,6 +7,7 @@ HEADERS = swresample.h \
version_major.h \
OBJS = audioconvert.o \
dsd2pcm.o \
dither.o \
options.o \
rematrix.o \
@@ -19,7 +20,7 @@ OBJS = audioconvert.o \
OBJS-$(CONFIG_LIBSOXR) += soxr_resample.o
# Objects duplicated from other libraries for shared builds
SHLIBOBJS += log2_tab.o
SHLIBOBJS += log2_tab.o reverse.o
# Windows resource file
SHLIBOBJS-$(HAVE_GNU_WINDRES) += swresampleres.o
+16 -2
View File
@@ -36,7 +36,7 @@
//FIXME rounding ?
#define CONV_FUNC(ofmt, otype, ifmt, expr)\
static void CONV_FUNC_NAME(ofmt, ifmt)(uint8_t *po, const uint8_t *pi, int is, int os, uint8_t *end)\
static void CONV_FUNC_NAME(ofmt, ifmt)(DSDContext *st, uint8_t *po, const uint8_t *pi, int is, int os, uint8_t *end)\
{\
uint8_t *end2 = end - 3*os;\
while(po < end2){\
@@ -87,6 +87,12 @@ CONV_FUNC(AV_SAMPLE_FMT_S32, int32_t, AV_SAMPLE_FMT_DBL, av_clipl_int32(llrint(*
CONV_FUNC(AV_SAMPLE_FMT_S64, int64_t, AV_SAMPLE_FMT_DBL, llrint(*(const double*)pi * (UINT64_C(1)<<63)))
CONV_FUNC(AV_SAMPLE_FMT_FLT, float , AV_SAMPLE_FMT_DBL, *(const double*)pi)
CONV_FUNC(AV_SAMPLE_FMT_DBL, double , AV_SAMPLE_FMT_DBL, *(const double*)pi)
CONV_FUNC(AV_SAMPLE_FMT_DSD, uint8_t, AV_SAMPLE_FMT_DSD, *(const uint8_t*)pi)
static void CONV_FUNC_NAME(AV_SAMPLE_FMT_FLT, AV_SAMPLE_FMT_DSD)(DSDContext *st, uint8_t *po, const uint8_t *pi, int is, int os, uint8_t *end)
{
swri_dsd2pcm_translate(st, (end - po) / os, pi, is, (float *)po, os / sizeof(float));
}
#define FMT_PAIR_FUNC(out, in) [(out) + AV_SAMPLE_FMT_NB*(in)] = CONV_FUNC_NAME(out, in)
@@ -127,6 +133,8 @@ static conv_func_type * const fmt_pair_to_conv_functions[AV_SAMPLE_FMT_NB*AV_SAM
FMT_PAIR_FUNC(AV_SAMPLE_FMT_FLT, AV_SAMPLE_FMT_S64),
FMT_PAIR_FUNC(AV_SAMPLE_FMT_DBL, AV_SAMPLE_FMT_S64),
FMT_PAIR_FUNC(AV_SAMPLE_FMT_S64, AV_SAMPLE_FMT_S64),
FMT_PAIR_FUNC(AV_SAMPLE_FMT_FLT, AV_SAMPLE_FMT_DSD),
FMT_PAIR_FUNC(AV_SAMPLE_FMT_DSD, AV_SAMPLE_FMT_DSD),
};
static void cpy1(uint8_t **dst, const uint8_t **src, int len){
@@ -166,6 +174,12 @@ AudioConvert *swri_audio_convert_alloc(enum AVSampleFormat out_fmt,
ctx->ch_map = ch_map;
if (in_fmt == AV_SAMPLE_FMT_U8 || in_fmt == AV_SAMPLE_FMT_U8P)
memset(ctx->silence, 0x80, sizeof(ctx->silence));
if (in_fmt == AV_SAMPLE_FMT_DSD) {
swri_dsd2pcm_init();
memset(ctx->silence, 0x69, sizeof(ctx->silence));
for (int ch = 0; ch < FF_ARRAY_ELEMS(ctx->dsd_state); ch++)
memset(ctx->dsd_state[ch].buf, 0x69, sizeof(ctx->dsd_state[ch].buf));
}
if(out_fmt == in_fmt && !ch_map) {
switch(av_get_bytes_per_sample(in_fmt)){
@@ -245,7 +259,7 @@ int swri_audio_convert(AudioConvert *ctx, AudioData *out, AudioData *in, int len
if(!po)
continue;
end = po + os * len;
ctx->conv_f(po+off*os, pi+off*is, is, os, end);
ctx->conv_f(&ctx->dsd_state[ch], po+off*os, pi+off*is, is, os, end);
}
return 0;
}
+3 -1
View File
@@ -30,9 +30,10 @@
#include "swresample_internal.h"
#include "dsd2pcm.h"
typedef void (conv_func_type)(uint8_t *po, const uint8_t *pi, int is, int os, uint8_t *end);
typedef void (conv_func_type)(DSDContext *st, uint8_t *po, const uint8_t *pi, int is, int os, uint8_t *end);
typedef void (simd_func_type)(uint8_t **dst, const uint8_t **src, int len);
typedef struct AudioConvert {
@@ -43,6 +44,7 @@ typedef struct AudioConvert {
simd_func_type *simd_f;
const int *ch_map;
uint8_t silence[8]; ///< silence input sample
DSDContext dsd_state[SWR_CH_MAX]; ///< per-channel state for DSD input
}AudioConvert;
/**
+130
View File
@@ -0,0 +1,130 @@
/*
* DSD to PCM conversion
* based on BSD licensed dsd2pcm by Sebastian Gesemann
* Copyright (c) 2009, 2011 Sebastian Gesemann. All rights reserved.
* Copyright (c) 2014 Peter Ross
*
* 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/attributes.h"
#include "libavutil/reverse.h"
#include "libavutil/thread.h"
#include "dsd2pcm.h"
#define DSD_CTABLES ((DSD_HTAPS + 7) / 8) /** number of "8 MACs" lookup tables */
/*
* Properties of this 96-tap lowpass filter when applied on a signal
* with sampling rate of 44100*64 Hz:
*
* () has a delay of 17 microseconds.
*
* () flat response up to 48 kHz
*
* () if you downsample afterwards by a factor of 8, the
* spectrum below 70 kHz is practically alias-free.
*
* () stopband rejection is about 160 dB
*
* The coefficient tables ("ctables") take only 6 Kibi Bytes and
* should fit into a modern processor's fast cache.
*/
/**
* The 2nd half (48 coeffs) of a 96-tap symmetric lowpass filter
*/
static const double htaps[DSD_HTAPS] = {
0.09950731974056658, 0.09562845727714668, 0.08819647126516944,
0.07782552527068175, 0.06534876523171299, 0.05172629311427257,
0.0379429484910187, 0.02490921351762261, 0.0133774746265897,
0.003883043418804416, -0.003284703416210726, -0.008080250212687497,
-0.01067241812471033, -0.01139427235000863, -0.0106813877974587,
-0.009007905078766049, -0.006828859761015335, -0.004535184322001496,
-0.002425035959059578, -0.0006922187080790708, 0.0005700762133516592,
0.001353838005269448, 0.001713709169690937, 0.001742046839472948,
0.001545601648013235, 0.001226696225277855, 0.0008704322683580222,
0.0005381636200535649, 0.000266446345425276, 7.002968738383528e-05,
-5.279407053811266e-05, -0.0001140625650874684, -0.0001304796361231895,
-0.0001189970287491285, -9.396247155265073e-05, -6.577634378272832e-05,
-4.07492895872535e-05, -2.17407957554587e-05, -9.163058931391722e-06,
-2.017460145032201e-06, 1.249721855219005e-06, 2.166655190537392e-06,
1.930520892991082e-06, 1.319400334374195e-06, 7.410039764949091e-07,
3.423230509967409e-07, 1.244182214744588e-07, 3.130441005359396e-08
};
static double ctables[DSD_CTABLES][256];
static av_cold void dsd2pcm_ctables_tableinit(void)
{
int t, e, m, sign;
double acc[DSD_CTABLES];
for (e = 0; e < 256; ++e) {
memset(acc, 0, sizeof(acc));
for (m = 0; m < 8; ++m) {
sign = (((e >> (7 - m)) & 1) * 2 - 1);
for (t = 0; t < DSD_CTABLES; ++t)
acc[t] += sign * htaps[t * 8 + m];
}
for (t = 0; t < DSD_CTABLES; ++t)
ctables[DSD_CTABLES - 1 - t][e] = acc[t];
}
}
av_cold void swri_dsd2pcm_init(void)
{
static AVOnce init_static_once = AV_ONCE_INIT;
ff_thread_once(&init_static_once, dsd2pcm_ctables_tableinit);
}
void swri_dsd2pcm_translate(DSDContext *s, size_t samples,
const uint8_t *src, ptrdiff_t src_stride,
float *dst, ptrdiff_t dst_stride)
{
uint8_t buf[DSD_FIFOSIZE];
unsigned pos, i;
uint8_t* p;
double sum;
pos = s->pos;
memcpy(buf, s->buf, sizeof(buf));
while (samples-- > 0) {
buf[pos] = *src;
src += src_stride;
p = buf + ((pos - DSD_CTABLES) & DSD_FIFOMASK);
*p = ff_reverse[*p];
sum = 0.0;
for (i = 0; i < DSD_CTABLES; i++) {
uint8_t a = buf[(pos - i) & DSD_FIFOMASK];
uint8_t b = buf[(pos - (DSD_CTABLES*2 - 1) + i) & DSD_FIFOMASK];
sum += ctables[i][a] + ctables[i][b];
}
*dst = (float)sum;
dst += dst_stride;
pos = (pos + 1) & DSD_FIFOMASK;
}
s->pos = pos;
memcpy(s->buf, buf, sizeof(buf));
}
+56
View File
@@ -0,0 +1,56 @@
/*
* DSD to PCM conversion
* based on BSD licensed dsd2pcm by Sebastian Gesemann
* Copyright (c) 2009, 2011 Sebastian Gesemann. All rights reserved.
* Copyright (c) 2014 Peter Ross
*
* 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
*/
#ifndef SWRESAMPLE_DSD2PCM_H
#define SWRESAMPLE_DSD2PCM_H
#include <stddef.h>
#include <stdint.h>
#define DSD_HTAPS 48 /** number of FIR constants */
#define DSD_FIFOSIZE 16 /** must be a power of two */
#define DSD_FIFOMASK (DSD_FIFOSIZE - 1) /** bit mask for FIFO offsets */
#if DSD_FIFOSIZE * 8 < DSD_HTAPS * 2
#error "DSD_FIFOSIZE too small"
#endif
/**
* Per-channel buffer
*/
typedef struct DSDContext {
uint8_t buf[DSD_FIFOSIZE];
unsigned pos;
} DSDContext;
void swri_dsd2pcm_init(void);
/**
* Convert one channel of MSB-first DSD data (one byte = 8 samples) to
* float PCM at 1/8th of the DSD bit rate. Strides are in elements.
*/
void swri_dsd2pcm_translate(DSDContext *s, size_t samples,
const uint8_t *src, ptrdiff_t src_stride,
float *dst, ptrdiff_t dst_stride);
#endif /* SWRESAMPLE_DSD2PCM_H */
+1
View File
@@ -0,0 +1 @@
#include "libavutil/reverse.c"
+29 -7
View File
@@ -177,6 +177,14 @@ av_cold int swr_init(struct SwrContext *s){
return AVERROR(EINVAL);
}
if (s->out_sample_fmt == AV_SAMPLE_FMT_DSD &&
!(s->in_sample_fmt == AV_SAMPLE_FMT_DSD &&
s->in_sample_rate == s->out_sample_rate &&
!(s->flags & SWR_FLAG_RESAMPLE))) {
av_log(s, AV_LOG_ERROR, "Conversion to DSD is not supported\n");
return AVERROR(EINVAL);
}
s->out.ch_count = s-> user_out_chlayout.nb_channels;
s-> in.ch_count = s-> user_in_chlayout.nb_channels;
@@ -225,8 +233,12 @@ av_cold int swr_init(struct SwrContext *s){
s->rematrix_custom;
if(s->int_sample_fmt == AV_SAMPLE_FMT_NONE){
// DSD to PCM conversion is done in floating point
if( s->in_sample_fmt == AV_SAMPLE_FMT_DSD
&& s->out_sample_fmt != AV_SAMPLE_FMT_DSD) {
s->int_sample_fmt= AV_SAMPLE_FMT_FLTP;
// 16bit or less to 16bit or less with the same sample rate
if( av_get_bytes_per_sample(s-> in_sample_fmt) <= 2
} else if( av_get_bytes_per_sample(s-> in_sample_fmt) <= 2
&& av_get_bytes_per_sample(s->out_sample_fmt) <= 2
&& s->out_sample_rate==s->in_sample_rate) {
s->int_sample_fmt= AV_SAMPLE_FMT_S16P;
@@ -346,7 +358,10 @@ av_assert0(s->out.ch_count);
if(!s->resample && !s->rematrix && !s->channel_map && !s->dither.method){
s->full_convert = swri_audio_convert_alloc(s->out_sample_fmt,
s-> in_sample_fmt, s-> in.ch_count, NULL, 0);
return 0;
// fall through to the generic path for conversions that have no
// direct implementation (e.g. DSD input to non-float output)
if (s->full_convert)
return 0;
}
s->in_convert = swri_audio_convert_alloc(s->int_sample_fmt,
@@ -355,7 +370,10 @@ av_assert0(s->out.ch_count);
s->int_sample_fmt, s->out.ch_count, NULL, 0);
if (!s->in_convert || !s->out_convert) {
ret = AVERROR(ENOMEM);
av_log(s, AV_LOG_ERROR, "Cannot convert %s sample format to %s sample format\n",
av_get_sample_fmt_name(!s->in_convert ? s->in_sample_fmt : s->int_sample_fmt),
av_get_sample_fmt_name(!s->in_convert ? s->int_sample_fmt : s->out_sample_fmt));
ret = AVERROR(EINVAL);
goto fail;
}
@@ -865,10 +883,14 @@ int swr_inject_silence(struct SwrContext *s, int count){
if((ret=swri_realloc_audio(&s->silence, count))<0)
return ret;
if(s->silence.planar) for(i=0; i<s->silence.ch_count; i++) {
memset(s->silence.ch[i], s->silence.bps==1 ? 0x80 : 0, count*s->silence.bps);
} else
memset(s->silence.ch[0], s->silence.bps==1 ? 0x80 : 0, count*s->silence.bps*s->silence.ch_count);
{
int fill = s->silence.fmt == AV_SAMPLE_FMT_DSD ? 0x69 :
s->silence.bps == 1 ? 0x80 : 0;
if(s->silence.planar) for(i=0; i<s->silence.ch_count; i++) {
memset(s->silence.ch[i], fill, count*s->silence.bps);
} else
memset(s->silence.ch[0], fill, count*s->silence.bps*s->silence.ch_count);
}
reversefill_audiodata(&s->silence, tmp_arg);
av_log(s, AV_LOG_VERBOSE, "adding %d audio samples of silence\n", count);
+1 -1
View File
@@ -30,7 +30,7 @@
#include "version_major.h"
#define LIBSWRESAMPLE_VERSION_MINOR 2
#define LIBSWRESAMPLE_VERSION_MINOR 3
#define LIBSWRESAMPLE_VERSION_MICRO 100
#define LIBSWRESAMPLE_VERSION_INT AV_VERSION_INT(LIBSWRESAMPLE_VERSION_MAJOR, \
+1
View File
@@ -16,6 +16,7 @@ libavformat/log2_tab.c
libavformat/rangecoder_dec.c
libavformat/riscv/cpu_common.c
libswresample/log2_tab.c
libswresample/reverse.c
libswscale/aarch64/ops_entries.c
libswscale/log2_tab.c
libswscale/riscv/cpu_common.c