From 725b06e1e8b93148a06f7c0028b626e58c288b4d Mon Sep 17 00:00:00 2001 From: ale275 Date: Sun, 12 Oct 2025 13:39:18 +0200 Subject: [PATCH 01/26] added binary build to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dab040f --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +cpiped From 6b262c06b8a825a60c9c63fd01183a172b779adb Mon Sep 17 00:00:00 2001 From: ale275 Date: Sun, 12 Oct 2025 13:13:59 +0200 Subject: [PATCH 02/26] fixed mylog code aesthetic --- cpiped.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/cpiped.c b/cpiped.c index 1899b34..1e825e8 100644 --- a/cpiped.c +++ b/cpiped.c @@ -38,15 +38,17 @@ int writefd = 0; void mylog(int level, const char *format, ...) { - va_list args; - va_start (args, format); + va_list args; + va_start (args, format); - if (daemonize) { - vsyslog(level, format, args); - } else { + if (daemonize) { + vsyslog(level, format, args); + } else { vprintf(format, args); - } - va_end(args); + } + + va_end(args); +} } void myterm() { From bea1ce28153b55613cda058cb244db7758a7ef74 Mon Sep 17 00:00:00 2001 From: ale275 Date: Sun, 12 Oct 2025 13:16:58 +0200 Subject: [PATCH 03/26] introduced log verbosity control functionality --- cpiped.c | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/cpiped.c b/cpiped.c index 1e825e8..c69d4d3 100644 --- a/cpiped.c +++ b/cpiped.c @@ -33,6 +33,7 @@ snd_pcm_t *handle; int daemonize = 0; +int log_verb = 0; int readfd = 0; int writefd = 0; @@ -49,6 +50,21 @@ void mylog(int level, const char *format, ...) va_end(args); } + +void mylogverb(int level, int msg_verb, const char *format, ...) +{ + va_list args_verb; + va_start (args_verb, format); + + if (msg_verb <= log_verb) { + if (daemonize) { + vsyslog(level, format, args_verb); + } else { + vprintf(format, args_verb); + } + } + + va_end(args_verb); } void myterm() { @@ -113,7 +129,7 @@ int main(int argc, char *argv[]) { signal(SIGTERM, myterm); // Process command-line options and arguments - while ((opt = getopt(argc, argv, "d:b:s:e:t:D")) != -1) { + while ((opt = getopt(argc, argv, "d:b:s:e:t:Dv")) != -1) { switch (opt) { case 'd': capdev = optarg; @@ -151,6 +167,9 @@ int main(int argc, char *argv[]) { case 'D': daemonize = 1; break; + case 'v': + log_verb = 1; + break; case '?': err = 1; mylog(LOG_ERR, "Invalid option: %s\n", argv[optind - 1]); @@ -168,6 +187,7 @@ int main(int argc, char *argv[]) { " -e : command to run when silence detected\n" " -t : silence threshold (1 - 32767, [100])\n" " -D : daemonize\n" + " -v : enable more verbose logs\n" " FIFO : path to a named pipe\n", argv[0]); } goto error; From a7c68939f9e3d09f333733498dc7e8db1d845657 Mon Sep 17 00:00:00 2001 From: ale275 Date: Sun, 12 Oct 2025 13:54:31 +0200 Subject: [PATCH 04/26] moved some LOG_INFO message under verbosity control --- cpiped.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cpiped.c b/cpiped.c index c69d4d3..ff77298 100644 --- a/cpiped.c +++ b/cpiped.c @@ -300,13 +300,13 @@ int main(int argc, char *argv[]) { if (bufused < capsize) { // Buffer is almost empty, don't send to pipe - mylog(LOG_INFO, "Filling buffer\n"); + mylogverb(LOG_INFO, 1, "Filling buffer\n"); fillbuf = 1; } if (bufused > (int)bufsize - 1 - capsize) { // Buffer is almost full, don't store captured samples - mylog(LOG_INFO, "Buffer full\n"); + mylogverb(LOG_INFO, 0, "Buffer full\n"); buffull = 1; } @@ -348,9 +348,9 @@ int main(int argc, char *argv[]) { if (silent && soundcount > 1) { silent = 0; - mylog(LOG_INFO, "Sound detected. Last two values: %d, %d\n", prevpower, (int)power); + mylogverb(LOG_INFO, 0, "Sound detected. Last two values: %d, %d\n", prevpower, (int)power); if (strcmp(startcmd, "") != 0) { - mylog(LOG_INFO, "Running '%s'\n", startcmd); + mylogverb(LOG_INFO, 0, "Running '%s'\n", startcmd); if (system(startcmd) != 0) { mylog(LOG_ERR, "Error running start command.\n"); } @@ -359,9 +359,9 @@ int main(int argc, char *argv[]) { if (!silent && silentcount > 55) { silent = 1; - mylog(LOG_INFO, "Silence detected. Last two values: %d, %d\n", prevpower, (int)power); + mylogverb(LOG_INFO, 0, "Silence detected. Last two values: %d, %d\n", prevpower, (int)power); if (strcmp(endcmd, "") != 0) { - mylog(LOG_INFO, "Running '%s'\n", endcmd); + mylogverb(LOG_INFO, 0, "Running '%s'\n", endcmd); if (system(endcmd) != 0) { mylog(LOG_ERR, "Error running end command.\n"); } From 152d7d5bf936f9bd2f1c4f428add107db62fd9be Mon Sep 17 00:00:00 2001 From: natedreger <45026176+natedreger@users.noreply.github.com> Date: Mon, 27 Apr 2020 12:27:17 -0500 Subject: [PATCH 05/26] Add files via upload --- NDpiped.c | 402 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 402 insertions(+) create mode 100644 NDpiped.c diff --git a/NDpiped.c b/NDpiped.c new file mode 100644 index 0000000..a87e8c5 --- /dev/null +++ b/NDpiped.c @@ -0,0 +1,402 @@ +/* + * Original cpiped.c code Copyright (C) 2014 Brian Fitzpatrick + * + * Modifications 2020 Nate Dreger + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#define _GNU_SOURCE + +// Use the newer ALSA API +#define ALSA_PCM_NEW_HW_PARAMS_API + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +snd_pcm_t *handle; +int daemonize = 0; +int readfd = 0; +int writefd = 0; + +void mylog(int level, const char *format, ...) +{ + va_list args; + va_start (args, format); + + if (daemonize) { + vsyslog(level, format, args); + } else { + vprintf(format, args); + } + va_end(args); +} + +void myterm() { + mylog(LOG_INFO, "Stopping due to interrupt/term.\n"); + snd_pcm_drain(handle); + snd_pcm_close(handle); + if (readfd > 0) close(readfd); + if (writefd > 0) close(writefd); + exit(EXIT_SUCCESS); +} + +int main(int argc, char *argv[]) { + int rc; + int capsize; + snd_pcm_hw_params_t *params; + unsigned int val = 44100; + unsigned int capusec; + int dir; + snd_pcm_uframes_t frames; + char *capbuffer; + int16_t *scapbuffer; + char *capdev = "default"; + char *fifonam; + struct stat status; + int readbytes = 0; + int writebytes = 0; + char *buf; + size_t bufsize = 176400; + int bufstart; + int bufend; + int bufused; + int fillbuf = 1; + int buffull = 0; + int bufendtoend; + int bufstarttoend; + int capcount = 0; + long power = 0; + int prevpower = 0; + int i; + int silent = 1; + int silentcount = 0; + int soundcount = 0; + char startcmd[1000] = ""; + char endcmd[1000] = ""; + int wrote = 0; + int silentt = 100; + + extern char *optarg; + extern int optind; + int opt; + int err = 0; + + pid_t pid, sid; + char pidstr[10]; + char pidfile[50]; + int pidfd; + + // Ignore pipe signals + signal(SIGPIPE, SIG_IGN); + // Flush the hardware buffer on interrupt/term + signal(SIGINT, myterm); + signal(SIGTERM, myterm); + + // Process command-line options and arguments + while ((opt = getopt(argc, argv, "d:b:s:e:t:D")) != -1) { + switch (opt) { + case 'd': + capdev = optarg; + break; + case 'b': + bufsize = atof(optarg) * val * 8; + if (bufsize < 33280 || bufsize > 1764000) { + mylog(LOG_ERR, "Invalid buffer. Range is 0.1-5.0 sec.\n"); + goto error; + } + break; + case 's': + strcpy(startcmd, optarg); + if (stat(startcmd, &status) != 0) { + mylog(LOG_ERR, "Invalid start command: %s\n", startcmd); + goto error; + } + strcat(startcmd, " &"); + break; + case 'e': + strcpy(endcmd, optarg); + if (stat(endcmd, &status) != 0) { + mylog(LOG_ERR, "Invalid end command: %s\n", endcmd); + goto error; + } + strcat(endcmd, " &"); + break; + case 't': + silentt = atoi(optarg); + if ((silentt < 1) || (silentt > 32767)) { + mylog(LOG_ERR, "Invalid silence threshold. Range is 1-32767.\n"); + goto error; + } + break; + case 'D': + daemonize = 1; + break; + case '?': + err = 1; + mylog(LOG_ERR, "Invalid option: %s\n", argv[optind - 1]); + break; + } + } + if ((optind + 1) != argc) err = 1; + if (err) { + if (!daemonize) { + printf("\nUsage:\n %s [-d arg] [-b arg] [-s arg] " + "[-e arg] [-t arg] [-D] FIFO\n" + " -d : ALSA capture device ['default']\n" + " -b : target buffer in seconds [.5]\n" + " -s : command to run when sound detected\n" + " -e : command to run when silence detected\n" + " -t : silence threshold (1 - 32767, [100])\n" + " -D : daemonize\n" + " FIFO : path to a named pipe\n", argv[0]); + } + goto error; + } + + mylog(LOG_INFO, "Starting up.\n"); + + fifonam = argv[argc - 1]; + + // Daemonize + if (daemonize) { + pid = fork(); + if (pid < 0) { + exit(EXIT_FAILURE); + } else if (pid > 0) { + exit(EXIT_SUCCESS); + } + + umask(0); + + sid = setsid(); + if (sid < 0) { + mylog(LOG_ERR, "Error creating new session: %s\n", strerror(errno)); + goto error; + } + + if (chdir("/") != 0) { + mylog(LOG_ERR, "Error on chdir /: %s\n", strerror(errno)); + goto error; + } + } + + // Write the pidfile + sprintf(pidfile, "/var/run/cpiped.pid"); + sprintf(pidstr, "%d\n", getpid()); + pidfd = open(pidfile, O_RDWR|O_CREAT, 0644); + write(pidfd, pidstr, strlen(pidstr)); + close(pidfd); + + // Open the FIFO for read first, so the open for write succeeds. + readfd = open(fifonam, O_RDONLY | O_NONBLOCK); + if (readfd <= 0) { + mylog(LOG_ERR, "Error opening FIFO for read: %s\n", strerror(errno)); + goto error; + } + if (fstat(readfd, &status) != 0) { + mylog(LOG_ERR, "fstat error on FIFO (%s).\n", fifonam); + goto error; + } + if (!S_ISFIFO(status.st_mode)) { + mylog(LOG_ERR, "%s is not a FIFO.\n", fifonam); + goto error; + } + writefd = open(fifonam, O_WRONLY); + if (writefd <= 0) { + mylog(LOG_ERR, "Error opening FIFO for write: %s\n", strerror(errno)); + goto error; + } + close(readfd); + + // Set the FIFO size to 8192 bytes to minimize latency + fcntl(writefd, F_SETPIPE_SZ, 8192); + + // Open PCM device for recording (capture). + rc = snd_pcm_open(&handle, capdev, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK); + if (rc != 0) { + mylog(LOG_ERR, "Unable to open pcm device: %s\n", snd_strerror(rc)); + goto error; + } + snd_pcm_hw_params_alloca(¶ms); + snd_pcm_hw_params_any(handle, params); + snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED); + snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE); + snd_pcm_hw_params_set_channels(handle, params, 2); + snd_pcm_hw_params_set_rate_near(handle, params, &val, &dir); + + // Set period size to 1024 frames. + frames = 1024; + snd_pcm_hw_params_set_period_size_near(handle, params, &frames, &dir); + + // Write the parameters to the driver + rc = snd_pcm_hw_params(handle, params); + if (rc != 0) { + mylog(LOG_ERR, "Unable to set hw parameters: %s\n", snd_strerror(rc)); + goto error; + } + + // Determine the elapsed time of one period + snd_pcm_hw_params_get_period_time(params, &capusec, &dir); + + // Create a capture buffer large enough to hold one period + snd_pcm_hw_params_get_period_size(params, &frames, &dir); + capsize = frames * 4; // 2 bytes/sample, 2 channels + capbuffer = calloc(capsize, sizeof(char)); + scapbuffer = (int16_t*)capbuffer; + + // Determine write size + writebytes = capsize - 32 ; // Write a bit less to make sure write controls pace + + // Create a ring buffer to handle clock mismatch + bufsize++; // Add one byte to permit determination of empty/full states + buf = calloc(bufsize, sizeof(char)); + bufstart = 0; + bufend = 0; + + while (1) { + // Capture samples + if (!wrote) // When not writing, wait a bit between captures. + usleep(capusec * .95); + rc = snd_pcm_readi(handle, capbuffer, frames); + if (rc == -EPIPE) { // EPIPE means overrun + mylog(LOG_NOTICE, "Overrun occurred\n"); + snd_pcm_prepare(handle); + } else if (rc == -EAGAIN) { + rc = 0; // Ignore "Not Available" errors + } else if (rc < 0) { + mylog(LOG_ERR, "Capture error: %s\n", snd_strerror(rc)); + } + + // Check if there is sound and if so compute RMS power level regularly and count sounds and silences + if (capcount > 10 && rc > 0) { // Approx. every quarter second + power = 0; + for (i = 0; i < rc / 2; i++) { + power += pow(scapbuffer[i], 2) + pow(scapbuffer[i + 1], 2); + } + power = sqrt(power/rc); + capcount = 0; + + if (power > silentt) { + soundcount++; + silentcount = 0; + } else { + silentcount++; + soundcount = 0; + } + + if (silent && soundcount > 1) { + silent = 0; + mylog(LOG_INFO, "Sound detected. Last two values: %d, %d\n", prevpower, (int)power); + if (strcmp(startcmd, "") != 0) { + mylog(LOG_INFO, "Running '%s'\n", startcmd); + if (system(startcmd) != 0) { + mylog(LOG_ERR, "Error running start command.\n"); + } + } + } + + if (!silent && silentcount > 55) { + silent = 1; + mylog(LOG_INFO, "Silence detected. Last two values: %d, %d\n", prevpower, (int)power); + if (strcmp(endcmd, "") != 0) { + mylog(LOG_INFO, "Running '%s'\n", endcmd); + if (system(endcmd) != 0) { + mylog(LOG_ERR, "Error running end command.\n"); + } + } + } + } + prevpower = power; + capcount++; + // end of soundcounter + + // Check if there is sound, if so write to the pipe + if (soundcount > 0) { + + // Determine the amount of buffer used + bufused = bufend - bufstart + (bufend < bufstart) * (bufsize - 1); + //printf("size:%d, start:%d, end:%d, used:%d, wrote:%d\n", bufsize, bufstart, bufend, bufused, wrote); + + if (bufused < capsize) { + // Buffer is almost empty, don't send to pipe + mylog(LOG_INFO, "Filling buffer\n"); + // nate code + mylog(LOG_INFO, "Last two values: %d, %d\n", prevpower, (int)power); + fillbuf = 1; + } + + if (bufused > (int)bufsize - 1 - capsize) { + // Buffer is almost full, don't store captured samples + mylog(LOG_INFO, "Buffer full\n"); + buffull = 1; + } + + // Resume both capture storage and write to pipe when the buffer reaches half-full + if ((fillbuf == 1) && (bufused > (int)bufsize / 2)) + fillbuf = 0; + if ((buffull == 1) && (bufused < (int)bufsize / 2)) + buffull = 0; + + + if (!buffull && rc > 0) { + // Store samples in buffer + readbytes = rc * 4; + bufendtoend = bufsize - bufend; + if (readbytes <= bufendtoend) { + // No wrap required + memcpy(buf + bufend, capbuffer, readbytes); + } else { + // Wrap required + memcpy(buf + bufend, capbuffer, bufendtoend); + memcpy(buf, capbuffer + bufendtoend, readbytes - bufendtoend); + } + bufend = (bufend + readbytes) % bufsize; + } + + wrote = 0; + if (!fillbuf) { + bufstarttoend = bufsize - bufstart; + if (writebytes <= bufstarttoend) { // No wrap required + rc = write(writefd, buf + bufstart, writebytes); + if (rc == writebytes) wrote = 1; + } else { // Wrap required + rc = write(writefd, buf + bufstart, bufstarttoend); + rc = write(writefd, buf, writebytes - bufstarttoend); + if (rc == writebytes - bufstarttoend) wrote = 1; + } + if (wrote) { + bufstart = (bufstart + writebytes) % bufsize; + } else { + bufstart = (bufstart + readbytes) % bufsize; // Keep buffer used constant + } + } + } // end if + } + exit(EXIT_SUCCESS); + +error: + if (readfd > 0) close(readfd); + if (writefd > 0) close(writefd); + mylog(LOG_INFO, "Stopping due to error."); + exit(EXIT_FAILURE); +} From f1f8c1c9cc29a7c2d0ad1191a908820baddd28ec Mon Sep 17 00:00:00 2001 From: natedreger <45026176+natedreger@users.noreply.github.com> Date: Mon, 27 Apr 2020 12:30:00 -0500 Subject: [PATCH 06/26] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 8f8f737..2136aba 100644 --- a/README.md +++ b/README.md @@ -5,3 +5,5 @@ to run a command on detection. I'm using this to capture line-in audio from a sound card and send it to forked-daapd (https://github.com/ejurgensen/forked-daapd). On sound detection, it runs a simple script to start playing the associated pipe in forked-daapd. + +NDpiped builds on cpiped but modifies code such that the buffer is only written if there is sound. Otherwise I found buffer was constantly writing and Forked-Daapd was playing silence rather then my playlist. From d7230106eb65ab6f57503e8d2364b9767d9efe89 Mon Sep 17 00:00:00 2001 From: natedreger <45026176+natedreger@users.noreply.github.com> Date: Mon, 27 Apr 2020 12:39:19 -0500 Subject: [PATCH 07/26] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2136aba..8f00049 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -cpiped captures an audio stream and outputs it to a pipe with a bit of buffering +cpiped (https://github.com/b-fitzpatrick/cpiped) captures an audio stream and outputs it to a pipe with a bit of buffering to handle clock mismatch. It also includes silence/sound detection and the ability to run a command on detection. From 6ec44c5c9b9a02aee61f92f85062d8218d4c0bbb Mon Sep 17 00:00:00 2001 From: ale275 Date: Sun, 12 Oct 2025 14:32:29 +0200 Subject: [PATCH 08/26] aligned NDpiped and cpiped --- NDpiped.c | 402 ------------------------------------------------------ README.md | 3 +- cpiped.c | 95 +++++++------ 3 files changed, 52 insertions(+), 448 deletions(-) delete mode 100644 NDpiped.c diff --git a/NDpiped.c b/NDpiped.c deleted file mode 100644 index a87e8c5..0000000 --- a/NDpiped.c +++ /dev/null @@ -1,402 +0,0 @@ -/* - * Original cpiped.c code Copyright (C) 2014 Brian Fitzpatrick - * - * Modifications 2020 Nate Dreger - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#define _GNU_SOURCE - -// Use the newer ALSA API -#define ALSA_PCM_NEW_HW_PARAMS_API - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -snd_pcm_t *handle; -int daemonize = 0; -int readfd = 0; -int writefd = 0; - -void mylog(int level, const char *format, ...) -{ - va_list args; - va_start (args, format); - - if (daemonize) { - vsyslog(level, format, args); - } else { - vprintf(format, args); - } - va_end(args); -} - -void myterm() { - mylog(LOG_INFO, "Stopping due to interrupt/term.\n"); - snd_pcm_drain(handle); - snd_pcm_close(handle); - if (readfd > 0) close(readfd); - if (writefd > 0) close(writefd); - exit(EXIT_SUCCESS); -} - -int main(int argc, char *argv[]) { - int rc; - int capsize; - snd_pcm_hw_params_t *params; - unsigned int val = 44100; - unsigned int capusec; - int dir; - snd_pcm_uframes_t frames; - char *capbuffer; - int16_t *scapbuffer; - char *capdev = "default"; - char *fifonam; - struct stat status; - int readbytes = 0; - int writebytes = 0; - char *buf; - size_t bufsize = 176400; - int bufstart; - int bufend; - int bufused; - int fillbuf = 1; - int buffull = 0; - int bufendtoend; - int bufstarttoend; - int capcount = 0; - long power = 0; - int prevpower = 0; - int i; - int silent = 1; - int silentcount = 0; - int soundcount = 0; - char startcmd[1000] = ""; - char endcmd[1000] = ""; - int wrote = 0; - int silentt = 100; - - extern char *optarg; - extern int optind; - int opt; - int err = 0; - - pid_t pid, sid; - char pidstr[10]; - char pidfile[50]; - int pidfd; - - // Ignore pipe signals - signal(SIGPIPE, SIG_IGN); - // Flush the hardware buffer on interrupt/term - signal(SIGINT, myterm); - signal(SIGTERM, myterm); - - // Process command-line options and arguments - while ((opt = getopt(argc, argv, "d:b:s:e:t:D")) != -1) { - switch (opt) { - case 'd': - capdev = optarg; - break; - case 'b': - bufsize = atof(optarg) * val * 8; - if (bufsize < 33280 || bufsize > 1764000) { - mylog(LOG_ERR, "Invalid buffer. Range is 0.1-5.0 sec.\n"); - goto error; - } - break; - case 's': - strcpy(startcmd, optarg); - if (stat(startcmd, &status) != 0) { - mylog(LOG_ERR, "Invalid start command: %s\n", startcmd); - goto error; - } - strcat(startcmd, " &"); - break; - case 'e': - strcpy(endcmd, optarg); - if (stat(endcmd, &status) != 0) { - mylog(LOG_ERR, "Invalid end command: %s\n", endcmd); - goto error; - } - strcat(endcmd, " &"); - break; - case 't': - silentt = atoi(optarg); - if ((silentt < 1) || (silentt > 32767)) { - mylog(LOG_ERR, "Invalid silence threshold. Range is 1-32767.\n"); - goto error; - } - break; - case 'D': - daemonize = 1; - break; - case '?': - err = 1; - mylog(LOG_ERR, "Invalid option: %s\n", argv[optind - 1]); - break; - } - } - if ((optind + 1) != argc) err = 1; - if (err) { - if (!daemonize) { - printf("\nUsage:\n %s [-d arg] [-b arg] [-s arg] " - "[-e arg] [-t arg] [-D] FIFO\n" - " -d : ALSA capture device ['default']\n" - " -b : target buffer in seconds [.5]\n" - " -s : command to run when sound detected\n" - " -e : command to run when silence detected\n" - " -t : silence threshold (1 - 32767, [100])\n" - " -D : daemonize\n" - " FIFO : path to a named pipe\n", argv[0]); - } - goto error; - } - - mylog(LOG_INFO, "Starting up.\n"); - - fifonam = argv[argc - 1]; - - // Daemonize - if (daemonize) { - pid = fork(); - if (pid < 0) { - exit(EXIT_FAILURE); - } else if (pid > 0) { - exit(EXIT_SUCCESS); - } - - umask(0); - - sid = setsid(); - if (sid < 0) { - mylog(LOG_ERR, "Error creating new session: %s\n", strerror(errno)); - goto error; - } - - if (chdir("/") != 0) { - mylog(LOG_ERR, "Error on chdir /: %s\n", strerror(errno)); - goto error; - } - } - - // Write the pidfile - sprintf(pidfile, "/var/run/cpiped.pid"); - sprintf(pidstr, "%d\n", getpid()); - pidfd = open(pidfile, O_RDWR|O_CREAT, 0644); - write(pidfd, pidstr, strlen(pidstr)); - close(pidfd); - - // Open the FIFO for read first, so the open for write succeeds. - readfd = open(fifonam, O_RDONLY | O_NONBLOCK); - if (readfd <= 0) { - mylog(LOG_ERR, "Error opening FIFO for read: %s\n", strerror(errno)); - goto error; - } - if (fstat(readfd, &status) != 0) { - mylog(LOG_ERR, "fstat error on FIFO (%s).\n", fifonam); - goto error; - } - if (!S_ISFIFO(status.st_mode)) { - mylog(LOG_ERR, "%s is not a FIFO.\n", fifonam); - goto error; - } - writefd = open(fifonam, O_WRONLY); - if (writefd <= 0) { - mylog(LOG_ERR, "Error opening FIFO for write: %s\n", strerror(errno)); - goto error; - } - close(readfd); - - // Set the FIFO size to 8192 bytes to minimize latency - fcntl(writefd, F_SETPIPE_SZ, 8192); - - // Open PCM device for recording (capture). - rc = snd_pcm_open(&handle, capdev, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK); - if (rc != 0) { - mylog(LOG_ERR, "Unable to open pcm device: %s\n", snd_strerror(rc)); - goto error; - } - snd_pcm_hw_params_alloca(¶ms); - snd_pcm_hw_params_any(handle, params); - snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED); - snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE); - snd_pcm_hw_params_set_channels(handle, params, 2); - snd_pcm_hw_params_set_rate_near(handle, params, &val, &dir); - - // Set period size to 1024 frames. - frames = 1024; - snd_pcm_hw_params_set_period_size_near(handle, params, &frames, &dir); - - // Write the parameters to the driver - rc = snd_pcm_hw_params(handle, params); - if (rc != 0) { - mylog(LOG_ERR, "Unable to set hw parameters: %s\n", snd_strerror(rc)); - goto error; - } - - // Determine the elapsed time of one period - snd_pcm_hw_params_get_period_time(params, &capusec, &dir); - - // Create a capture buffer large enough to hold one period - snd_pcm_hw_params_get_period_size(params, &frames, &dir); - capsize = frames * 4; // 2 bytes/sample, 2 channels - capbuffer = calloc(capsize, sizeof(char)); - scapbuffer = (int16_t*)capbuffer; - - // Determine write size - writebytes = capsize - 32 ; // Write a bit less to make sure write controls pace - - // Create a ring buffer to handle clock mismatch - bufsize++; // Add one byte to permit determination of empty/full states - buf = calloc(bufsize, sizeof(char)); - bufstart = 0; - bufend = 0; - - while (1) { - // Capture samples - if (!wrote) // When not writing, wait a bit between captures. - usleep(capusec * .95); - rc = snd_pcm_readi(handle, capbuffer, frames); - if (rc == -EPIPE) { // EPIPE means overrun - mylog(LOG_NOTICE, "Overrun occurred\n"); - snd_pcm_prepare(handle); - } else if (rc == -EAGAIN) { - rc = 0; // Ignore "Not Available" errors - } else if (rc < 0) { - mylog(LOG_ERR, "Capture error: %s\n", snd_strerror(rc)); - } - - // Check if there is sound and if so compute RMS power level regularly and count sounds and silences - if (capcount > 10 && rc > 0) { // Approx. every quarter second - power = 0; - for (i = 0; i < rc / 2; i++) { - power += pow(scapbuffer[i], 2) + pow(scapbuffer[i + 1], 2); - } - power = sqrt(power/rc); - capcount = 0; - - if (power > silentt) { - soundcount++; - silentcount = 0; - } else { - silentcount++; - soundcount = 0; - } - - if (silent && soundcount > 1) { - silent = 0; - mylog(LOG_INFO, "Sound detected. Last two values: %d, %d\n", prevpower, (int)power); - if (strcmp(startcmd, "") != 0) { - mylog(LOG_INFO, "Running '%s'\n", startcmd); - if (system(startcmd) != 0) { - mylog(LOG_ERR, "Error running start command.\n"); - } - } - } - - if (!silent && silentcount > 55) { - silent = 1; - mylog(LOG_INFO, "Silence detected. Last two values: %d, %d\n", prevpower, (int)power); - if (strcmp(endcmd, "") != 0) { - mylog(LOG_INFO, "Running '%s'\n", endcmd); - if (system(endcmd) != 0) { - mylog(LOG_ERR, "Error running end command.\n"); - } - } - } - } - prevpower = power; - capcount++; - // end of soundcounter - - // Check if there is sound, if so write to the pipe - if (soundcount > 0) { - - // Determine the amount of buffer used - bufused = bufend - bufstart + (bufend < bufstart) * (bufsize - 1); - //printf("size:%d, start:%d, end:%d, used:%d, wrote:%d\n", bufsize, bufstart, bufend, bufused, wrote); - - if (bufused < capsize) { - // Buffer is almost empty, don't send to pipe - mylog(LOG_INFO, "Filling buffer\n"); - // nate code - mylog(LOG_INFO, "Last two values: %d, %d\n", prevpower, (int)power); - fillbuf = 1; - } - - if (bufused > (int)bufsize - 1 - capsize) { - // Buffer is almost full, don't store captured samples - mylog(LOG_INFO, "Buffer full\n"); - buffull = 1; - } - - // Resume both capture storage and write to pipe when the buffer reaches half-full - if ((fillbuf == 1) && (bufused > (int)bufsize / 2)) - fillbuf = 0; - if ((buffull == 1) && (bufused < (int)bufsize / 2)) - buffull = 0; - - - if (!buffull && rc > 0) { - // Store samples in buffer - readbytes = rc * 4; - bufendtoend = bufsize - bufend; - if (readbytes <= bufendtoend) { - // No wrap required - memcpy(buf + bufend, capbuffer, readbytes); - } else { - // Wrap required - memcpy(buf + bufend, capbuffer, bufendtoend); - memcpy(buf, capbuffer + bufendtoend, readbytes - bufendtoend); - } - bufend = (bufend + readbytes) % bufsize; - } - - wrote = 0; - if (!fillbuf) { - bufstarttoend = bufsize - bufstart; - if (writebytes <= bufstarttoend) { // No wrap required - rc = write(writefd, buf + bufstart, writebytes); - if (rc == writebytes) wrote = 1; - } else { // Wrap required - rc = write(writefd, buf + bufstart, bufstarttoend); - rc = write(writefd, buf, writebytes - bufstarttoend); - if (rc == writebytes - bufstarttoend) wrote = 1; - } - if (wrote) { - bufstart = (bufstart + writebytes) % bufsize; - } else { - bufstart = (bufstart + readbytes) % bufsize; // Keep buffer used constant - } - } - } // end if - } - exit(EXIT_SUCCESS); - -error: - if (readfd > 0) close(readfd); - if (writefd > 0) close(writefd); - mylog(LOG_INFO, "Stopping due to error."); - exit(EXIT_FAILURE); -} diff --git a/README.md b/README.md index 8f00049..adf9711 100644 --- a/README.md +++ b/README.md @@ -6,4 +6,5 @@ I'm using this to capture line-in audio from a sound card and send it to forked-daapd (https://github.com/ejurgensen/forked-daapd). On sound detection, it runs a simple script to start playing the associated pipe in forked-daapd. -NDpiped builds on cpiped but modifies code such that the buffer is only written if there is sound. Otherwise I found buffer was constantly writing and Forked-Daapd was playing silence rather then my playlist. +#### Merged work +- NDpiped builds on cpiped but modifies code such that the buffer is only written if there is sound. Otherwise I found buffer was constantly writing and Forked-Daapd was playing silence rather then my playlist. (https://github.com/natedreger/cpiped) and (https://github.com/natedreger/NDpiped) diff --git a/cpiped.c b/cpiped.c index ff77298..c5f1467 100644 --- a/cpiped.c +++ b/cpiped.c @@ -111,12 +111,12 @@ int main(int argc, char *argv[]) { char endcmd[1000] = ""; int wrote = 0; int silentt = 100; - + extern char *optarg; extern int optind; int opt; int err = 0; - + pid_t pid, sid; char pidstr[10]; char pidfile[50]; @@ -192,9 +192,9 @@ int main(int argc, char *argv[]) { } goto error; } - + mylog(LOG_INFO, "Starting up.\n"); - + fifonam = argv[argc - 1]; // Daemonize @@ -205,15 +205,15 @@ int main(int argc, char *argv[]) { } else if (pid > 0) { exit(EXIT_SUCCESS); } - + umask(0); - + sid = setsid(); if (sid < 0) { mylog(LOG_ERR, "Error creating new session: %s\n", strerror(errno)); goto error; } - + if (chdir("/") != 0) { mylog(LOG_ERR, "Error on chdir /: %s\n", strerror(errno)); goto error; @@ -226,7 +226,7 @@ int main(int argc, char *argv[]) { pidfd = open(pidfile, O_RDWR|O_CREAT, 0644); write(pidfd, pidstr, strlen(pidstr)); close(pidfd); - + // Open the FIFO for read first, so the open for write succeeds. readfd = open(fifonam, O_RDONLY | O_NONBLOCK); if (readfd <= 0) { @@ -247,10 +247,10 @@ int main(int argc, char *argv[]) { goto error; } close(readfd); - + // Set the FIFO size to 8192 bytes to minimize latency fcntl(writefd, F_SETPIPE_SZ, 8192); - + // Open PCM device for recording (capture). rc = snd_pcm_open(&handle, capdev, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK); if (rc != 0) { @@ -277,13 +277,13 @@ int main(int argc, char *argv[]) { // Determine the elapsed time of one period snd_pcm_hw_params_get_period_time(params, &capusec, &dir); - + // Create a capture buffer large enough to hold one period snd_pcm_hw_params_get_period_size(params, &frames, &dir); capsize = frames * 4; // 2 bytes/sample, 2 channels capbuffer = calloc(capsize, sizeof(char)); scapbuffer = (int16_t*)capbuffer; - + // Determine write size writebytes = capsize - 32 ; // Write a bit less to make sure write controls pace @@ -292,30 +292,8 @@ int main(int argc, char *argv[]) { buf = calloc(bufsize, sizeof(char)); bufstart = 0; bufend = 0; - - while (1) { - // Determine the amount of buffer used - bufused = bufend - bufstart + (bufend < bufstart) * (bufsize - 1); - //printf("size:%d, start:%d, end:%d, used:%d, wrote:%d\n", bufsize, bufstart, bufend, bufused, wrote); - - if (bufused < capsize) { - // Buffer is almost empty, don't send to pipe - mylogverb(LOG_INFO, 1, "Filling buffer\n"); - fillbuf = 1; - } - - if (bufused > (int)bufsize - 1 - capsize) { - // Buffer is almost full, don't store captured samples - mylogverb(LOG_INFO, 0, "Buffer full\n"); - buffull = 1; - } - // Resume both capture storage and write to pipe when the buffer reaches half-full - if ((fillbuf == 1) && (bufused > (int)bufsize / 2)) - fillbuf = 0; - if ((buffull == 1) && (bufused < (int)bufsize / 2)) - buffull = 0; - + while (1) { // Capture samples if (!wrote) // When not writing, wait a bit between captures. usleep(capusec * .95); @@ -328,8 +306,8 @@ int main(int argc, char *argv[]) { } else if (rc < 0) { mylog(LOG_ERR, "Capture error: %s\n", snd_strerror(rc)); } - - // Compute RMS power level regularly + + // Check if there is sound and if so compute RMS power level regularly and count sounds and silences if (capcount > 10 && rc > 0) { // Approx. every quarter second power = 0; for (i = 0; i < rc / 2; i++) { @@ -337,7 +315,7 @@ int main(int argc, char *argv[]) { } power = sqrt(power/rc); capcount = 0; - + if (power > silentt) { soundcount++; silentcount = 0; @@ -345,7 +323,7 @@ int main(int argc, char *argv[]) { silentcount++; soundcount = 0; } - + if (silent && soundcount > 1) { silent = 0; mylogverb(LOG_INFO, 0, "Sound detected. Last two values: %d, %d\n", prevpower, (int)power); @@ -356,7 +334,7 @@ int main(int argc, char *argv[]) { } } } - + if (!silent && silentcount > 55) { silent = 1; mylogverb(LOG_INFO, 0, "Silence detected. Last two values: %d, %d\n", prevpower, (int)power); @@ -370,8 +348,34 @@ int main(int argc, char *argv[]) { } prevpower = power; capcount++; - - if (!buffull && rc > 0) { + // end of soundcounter + + // Check if there is sound, if so write to the pipe + if (soundcount > 0) { + + // Determine the amount of buffer used + bufused = bufend - bufstart + (bufend < bufstart) * (bufsize - 1); + //printf("size:%d, start:%d, end:%d, used:%d, wrote:%d\n", bufsize, bufstart, bufend, bufused, wrote); + + if (bufused < capsize) { + // Buffer is almost empty, don't send to pipe + mylogverb(LOG_INFO, 1, "Filling buffer\n"); + fillbuf = 1; + } + + if (bufused > (int)bufsize - 1 - capsize) { + // Buffer is almost full, don't store captured samples + mylogverb(LOG_INFO, 0, "Buffer full\n"); + buffull = 1; + } + + // Resume both capture storage and write to pipe when the buffer reaches half-full + if ((fillbuf == 1) && (bufused > (int)bufsize / 2)) + fillbuf = 0; + if ((buffull == 1) && (bufused < (int)bufsize / 2)) + buffull = 0; + + if (!buffull && rc > 0) { // Store samples in buffer readbytes = rc * 4; bufendtoend = bufsize - bufend; @@ -385,7 +389,7 @@ int main(int argc, char *argv[]) { } bufend = (bufend + readbytes) % bufsize; } - + wrote = 0; if (!fillbuf) { bufstarttoend = bufsize - bufstart; @@ -403,9 +407,10 @@ int main(int argc, char *argv[]) { bufstart = (bufstart + readbytes) % bufsize; // Keep buffer used constant } } - } + } // end if + } exit(EXIT_SUCCESS); - + error: if (readfd > 0) close(readfd); if (writefd > 0) close(writefd); From 4da6f1bb53a93ffd754b7f1a90fce638c19323b2 Mon Sep 17 00:00:00 2001 From: Mario Wenzel Date: Tue, 7 Jan 2020 20:56:12 +0100 Subject: [PATCH 09/26] parameterize pid --- cpiped.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/cpiped.c b/cpiped.c index c5f1467..67f4136 100644 --- a/cpiped.c +++ b/cpiped.c @@ -87,6 +87,7 @@ int main(int argc, char *argv[]) { char *capbuffer; int16_t *scapbuffer; char *capdev = "default"; + char *pidpath = "/var/run/cpiped.pid"; char *fifonam; struct stat status; int readbytes = 0; @@ -129,7 +130,7 @@ int main(int argc, char *argv[]) { signal(SIGTERM, myterm); // Process command-line options and arguments - while ((opt = getopt(argc, argv, "d:b:s:e:t:Dv")) != -1) { + while ((opt = getopt(argc, argv, "d:b:s:e:t:Dp:v")) != -1) { switch (opt) { case 'd': capdev = optarg; @@ -167,6 +168,9 @@ int main(int argc, char *argv[]) { case 'D': daemonize = 1; break; + case 'p': + pidpath = optarg; + break; case 'v': log_verb = 1; break; @@ -187,6 +191,7 @@ int main(int argc, char *argv[]) { " -e : command to run when silence detected\n" " -t : silence threshold (1 - 32767, [100])\n" " -D : daemonize\n" + " -p : path to pidfile [/var/run/cpiped.pid]\n" " -v : enable more verbose logs\n" " FIFO : path to a named pipe\n", argv[0]); } @@ -221,7 +226,7 @@ int main(int argc, char *argv[]) { } // Write the pidfile - sprintf(pidfile, "/var/run/cpiped.pid"); + sprintf(pidfile, pidpath); sprintf(pidstr, "%d\n", getpid()); pidfd = open(pidfile, O_RDWR|O_CREAT, 0644); write(pidfd, pidstr, strlen(pidstr)); From b8e44229e1ec667974cca844a9ccdb5d36b7e0b6 Mon Sep 17 00:00:00 2001 From: ale275 Date: Sun, 12 Oct 2025 15:10:00 +0200 Subject: [PATCH 10/26] README update --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index adf9711..9721f5e 100644 --- a/README.md +++ b/README.md @@ -8,3 +8,4 @@ runs a simple script to start playing the associated pipe in forked-daapd. #### Merged work - NDpiped builds on cpiped but modifies code such that the buffer is only written if there is sound. Otherwise I found buffer was constantly writing and Forked-Daapd was playing silence rather then my playlist. (https://github.com/natedreger/cpiped) and (https://github.com/natedreger/NDpiped) +- Parametrized pid file by Mario Wenzel (https://github.com/maweki/cpiped) \ No newline at end of file From b11c922c6c675418c6c7ecf35c2cf8e32613869e Mon Sep 17 00:00:00 2001 From: ale275 Date: Sun, 12 Oct 2025 15:10:51 +0200 Subject: [PATCH 11/26] README update with ToDo list --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9721f5e..9a0f1fd 100644 --- a/README.md +++ b/README.md @@ -8,4 +8,8 @@ runs a simple script to start playing the associated pipe in forked-daapd. #### Merged work - NDpiped builds on cpiped but modifies code such that the buffer is only written if there is sound. Otherwise I found buffer was constantly writing and Forked-Daapd was playing silence rather then my playlist. (https://github.com/natedreger/cpiped) and (https://github.com/natedreger/NDpiped) -- Parametrized pid file by Mario Wenzel (https://github.com/maweki/cpiped) \ No newline at end of file +- Parametrized pid file by Mario Wenzel (https://github.com/maweki/cpiped) + +#### ToDo(s) +- [ ] Improve Makefile to create dedicated build folder +- [ ] Improve Makefile to add install directive \ No newline at end of file From 3f7027b5c079a8ebfd57d465e81aef68d2aa515e Mon Sep 17 00:00:00 2001 From: ale275 Date: Sat, 25 Oct 2025 12:05:36 +0200 Subject: [PATCH 12/26] introduced variables for sample size and channel count - refactored val variable to samplerate - made buffer and capture sizes calculation function of samplerate and captured channels count --- cpiped.c | 128 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 65 insertions(+), 63 deletions(-) diff --git a/cpiped.c b/cpiped.c index 67f4136..59cec34 100644 --- a/cpiped.c +++ b/cpiped.c @@ -80,7 +80,9 @@ int main(int argc, char *argv[]) { int rc; int capsize; snd_pcm_hw_params_t *params; - unsigned int val = 44100; + unsigned int samplerate = 41000; + unsigned int samplesize = 16; + unsigned int capchannels = 2; unsigned int capusec; int dir; snd_pcm_uframes_t frames; @@ -136,7 +138,7 @@ int main(int argc, char *argv[]) { capdev = optarg; break; case 'b': - bufsize = atof(optarg) * val * 8; + bufsize = atof(optarg) * samplerate * 8; if (bufsize < 33280 || bufsize > 1764000) { mylog(LOG_ERR, "Invalid buffer. Range is 0.1-5.0 sec.\n"); goto error; @@ -266,8 +268,8 @@ int main(int argc, char *argv[]) { snd_pcm_hw_params_any(handle, params); snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED); snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE); - snd_pcm_hw_params_set_channels(handle, params, 2); - snd_pcm_hw_params_set_rate_near(handle, params, &val, &dir); + snd_pcm_hw_params_set_channels(handle, params, capchannels); + snd_pcm_hw_params_set_rate_near(handle, params, &samplerate, &dir); // Set period size to 1024 frames. frames = 1024; @@ -285,7 +287,7 @@ int main(int argc, char *argv[]) { // Create a capture buffer large enough to hold one period snd_pcm_hw_params_get_period_size(params, &frames, &dir); - capsize = frames * 4; // 2 bytes/sample, 2 channels + capsize = frames * (samplesize / 8) * capchannels; // ( / 8) bytes/sample * channels capbuffer = calloc(capsize, sizeof(char)); scapbuffer = (int16_t*)capbuffer; @@ -353,72 +355,72 @@ int main(int argc, char *argv[]) { } prevpower = power; capcount++; - // end of soundcounter + // end of soundcounter - // Check if there is sound, if so write to the pipe - if (soundcount > 0) { + // Check if there is sound, if so write to the pipe + if (soundcount > 0) { - // Determine the amount of buffer used - bufused = bufend - bufstart + (bufend < bufstart) * (bufsize - 1); - //printf("size:%d, start:%d, end:%d, used:%d, wrote:%d\n", bufsize, bufstart, bufend, bufused, wrote); + // Determine the amount of buffer used + bufused = bufend - bufstart + (bufend < bufstart) * (bufsize - 1); + //printf("size:%d, start:%d, end:%d, used:%d, wrote:%d\n", bufsize, bufstart, bufend, bufused, wrote); - if (bufused < capsize) { - // Buffer is almost empty, don't send to pipe - mylogverb(LOG_INFO, 1, "Filling buffer\n"); - fillbuf = 1; - } - - if (bufused > (int)bufsize - 1 - capsize) { - // Buffer is almost full, don't store captured samples - mylogverb(LOG_INFO, 0, "Buffer full\n"); - buffull = 1; - } + if (bufused < capsize) { + // Buffer is almost empty, don't send to pipe + mylogverb(LOG_INFO, 1, "Filling buffer\n"); + fillbuf = 1; + } - // Resume both capture storage and write to pipe when the buffer reaches half-full - if ((fillbuf == 1) && (bufused > (int)bufsize / 2)) - fillbuf = 0; - if ((buffull == 1) && (bufused < (int)bufsize / 2)) - buffull = 0; - - if (!buffull && rc > 0) { - // Store samples in buffer - readbytes = rc * 4; - bufendtoend = bufsize - bufend; - if (readbytes <= bufendtoend) { - // No wrap required - memcpy(buf + bufend, capbuffer, readbytes); - } else { - // Wrap required - memcpy(buf + bufend, capbuffer, bufendtoend); - memcpy(buf, capbuffer + bufendtoend, readbytes - bufendtoend); + if (bufused > (int)bufsize - 1 - capsize) { + // Buffer is almost full, don't store captured samples + mylogverb(LOG_INFO, 0, "Buffer full\n"); + buffull = 1; } - bufend = (bufend + readbytes) % bufsize; - } - wrote = 0; - if (!fillbuf) { - bufstarttoend = bufsize - bufstart; - if (writebytes <= bufstarttoend) { // No wrap required - rc = write(writefd, buf + bufstart, writebytes); - if (rc == writebytes) wrote = 1; - } else { // Wrap required - rc = write(writefd, buf + bufstart, bufstarttoend); - rc = write(writefd, buf, writebytes - bufstarttoend); - if (rc == writebytes - bufstarttoend) wrote = 1; + // Resume both capture storage and write to pipe when the buffer reaches half-full + if ((fillbuf == 1) && (bufused > (int)bufsize / 2)) + fillbuf = 0; + if ((buffull == 1) && (bufused < (int)bufsize / 2)) + buffull = 0; + + if (!buffull && rc > 0) { + // Store samples in buffer + readbytes = rc * 4; + bufendtoend = bufsize - bufend; + if (readbytes <= bufendtoend) { + // No wrap required + memcpy(buf + bufend, capbuffer, readbytes); + } else { + // Wrap required + memcpy(buf + bufend, capbuffer, bufendtoend); + memcpy(buf, capbuffer + bufendtoend, readbytes - bufendtoend); + } + bufend = (bufend + readbytes) % bufsize; } - if (wrote) { - bufstart = (bufstart + writebytes) % bufsize; - } else { - bufstart = (bufstart + readbytes) % bufsize; // Keep buffer used constant + + wrote = 0; + if (!fillbuf) { + bufstarttoend = bufsize - bufstart; + if (writebytes <= bufstarttoend) { // No wrap required + rc = write(writefd, buf + bufstart, writebytes); + if (rc == writebytes) wrote = 1; + } else { // Wrap required + rc = write(writefd, buf + bufstart, bufstarttoend); + rc = write(writefd, buf, writebytes - bufstarttoend); + if (rc == writebytes - bufstarttoend) wrote = 1; + } + if (wrote) { + bufstart = (bufstart + writebytes) % bufsize; + } else { + bufstart = (bufstart + readbytes) % bufsize; // Keep buffer used constant + } } - } - } // end if - } + } // end if + } exit(EXIT_SUCCESS); -error: - if (readfd > 0) close(readfd); - if (writefd > 0) close(writefd); - mylog(LOG_INFO, "Stopping due to error."); - exit(EXIT_FAILURE); + error: + if (readfd > 0) close(readfd); + if (writefd > 0) close(writefd); + mylog(LOG_INFO, "Stopping due to error."); + exit(EXIT_FAILURE); } From dab9dcbb48d962208ac77db4b4fcd359f3a57087 Mon Sep 17 00:00:00 2001 From: ale275 Date: Sat, 25 Oct 2025 12:42:35 +0200 Subject: [PATCH 13/26] made bufsize truly reliant on buffer duration, sample rate and capture channels --- cpiped.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/cpiped.c b/cpiped.c index 59cec34..7dd7764 100644 --- a/cpiped.c +++ b/cpiped.c @@ -95,7 +95,8 @@ int main(int argc, char *argv[]) { int readbytes = 0; int writebytes = 0; char *buf; - size_t bufsize = 176400; + size_t bufsize = 0; + float bufdur = 0.25; int bufstart; int bufend; int bufused; @@ -138,8 +139,8 @@ int main(int argc, char *argv[]) { capdev = optarg; break; case 'b': - bufsize = atof(optarg) * samplerate * 8; - if (bufsize < 33280 || bufsize > 1764000) { + bufdur = atof(optarg); + if (bufdur < 0.1 || bufdur > 5.0) { mylog(LOG_ERR, "Invalid buffer. Range is 0.1-5.0 sec.\n"); goto error; } @@ -271,6 +272,9 @@ int main(int argc, char *argv[]) { snd_pcm_hw_params_set_channels(handle, params, capchannels); snd_pcm_hw_params_set_rate_near(handle, params, &samplerate, &dir); + // set buffer size in bit + bufsize = bufdur * samplerate * capchannels * 8; + // Set period size to 1024 frames. frames = 1024; snd_pcm_hw_params_set_period_size_near(handle, params, &frames, &dir); From 6ea4e4bb301167c203071ab9e6993820ff35c900 Mon Sep 17 00:00:00 2001 From: ale275 Date: Sat, 25 Oct 2025 16:19:43 +0200 Subject: [PATCH 14/26] linked size of readbuffer to sample size and captured channels --- cpiped.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cpiped.c b/cpiped.c index 7dd7764..64750b6 100644 --- a/cpiped.c +++ b/cpiped.c @@ -366,7 +366,7 @@ int main(int argc, char *argv[]) { // Determine the amount of buffer used bufused = bufend - bufstart + (bufend < bufstart) * (bufsize - 1); - //printf("size:%d, start:%d, end:%d, used:%d, wrote:%d\n", bufsize, bufstart, bufend, bufused, wrote); + mylogverb(LOG_INFO, 3, "size:%d, start:%d, end:%d, used:%d, wrote:%d\n", bufsize, bufstart, bufend, bufused, wrote); if (bufused < capsize) { // Buffer is almost empty, don't send to pipe @@ -388,7 +388,7 @@ int main(int argc, char *argv[]) { if (!buffull && rc > 0) { // Store samples in buffer - readbytes = rc * 4; + readbytes = rc * (samplesize / 8) * capchannels; bufendtoend = bufsize - bufend; if (readbytes <= bufendtoend) { // No wrap required From 1bcc48012f15eedcd3d9df03f36e4be48f86b9a7 Mon Sep 17 00:00:00 2001 From: ale275 Date: Sat, 25 Oct 2025 17:37:07 +0200 Subject: [PATCH 15/26] added -vv and -vvv options for higher verbosity levels --- cpiped.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/cpiped.c b/cpiped.c index 64750b6..e4a371f 100644 --- a/cpiped.c +++ b/cpiped.c @@ -20,16 +20,16 @@ // Use the newer ALSA API #define ALSA_PCM_NEW_HW_PARAMS_API -#include -#include #include +#include +#include #include #include +#include +#include #include #include #include -#include -#include snd_pcm_t *handle; int daemonize = 0; @@ -133,7 +133,7 @@ int main(int argc, char *argv[]) { signal(SIGTERM, myterm); // Process command-line options and arguments - while ((opt = getopt(argc, argv, "d:b:s:e:t:Dp:v")) != -1) { + while ((opt = getopt(argc, argv, "d:b:s:e:t:Dp:v:")) != -1) { switch (opt) { case 'd': capdev = optarg; @@ -176,6 +176,8 @@ int main(int argc, char *argv[]) { break; case 'v': log_verb = 1; + if (strcmp(optarg, "v" ) == 0) {log_verb = 2;} + if (strcmp(optarg, "vv") == 0) {log_verb = 3;} break; case '?': err = 1; @@ -195,7 +197,7 @@ int main(int argc, char *argv[]) { " -t : silence threshold (1 - 32767, [100])\n" " -D : daemonize\n" " -p : path to pidfile [/var/run/cpiped.pid]\n" - " -v : enable more verbose logs\n" + " -v : enable more verbose logs (-vv and -vvv for higher verbosity levels)\n" " FIFO : path to a named pipe\n", argv[0]); } goto error; From 0944a931ab073e9df6324f9f6ecc0c05918c3de7 Mon Sep 17 00:00:00 2001 From: ale275 Date: Sun, 2 Nov 2025 19:34:48 +0100 Subject: [PATCH 16/26] cleaned up pid file code --- cpiped.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/cpiped.c b/cpiped.c index e4a371f..9a7fdea 100644 --- a/cpiped.c +++ b/cpiped.c @@ -123,7 +123,6 @@ int main(int argc, char *argv[]) { pid_t pid, sid; char pidstr[10]; - char pidfile[50]; int pidfd; // Ignore pipe signals @@ -172,7 +171,8 @@ int main(int argc, char *argv[]) { daemonize = 1; break; case 'p': - pidpath = optarg; + pidpath = calloc(strlen(optarg), sizeof(char)); + sprintf(pidpath, optarg); break; case 'v': log_verb = 1; @@ -231,9 +231,8 @@ int main(int argc, char *argv[]) { } // Write the pidfile - sprintf(pidfile, pidpath); sprintf(pidstr, "%d\n", getpid()); - pidfd = open(pidfile, O_RDWR|O_CREAT, 0644); + pidfd = open(pidpath, O_RDWR|O_CREAT, 0644); write(pidfd, pidstr, strlen(pidstr)); close(pidfd); From abe9841296d40ef29568c564591424f517a1318d Mon Sep 17 00:00:00 2001 From: ale275 Date: Sun, 2 Nov 2025 19:37:44 +0100 Subject: [PATCH 17/26] added -f command line parameter to set sample frequency - cleaned up help message in case of error --- cpiped.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/cpiped.c b/cpiped.c index 9a7fdea..fc32b8e 100644 --- a/cpiped.c +++ b/cpiped.c @@ -132,7 +132,7 @@ int main(int argc, char *argv[]) { signal(SIGTERM, myterm); // Process command-line options and arguments - while ((opt = getopt(argc, argv, "d:b:s:e:t:Dp:v:")) != -1) { + while ((opt = getopt(argc, argv, "d:b:s:e:f:t:Dp:v:")) != -1) { switch (opt) { case 'd': capdev = optarg; @@ -167,6 +167,13 @@ int main(int argc, char *argv[]) { goto error; } break; + case 'f': + samplerate = atoi(optarg); + if ((samplerate < 16000) || (samplerate > 192000)) { + mylog(LOG_ERR, "Invalid sample rate. Range is 16000-192000.\n"); + goto error; + } + break; case 'D': daemonize = 1; break; @@ -188,19 +195,19 @@ int main(int argc, char *argv[]) { if ((optind + 1) != argc) err = 1; if (err) { if (!daemonize) { - printf("\nUsage:\n %s [-d arg] [-b arg] [-s arg] " - "[-e arg] [-t arg] [-D] FIFO\n" + printf("\nUsage:\n %s [-s arg] [-e arg] [OPTIONS] FIFO\n" " -d : ALSA capture device ['default']\n" - " -b : target buffer in seconds [.5]\n" + " -b : target buffer in seconds [.25]\n" " -s : command to run when sound detected\n" " -e : command to run when silence detected\n" " -t : silence threshold (1 - 32767, [100])\n" + " -f : sample rate (16000 - 192000, [44100])\n" " -D : daemonize\n" " -p : path to pidfile [/var/run/cpiped.pid]\n" " -v : enable more verbose logs (-vv and -vvv for higher verbosity levels)\n" " FIFO : path to a named pipe\n", argv[0]); } - goto error; + exit(EXIT_FAILURE); } mylog(LOG_INFO, "Starting up.\n"); From 608e5c09b77195847f7c9f44825ca7013a788ac6 Mon Sep 17 00:00:00 2001 From: ale275 Date: Sun, 2 Nov 2025 19:41:42 +0100 Subject: [PATCH 18/26] added settings logging for log verbosity level 1 --- cpiped.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/cpiped.c b/cpiped.c index fc32b8e..27905e9 100644 --- a/cpiped.c +++ b/cpiped.c @@ -243,6 +243,19 @@ int main(int argc, char *argv[]) { write(pidfd, pidstr, strlen(pidstr)); close(pidfd); + // Log configurations + if (log_verb) { + mylogverb(LOG_INFO, 1, "Demonize: '%d'\n", daemonize); + mylogverb(LOG_INFO, 1, "Log verbosity level: '%d'\n", log_verb); + mylogverb(LOG_INFO, 1, "Pid path: '%s'\n", pidpath); + mylogverb(LOG_INFO, 1, "Capture device: '%s'\n", capdev); + mylogverb(LOG_INFO, 1, "Sample rate: '%d'\n", samplerate); + mylogverb(LOG_INFO, 1, "Buffer duration: '%f'\n", bufdur); + mylogverb(LOG_INFO, 1, "Sound detect command: '%s'\n", startcmd); + mylogverb(LOG_INFO, 1, "Silence detect command: '%s'\n", endcmd); + mylogverb(LOG_INFO, 1, "Silence threshold: '%d'\n", silentt); + } + // Open the FIFO for read first, so the open for write succeeds. readfd = open(fifonam, O_RDONLY | O_NONBLOCK); if (readfd <= 0) { From c803b27c42de156abe4337937c49a64530912a03 Mon Sep 17 00:00:00 2001 From: ale275 Date: Sun, 2 Nov 2025 19:47:29 +0100 Subject: [PATCH 19/26] modified bufsize calculation - made out pipe size twice the capture buffer size - added error message when pipe resizing fails - added warning when pipe size different from the requested one --- cpiped.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/cpiped.c b/cpiped.c index 27905e9..001330b 100644 --- a/cpiped.c +++ b/cpiped.c @@ -277,9 +277,6 @@ int main(int argc, char *argv[]) { } close(readfd); - // Set the FIFO size to 8192 bytes to minimize latency - fcntl(writefd, F_SETPIPE_SZ, 8192); - // Open PCM device for recording (capture). rc = snd_pcm_open(&handle, capdev, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK); if (rc != 0) { @@ -293,8 +290,8 @@ int main(int argc, char *argv[]) { snd_pcm_hw_params_set_channels(handle, params, capchannels); snd_pcm_hw_params_set_rate_near(handle, params, &samplerate, &dir); - // set buffer size in bit - bufsize = bufdur * samplerate * capchannels * 8; + // set buffer size in byte + bufsize = bufdur * samplerate * capchannels * (samplesize / 8); // Set period size to 1024 frames. frames = 1024; @@ -316,6 +313,16 @@ int main(int argc, char *argv[]) { capbuffer = calloc(capsize, sizeof(char)); scapbuffer = (int16_t*)capbuffer; + // Set the FIFO size to twice the period size + int pipesize = fcntl(writefd, F_SETPIPE_SZ, 2 * capsize); + if (pipesize < 0) { + mylog(LOG_ERR, "Error setting FIFO size: %s\n", strerror(errno)); + goto error; + } + if (pipesize != 2 * capsize) { + mylog(LOG_NOTICE, "WARN setting FIFO size to %ld, actual size %ld\n", 2*capsize, pipesize); + } + // Determine write size writebytes = capsize - 32 ; // Write a bit less to make sure write controls pace From 3db6206dc735895b811f84cef4a4622d8077cded Mon Sep 17 00:00:00 2001 From: ale275 Date: Sun, 2 Nov 2025 19:50:17 +0100 Subject: [PATCH 20/26] added export as env variables for sample size, rate and capture channel count - variable name for sample rate is CPIPED_SR - variable name for sample size is CPIPED_SS - variable name for channel count is CPIPED_CC - those can be used by the called script to perform format conversion or other arithmetic if needed --- cpiped.c | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/cpiped.c b/cpiped.c index 001330b..07b9bb5 100644 --- a/cpiped.c +++ b/cpiped.c @@ -76,6 +76,34 @@ void myterm() { exit(EXIT_SUCCESS); } +int setenvvar(const char *var_name, size_t var_val_len, unsigned int var_overwrite, const char *format, ...) { + va_list args; + va_start (args, format); + + char *var_value; + int ret; + + // allocate space for the variable value + var_value = malloc((var_val_len + 1) * sizeof(char)); + + if (var_value == NULL) { + mylog(LOG_ERR, "Unable to allocate memory for ENV variable setup '%s'\n", strerror(errno)); + return 1; + } + + // set variable value as string + vsnprintf(var_value, var_val_len + 1, format, args); + + // Set the variable + ret = setenv(var_name, var_value, var_overwrite); + + // Clean-up + free(var_value); + va_end(args); + + return ret; +} + int main(int argc, char *argv[]) { int rc; int capsize; @@ -332,6 +360,14 @@ int main(int argc, char *argv[]) { bufstart = 0; bufend = 0; + // Export capture parameters to ENV variables + if (setenvvar("CPIPED_SR", 6, 1, "%ld", samplerate) == -1) + mylog(LOG_NOTICE, "WARN CPIPED_SR to '%ld' FAILED\n", samplerate); + if (setenvvar("CPIPED_SS", 2, 1, "%ld", samplesize) == -1) + mylog(LOG_NOTICE, "WARN CPIPED_SS to '%ld' FAILED\n", samplesize); + if (setenvvar("CPIPED_CC", 2, 1, "%d", capchannels) == -1) + mylog(LOG_NOTICE, "WARN CPIPED_CC to '%d' FAILED\n", capchannels); + while (1) { // Capture samples if (!wrote) // When not writing, wait a bit between captures. From ee3b52c327f7628ab91d7b9bf84185d054ca8985 Mon Sep 17 00:00:00 2001 From: ale275 Date: Sun, 2 Nov 2025 19:51:17 +0100 Subject: [PATCH 21/26] added comments in the signal processing section --- cpiped.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cpiped.c b/cpiped.c index 07b9bb5..ba42e41 100644 --- a/cpiped.c +++ b/cpiped.c @@ -372,7 +372,7 @@ int main(int argc, char *argv[]) { // Capture samples if (!wrote) // When not writing, wait a bit between captures. usleep(capusec * .95); - rc = snd_pcm_readi(handle, capbuffer, frames); + rc = snd_pcm_readi(handle, capbuffer, frames); // return number of captured frames if (rc == -EPIPE) { // EPIPE means overrun mylog(LOG_NOTICE, "Overrun occurred\n"); snd_pcm_prepare(handle); @@ -385,6 +385,7 @@ int main(int argc, char *argv[]) { // Check if there is sound and if so compute RMS power level regularly and count sounds and silences if (capcount > 10 && rc > 0) { // Approx. every quarter second power = 0; + // Each frame holds one sample per channel for (i = 0; i < rc / 2; i++) { power += pow(scapbuffer[i], 2) + pow(scapbuffer[i + 1], 2); } @@ -450,7 +451,7 @@ int main(int argc, char *argv[]) { if ((buffull == 1) && (bufused < (int)bufsize / 2)) buffull = 0; - if (!buffull && rc > 0) { + if (!buffull && rc > 0) { // Store samples in buffer readbytes = rc * (samplesize / 8) * capchannels; bufendtoend = bufsize - bufend; From 8594b2d147d104adfb24ea2e140bfb4692ff36c0 Mon Sep 17 00:00:00 2001 From: ale275 Date: Sun, 14 Dec 2025 16:40:57 +0100 Subject: [PATCH 22/26] fixed default sample rate --- cpiped.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpiped.c b/cpiped.c index ba42e41..d9034b4 100644 --- a/cpiped.c +++ b/cpiped.c @@ -108,7 +108,7 @@ int main(int argc, char *argv[]) { int rc; int capsize; snd_pcm_hw_params_t *params; - unsigned int samplerate = 41000; + unsigned int samplerate = 44100; unsigned int samplesize = 16; unsigned int capchannels = 2; unsigned int capusec; From cb31ba11887ea1cce07ac98bb3b45f6e5834666d Mon Sep 17 00:00:00 2001 From: Alessandro Gritti Date: Sun, 28 Dec 2025 19:08:34 +0100 Subject: [PATCH 23/26] Create build workflow --- .github/workflows/cpiped-build.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 .github/workflows/cpiped-build.yml diff --git a/.github/workflows/cpiped-build.yml b/.github/workflows/cpiped-build.yml new file mode 100644 index 0000000..c121090 --- /dev/null +++ b/.github/workflows/cpiped-build.yml @@ -0,0 +1,21 @@ +name: cpiped build + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - name: install alsasound + run: sudo apt install -y libasound2-dev + - uses: actions/checkout@v4 + - name: make + run: make + - name: check executable + run: bash -c "[[ -x cpiped ]]" From 373c001550dc752fe5ab2affe9534924c94d5e7d Mon Sep 17 00:00:00 2001 From: ale275 Date: Sun, 28 Dec 2025 20:53:01 +0100 Subject: [PATCH 24/26] README overhaul --- README.md | 131 +++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 120 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 9a0f1fd..62c1b28 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,124 @@ -cpiped (https://github.com/b-fitzpatrick/cpiped) captures an audio stream and outputs it to a pipe with a bit of buffering -to handle clock mismatch. It also includes silence/sound detection and the ability -to run a command on detection. +

cpiped

-I'm using this to capture line-in audio from a sound card and send it to -forked-daapd (https://github.com/ejurgensen/forked-daapd). On sound detection, it -runs a simple script to start playing the associated pipe in forked-daapd. +[![Release][release-shield]][release-url] +![GitHub commit activity][commit-shield] +[![Issues][issues-shield]][issues-url] +[![Contributors][contributors-shield]][contributors-url] +[![project_license][license-shield]][license-url] +[![Stargazers][stars-shield]][stars-url] +[![Forks][forks-shield]][forks-url] +![Build flow][build-shield] -#### Merged work -- NDpiped builds on cpiped but modifies code such that the buffer is only written if there is sound. Otherwise I found buffer was constantly writing and Forked-Daapd was playing silence rather then my playlist. (https://github.com/natedreger/cpiped) and (https://github.com/natedreger/NDpiped) -- Parametrized pid file by Mario Wenzel (https://github.com/maweki/cpiped) + cpiped captures an audio stream and outputs it to a pipe -#### ToDo(s) +`cpiped` was born to capture line-in audio from a sound card and send it to [forked-daapd](https://github.com/ejurgensen/forked-daapd). On sound detection, it runs a simple script to start playing the associated pipe in forked-daapd. + +## Contents +- [Contents](#contents) +- [Features](#features) +- [Getting Started](#getting-started) + - [Prerequisites](#prerequisites) + - [Installation](#installation) +- [Usage](#usage) + - [Options](#options) + - [Env variables](#env-variables) +- [ToDo(s)](#todos) +- [Acknowledgments](#acknowledgments) + - [Top contributors:](#top-contributors) + +## Features +- Buffering to handle clock mismatch +- Command execution on sound and silence detection +- User defined noise threshold +- Different sample rate handling +- Multiple instances running in parallel thanks to parametrized pid + +## Getting Started + +### Prerequisites +`cpiped` relies on [ALSA lib]( http://www.alsa-project.org) as only prerequisite + +- Debian + ```sh + sudo apt install -y libasound2-dev + ``` + +### Installation +1. Clone the repository + ```sh + git clone https://github.com/ale275/cpiped.git + ``` +2. Make cpiped + ```sh + cd cpiped + make + ``` +4. Install cpiped + ```sh + sudo install cpiped /usr/local/sbin/ + ``` + +## Usage + +`cpiped` is an application meant to be run from command line or demonized + +```sh +cpiped [-s arg] [-e arg] [OPTIONS] FIFO +``` +FIFO: path to the named pipe where sound will be written + +### Options + +| Option | Description | Type | Default | Range | Required? | +| ------ | ------------------------------------------------------------- | --------- | ----------------------- | ---------------- | --------- | +| `-d` | ALSA capture device in *hw:\,\* format. | `string` | `'default'` | | Yes | +| `-f` | ALSA capture device sample rate in Hz. | `integer` | `44100` | `16000 - 192000` | No | +| `-b` | Buffer size in seconds. | `float` | `.25` | `.1 - 5.0` | No | +| `-s` | Command to run, in background, when sound is detected. | `string` | | | Yes | +| `-e` | Command to run, in background, when silence is detected. | `string` | | | Yes | +| `-t` | Silence threshold expressed as signal power. | `integer` | `100` | `1 - 32767` | No | +| `-D` | Daemonize. | `bool` | | | No | +| `-p` | Path to pidfile. | `string` | `'/var/run/cpiped.pid'` | | No | +| `-v` | Log verbosity. (`-vv` and `-vvv` for higher verbosity levels) | `bool` | | | No | + +### Env variables +On start `cpiped` will set the following env variables +- *CPIPED_SR* audio capture sample rate +- *CPIPED_SS* audio capture sample size +- *CPIPED_CC* audio capture channel(s) count + +Those variables are visible by the sound and silence detect commands + +## ToDo(s) - [ ] Improve Makefile to create dedicated build folder -- [ ] Improve Makefile to add install directive \ No newline at end of file +- [ ] Improve Makefile to add install directive + +See the [open issues](https://github.com/ale275/cpiped/issues) for a full list of proposed features (and known issues). + +## Acknowledgments + +* [cpiped](https://github.com/b-fitzpatrick/cpiped) original code of cpiped by [b-fitzpatrick](https://github.com/b-fitzpatrick) +* [NDpiped](https://github.com/natedreger/NDpiped) modified version writing the buffer only when sound was detected +* [maweki cpiped fork](https://github.com/maweki/cpiped) for parametrized pid file + +### Top contributors: + + + contrib.rocks image + + + +[build-shield]: https://github.com/ale275/cpiped/actions/workflows/cpiped-build.yml/badge.svg +[commit-shield]: https://img.shields.io/github/commit-activity/t/ale275/cpiped?style=flat +[release-shield]: https://img.shields.io/github/release/ale275/cpiped.svg?colorB=58839b +[release-url]: https://github.com/ale275/cpiped/releases/latest +[contributors-shield]: https://img.shields.io/github/contributors/ale275/cpiped.svg +[contributors-url]: https://github.com/ale275/cpiped/graphs/contributors +[forks-shield]: https://img.shields.io/github/forks/ale275/cpiped.svg?style=flat +[forks-url]: https://github.com/github_usale275ername/cpiped/network/members +[stars-shield]: https://img.shields.io/github/stars/ale275/cpiped.svg?style=flat +[stars-url]: https://github.com/ale275/cpiped/stargazers +[issues-shield]: https://img.shields.io/github/issues/ale275/cpiped.svg +[issues-url]: https://github.com/ale275/cpiped/issues +[license-shield]: https://img.shields.io/github/license/ale275/cpiped.svg +[license-url]: https://github.com/ale275/cpiped/blob/master/LICENSE \ No newline at end of file From 9d85652e1290dc7a30fc73b1a89835555d96fe59 Mon Sep 17 00:00:00 2001 From: ale275 Date: Mon, 26 Jan 2026 20:46:17 +0100 Subject: [PATCH 25/26] added check to revert to default sample rate if no value is specified as -f argument --- cpiped.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cpiped.c b/cpiped.c index d9034b4..a4d44f7 100644 --- a/cpiped.c +++ b/cpiped.c @@ -197,7 +197,10 @@ int main(int argc, char *argv[]) { break; case 'f': samplerate = atoi(optarg); - if ((samplerate < 16000) || (samplerate > 192000)) { + if (samplerate == 0) { + mylog(LOG_INFO, "No sample rate specified. Reverting to default.\n"); + samplerate = 44100; + } else if ((samplerate < 16000) || (samplerate > 192000)) { mylog(LOG_ERR, "Invalid sample rate. Range is 16000-192000.\n"); goto error; } From c349cdceb55bc75921d4fcff3eac09bd45763ce1 Mon Sep 17 00:00:00 2001 From: ale275 Date: Mon, 26 Jan 2026 21:18:33 +0100 Subject: [PATCH 26/26] README updated to add "made with C" shield --- README.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 62c1b28..202e382 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,8 @@ [![Forks][forks-shield]][forks-url] ![Build flow][build-shield] +![c][c-shield] + cpiped captures an audio stream and outputs it to a pipe `cpiped` was born to capture line-in audio from a sound card and send it to [forked-daapd](https://github.com/ejurgensen/forked-daapd). On sound detection, it runs a simple script to start playing the associated pipe in forked-daapd. @@ -36,7 +38,7 @@ ## Getting Started ### Prerequisites -`cpiped` relies on [ALSA lib]( http://www.alsa-project.org) as only prerequisite +`cpiped` relies on [ALSA lib](http://www.alsa-project.org) as only prerequisite - Debian ```sh @@ -71,7 +73,7 @@ FIFO: path to the named pipe where sound will be written | Option | Description | Type | Default | Range | Required? | | ------ | ------------------------------------------------------------- | --------- | ----------------------- | ---------------- | --------- | -| `-d` | ALSA capture device in *hw:\,\* format. | `string` | `'default'` | | Yes | +| `-d` | ALSA capture device in *hw:\,\* format. | `string` | `'default'` | | No | | `-f` | ALSA capture device sample rate in Hz. | `integer` | `44100` | `16000 - 192000` | No | | `-b` | Buffer size in seconds. | `float` | `.25` | `.1 - 5.0` | No | | `-s` | Command to run, in background, when sound is detected. | `string` | | | Yes | @@ -83,8 +85,8 @@ FIFO: path to the named pipe where sound will be written ### Env variables On start `cpiped` will set the following env variables -- *CPIPED_SR* audio capture sample rate -- *CPIPED_SS* audio capture sample size +- *CPIPED_SR* audio capture sample rate in *Hz* +- *CPIPED_SS* audio capture sample size in *bit(s)* - *CPIPED_CC* audio capture channel(s) count Those variables are visible by the sound and silence detect commands @@ -115,10 +117,11 @@ See the [open issues](https://github.com/ale275/cpiped/issues) for a full list o [contributors-shield]: https://img.shields.io/github/contributors/ale275/cpiped.svg [contributors-url]: https://github.com/ale275/cpiped/graphs/contributors [forks-shield]: https://img.shields.io/github/forks/ale275/cpiped.svg?style=flat -[forks-url]: https://github.com/github_usale275ername/cpiped/network/members +[forks-url]: https://github.com/ale275/cpiped/network/members [stars-shield]: https://img.shields.io/github/stars/ale275/cpiped.svg?style=flat [stars-url]: https://github.com/ale275/cpiped/stargazers [issues-shield]: https://img.shields.io/github/issues/ale275/cpiped.svg [issues-url]: https://github.com/ale275/cpiped/issues [license-shield]: https://img.shields.io/github/license/ale275/cpiped.svg -[license-url]: https://github.com/ale275/cpiped/blob/master/LICENSE \ No newline at end of file +[license-url]: https://github.com/ale275/cpiped/blob/master/LICENSE +[c-shield]: https://img.shields.io/badge/made%20with%20C-1f425f?style=flat&logo=c&logoColor=white \ No newline at end of file