Rockbox open source high quality audio player as a Music Player Daemon
mpris rockbox mpd libadwaita audio rust zig deno
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

Force-link all PCM sink crates with their ffi features enabled

dlopen kept failing with "cannot locate symbol pcm_airplay_connect" then
"pcm_squeezelite_start" etc. Two issues stacked:

1. The PCM sink crates (rockbox-airplay, rockbox-slim, rockbox-chromecast,
rockbox-upnp) gate their #[no_mangle] pub extern "C" exports behind
a `ffi` cargo feature (so desktop dev builds without the firmware
don't pay for them). Enable `ffi` per crate in the embedded-daemon
feature group.

2. rustc's #[used] on the empty `_link_<name>()` helper isn't enough —
it keeps that one fn alive but the unrelated pcm_<sink>_* exports
still get dead-code-stripped. Take addresses of actual C-ABI fns
(pcm_airplay_set_host, pcm_squeezelite_set_slim_port,
pcm::pcm_chromecast_set_device_host, pcm_upnp_set_http_port) so
rustc pulls each crate's full export set into the cdylib.

Result: librockbox_expo.so is now 45.1 MB with every PCM sink's
pcm_* C-ABI exports T (defined) instead of U (undefined). The phone
can now stream out via FIFO/Snapcast/AirPlay/Squeezelite/Chromecast/UPnP
in addition to local AAudio playback.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

+37 -3
+19 -3
crates/expo/Cargo.toml
··· 23 23 "dep:rockbox-settings", 24 24 "dep:rockbox-library", 25 25 "dep:rockbox-sys", 26 - "dep:rbnetstream", # for the #[used] keepalives in daemon.rs 27 - "rockbox-server/fts5", # cascades to rockbox-graphql/fts5 + rockbox-rpc/fts5 26 + "dep:rbnetstream", # for the #[used] keepalives in daemon.rs 27 + # PCM sink crates — each provides a `_link_<name>()` fn that we call 28 + # from the keepalive so rustc keeps the rlib in the cdylib link. 29 + # Enabling `ffi` on each makes their #[no_mangle] pcm_*_* C-ABI 30 + # exports actually compile (gated otherwise so desktop dev builds 31 + # without the firmware don't pay for them). 32 + "dep:rockbox-airplay", 33 + "dep:rockbox-slim", "rockbox-slim/ffi", 34 + "dep:rockbox-chromecast", "rockbox-chromecast/ffi", 35 + "dep:rockbox-upnp", "rockbox-upnp/ffi", 36 + "rockbox-server/fts5", # cascades to rockbox-graphql/fts5 + rockbox-rpc/fts5 28 37 ] 29 38 30 39 # ── Always-on dependencies (gRPC client + JNI/Swift glue) ────────────────── ··· 49 58 rockbox-sys = { path = "../sys", optional = true, default-features = false } 50 59 # netstream's lib is named "rbnetstream"; we depend on it directly so the 51 60 # #[used] keepalives in daemon.rs can take addresses of its C ABI exports. 52 - rbnetstream = { path = "../netstream", package = "netstream", optional = true } 61 + rbnetstream = { path = "../netstream", package = "netstream", optional = true } 62 + # PCM sink crates with C ABI exports the firmware calls (pcm_airplay_*, 63 + # pcm_squeezelite_*, pcm_chromecast_*, pcm_upnp_*). Each has a tiny 64 + # _link_<name>() helper specifically for cdylib keepalive. 65 + rockbox-airplay = { path = "../airplay", optional = true } 66 + rockbox-slim = { path = "../slim", optional = true } 67 + rockbox-chromecast = { path = "../chromecast", optional = true } 68 + rockbox-upnp = { path = "../upnp", optional = true } 53 69 54 70 # ── Android-only ─────────────────────────────────────────────────────────── 55 71 [target.'cfg(target_os = "android")'.dependencies]
+18
crates/expo/src/daemon.rs
··· 60 60 #[used] 61 61 static _KEEPALIVE_ROCKBOX_SERVER: &[&str] = &rockbox_server::AUDIO_EXTENSIONS; 62 62 63 + /// Same trick for the PCM sink crates. Take the address of one actual 64 + /// C-ABI export from each crate — `#[used]` on the empty `_link_<name>()` 65 + /// helper isn't enough because rustc keeps just that one fn and GCs the 66 + /// unrelated `pcm_<sink>_*` exports. Referencing a real C-ABI fn pulls 67 + /// the entire crate's #[no_mangle] export set into the cdylib. 68 + #[used] 69 + static _KEEPALIVE_AIRPLAY: unsafe extern "C" fn(*const c_char, u16) = 70 + rockbox_airplay::pcm_airplay_set_host; 71 + #[used] 72 + static _KEEPALIVE_SLIM: extern "C" fn(u16) = 73 + rockbox_slim::pcm_squeezelite_set_slim_port; 74 + #[used] 75 + static _KEEPALIVE_CHROMECAST: unsafe extern "C" fn(*const c_char) = 76 + rockbox_chromecast::pcm::pcm_chromecast_set_device_host; 77 + #[used] 78 + static _KEEPALIVE_UPNP: extern "C" fn(u16) = 79 + rockbox_upnp::pcm_upnp_set_http_port; 80 + 63 81 /// Same keepalive trick for the netstream Rust crate's C-ABI exports — 64 82 /// the C firmware's streamfd.c calls rb_net_open / rb_net_read / etc., but 65 83 /// rustc dead-code-strips them from the cdylib link unless we reference