ncmixer

ncurses audio mixer for DJ'ing
git clone git://git.2f30.org/ncmixer
Log | Files | Refs | README | LICENSE

commit 70b6e37e934710321226e5635613b54d8aa1fcc9
parent bf938ec7b4003e32c443088059f667c0829a85eb
Author: sin <sin@2f30.org>
Date:   Wed, 20 Jul 2016 16:26:20 +0100

Split ncmixer into a daemon and a client

Diffstat:
MMakefile | 16+++++++++++-----
Aarg.h | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Amixerd.c | 573+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dncmixer.c | 608-------------------------------------------------------------------------------
Ancmixerc.c | 435+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apatches/0001-output-plugins-Add-UNIX-domain-socket-output.patch | 326+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aproto.h | 44++++++++++++++++++++++++++++++++++++++++++++
7 files changed, 1452 insertions(+), 613 deletions(-)

diff --git a/Makefile b/Makefile @@ -2,11 +2,16 @@ VERSION = 0.1 CPPFLAGS = -DDEBUG CFLAGS = -Wall LDLIBS = -lsndio -lcurses -lm -DISTFILES = ncmixer.c LICENSE Makefile README -OBJ = ncmixer.o -BIN = ncmixer +DISTFILES = arg.h mixerd.c ncmixerc.c proto.h LICENSE Makefile README +NCMIXERC_OBJ = ncmixerc.o +NCMIXERC_BIN = ncmixerc +MIXERD_OBJ = mixerd.o +MIXERD_BIN = mixerd -all: $(BIN) +all: $(NCMIXERC_BIN) $(MIXERD_BIN) + +NCMIXERC_BIN: $(NCMIXERC_OBJ) +MIXERD_BIN: $(MIXERD_OBJ) dist: mkdir -p ncmixer-$(VERSION) @@ -16,6 +21,7 @@ dist: rm -rf ncmixer-$(VERSION) clean: - rm -f $(OBJ) $(BIN) ncmixer-$(VERSION).tar.gz + rm -f $(NCMIXERC_OBJ) $(MIXERD_OBJ) $(NCMIXERC_BIN) $(MIXERD_BIN) \ + ncmixer-$(VERSION).tar.gz .PHONY: all dist clean diff --git a/arg.h b/arg.h @@ -0,0 +1,63 @@ +/* + * Copy me if you can. + * by 20h + */ + +#ifndef ARG_H__ +#define ARG_H__ + +extern char *argv0; + +/* use main(int argc, char *argv[]) */ +#define ARGBEGIN for (argv0 = *argv, argv++, argc--;\ + argv[0] && argv[0][1]\ + && argv[0][0] == '-';\ + argc--, argv++) {\ + char argc_;\ + char **argv_;\ + int brk_;\ + if (argv[0][1] == '-' && argv[0][2] == '\0') {\ + argv++;\ + argc--;\ + break;\ + }\ + for (brk_ = 0, argv[0]++, argv_ = argv;\ + argv[0][0] && !brk_;\ + argv[0]++) {\ + if (argv_ != argv)\ + break;\ + argc_ = argv[0][0];\ + switch (argc_) + +/* Handles obsolete -NUM syntax */ +#define ARGNUM case '0':\ + case '1':\ + case '2':\ + case '3':\ + case '4':\ + case '5':\ + case '6':\ + case '7':\ + case '8':\ + case '9' + +#define ARGEND }\ + } + +#define ARGC() argc_ + +#define ARGNUMF(base) (brk_ = 1, estrtol(argv[0], (base))) + +#define EARGF(x) ((argv[0][1] == '\0' && argv[1] == NULL)?\ + ((x), abort(), (char *)0) :\ + (brk_ = 1, (argv[0][1] != '\0')?\ + (&argv[0][1]) :\ + (argc--, argv++, argv[0]))) + +#define ARGF() ((argv[0][1] == '\0' && argv[1] == NULL)?\ + (char *)0 :\ + (brk_ = 1, (argv[0][1] != '\0')?\ + (&argv[0][1]) :\ + (argc--, argv++, argv[0]))) + +#endif diff --git a/mixerd.c b/mixerd.c @@ -0,0 +1,573 @@ +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/un.h> + +#include <err.h> +#include <errno.h> +#include <math.h> +#include <poll.h> +#include <signal.h> +#include <sndio.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> + +#include "arg.h" +#include "proto.h" + +#define LEN(x) (sizeof (x) / sizeof *(x)) +#undef MIN +#define MIN(x, y) ((x) < (y) ? (x) : (y)) +#undef MAX +#define MAX(x, y) ((x) > (y) ? (x) : (y)) +#define SA struct sockaddr +#define CTL_SOCK_PATH "/tmp/mixerctl" +#define CH0_SOCK_PATH "/tmp/mixer0" +#define CH1_SOCK_PATH "/tmp/mixer1" +#define MASTER_DEV "snd/0" +#define MONITOR_DEV "rsnd/1" +#define BITS 16 +#define BPS (BITS / 8) +#define RATE 44100 +#define CHANS 2 +#define NSAMPLES 128 +#define BUFFER_TIME_MS 100 +#define TIMEO_MS 40 + +char *argv0; +int debug; + +/* mixer state */ +float xf_pos = 0.0f; /* values in [-1.0, 1.0] */ + +/* forward decls */ +struct input; +struct output; + +int mix_master(struct output *, struct input *, struct input *); +int mix_monitor(struct output *, struct input *, struct input *); + +struct input { + int listenfd; + int clifd; + short buf[NSAMPLES]; + short attenuated_buf[NSAMPLES]; + int nsamples; + int mon_on; + float level; +} inputs[] = { + { .listenfd = -1, .clifd = -1, .level = 1.0f }, + { .listenfd = -1, .clifd = -1, .level = 1.0f } +}; + +struct output { + char *name; + struct sio_hdl *sio_hdl; + short buf[NSAMPLES]; + int nsamples; + int (*mix)(struct output *, struct input *, struct input *); +} outputs[] = { + { .name = MASTER_DEV, .mix = mix_master }, + { .name = MONITOR_DEV, .mix = mix_monitor } +}; + +void +log_msg(int prio, const char *msg, ...) +{ + va_list ap; + + if (debug) { + va_start(ap, msg); + vfprintf(stderr, msg, ap); + fprintf(stderr, "\n"); + va_end(ap); + } else { + va_start(ap, msg); + vsyslog(LOG_DAEMON | prio, msg, ap); + va_end(ap); + } +} + +void +log_err(const char *msg, ...) +{ + va_list ap; + + if (debug) { + va_start(ap, msg); + fprintf(stderr, "rebound: "); + vfprintf(stderr, msg, ap); + fprintf(stderr, "\n"); + va_end(ap); + } else { + va_start(ap, msg); + vsyslog(LOG_DAEMON | LOG_ERR, msg, ap); + va_end(ap); + } + exit(1); +} + +struct sio_hdl * +audio_open(char *devname) +{ + struct sio_hdl *sio_hdl; + struct sio_par par; + + sio_hdl = sio_open(devname, SIO_PLAY, 0); + if (sio_hdl == NULL) + return NULL; + + sio_initpar(&par); + par.bits = BITS; + par.rate = RATE; + par.pchan = CHANS; + par.sig = 1; + par.le = SIO_LE_NATIVE; + par.appbufsz = RATE * BUFFER_TIME_MS / 1000; + + if (sio_setpar(sio_hdl, &par) == 0) { + log_msg(LOG_WARNING, "%s: failed to set params", devname); + goto err0; + } + + if (sio_getpar(sio_hdl, &par) == 0) { + log_msg(LOG_WARNING, "%s: failed to get params", devname); + goto err0; + } + + if (par.pchan != CHANS) { + log_msg(LOG_WARNING, "%s: failed to set number of channels", + devname); + goto err0; + } + + if (par.sig != 1 || + par.bits != BITS || + par.le != SIO_LE_NATIVE) { + log_msg(LOG_WARNING, "%s: failed to set format", devname); + goto err0; + } + + /* allow 0.5% tolerance for actual sample rate */ + if (par.rate < RATE * 995 / 1000 || + par.rate > RATE * 1005 / 1000) { + log_msg(LOG_WARNING, "%s: failed to set rate", devname); + goto err0; + } + + if (sio_start(sio_hdl) == 0) { + log_msg(LOG_WARNING, "%s: failed to start audio device", + devname); + goto err0; + } + + return sio_hdl; +err0: + sio_close(sio_hdl); + return NULL; +} + +int +audio_play(struct sio_hdl *sio_hdl, void *buf, int len) +{ + int n; + + n = sio_write(sio_hdl, buf, len); + if (n == 0 && sio_eof(sio_hdl) != 0) + return -1; + return n; +} + +void +audio_close(struct sio_hdl **sio_hdl) +{ + if (*sio_hdl != NULL) + sio_close(*sio_hdl); + *sio_hdl = NULL; +} + +int +consume(struct input *in) +{ + int n; + + do { + n = read(in->clifd, in->buf, sizeof(in->buf)); + } while (n == -1 && errno == EINTR); + if (n == 0 || n == -1) + return -1; + in->nsamples = n / BPS; + return in->nsamples; +} + +void +level(struct input *in) +{ + short *ch0 = (short *)in->buf; + int i; + + for (i = 0; i < in->nsamples; i++) { + *ch0 *= in->level; + ch0++; + } +} + +void +attenuate(struct input *in, float factor) +{ + short *ch0 = (short *)in->buf; + short *out = (short *)in->attenuated_buf; + int i; + + for (i = 0; i < in->nsamples; i++) { + *out = *ch0 * factor; + ch0++, out++; + } +} + +int +mix_master(struct output *out, struct input *in0, struct input *in1) +{ + short *ch0 = (short *)in0->attenuated_buf; + short *ch1 = (short *)in1->attenuated_buf; + short *buf = (short *)out->buf; + int i; + + out->nsamples = MAX(in0->nsamples, in1->nsamples); + for (i = 0; i < out->nsamples; i++) { + *buf = *ch0 * 0.5 + *ch1 * 0.5; + ch0++, ch1++, buf++; + } + return out->nsamples; +} + +int +mix_monitor(struct output *out, struct input *in0, struct input *in1) +{ + short *ch0 = (short *)in0->buf; + short *ch1 = (short *)in1->buf; + short *buf = (short *)out->buf; + int i; + + out->nsamples = MAX(in0->nsamples, in1->nsamples); + for (i = 0; i < out->nsamples; i++) { + *ch0 *= in0->mon_on; + *ch1 *= in1->mon_on; + *buf = *ch0 * 0.5 + *ch1 * 0.5; + ch0++, ch1++, buf++; + } + return out->nsamples; +} + +int +server_listen(char *name, int ctl) +{ + struct sockaddr_un sun; + socklen_t len; + int fd, type, oldumask; + + if (ctl) + type = SOCK_SEQPACKET; + else + type = SOCK_STREAM; + + fd = socket(AF_UNIX, type, 0); + if (fd == -1) + log_err("socket failed"); + unlink(name); + memset(&sun, 0, sizeof(sun)); + sun.sun_family = AF_UNIX; + snprintf(sun.sun_path, sizeof(sun.sun_path), "%s", name); + len = sizeof(sun); + oldumask = umask(0111); + if (bind(fd, (SA *)&sun, len) == -1) + log_err("bind failed"); + umask(oldumask); + if (listen(fd, 5) == -1) + log_err("listen failed"); + return fd; +} + +int +msg_handler(int fd) +{ + struct msg msg; + int n, tx_reply = 0; + + do { + n = read(fd, &msg, sizeof(msg)); + } while (n == -1 && errno == EINTR); + if (n == 0 || n == -1) + return -1; + + switch (msg.type) { + case SET_XF: + if (msg.m_xf_pos < -1.0f || + msg.m_xf_pos > 1.0f) { + log_msg(LOG_WARNING, "xf pos out of bounds: %f", + msg.m_xf_pos); + return 0; + } + xf_pos = msg.m_xf_pos; + break; + case GET_XF: + msg.m_xf_pos = xf_pos; + tx_reply = 1; + break; + case SET_MON: + if (msg.m_mon_index != 0 && + msg.m_mon_index != 1) { + log_msg(LOG_WARNING, "invalid monitor index: %d", + msg.m_mon_index); + return 0; + } + inputs[msg.m_mon_index].mon_on = !inputs[msg.m_mon_index].mon_on; + break; + case GET_MON: + if (msg.m_mon_index != 0 && + msg.m_mon_index != 1) { + log_msg(LOG_WARNING, "invalid monitor index: %d", + msg.m_mon_index); + return 0; + } + msg.m_mon_on = inputs[msg.m_mon_index].mon_on; + tx_reply = 1; + break; + case SET_VOL: + if (msg.m_vol_chan != 0 && + msg.m_vol_chan != 1) { + log_msg(LOG_WARNING, "invalid input channel: %d", + msg.m_vol_chan); + return 0; + } + if (msg.m_vol_level < 0.0f || + msg.m_vol_level > 1.0f) { + log_msg(LOG_WARNING, "invalid input channel volume: %f", + msg.m_vol_level); + return 0; + } + inputs[msg.m_vol_chan].level = msg.m_vol_level; + break; + case GET_VOL: + if (msg.m_vol_chan != 0 && + msg.m_vol_chan != 1) { + log_msg(LOG_WARNING, "invalid input channel: %d", + msg.m_vol_chan); + return 0; + } + msg.m_vol_level = inputs[msg.m_vol_chan].level; + tx_reply = 1; + break; + case GET_IN: + if (msg.m_input_index != 0 && + msg.m_input_index != 1) { + log_msg(LOG_WARNING, "invalid input index: %d", + msg.m_input_index); + return 0; + } + msg.m_input_on = inputs[msg.m_input_index].clifd != -1; + tx_reply = 1; + break; + case GET_OUT: + if (msg.m_output_index != 0 && + msg.m_output_index != 1) { + log_msg(LOG_WARNING, "invalid output index: %d", + msg.m_output_index); + return 0; + } + msg.m_output_on = outputs[msg.m_output_index].sio_hdl != NULL; + tx_reply = 1; + break; + default: + log_msg(LOG_WARNING, "unknown message type: %d", msg.type); + break; + } + + /* for GET_* requests, send a reply */ + if (tx_reply) { + do { + n = write(fd, &msg, sizeof(msg)); + } while (n == -1 && errno == EINTR); + if (n == -1) + return -1; + } + return 0; +} + +/* do not re-order */ +#define CTL_LISTEN 0 +#define IN0_LISTEN 1 +#define IN1_LISTEN 2 +#define CTL_CLIENT 3 +#define IN0_CLIENT 4 +#define IN1_CLIENT 5 +#define NR_SOCKETS 6 +void +loop(void) +{ + struct sockaddr_un sun; + socklen_t len; + struct pollfd pfd[NR_SOCKETS]; + struct input *in; + struct output *out; + int i, ret, clifd, nready; + + for (i = 0; i < LEN(pfd); i++) { + pfd[i].fd = -1; + pfd[i].events = 0; + } + + inputs[0].listenfd = server_listen(CH0_SOCK_PATH, 0); + inputs[1].listenfd = server_listen(CH1_SOCK_PATH, 0); + + pfd[CTL_LISTEN].fd = server_listen(CTL_SOCK_PATH, 1); + pfd[CTL_LISTEN].events = POLLIN; + pfd[IN0_LISTEN].fd = inputs[0].listenfd; + pfd[IN0_LISTEN].events = POLLIN; + pfd[IN1_LISTEN].fd = inputs[1].listenfd; + pfd[IN1_LISTEN].events = POLLIN; + + for (;;) { + nready = poll(pfd, LEN(pfd), TIMEO_MS); + if (nready == -1) { + if (errno != EINTR) + log_err("poll failed"); + continue; + } + + for (i = 0; i < LEN(pfd); i++) + if (pfd[i].revents & POLLERR) + log_err("bad fd"); + + /* handle client request */ + if (pfd[CTL_CLIENT].revents & (POLLIN | POLLHUP)) { + if (msg_handler(pfd[CTL_CLIENT].fd) == -1) { + close(pfd[CTL_CLIENT].fd); + pfd[CTL_CLIENT].fd = -1; + pfd[CTL_CLIENT].events = 0; + } + } + + /* accept control socket connection */ + if (pfd[CTL_LISTEN].revents & POLLIN) { + len = sizeof(sun); + do { + clifd = accept(pfd[CTL_LISTEN].fd, + (SA *)&sun, &len); + } while (clifd == -1 && errno == EINTR); + if (clifd == -1) + log_err("accept failed"); + /* control socket already connected, reject this one */ + if (pfd[CTL_CLIENT].fd != -1) { + close(clifd); + } else { + pfd[CTL_CLIENT].fd = clifd; + pfd[CTL_CLIENT].events = POLLIN; + } + } + + /* accept input channel connections */ + for (i = 0; i < LEN(inputs); i++) { + in = &inputs[i]; + if (pfd[IN0_LISTEN + i].revents & POLLIN) { + len = sizeof(sun); + do { + clifd = accept(in->listenfd, + (SA *)&sun, &len); + } while (clifd == -1 && errno == EINTR); + if (clifd == -1) + log_err("accept failed"); + /* input already connnected, reject this one */ + if (in->clifd != -1) { + close(clifd); + continue; + } + in->clifd = clifd; + pfd[IN0_CLIENT + i].fd = clifd; + pfd[IN0_CLIENT + i].events = POLLIN; + } + } + + /* reinit input channel buffers to avoid mixing stale data */ + for (i = 0; i < LEN(inputs); i++) { + in = &inputs[i]; + memset(in->buf, 0, sizeof(in->buf)); + memset(in->attenuated_buf, 0, + sizeof(in->attenuated_buf)); + in->nsamples = 0; + } + + /* consume pcm data on input channels */ + for (i = 0; i < LEN(inputs); i++) { + in = &inputs[i]; + if (pfd[IN0_CLIENT + i].revents & (POLLIN | POLLHUP)) { + if (consume(in) == -1) { + close(in->clifd); + in->clifd = -1; + pfd[IN0_CLIENT + i].fd = -1; + pfd[IN0_CLIENT + i].events = 0; + } else { + level(in); + } + } + } + + /* attenuate inputs based on cross-fader position */ + if (xf_pos <= 0) { + attenuate(&inputs[0], 1.0f); + attenuate(&inputs[1], 1.0f - fabsf(xf_pos)); + } else { + attenuate(&inputs[0], 1.0f - fabsf(xf_pos)); + attenuate(&inputs[1], 1.0f); + } + + /* play dat shit! */ + for (i = 0; i < LEN(outputs); i++) { + out = &outputs[i]; + /* open output device on demand */ + if (out->sio_hdl == NULL) { + out->sio_hdl = audio_open(out->name); + if (out->sio_hdl == NULL) + continue; + } + out->mix(out, &inputs[0], &inputs[1]); + ret = audio_play(out->sio_hdl, out->buf, + out->nsamples * BPS); + if (ret == -1) + audio_close(&out->sio_hdl); + } + } +} + +void +usage(void) +{ + fprintf(stderr, "usage: mixerd [-d] [master-output] [monitor-output]\n"); + exit(1); +} + +int +main(int argc, char *argv[]) +{ + ARGBEGIN { + case 'd': + debug = 1; + break; + default: + usage(); + } ARGEND + + if (argc > 0) + outputs[0].name = argv[0]; + if (argc > 1) + outputs[1].name = argv[1]; + if (!debug && daemon(0, 0) == -1) + err(1, "daemon"); + signal(SIGPIPE, SIG_IGN); + loop(); + return 0; +} diff --git a/ncmixer.c b/ncmixer.c @@ -1,608 +0,0 @@ -#include <sys/types.h> -#include <sys/socket.h> -#include <sys/stat.h> -#include <sys/un.h> - -#include <curses.h> -#include <err.h> -#include <errno.h> -#include <fcntl.h> -#include <math.h> -#include <poll.h> -#include <signal.h> -#include <sndio.h> -#include <stdarg.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> - -#define LEN(x) (sizeof (x) / sizeof *(x)) -#undef MIN -#define MIN(x, y) ((x) < (y) ? (x) : (y)) -#undef MAX -#define MAX(x, y) ((x) > (y) ? (x) : (y)) -#define ISODD(x) ((x) & 1) -#define CONTROL(c) ((c) ^ 0x40) -#define SA struct sockaddr -#define CH0_NAME "/tmp/ncmixer0" -#define CH1_NAME "/tmp/ncmixer1" -#define MASTER_DEV "rsnd/0" /* bypass sndiod by default */ -#define MONITOR_DEV "rsnd/1" -#define BITS 16 -#define BPS (BITS / 8) -#define RATE 44100 -#define CHANS 2 -#define NSAMPLES 2048 -#define BUFFER_TIME_MS 1000 - -#ifdef DEBUG -#define DEBUG_FD 8 -#define DPRINTF(x...) dprintf(DEBUG_FD, x) -#define DPRINTF_D(x) dprintf(DEBUG_FD, #x "=%d\n", x) -#define DPRINTF_F(x) dprintf(DEBUG_FD, #x "=%0.2f\n", x) -#else -#define DPRINTF -#define DPRINTF_D(x) -#define DPRINTF_F(x) -#endif /* DEBUG */ - -/* mixer state */ -float xfpos = 0.; /* values in [-1.0, 1.0] */ -float step = 0.0625f; -float level_step = 0.125f; - -/* forward decls */ -struct input; -struct output; - -int mix_master(struct output *, struct input *, struct input *); -int mix_monitor(struct output *, struct input *, struct input *); - -struct input { - int listenfd; - int clifd; - short buf[NSAMPLES]; - short attenuated_buf[NSAMPLES]; - int nsamples; - int monitor; - float level; -} inputs[] = { - { .listenfd = -1, .clifd = -1, .level = 1.0f }, - { .listenfd = -1, .clifd = -1, .level = 1.0f } -}; - -struct output { - char *name; - struct sio_hdl *sio_hdl; - short buf[NSAMPLES]; - int nsamples; - int (*mix)(struct output *, struct input *, struct input *); -} outputs[] = { - { .name = MASTER_DEV, .mix = mix_master }, - { .name = MONITOR_DEV, .mix = mix_monitor } -}; - -void -printerr(int ret, char *fmt, ...) -{ - va_list ap; - - endwin(); - va_start(ap, fmt); - verr(ret, fmt, ap); - va_end(ap); - exit(ret); -} - -struct sio_hdl * -audio_open(char *devname) -{ - struct sio_hdl *sio_hdl; - struct sio_par par; - - sio_hdl = sio_open(devname, SIO_PLAY, 0); - if (sio_hdl == NULL) { - DPRINTF("%s: failed to open audio device\n", devname); - return NULL; - } - - sio_initpar(&par); - par.bits = BITS; - par.rate = RATE; - par.pchan = CHANS; - par.sig = 1; - par.le = SIO_LE_NATIVE; - par.appbufsz = RATE * BUFFER_TIME_MS / 1000; - - if (sio_setpar(sio_hdl, &par) == 0) { - DPRINTF("%s: failed to set params\n", devname); - goto err0; - } - - if (sio_getpar(sio_hdl, &par) == 0) { - DPRINTF("%s: failed to get params\n", devname); - goto err0; - } - - if (par.pchan != CHANS) { - DPRINTF("%s: failed to set number of channels\n", devname); - goto err0; - } - - if (par.sig != 1 || - par.bits != BITS || - par.le != SIO_LE_NATIVE) { - DPRINTF("%s: failed to set format\n", devname); - goto err0; - } - - /* allow 0.5% tolerance for actual sample rate */ - if (par.rate < RATE * 995 / 1000 || - par.rate > RATE * 1005 / 1000) { - DPRINTF("%s: failed to set rate\n", devname); - goto err0; - } - - if (sio_start(sio_hdl) == 0) { - DPRINTF("%s: failed to start audio device\n", devname); - goto err0; - } - - return sio_hdl; -err0: - sio_close(sio_hdl); - return NULL; -} - -int -audio_play(struct sio_hdl *sio_hdl, void *buf, int len) -{ - int n; - - n = sio_write(sio_hdl, buf, len); - if (n == 0 && sio_eof(sio_hdl) != 0) - return -1; - return n; -} - -void -audio_close(struct sio_hdl **sio_hdl) -{ - if (*sio_hdl != NULL) - sio_close(*sio_hdl); - *sio_hdl = NULL; -} - -int -key_cb(int fd) -{ - int c; - - c = getch(); - DPRINTF_D(c); - switch (c) { - case 'h': - xfpos = MAX(xfpos - step, -1.0f); - break; - case 'l': - xfpos = MIN(xfpos + step, 1.0f); - break; - case 'j': - step = MAX(step / 2, 0.015625f); - break; - case 'k': - step = MIN(step * 2, 0.25f); - break; - case CONTROL('H'): - if (xfpos > 0.) - xfpos = 0.; - else - xfpos = -1.; - break; - case CONTROL('L'): - if (xfpos < 0.) - xfpos = 0.; - else - xfpos = 1.; - break; - case '1': - inputs[0].monitor = !inputs[0].monitor; - break; - case '2': - inputs[1].monitor = !inputs[1].monitor; - break; - case 'q': - inputs[0].level = MAX(inputs[0].level - level_step, 0.0f); - break; - case 'w': - inputs[0].level = MIN(inputs[0].level + level_step, 1.0f); - break; - case 'o': - inputs[1].level = MAX(inputs[1].level - level_step, 0.0f); - break; - case 'p': - inputs[1].level = MIN(inputs[1].level + level_step, 1.0f); - break; - } - return 0; -} - -int -consume(struct input *in) -{ - int n; - - do { - n = read(in->clifd, in->buf, sizeof(in->buf)); - } while (n == -1 && errno == EINTR); - if (n == 0 || n == -1) - return -1; - in->nsamples = n / BPS; - return in->nsamples; -} - -void -level(struct input *in) -{ - short *ch0 = (short *)in->buf; - int i; - - for (i = 0; i < in->nsamples; i++) { - *ch0 *= in->level; - ch0++; - } -} - -void -attenuate(struct input *in, float factor) -{ - short *ch0 = (short *)in->buf; - short *out = (short *)in->attenuated_buf; - int i; - - for (i = 0; i < in->nsamples; i++) { - *out = *ch0 * factor; - ch0++, out++; - } -} - -int -mix_master(struct output *out, struct input *in0, struct input *in1) -{ - short *ch0 = (short *)in0->attenuated_buf; - short *ch1 = (short *)in1->attenuated_buf; - short *buf = (short *)out->buf; - int i; - - out->nsamples = MAX(in0->nsamples, in1->nsamples); - for (i = 0; i < out->nsamples; i++) { - *buf = *ch0 * 0.5 + *ch1 * 0.5; - ch0++, ch1++, buf++; - } - return out->nsamples; -} - -int -mix_monitor(struct output *out, struct input *in0, struct input *in1) -{ - short *ch0 = (short *)in0->buf; - short *ch1 = (short *)in1->buf; - short *buf = (short *)out->buf; - int i; - - out->nsamples = MAX(in0->nsamples, in1->nsamples); - for (i = 0; i < out->nsamples; i++) { - *ch0 *= in0->monitor; - *ch1 *= in1->monitor; - *buf = *ch0 * 0.5 + *ch1 * 0.5; - ch0++, ch1++, buf++; - } - return out->nsamples; -} - -int -server_listen(char *name) -{ - struct sockaddr_un sun; - socklen_t len; - int fd, oldumask; - - fd = socket(AF_UNIX, SOCK_STREAM, 0); - if (fd == -1) - printerr(1, "socket"); - unlink(name); - memset(&sun, 0, sizeof(sun)); - sun.sun_family = AF_UNIX; - snprintf(sun.sun_path, sizeof(sun.sun_path), "%s", name); - len = sizeof(sun); - oldumask = umask(0111); - if (bind(fd, (SA *)&sun, len) == -1) - printerr(1, "bind"); - umask(oldumask); - if (listen(fd, 5) == -1) - printerr(1, "listen"); - return fd; -} - -void -draw_status(void) -{ - int offs; - - offs = ISODD(COLS) ? 2 : 3; - if (COLS < 2 + strlen("[CH0: ] [MON: ] [CH1: ] [MON: ]") + offs) { - move(getcury(stdscr), 0); - printw("\n"); - return; - } - - move(getcury(stdscr), 2); - printw("[CH0:%c] [MON:%c]", - inputs[0].clifd != -1 ? '*' : ' ', - inputs[0].monitor != 0 ? '*' : ' '); - move(getcury(stdscr), COLS - strlen("[MON: ] [CH1: ]") - offs); - printw("[CH1:%c] [MON:%c]", - inputs[1].clifd != -1 ? '*' : ' ', - inputs[1].monitor != 0 ? '*' : ' '); - printw("\n"); -} - -void -draw_levels(void) -{ - int start, end, len, nsteps, i; - - start = 2; - end = ISODD(COLS) ? COLS - 2 : COLS - 3; - len = 1.0f / level_step; - - move(getcury(stdscr), start); - nsteps = inputs[0].level / level_step; - for (i = 0; i < nsteps; i++) - printw("|"); - nsteps = inputs[1].level / level_step; - move(getcury(stdscr), end - len); - for (i = 0; i < nsteps; i++) - printw("|"); - printw("\n"); -} - -void -draw_xfader(void) -{ - int start, end, len, center, pos, i; - - if (COLS < strlen(" -|x|- ")) - return; - - start = 2; - end = ISODD(COLS) ? COLS - 2 : COLS - 3; - len = end - start; - center = start + (len / 2); - if (xfpos <= 0.0f) - pos = center - ceil((len / 2) * fabsf(xfpos)); - else - pos = center + ceil((len / 2) * fabsf(xfpos)); - - move(getcury(stdscr), center - strlen("[step: 0.000000") / 2); - printw("step: %0.6f\n", step); - move(getcury(stdscr), center - strlen("[pos: +0.000000") / 2); - printw("pos: %+0.6f\n", xfpos); - printw("\n"); - - move(getcury(stdscr), start); - for (i = 0; i < len; i++) - printw("-"); - move(getcury(stdscr), center); - printw("+"); - move(getcury(stdscr), pos - 1); - if (pos == center) - printw("|x|"); - else - printw("|||"); - move(getcury(stdscr), end + 1); - printw("\n"); -} - -void -draw_outputs(void) -{ - int start, end, len, center; - - start = 2; - end = ISODD(COLS) ? COLS - 2 : COLS - 3; - len = end - start; - center = start + (len / 2); - - move(getcury(stdscr), center - strlen(" master: off") / 2); - printw(" master: %s\n", outputs[0].sio_hdl ? "on" : "off"); - move(getcury(stdscr), center - strlen("monitor: off") / 2); - printw("monitor: %s\n", outputs[1].sio_hdl ? "on" : "off"); -} - -void -draw(void) -{ - erase(); - draw_status(); - draw_xfader(); - draw_levels(); - draw_outputs(); - refresh(); -} - -void -loop(void) -{ - struct sockaddr_un sun; - socklen_t len; - struct pollfd pfd[16]; - struct input *in; - struct output *out; - int i, ret, clifd, nready; - - for (i = 0; i < LEN(pfd); i++) { - pfd[i].fd = -1; - pfd[i].events = 0; - } - - inputs[0].listenfd = server_listen(CH0_NAME); - inputs[1].listenfd = server_listen(CH1_NAME); - - pfd[0].fd = 0; - pfd[0].events = POLLIN; - pfd[inputs[0].listenfd].fd = inputs[0].listenfd; - pfd[inputs[0].listenfd].events = POLLIN; - pfd[inputs[1].listenfd].fd = inputs[1].listenfd; - pfd[inputs[1].listenfd].events = POLLIN; - - for (;;) { - draw(); - - nready = poll(pfd, LEN(pfd), 40); - if (nready == -1) { - if (errno != EINTR) - printerr(1, "poll"); - /* force refresh to update COLS on window resize */ - refresh(); - continue; - } - - for (i = 0; i < LEN(pfd); i++) - if (pfd[i].revents & POLLERR) - printerr(1, "bad fd"); - - /* handle key presses */ - if (pfd[0].revents & POLLIN) - key_cb(pfd[0].fd); - - /* accept new connections on sockets */ - for (i = 0; i < LEN(inputs); i++) { - in = &inputs[i]; - if (pfd[in->listenfd].revents & POLLIN) { - len = sizeof(sun); - clifd = accept(in->listenfd, (SA *)&sun, &len); - if (clifd == -1) { - if (errno != EINTR) - printerr(1, "accept"); - continue; - } - /* input already connnected, reject this one */ - if (in->clifd != -1) { - close(clifd); - continue; - } - in->clifd = clifd; - pfd[in->clifd].fd = clifd; - pfd[in->clifd].events = POLLIN; - } - } - - /* read pcm data from sockets */ - for (i = 0; i < LEN(inputs); i++) { - in = &inputs[i]; - memset(in->buf, 0, sizeof(in->buf)); - memset(in->attenuated_buf, 0, - sizeof(in->attenuated_buf)); - in->nsamples = 0; - if (in->clifd == -1) - continue; - if (pfd[in->clifd].revents & (POLLIN | POLLHUP)) { - if (consume(in) == -1) { - /* we lost the input, reset state */ - pfd[in->clifd].fd = -1; - pfd[in->clifd].events = 0; - close(in->clifd); - in->clifd = -1; - } else { - level(in); - } - } - } - - /* attenuate inputs based on cross-fader position */ - if (xfpos <= 0) { - attenuate(&inputs[0], 1.0f); - attenuate(&inputs[1], 1.0f - fabsf(xfpos)); - } else { - attenuate(&inputs[0], 1.0f - fabsf(xfpos)); - attenuate(&inputs[1], 1.0f); - } - - /* play dat shit! */ - for (i = 0; i < LEN(outputs); i++) { - out = &outputs[i]; - /* open output device on demand */ - if (out->sio_hdl == NULL) { - out->sio_hdl = audio_open(out->name); - if (out->sio_hdl == NULL) - continue; - } - out->mix(out, &inputs[0], &inputs[1]); - ret = audio_play(out->sio_hdl, out->buf, - out->nsamples * BPS); - if (ret == -1) - audio_close(&out->sio_hdl); - } - } -} - -void -curses_init(void) -{ - char *term; - - if (initscr() == NULL) { - term = getenv("TERM"); - if (term != NULL) - errx(1, "error opening terminal: %s", term); - else - errx(1, "failed to initialize curses"); - } - cbreak(); - noecho(); - nonl(); - intrflush(stdscr, FALSE); - curs_set(FALSE); -} - -void -curses_exit(void) -{ - endwin(); -} - -void -nuke_fds(void) -{ - int i; - - for (i = 3; i < getdtablesize(); i++) { -#ifdef DEBUG - if (i == DEBUG_FD) - continue; -#endif - close(i); - } -} - -int -main(int argc, char *argv[]) -{ - if (argc > 1) - outputs[0].name = argv[1]; - if (argc > 2) - outputs[1].name = argv[2]; - if (isatty(0) == 0 || isatty(1) == 0) - errx(1, "stdin or stdout is not a tty"); -#ifndef DEBUG - signal(SIGINT, SIG_IGN); - signal(SIGQUIT, SIG_IGN); -#endif - nuke_fds(); - curses_init(); - loop(); - curses_exit(); - return 0; -} diff --git a/ncmixerc.c b/ncmixerc.c @@ -0,0 +1,435 @@ +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/un.h> + +#include <curses.h> +#include <err.h> +#include <errno.h> +#include <math.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "proto.h" + +#define LEN(x) (sizeof (x) / sizeof *(x)) +#undef MIN +#define MIN(x, y) ((x) < (y) ? (x) : (y)) +#undef MAX +#define SA struct sockaddr +#define MAX(x, y) ((x) > (y) ? (x) : (y)) +#define ISODD(x) ((x) & 1) +#define CONTROL(c) ((c) ^ 0x40) +#define CTL_SOCK_PATH "/tmp/mixerctl" +#define TIMEO_MS 40 + +#ifdef DEBUG +#define DEBUG_FD 8 +#define DPRINTF(x...) dprintf(DEBUG_FD, x) +#define DPRINTF_D(x) dprintf(DEBUG_FD, #x "=%d\n", x) +#define DPRINTF_F(x) dprintf(DEBUG_FD, #x "=%0.2f\n", x) +#else +#define DPRINTF +#define DPRINTF_D(x) +#define DPRINTF_F(x) +#endif /* DEBUG */ + +float xf_pos = 0.0f; +float step = 0.0625f; +float level_step = 0.125f; +float ch0_level = 1.0f; +float ch1_level = 1.0f; +int mon0_on; +int mon1_on; +int in0_on; +int in1_on; +int out0_on; +int out1_on; +int sock; + +void +curses_init(void) +{ + char *term; + + if (initscr() == NULL) { + term = getenv("TERM"); + if (term != NULL) + errx(1, "error opening terminal: %s", term); + else + errx(1, "failed to initialize curses"); + } + cbreak(); + noecho(); + nonl(); + intrflush(stdscr, FALSE); + curs_set(FALSE); + timeout(TIMEO_MS); +} + +void +curses_exit(void) +{ + endwin(); +} + +void +tx_msg(struct msg *msg) +{ + int n; + + do { + n = write(sock, msg, sizeof(*msg)); + } while (n == -1 && errno == EINTR); + if (n == -1) { + curses_exit(); + errx(1, "lost connection to daemon"); + } +} + +void +rx_msg(struct msg *msg) +{ + int n; + + do { + n = read(sock, msg, sizeof(*msg)); + } while (n == -1 && errno == EINTR); + if (n == 0 || n == -1) { + curses_exit(); + errx(1, "lost connection to daemon"); + } +} + +void +get_xf_pos(float *xf_pos) +{ + struct msg msg; + + msg.type = GET_XF; + tx_msg(&msg); + rx_msg(&msg); + *xf_pos = msg.m_xf_pos; +} + +void +set_xf_pos(float xf_pos) +{ + struct msg msg; + + msg.type = SET_XF; + msg.m_xf_pos = xf_pos; + tx_msg(&msg); +} + +void +get_mon_on(int index, int *on) +{ + struct msg msg; + + msg.type = GET_MON; + msg.m_mon_index = index; + tx_msg(&msg); + rx_msg(&msg); + *on = msg.m_mon_on; +} + +void +set_mon_on(int index, int on) +{ + struct msg msg; + + msg.type = SET_MON; + msg.m_mon_index = index; + msg.m_mon_on = on; + tx_msg(&msg); +} + +void +get_vol_level(int chan, float *level) +{ + struct msg msg; + + msg.type = GET_VOL; + msg.m_vol_chan = chan; + tx_msg(&msg); + rx_msg(&msg); + *level = msg.m_vol_level; +} + +void +set_vol_level(int chan, float level) +{ + struct msg msg; + + msg.type = SET_VOL; + msg.m_vol_chan = chan; + msg.m_vol_level = level; + tx_msg(&msg); +} + +void +get_input_on(int index, int *on) +{ + struct msg msg; + + msg.type = GET_IN; + msg.m_input_index = index; + tx_msg(&msg); + rx_msg(&msg); + *on = msg.m_input_on; +} + +void +get_output_on(int index, int *on) +{ + struct msg msg; + + msg.type = GET_OUT; + msg.m_output_index = index; + tx_msg(&msg); + rx_msg(&msg); + *on = msg.m_output_on; +} + +void +kb_handler(void) +{ + int c; + + c = getch(); + DPRINTF_D(c); + switch (c) { + case 'h': + get_xf_pos(&xf_pos); + xf_pos = MAX(xf_pos - step, -1.0f); + set_xf_pos(xf_pos); + break; + case 'l': + get_xf_pos(&xf_pos); + xf_pos = MIN(xf_pos + step, 1.0f); + set_xf_pos(xf_pos); + break; + case 'j': + get_xf_pos(&xf_pos); + step = MAX(step / 2, 0.015625f); + set_xf_pos(xf_pos); + break; + case 'k': + get_xf_pos(&xf_pos); + step = MIN(step * 2, 0.25f); + set_xf_pos(xf_pos); + break; + case CONTROL('H'): + get_xf_pos(&xf_pos); + if (xf_pos > 0.) + xf_pos = 0.f; + else + xf_pos = -1.f; + set_xf_pos(xf_pos); + break; + case CONTROL('L'): + get_xf_pos(&xf_pos); + if (xf_pos < 0.) + xf_pos = 0.f; + else + xf_pos = 1.f; + set_xf_pos(xf_pos); + break; + case '1': + get_mon_on(0, &mon0_on); + mon0_on = !mon0_on; + set_mon_on(0, mon0_on); + break; + case '2': + get_mon_on(1, &mon1_on); + mon1_on = !mon1_on; + set_mon_on(1, mon1_on); + break; + case 'q': + get_vol_level(0, &ch0_level); + ch0_level = MAX(ch0_level - level_step, 0.0f); + set_vol_level(0, ch0_level); + break; + case 'w': + get_vol_level(0, &ch0_level); + ch0_level = MIN(ch0_level + level_step, 1.0f); + set_vol_level(0, ch0_level); + break; + case 'o': + get_vol_level(1, &ch1_level); + ch1_level = MAX(ch1_level - level_step, 0.0f); + set_vol_level(1, ch1_level); + break; + case 'p': + get_vol_level(1, &ch1_level); + ch1_level = MIN(ch1_level + level_step, 1.0f); + set_vol_level(1, ch1_level); + break; + case 'x': + curses_exit(); + exit(0); + } +} + +void +draw_status(void) +{ + int offs; + + offs = ISODD(COLS) ? 2 : 3; + if (COLS < 2 + strlen("[CH0: ] [MON: ] [CH1: ] [MON: ]") + offs) { + move(getcury(stdscr), 0); + printw("\n"); + return; + } + + get_input_on(0, &in0_on); + get_input_on(1, &in1_on); + get_mon_on(0, &mon0_on); + get_mon_on(1, &mon1_on); + + move(getcury(stdscr), 2); + printw("[CH0:%c] [MON:%c]", + in0_on ? '*' : ' ', + mon0_on ? '*' : ' '); + move(getcury(stdscr), COLS - strlen("[MON: ] [CH1: ]") - offs); + printw("[CH1:%c] [MON:%c]", + in1_on ? '*' : ' ', + mon1_on ? '*' : ' '); + printw("\n"); +} + +void +draw_levels(void) +{ + int start, end, len, nsteps, i; + + start = 2; + end = ISODD(COLS) ? COLS - 2 : COLS - 3; + len = 1.0f / level_step; + + get_vol_level(0, &ch0_level); + get_vol_level(1, &ch1_level); + + move(getcury(stdscr), start); + nsteps = ch0_level / level_step; + for (i = 0; i < nsteps; i++) + printw("|"); + nsteps = ch1_level / level_step; + move(getcury(stdscr), end - len); + for (i = 0; i < nsteps; i++) + printw("|"); + printw("\n"); +} + +void +draw_xfader(void) +{ + int start, end, len, center, pos, i; + + if (COLS < strlen(" -|x|- ")) + return; + + get_xf_pos(&xf_pos); + + start = 2; + end = ISODD(COLS) ? COLS - 2 : COLS - 3; + len = end - start; + center = start + (len / 2); + if (xf_pos <= 0.0f) + pos = center - ceil((len / 2) * fabsf(xf_pos)); + else + pos = center + ceil((len / 2) * fabsf(xf_pos)); + + move(getcury(stdscr), center - strlen("[step: 0.000000") / 2); + printw("step: %0.6f\n", step); + move(getcury(stdscr), center - strlen("[pos: +0.000000") / 2); + printw("pos: %+0.6f\n", xf_pos); + printw("\n"); + + move(getcury(stdscr), start); + for (i = 0; i < len; i++) + printw("-"); + move(getcury(stdscr), center); + printw("+"); + move(getcury(stdscr), pos - 1); + if (pos == center) + printw("|x|"); + else + printw("|||"); + move(getcury(stdscr), end + 1); + printw("\n"); +} + +void +draw_outputs(void) +{ + int start, end, len, center; + + start = 2; + end = ISODD(COLS) ? COLS - 2 : COLS - 3; + len = end - start; + center = start + (len / 2); + + get_output_on(0, &out0_on); + get_output_on(1, &out1_on); + + move(getcury(stdscr), center - strlen(" master: off") / 2); + printw(" master: %s\n", out0_on ? "on" : "off"); + move(getcury(stdscr), center - strlen("monitor: off") / 2); + printw("monitor: %s\n", out1_on ? "on" : "off"); +} + +void +draw(void) +{ + erase(); + draw_status(); + draw_xfader(); + draw_levels(); + draw_outputs(); + refresh(); +} + +void +loop(void) +{ + for (;;) { + draw(); + kb_handler(); + } +} + +int +client_connect(char *name) +{ + struct sockaddr_un sun; + socklen_t len; + int fd; + + fd = socket(AF_UNIX, SOCK_SEQPACKET, 0); + if (fd == -1) + err(1, "socket"); + sun.sun_family = AF_UNIX; + strlcpy(sun.sun_path, name, sizeof(sun.sun_path)); + len = sizeof(sun); + if (connect(fd, (SA *)&sun, len) == -1) + errx(1, "cannot connect to daemon"); + return fd; +} + +int +main(void) +{ + if (isatty(0) == 0 || isatty(1) == 0) + errx(1, "stdin or stdout is not a tty"); + signal(SIGPIPE, SIG_IGN); + sock = client_connect(CTL_SOCK_PATH); + curses_init(); + loop(); + curses_exit(); + return 0; +} diff --git a/patches/0001-output-plugins-Add-UNIX-domain-socket-output.patch b/patches/0001-output-plugins-Add-UNIX-domain-socket-output.patch @@ -0,0 +1,326 @@ +From 73246507075f772c40e717a741459d31b5a3a784 Mon Sep 17 00:00:00 2001 +From: Dimitris Papastamos <sin@2f30.org> +Date: Sun, 12 Jun 2016 15:06:55 +0100 +Subject: [PATCH] output/plugins: Add UNIX domain socket output + +Signed-off-by: Dimitris Papastamos <sin@2f30.org> +Tested-by: Lazaros Koromilas <lostd@2f30.org> +--- + Makefile.am | 6 + + configure.ac | 16 +++ + src/output/Registry.cxx | 4 + + src/output/plugins/UnixOutputPlugin.cxx | 188 ++++++++++++++++++++++++++++++++ + src/output/plugins/UnixOutputPlugin.hxx | 26 +++++ + 5 files changed, 240 insertions(+) + create mode 100644 src/output/plugins/UnixOutputPlugin.cxx + create mode 100644 src/output/plugins/UnixOutputPlugin.hxx + +diff --git a/Makefile.am b/Makefile.am +index 2cf8e05..bd94ae1 100644 +--- a/Makefile.am ++++ b/Makefile.am +@@ -1409,6 +1409,12 @@ liboutput_plugins_a_SOURCES += \ + src/output/plugins/SndioOutputPlugin.hxx + endif + ++if HAVE_UNIX ++liboutput_plugins_a_SOURCES += \ ++ src/output/plugins/UnixOutputPlugin.cxx \ ++ src/output/plugins/UnixOutputPlugin.hxx ++endif ++ + if HAVE_HAIKU + liboutput_plugins_a_SOURCES += \ + src/output/plugins/HaikuOutputPlugin.cxx \ +diff --git a/configure.ac b/configure.ac +index 51ab2e0..51f99a2 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -339,6 +339,11 @@ AC_ARG_ENABLE(sndio, + [enable support for sndio output plugin (default: auto)]),, + enable_sndio=auto) + ++AC_ARG_ENABLE(unix, ++ AS_HELP_STRING([--disable-unix], ++ [disable support for UNIX domain socket output (default: enable)]),, ++ enable_unix=yes) ++ + AC_ARG_ENABLE(haiku, + AS_HELP_STRING([--enable-haiku], + [enable the Haiku output plugin (default: auto)]),, +@@ -1128,6 +1133,16 @@ fi + + AM_CONDITIONAL(HAVE_SNDIO, test x$enable_sndio = xyes) + ++dnl ----------------------------------- UNIX ---------------------------------- ++if test x$enable_unix = xyes; then ++ AC_CHECK_HEADER(sys/un.h, ++ [enable_unix=yes], ++ [enable_unix=no;AC_MSG_WARN([sys/un.h not found -- disabling support for UNIX domain socket output])]) ++fi ++ ++MPD_DEFINE_CONDITIONAL(enable_unix, HAVE_UNIX, ++ [support for UNIX domain socket output]) ++ + dnl ----------------------------------- Haiku --------------------------------- + if test x$enable_haiku = xauto; then + AC_CHECK_HEADER(media/MediaDefs.h, +@@ -1449,6 +1464,7 @@ printf '\nPlayback support:\n\t' + results(alsa,ALSA) + results(fifo,FIFO) + results(sndio,[SNDIO]) ++results(unix,[UNIX domain socket]) + results(recorder_output,[File Recorder]) + results(haiku,[Haiku]) + results(httpd_output,[HTTP Daemon]) +diff --git a/src/output/Registry.cxx b/src/output/Registry.cxx +index 9d8d826..3961710 100644 +--- a/src/output/Registry.cxx ++++ b/src/output/Registry.cxx +@@ -24,6 +24,7 @@ + #include "plugins/AoOutputPlugin.hxx" + #include "plugins/FifoOutputPlugin.hxx" + #include "plugins/SndioOutputPlugin.hxx" ++#include "plugins/UnixOutputPlugin.hxx" + #include "plugins/httpd/HttpdOutputPlugin.hxx" + #include "plugins/HaikuOutputPlugin.hxx" + #include "plugins/JackOutputPlugin.hxx" +@@ -56,6 +57,9 @@ const AudioOutputPlugin *const audio_output_plugins[] = { + #ifdef HAVE_SNDIO + &sndio_output_plugin, + #endif ++#ifdef HAVE_UNIX ++ &unix_output_plugin, ++#endif + #ifdef HAVE_HAIKU + &haiku_output_plugin, + #endif +diff --git a/src/output/plugins/UnixOutputPlugin.cxx b/src/output/plugins/UnixOutputPlugin.cxx +new file mode 100644 +index 0000000..2b85d64 +--- /dev/null ++++ b/src/output/plugins/UnixOutputPlugin.cxx +@@ -0,0 +1,188 @@ ++/* ++ * Copyright (C) 2016 The Music Player Daemon Project ++ * http://www.musicpd.org ++ * ++ * 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 2 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, write to the Free Software Foundation, Inc., ++ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ++ */ ++ ++#include <sys/socket.h> ++#include <sys/stat.h> ++#include <sys/un.h> ++ ++#include <errno.h> ++#include <string.h> ++#include <unistd.h> ++ ++#include "config.h" ++#include "UnixOutputPlugin.hxx" ++#include "config/ConfigError.hxx" ++#include "../OutputAPI.hxx" ++#include "../Wrapper.hxx" ++#include "fs/AllocatedPath.hxx" ++#include "util/Error.hxx" ++#include "util/Domain.hxx" ++#include "Log.hxx" ++ ++class UnixOutput { ++ friend struct AudioOutputWrapper<UnixOutput>; ++ AudioOutput base; ++ AllocatedPath path; ++ std::string path_utf8; ++ int sock; ++ ++ bool Connect(Error &error); ++ void Disconnect(); ++public: ++ UnixOutput() ++ :base(unix_output_plugin), ++ path(AllocatedPath::Null()), sock(-1) {} ++ ~UnixOutput() { ++ Disconnect(); ++ } ++ ++ bool Initialize(const ConfigBlock &block, Error &error) { ++ return base.Configure(block, error); ++ } ++ ++ static UnixOutput *Create(const ConfigBlock &block, Error &error); ++ ++ bool Open(AudioFormat &audio_format, Error &error); ++ void Close(); ++ size_t Play(const void *chunk, size_t size, Error &error); ++}; ++ ++static constexpr Domain unix_output_domain("unix_output"); ++ ++bool ++UnixOutput::Connect(Error &error) ++{ ++ struct sockaddr_un sun; ++ socklen_t len; ++ int ret; ++ ++ sock = socket(AF_UNIX, SOCK_STREAM, 0); ++ if (sock == -1) { ++ error.FormatErrno("Could not create UNIX socket"); ++ return false; ++ } ++ ++ memset(&sun, 0, sizeof(sun)); ++ sun.sun_family = AF_UNIX; ++ snprintf(sun.sun_path, sizeof(sun.sun_path), "%s", path_utf8.c_str()); ++ len = sizeof(sun); ++ ret = connect(sock, (struct sockaddr *)&sun, len); ++ if (ret == -1) { ++ error.FormatErrno("Could not connect to UNIX socket \"%s\"", ++ path_utf8.c_str()); ++ Disconnect(); ++ return false; ++ } ++ ++ return true; ++} ++ ++void ++UnixOutput::Disconnect() ++{ ++ if (sock >= 0) { ++ close(sock); ++ sock = -1; ++ } ++} ++ ++UnixOutput * ++UnixOutput::Create(const ConfigBlock &block, Error &error) ++{ ++ UnixOutput *ao = new UnixOutput(); ++ ++ ao->path = block.GetBlockPath("path", error); ++ if (ao->path.IsNull()) { ++ delete ao; ++ ++ if (!error.IsDefined()) ++ error.Set(config_domain, ++ "No \"path\" parameter specified"); ++ return nullptr; ++ } ++ ++ ao->path_utf8 = ao->path.ToUTF8(); ++ ++ if (!ao->Initialize(block, error)) { ++ delete ao; ++ return nullptr; ++ } ++ ++ return ao; ++} ++ ++bool ++UnixOutput::Open(gcc_unused AudioFormat &audio_format, ++ gcc_unused Error &error) ++{ ++ if (sock == -1 && !Connect(error)) ++ return false; ++ audio_format.sample_rate = 44100; ++ audio_format.format = SampleFormat::S16; ++ audio_format.channels = 2; ++ return true; ++} ++ ++void ++UnixOutput::Close() ++{ ++ Disconnect(); ++} ++ ++size_t ++UnixOutput::Play(const void *chunk, size_t size, Error &error) ++{ ++ ssize_t bytes; ++ ++ while (true) { ++ bytes = write(sock, chunk, size); ++ if (bytes > 0) ++ return (size_t)bytes; ++ if (bytes < 0) { ++ switch (errno) { ++ case EAGAIN: ++ case EINTR: ++ continue; ++ } ++ error.FormatErrno("Failed to write to UNIX socket%s", ++ path_utf8.c_str()); ++ return 0; ++ } ++ } ++} ++ ++typedef AudioOutputWrapper<UnixOutput> Wrapper; ++ ++const struct AudioOutputPlugin unix_output_plugin = { ++ "unix", ++ nullptr, ++ &Wrapper::Init, ++ &Wrapper::Finish, ++ nullptr, ++ nullptr, ++ &Wrapper::Open, ++ &Wrapper::Close, ++ nullptr, ++ nullptr, ++ &Wrapper::Play, ++ nullptr, ++ nullptr, ++ nullptr, ++ nullptr, ++}; +diff --git a/src/output/plugins/UnixOutputPlugin.hxx b/src/output/plugins/UnixOutputPlugin.hxx +new file mode 100644 +index 0000000..3fda10b +--- /dev/null ++++ b/src/output/plugins/UnixOutputPlugin.hxx +@@ -0,0 +1,26 @@ ++/* ++ * Copyright (C) 2016 The Music Player Daemon Project ++ * http://www.musicpd.org ++ * ++ * 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 2 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, write to the Free Software Foundation, Inc., ++ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ++ */ ++ ++#ifndef MPD_UNIX_OUTPUT_PLUGIN_HXX ++#define MPD_UNIX_OUTPUT_PLUGIN_HXX ++ ++extern const struct AudioOutputPlugin unix_output_plugin; ++ ++#endif ++ +-- +2.9.0 + diff --git a/proto.h b/proto.h @@ -0,0 +1,44 @@ +enum { + SET_XF, + GET_XF, + SET_MON, + GET_MON, + SET_VOL, + GET_VOL, + GET_IN, + GET_OUT +}; + +struct msg { + int type; + union { + struct { + float pos; /* in/out */ + } xf; + struct { + int index; /* in */ + int on; /* in/out */ + } mon; + struct { + int chan; /* in */ + float level; /* in/out */ + } vol; + struct { + int index; /* in */ + int on; /* out */ + } input; + struct { + int index; /* in */ + int on; /* out */ + } output; + } u; +}; +#define m_xf_pos u.xf.pos +#define m_mon_index u.mon.index +#define m_mon_on u.mon.on +#define m_vol_chan u.vol.chan +#define m_vol_level u.vol.level +#define m_input_index u.input.index +#define m_input_on u.input.on +#define m_output_index u.output.index +#define m_output_on u.output.on