Conversation
0a6e689 to
3dd3716
Compare
3dd3716 to
87cb914
Compare
| # Not offered on big-endian: software volume and the Opus decoder write | ||
| # samples in host byte order into buffers the ALSA sink opens as _LE. | ||
| # Reproduced under qemu-mips, see the README. Drop once fixed upstream. |
There was a problem hiding this comment.
see the README points at a file this PR does not add, so the reader has nowhere to go. The site list is also one short of the commit message — micro-flac's sample packing is missing.
| # Not offered on big-endian: software volume and the Opus decoder write | |
| # samples in host byte order into buffers the ALSA sink opens as _LE. | |
| # Reproduced under qemu-mips, see the README. Drop once fixed upstream. | |
| # Not offered on big-endian: software volume, the Opus decoder and | |
| # micro-flac's sample packing write samples in host byte order into buffers | |
| # the ALSA sink opens as _LE. Drop once fixed upstream. |
Generated by Claude Code
There was a problem hiding this comment.
Both right, thank you — the dangling README reference especially, since that file lives in my own feed repository and a reader here would have had nowhere to go.
I went slightly further than the suggestion and kept the evidence while replacing the pointer, so the comment now names where a reader can actually check the claim:
# Not offered on big-endian: software volume, the Opus decoder and
# micro-flac's sample packing write samples in host byte order into buffers
# the ALSA sink opens as _LE. Reproduced under qemu-mips and reported as
# Sendspin/sendspin-cpp-cli#70, Sendspin/sendspin-cpp#132 and
# esphome-libs/micro-flac#36. Drop once they are fixed.
The three issues carry the reproductions: each site built unchanged for MIPS big-endian and run under qemu-mips-static, with the same harness on x86_64 as the control.
|
|
||
| [ "$1" = sendspin-cli ] || exit 0 | ||
|
|
||
| state=$(mktemp -d) |
There was a problem hiding this comment.
nit: the mktemp -d directory is never removed, so each run leaks one. libs/redis/test.sh traps EXIT for this.
| state=$(mktemp -d) | |
| state=$(mktemp -d) | |
| trap 'rm -rf "$state"' EXIT |
Generated by Claude Code
There was a problem hiding this comment.
Fixed, and I followed the redis precedent a bit further than the suggestion. Its cleanup also kills the server, which matters here for the same reason: if CI interrupts the script between the launch and the kill, the player is left running as well as the directory left behind.
state=$(mktemp -d)
pid=""
cleanup() { [ -n "$pid" ] && kill "$pid" 2>/dev/null; rm -rf "$state"; }
trap cleanup EXITThe explicit kill/wait before the final grep stays, so the normal path is unchanged and the trap only covers abnormal exits.
Sendspin is the synchronized multi-room audio protocol used by Music Assistant, and sendspin-cli is its headless player. It plays through ALSA, so a router with a USB DAC becomes a player in the same way snapcast and shairport-sync already allow here. Upstream builds its dependencies with CMake FetchContent. Rather than patch that out, each archive is downloaded through its own Download block and handed to CMake with FETCHCONTENT_SOURCE_DIR_<NAME>, with FETCHCONTENT_FULLY_DISCONNECTED=ON so the build never reaches the network. micro-opus, which bundles a copy of Opus, is replaced by a small CMake shim that links the feed's libopus through pkg-config. The package therefore carries no patches and no vendored code. The init script is procd-based and validates its UCI section with uci_load_validate. The player is built without mDNS and announced by umdns through procd_add_mdns instead. Two behaviours are worth explaining rather than leaving to be found: service_stopped() waits for the player to exit and then reloads umdns. The player says goodbye with reason 'shutdown' whenever it stops, and a server reconnects only once mDNS announces it again; umdns announces a service when it appears and withdraws it when it disappears, but a restart that swaps a service for an identical one does neither. The iface hotplug script restarts the player when a network umdns announces on comes up. The umdns in 25.12 does not announce service instances when a network comes up, and on a network with an mDNS reflector it takes its own reflected probe for a name conflict and stops announcing, so the player is not rediscovered after a reboot. Both are addressed by a pending pull request, openwrt/mdnsd#36; the script goes away when it ships. The package is not offered on big-endian targets. Software volume, the Opus decoder and micro-flac's sample packing all write samples in host byte order into buffers the ALSA sink opens as _LE, so playback there would be wrong rather than merely imperfect. All three are reproduced under qemu-mips-static and reported upstream; the guard is one line and comes off when they are fixed. Tested on ramips/mt7621 (D-Link DIR-3040, mipsel_24kc) with a USB DAC: over 49 hours of uninterrupted playback, no XRUN or underrun, 1.45 MiB installed, 5 MiB resident, and 6.3% of one 880 MHz thread decoding FLAC 48 kHz/16-bit with software volume. The in-place upgrade path and config preservation were verified on the same device. Signed-off-by: Michaël Guay-Lambert <mguaylam.dev@gmail.com>
87cb914 to
e469eb5
Compare
Maintainer
Michaël Guay-Lambert mguaylam.dev@gmail.com, submitting as the maintainer of this package.
Description
Sendspin is the synchronized multi-room audio protocol used by Music Assistant, and
sendspin-cliis its headless player. It plays through ALSA, so a router with a USB DAC becomes a player — the same rolesnapcastandshairport-syncalready fill in this feed.No patches, no vendored code
Upstream builds its dependencies with CMake
FetchContent. Rather than patch that out, each archive is downloaded through its ownDownloadblock and handed to CMake withFETCHCONTENT_SOURCE_DIR_<NAME>, withFETCHCONTENT_FULLY_DISCONNECTED=ONso the build never reaches the network.micro-opus, which bundles a copy of Opus, is replaced by a small CMake shim linking the feed'slibopusthrough pkg-config. The result has nopatches/directory and no third-party sources in the tree.Two behaviours that are easier to read explained than discovered
service_stopped()reloads umdns after the player exits. The player says goodbye with reasonshutdownwhenever it stops, and a server reconnects only once mDNS announces it again. umdns announces a service when it appears and withdraws it when it disappears, but a restart that swaps a service for an identical one does neither.The iface hotplug script restarts the player when a network comes up. The umdns in 25.12 does not announce service instances on
ifup, and on a network with an mDNS reflector it takes its own reflected probe for a name conflict and stops announcing, so the player is not rediscovered after a reboot. Both are addressed by a pending pull request, openwrt/mdnsd#36, and the script goes away when it ships. It is a workaround and is commented as one.Why
@!BIG_ENDIANSoftware volume, the Opus decoder and micro-flac's sample packing all write samples in host byte order into buffers the ALSA sink opens as
_LE. Playback on a big-endian target would be wrong, not merely imperfect, so the package declines to build there rather than shipping noise.This is not a reading of the code. Each site was built unchanged for MIPS big-endian and run under
qemu-mips-static, with the same harness on x86_64 as the control:apply_volume(), software volumewrite_samples(), FLAC sample packingopus_decode(), Opus output bufferUpstream has confirmed the first and has no fix yet. The guard is one line and comes off when they land.
Testing
Built for
ramips/mt7621,mediatek/filogicandx86/64against both 25.12 and snapshot.Run on a D-Link DIR-3040 (
mipsel_24kc, 880 MHz MIPS 1004Kc, no FPU) with a USB DAC:libopus,libstdcpp6andumdnsconffileis preserved and the new default lands as.apk-newTwo things that look like findings and are not
test-version.shis not needed.sendspin-cli --versionprintssendspin-cli 0.3.0, so the generic version check passes.test.shis present for a functional check: it starts the player on the null output and waits for it to listen.codeload.github.comrather than@GITHUB. That macro resolves tohttps://raw.githubusercontent.com, which serves individual files and cannot serve a release tarball.