The SACD plugin adds SACD-oriented DSF/DFF support to LMS 9.0+ (tested with 9.0.0 and 9.1.1):
- scans
.dsfand.dff(including DST-compressed DSDIFF), - reads tags/cover art from headers only, never by decoding the whole file,
- outputs PCM/FLAC, DoP (DSD over PCM), or native DSD,
- automatically falls back to PCM when the player or helper cannot do the selected mode.
The plugin mirrors the structure of plugin-DSDPlayer, but adds DST header parsing and a bounded tag reader.
- Lyrion Music Server 9.0+ (tested: 9.0.0 and 9.1.1).
- FFmpeg 7.1+ for the PCM/FLAC path (the
ffmpegexecutable is enough). The officiallmscommunity/lyrionmusicserverimage does not include ffmpeg; PCM mode therefore needs ffmpeg installed, while native DFF/DoP DST works with the bundled helper alone. Audio::Scan >= 0.94(shipped with LMS 9.0+).- For native DSD/DoP: build the small
sacd_dsdhelper inhelper/src. - DST native/DoP uses the vendored libdstdec decoder, so it does not require a special FFmpeg build. libdstdec handles DST at DSD64, DSD128 and DSD256.
- Use the packaged
dist/SACDfolder (or the release ZIP/tarball) and copy it toPlugins/SACDunder the LMS installation. The source tree containsinstall.xml.inonly; it is not directly installable beforemake dist. - Build the helper:
This installs
sudo apt-get install build-essential make -C helper/src make -C helper/src install
Bin/x86_64-linux/sacd_dsd(or the matching arch). The helper includes libdstdec, so DST native/DoP is available without FFmpeg development headers. - Restart LMS. Enable the plugin under Settings > Plugins.
- Open the plugin settings to check FFmpeg/helper status, then per-player settings to choose the output mode.
The helper can also be placed on PATH; the plugin settings page allows an
absolute path.
| Mode | Output | Notes |
|---|---|---|
| PCM | 24-bit FLAC via FFmpeg | Default. DSD64 → 352.8 kHz; higher DSD rates are reduced to at most 352.8 kHz and to a rate the player accepts. |
| DoP | 24-bit PCM WAV at DSD/16 | Bit-perfect DSD transport; DAC must support DoP. Default cap DSD128 (configurable DSD64/128/256). |
| Native | DFF (raw DSD) via helper | Player must report dff (the helper emits DFF). DST requires helper dst_native=1; otherwise PCM fallback. Recommended for Squeezelite and for Docker installs without ffmpeg. |
DoP is not the same as native DSD: it is DSD data carried in 24-bit PCM samples with the DoP marker bytes. The helper wraps it in a WAV container so LMS/players see the correct DSD/16 sample rate even on LMS 9.0/9.1, which have no per-stream sample-rate hook.
The SACD plugin is a decode-only plugin: it turns DSD/DST into lossless FLAC
or raw PCM and never defines a final lossy profile (AAC/Ogg Vorbis/Opus). When
a lossy target is requested, CodecSwitch composes the pipeline
(DSD -> FLAC/PCM -> target) and FFmpeg performs the encoder step.
An orchestration layer can ask the SACD plugin for its decode capabilities and for
a ready-to-use decode command through two optional methods. Both are safe to
call when CodecSwitch is not installed, and neither changes standalone
playback:
-
Plugins::SACDPlayer::Transcoder->codec_capabilities($client)returns{ provider => 'sacd', priority => 50, decode => [ { from => [qw(dff dsf)], to => ['flc'], scope => ['local'] }, { from => [qw(dff dsf)], to => ['pcm'], scope => ['local'], pcm => { sample_fmt => 's16le', rate => 44100, channels => 2 } }, ], encode => [], } -
Plugins::SACDPlayer::Transcoder->decode_plan($client, $format, $opts)returns{ command, capabilities, format }, orundefwhen the requested format is not a DSD decode target (for exampleaac,ogg,ops).$format = 'flc'reuses the FFmpeg FLAC output command (-f flac -).$format = 'pcm'returns raw little-endian PCM with no container.$optsacceptssample_fmt(s16leors24le, defaults16le),rate(default44100) andchannels(default2).commandis an LMS profile-style command using the$FILE$,$START$,$END$and$RESAMPLE$placeholders;capabilitiesis an LMS capability hashref (F,T,U,D).
Example:
my $plan = Plugins::SACDPlayer::Transcoder->decode_plan(
$client, 'pcm', { sample_fmt => 's16le', rate => 48000, channels => 2 },
);
my $command = $plan->{command} . ' | ' . $encoder_command;- DSF:
Audio::Scanreads the 80-byte header and seeks to the ID3 block. If Audio::Scan is unavailable, the plugin parses the header and ID3 itself. - DFF/DSD: the plugin parses
FRM8,PROP,DIINandID3chunks and seeks over theDSDaudio payload. - DFF/DST: Audio::Scan rejects DST; the plugin parses
FRTEfor duration andDSTFheaders are skipped, never read. ID3/DIIN tags are read from bounded chunks. ffprobeis only a fallback when header/metadata parsing fails. It is run with-probesize 5000000 -analyzeduration 2000000.
A unit test parses a synthetic DST file, reads its ID3 tags and derives the
duration from FRTE; the DSTF payload is skipped (see t/01-tag.t).
Both plugins replace the dsf/dff tag reader. Enable only one of them. If
both are enabled, the plugin logs a warning; the last loaded plugin wins.
The SACD plugin is the one that handles DST and provides the additional output
modes.
- SACD ISO (
.iso) is not supported. It needs a virtual-track or extraction-cache design; see the project plan for the proposed phase 2. - Native/DoP DST decoding uses the vendored libdstdec and currently
supports DSD64, DSD128 and DSD256 DST (DSD64 is the SACD format).
DSD256 uses the same 75 fps frame layout and libdstdec's 18816-byte frame
limit; it has not been tested against a real DSD256 DST sample yet.
See
THIRD_PARTY_NOTICES.mdfor license/source details. - Native DST seeking decodes from the beginning and discards; it is correct but not instant.
- Multi-channel output is passed through, but the player/DAC must support the channel count.
- Windows builds are not part of the initial verification; Linux is primary.
The plugin itself is Perl (LMS loads Perl modules), but the architecture-specific helper is a native binary. To make a deployment folder that contains everything needed on the LMS host:
make distThis creates:
dist/SACD/
├── install.xml
├── lib/ # plugin Perl modules
├── HTML/ # settings pages
├── strings.txt
├── Bin/x86_64-linux/sacd_dsd # plus aarch64/armhf/arm/i386/Windows
├── README.md
├── README.zh-CN.md
├── THIRD_PARTY_NOTICES.md
└── LICENSE
Copy the whole SACD folder into <LMS>/Plugins/. The package folder is
SACD; the Perl module keeps its historical name Plugins::SACDPlayer, which
LMS reads from install.xml:
cp -a dist/SACD /path/to/lms/Plugins/SACDFor the official Docker image (lmscommunity/lyrionmusicserver), the
third-party plugin directory is /config/cache/Plugins, not
/config/Plugins:
docker cp dist/SACD <container>:/config/cache/Plugins/SACDor as a read-only mount:
-v /path/to/dist/SACD:/config/cache/Plugins/SACD:roNo compiler or FFmpeg development headers are needed on the LMS host. The
default make dist helper is statically linked and contains the vendored
libdstdec decoder, so dst_native=1 for DSD64/128/256 DST.
make install PLUGINS_DIR=/path/to/lms/Plugins copies the same folder directly.
make bundle creates a host-architecture archive; make bundle-all builds
all supported Linux architectures and creates dist/SACD-<version>-all.tar.gz.
x86_64-linux
aarch64-linux
armhf-linux
arm-linux
i386-linux
MSWin32-x64-multi-thread
make bins builds all supported targets into Bin/; make verify-bins
checks them with file plus QEMU/Wine. On Debian the cross builds require
both the gcc-*-linux-gnu/MinGW compilers and the matching
libc6-dev-*-cross header packages (the compilers only recommend them). Release CI uses bundle-all, so the
package contains every target. macOS (darwin) is not supported.
VERSION is the single source of truth. The tracked files
install.xml.in and repo.xml.in contain only @VERSION@ / @RELEASE_URL@
placeholders; the real XML is generated into build/ for a release and is not
committed.
To bump everything at once:
make bump VERSION=0.3.1make dist / make bundle render build/install.xml and build/repo.xml
from VERSION; the helper's --probe version is compiled from the same value.
CI passes the tag version as VERSION=.... The release URL base and tag prefix
are configurable with RELEASE_BASE=... and RELEASE_TAG_PREFIX=...
(defaults: this project's Forgejo and v).
To generate only the plugin-catalog file:
make repo
# -> build/repo.xmlmake -C helper/src
prove -l -It/lib t
make -C helper/src testThe plugin modules live under lib/Plugins/SACD/; the helper source is
under helper/src/.
Key files:
lib/Plugins/SACD/TagReader.pm– bounded metadata reader.lib/Plugins/SACD/DFFParser.pm– DSDIFF/DFF chunk walker (DST aware).lib/Plugins/SACD/ID3.pm– minimal ID3v2 parser/cover reader.lib/Plugins/SACD/Transcoder.pm– LMS command profiles.lib/Plugins/SACD/Hooks.pm– sampling-rate and format-selection hooks.helper/src/sacd_dsd.c– DoP packer and DFF writer.