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.

Add PCM FIFO sink and adjust .gitignore

Add a new PCM sink implementation (firmware/target/hosted/pcm-fifo.c)
that writes raw S16LE stereo PCM to a named FIFO or stdout (for
Snapcast/pipe://). Provides pcm_fifo_set_path, a thread-based writer,
blocking writes paced by the reader, stdout redirection support, and
keeps the FIFO open between tracks to avoid EOF on readers.

Update .gitignore to unignore firmware/target and add a
crates/netstream/.gitignore that ignores its target directory.

+259 -1
+2 -1
.gitignore
··· 30 30 zig-out 31 31 32 32 # Rust build artifacts 33 - target 33 + /target 34 + !firmware/target 34 35 35 36 # Intermediate language files 36 37 /apps/lang/*.update
+1
crates/netstream/.gitignore
··· 1 + target
+256
firmware/target/hosted/pcm-fifo.c
··· 1 + /*************************************************************************** 2 + * __________ __ ___. 3 + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 + * \/ \/ \/ \/ \/ 8 + * 9 + * PCM sink that writes raw S16LE stereo PCM to a named FIFO or stdout. 10 + * Intended for use with Snapcast (pipe:// source) or similar consumers. 11 + * 12 + * Usage: 13 + * pcm_fifo_set_path("/tmp/rockbox.fifo"); // or "-" for stdout 14 + * pcm_switch_sink(PCM_SINK_FIFO); 15 + * 16 + * Copyright (C) 2026 Rockbox contributors 17 + * 18 + * This program is free software; you can redistribute it and/or 19 + * modify it under the terms of the GNU General Public License 20 + * as published by the Free Software Foundation; either version 2 21 + * of the License, or (at your option) any later version. 22 + * 23 + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 24 + * KIND, either express or implied. 25 + * 26 + ****************************************************************************/ 27 + 28 + #include "autoconf.h" 29 + #include "config.h" 30 + 31 + #include <errno.h> 32 + #include <fcntl.h> 33 + #include <pthread.h> 34 + #include <stdbool.h> 35 + #include <stddef.h> 36 + #include <stdio.h> 37 + #include <stdlib.h> 38 + #include <string.h> 39 + #include <sys/stat.h> 40 + #include <sys/types.h> 41 + #include <unistd.h> 42 + 43 + #include "pcm.h" 44 + #include "pcm-internal.h" 45 + #include "pcm_mixer.h" 46 + #include "pcm_sampr.h" 47 + #include "pcm_sink.h" 48 + 49 + #define LOGF_ENABLE 50 + #include "logf.h" 51 + 52 + #define DEFAULT_FIFO_PATH "/tmp/rockbox.fifo" 53 + 54 + static const char *fifo_path = DEFAULT_FIFO_PATH; 55 + static int fifo_fd = -1; 56 + 57 + static const void *pcm_data = NULL; 58 + static size_t pcm_size = 0; 59 + 60 + static pthread_mutex_t fifo_mtx; 61 + static pthread_t fifo_tid; 62 + static volatile bool fifo_running = false; 63 + static volatile bool fifo_stop = false; 64 + 65 + /* When writing to stdout, we dup the real stdout fd so that any printf() 66 + inside Rockbox (which goes to fd 1) is redirected to stderr, keeping 67 + the PCM stream on the original stdout clean. */ 68 + static int stdout_pcm_fd = -1; 69 + 70 + static void redirect_stdout_to_stderr(void) 71 + { 72 + /* Save a copy of the real stdout */ 73 + stdout_pcm_fd = dup(STDOUT_FILENO); 74 + /* Point fd 1 at stderr so printf/puts don't corrupt the PCM stream */ 75 + dup2(STDERR_FILENO, STDOUT_FILENO); 76 + } 77 + 78 + void pcm_fifo_set_path(const char *path) 79 + { 80 + fifo_path = path; 81 + 82 + if (strcmp(path, "-") == 0) { 83 + if (stdout_pcm_fd < 0) 84 + redirect_stdout_to_stderr(); 85 + fifo_fd = stdout_pcm_fd; 86 + return; 87 + } 88 + 89 + /* Pre-create the FIFO so external readers (e.g. snapserver) can open it 90 + * before playback starts. Open with O_RDWR so we hold a permanent writer 91 + * reference: this prevents readers from ever seeing a premature EOF when 92 + * we're between tracks or haven't started playing yet. */ 93 + if (mkfifo(path, 0666) < 0 && errno != EEXIST) 94 + logf("pcm-fifo: mkfifo(%s) failed: %s", path, strerror(errno)); 95 + 96 + if (fifo_fd >= 0 && fifo_fd != stdout_pcm_fd) { 97 + close(fifo_fd); 98 + fifo_fd = -1; 99 + } 100 + 101 + /* O_RDWR|O_NONBLOCK: open succeeds immediately without a reader present. 102 + * Then clear O_NONBLOCK so writes in fifo_thread block naturally, 103 + * paced by the reader (snapcast/ffplay). */ 104 + fifo_fd = open(path, O_RDWR | O_NONBLOCK); 105 + if (fifo_fd < 0) { 106 + logf("pcm-fifo: pre-open(%s) failed: %s", path, strerror(errno)); 107 + } else { 108 + int flags = fcntl(fifo_fd, F_GETFL); 109 + if (flags >= 0) 110 + fcntl(fifo_fd, F_SETFL, flags & ~O_NONBLOCK); 111 + logf("pcm-fifo: pre-opened %s fd=%d (blocking)", path, fifo_fd); 112 + } 113 + } 114 + 115 + static void *fifo_thread(void *arg) 116 + { 117 + (void)arg; 118 + 119 + while (!fifo_stop) { 120 + pthread_mutex_lock(&fifo_mtx); 121 + const void *data = pcm_data; 122 + size_t size = pcm_size; 123 + pcm_data = NULL; 124 + pcm_size = 0; 125 + pthread_mutex_unlock(&fifo_mtx); 126 + 127 + /* Write current chunk in pieces so stop() can interrupt promptly */ 128 + while (size > 0 && !fifo_stop) { 129 + ssize_t n = write(fifo_fd, data, size); 130 + if (n < 0) { 131 + if (errno == EINTR || errno == EAGAIN) 132 + continue; 133 + logf("pcm-fifo: write error: %s", strerror(errno)); 134 + fifo_stop = true; 135 + break; 136 + } 137 + data = (const char *)data + n; 138 + size -= n; 139 + } 140 + 141 + if (fifo_stop) 142 + break; 143 + 144 + /* Ask for the next buffer */ 145 + pthread_mutex_lock(&fifo_mtx); 146 + bool got_more = pcm_play_dma_complete_callback(PCM_DMAST_OK, 147 + &pcm_data, &pcm_size); 148 + pthread_mutex_unlock(&fifo_mtx); 149 + 150 + if (!got_more) { 151 + logf("pcm-fifo: no more PCM data"); 152 + break; 153 + } 154 + 155 + pcm_play_dma_status_callback(PCM_DMAST_STARTED); 156 + } 157 + 158 + fifo_running = false; 159 + return NULL; 160 + } 161 + 162 + static void sink_dma_init(void) 163 + { 164 + pthread_mutexattr_t attr; 165 + pthread_mutexattr_init(&attr); 166 + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); 167 + pthread_mutex_init(&fifo_mtx, &attr); 168 + pthread_mutexattr_destroy(&attr); 169 + } 170 + 171 + static void sink_dma_postinit(void) 172 + { 173 + } 174 + 175 + static void sink_set_freq(uint16_t freq) 176 + { 177 + (void)freq; 178 + } 179 + 180 + static void sink_lock(void) 181 + { 182 + pthread_mutex_lock(&fifo_mtx); 183 + } 184 + 185 + static void sink_unlock(void) 186 + { 187 + pthread_mutex_unlock(&fifo_mtx); 188 + } 189 + 190 + static void sink_dma_start(const void *addr, size_t size) 191 + { 192 + logf("pcm-fifo: start (%p, %zu) fd=%d", addr, size, fifo_fd); 193 + 194 + /* fifo_fd should already be open (set by pcm_fifo_set_path). 195 + * Fallback: try a blocking open so we wait for a reader. */ 196 + if (fifo_fd < 0) { 197 + if (strcmp(fifo_path, "-") == 0) { 198 + fifo_fd = stdout_pcm_fd; 199 + } else { 200 + if (mkfifo(fifo_path, 0666) < 0 && errno != EEXIST) 201 + logf("pcm-fifo: mkfifo(%s) failed: %s", fifo_path, strerror(errno)); 202 + fifo_fd = open(fifo_path, O_RDWR | O_NONBLOCK); 203 + } 204 + if (fifo_fd < 0) { 205 + logf("pcm-fifo: no reader on %s — dropping audio", fifo_path); 206 + return; 207 + } 208 + } 209 + 210 + pthread_mutex_lock(&fifo_mtx); 211 + pcm_data = addr; 212 + pcm_size = size; 213 + pthread_mutex_unlock(&fifo_mtx); 214 + 215 + fifo_stop = false; 216 + fifo_running = true; 217 + pthread_create(&fifo_tid, NULL, fifo_thread, NULL); 218 + } 219 + 220 + static void sink_dma_stop(void) 221 + { 222 + logf("pcm-fifo: stop"); 223 + 224 + fifo_stop = true; 225 + 226 + if (fifo_running) { 227 + pthread_join(fifo_tid, NULL); 228 + fifo_running = false; 229 + } 230 + 231 + pthread_mutex_lock(&fifo_mtx); 232 + pcm_data = NULL; 233 + pcm_size = 0; 234 + pthread_mutex_unlock(&fifo_mtx); 235 + 236 + /* Keep fifo_fd open between tracks so the reader (e.g. snapserver) never 237 + * sees EOF and doesn't disconnect. The fd is only ever closed if 238 + * pcm_fifo_set_path() is called with a new path. */ 239 + } 240 + 241 + struct pcm_sink fifo_pcm_sink = { 242 + .caps = { 243 + .samprs = hw_freq_sampr, 244 + .num_samprs = HW_NUM_FREQ, 245 + .default_freq = HW_FREQ_DEFAULT, 246 + }, 247 + .ops = { 248 + .init = sink_dma_init, 249 + .postinit = sink_dma_postinit, 250 + .set_freq = sink_set_freq, 251 + .lock = sink_lock, 252 + .unlock = sink_unlock, 253 + .play = sink_dma_start, 254 + .stop = sink_dma_stop, 255 + }, 256 + };