sad

simple audio daemon
git clone git://git.2f30.org/sad.git
Log | Files | Refs | LICENSE

commit a60e06ce7795add484ae86635c29152554c1cf6f
Author: sin <sin@2f30.org>
Date:   Wed Dec 24 14:33:20 +0000

Initial commit

Diffstat:
LICENSE | 13+++++++++++++
Makefile | 33+++++++++++++++++++++++++++++++++
PROTOCOL | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ao.c | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
cmd.c | 221+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
mp3.c | 80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
playlist.c | 82+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
sad.c | 139+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
sad.h | 70++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
tokenizer.c | 106+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
10 files changed, 872 insertions(+), 0 deletions(-)
diff --git a/LICENSE b/LICENSE @@ -0,0 +1,13 @@ +© 2014 Dimitris Papastamos <sin@2f30.org> + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/Makefile b/Makefile @@ -0,0 +1,33 @@ +VERSION = 0.0 + +PREFIX = /usr/local +MANPREFIX = $(PREFIX)/man + +CFLAGS = -I/usr/local/include +LDFLAGS = -L /usr/local/lib +LDLIBS = -lmpg123 -lao + +OBJ = ao.o cmd.o mp3.o playlist.o sad.o tokenizer.o +BIN = sad + +all: $(BIN) + +$(BIN): $(OBJ) + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(OBJ) $(LDLIBS) + +ao.o: sad.h +cmd.o: sad.h +mp3.o: sad.h +playlist.o: sad.h +sad.o: sad.h +tokenizer.o: sad.h + +install: all + mkdir -p $(DESTDIR)$(PREFIX)/bin + cp -f $(BIN) $(DESTDIR)$(PREFIX)/bin + +uninstall: + rm -f $(DESTDIR)$(PREFIX)/bin/$(BIN) + +clean: + rm -f $(BIN) $(OBJ) diff --git a/PROTOCOL b/PROTOCOL @@ -0,0 +1,66 @@ +init +---- + +foo VERSION + +where VERSION is of the form maj.min + +requests +-------- +cmd [arg...] + +If arguments contain spaces, they should be surrounded by double quotation marks. +Argument strings are separated from the command and any other arguments by linear +white-space (' ' or '\t'). + +responses +--------- +output + OK on success, ERR "descriptive error message" on error. + +commands +-------- + +status + -> songid: playlist songid of the current song stopped on or playing + -> state: play, stop or pause + -> volume: 0-100 + -> elapsed: total time elapsed within current song + +volume 0-100 + -> OK or ERR + +next + -> OK or ERR + +pause 0|1 + -> OK or ERR + +play songid + -> OK or ERR + +previous + -> OK or ERR + +stop + -> OK or ERR + +add path + -> OK or ERR + +clear + -> OK or ERR + +delete songid + -> OK or ERR + +playlist + -> OK or ERR + +close + -> OK or ERR + +kill + -> OK or ERR + +ping + -> OK or ERR diff --git a/ao.c b/ao.c @@ -0,0 +1,62 @@ +#include <sys/select.h> + +#include <limits.h> +#include <stdio.h> + +#include <ao/ao.h> + +#include "sad.h" + +static ao_sample_format format; +static ao_device *dev; +static int driver; + +static int +aoinit(void) +{ + ao_initialize(); + driver = ao_default_driver_id(); +} + +static int +aoputfmt(long rate, int channels, int bits) +{ + format.rate = rate; + format.channels = channels; + format.bits = bits; + format.byte_format = AO_FMT_NATIVE; + format.matrix = 0; +} + +static int +aoopen(void) +{ + dev = ao_open_live(driver, &format, NULL); +} + +static int +aowrite(void *buf, size_t size) +{ + ao_play(dev, buf, size); +} + +static int +aoclose(void) +{ + ao_close(dev); +} + +static void +aoexit(void) +{ + ao_shutdown(); +} + +Output aooutput = { + .init = aoinit, + .open = aoopen, + .putfmt = aoputfmt, + .write = aowrite, + .close = aoclose, + .exit = aoexit +}; diff --git a/cmd.c b/cmd.c @@ -0,0 +1,221 @@ +#include <sys/select.h> + +#include <fcntl.h> +#include <limits.h> +#include <stdio.h> +#include <unistd.h> + +#include "sad.h" + +void +cmdstatus(int fd, int argc, char **argv) +{ +} + +void +cmdvolume(int fd, int argc, char **argv) +{ +} + +void +cmdnext(int fd, int argc, char **argv) +{ +} + +void +cmdpause(int fd, int argc, char **argv) +{ + Song *s; + int i; + int pause; + + if (argc != 2) { + dprintf(fd, "ERR \"expected 0 or 1\"\n"); + return; + } + + pause = atoi(argv[1]); + if (pause != 0 && pause != 1) { + dprintf(fd, "ERR \"expected 0 or 1\"\n"); + return; + } + + s = getcursong(); + if (!s) { + dprintf(fd, "ERR \"no song is active\"\n"); + return; + } + + switch (s->state) { + case PLAYING: + if (pause == 1) { + s->state = PAUSED; + FD_CLR(s->fd, &master); + } + break; + case PAUSED: + if (pause == 0) { + s->state = PLAYING; + FD_SET(s->fd, &master); + if (s->fd > fdmax) + fdmax = s->fd; + } + break; + } + printf("Song %s with id %d is %s\n", + s->path, s->id, s->state == PAUSED ? "paused" : "playing"); + dprintf(fd, "OK\n"); +} + +void +cmdplay(int fd, int argc, char **argv) +{ + Song *s; + int id, i; + + if (argc != 2) { + dprintf(fd, "ERR \"expected a song id\"\n"); + return; + } + + id = atoi(argv[1]); + s = findsongid(id); + if (!s) { + dprintf(fd, "ERR \"invalid id\"\n"); + return; + } + + s->fd = open(s->path, O_RDONLY); + if (s->fd < 0) { + dprintf(fd, "ERR \"file doesn't exist\"\n"); + return; + } + FD_SET(s->fd, &master); + if (s->fd > fdmax) + fdmax = s->fd; + s->state = READYTOPLAY; + putcursong(s); + + printf("Song %s with %d ready to play\n", + s->path, s->id); +} + +void +cmdprev(int fd, int argc, char **argv) +{ +} + +void +cmdstop(int fd, int argc, char **argv) +{ + Song *s; + + if (argc != 1) { + dprintf(fd, "ERR \"no arguments expected\"\n"); + return; + } + + s = getcursong(); + if (s) + s->state = NONE; + dprintf(fd, "OK\n"); +} + +void +cmdadd(int fd, int argc, char **argv) +{ + Song *s; + + if (argc != 2) { + dprintf(fd, "ERR \"expected path\"\n"); + return; + } + if (access(argv[1], F_OK) < 0) { + dprintf(fd, "ERR \"file doesn't exist\"\n"); + return; + } + s = addplaylist(argv[1]); + printf("Added song with path %s and id %d\n", + s->path, s->id); + dprintf(fd, "OK\n"); +} + +void +cmdclear(int fd, int argc, char **argv) +{ +} + +void +cmddelete(int fd, int argc, char **argv) +{ +} + +void +cmdplaylist(int fd, int argc, char **argv) +{ + if (argc != 1) { + dprintf(fd, "ERR \"no arguments expected\"\n"); + return; + } + + playlistdump(fd); + dprintf(fd, "OK\n"); +} + +void +cmdclose(int fd, int argc, char **argv) +{ +} + +void +cmdkill(int fd, int argc, char **argv) +{ +} + +void +cmdping(int fd, int argc, char **argv) +{ + dprintf(fd, "OK\n"); +} + +Cmd cmds[] = { + { "status", cmdstatus }, + { "volume", cmdvolume }, + { "next", cmdnext }, + { "pause", cmdpause }, + { "play", cmdplay }, + { "prev", cmdprev }, + { "stop", cmdstop }, + { "add", cmdadd }, + { "clear", cmdclear }, + { "delete", cmddelete }, + { "playlist", cmdplaylist }, + { "close", cmdclose }, + { "kill", cmdkill }, + { "ping", cmdping }, +}; + +/* 0 on success, -1 on error or EOF */ +int +docmd(int clifd) +{ + char buf[BUFSIZ]; + int i; + ssize_t n; + int argc; + char *argv[2]; + + n = read(clifd, buf, sizeof(buf) - 1); + if (n <= 0) + return -1; + buf[n] = '\0'; + + argc = tokenize(buf, argv, 2); + for (i = 0; i < LEN(cmds); i++) { + if (!strcmp(cmds[i].name, argv[0])) { + cmds[i].fn(clifd, argc, argv); + break; + } + } + return 0; +} diff --git a/mp3.c b/mp3.c @@ -0,0 +1,80 @@ +#include <sys/select.h> + +#include <limits.h> +#include <stdio.h> + +#include <mpg123.h> + +#include "sad.h" + +static mpg123_handle *handle; + +static int +mp3init(void) +{ + int error; + + mpg123_init(); + handle = mpg123_new(NULL, &error); +} + +static int +mp3open(const char *path) +{ + mpg123_open(handle, path); +} + +static size_t +mp3bufsz(void) +{ + return mpg123_outblock(handle); +} + +static int +mp3getfmt(long *rate, int *channels, int *bits) +{ + long r; + int c, e; + + mpg123_getformat(handle, &r, &c, &e); + *rate = r; + *channels = c; + *bits = mpg123_encsize(e) * 8; + return 0; +} + +static int +mp3read(void *buf, size_t size) +{ + size_t done; + int r; + + r = mpg123_read(handle, buf, size, &done); + if (r == MPG123_OK) + return done; + return 0; +} + +static int +mp3close(void) +{ + mpg123_close(handle); +} + +static void +mp3exit(void) +{ + mpg123_delete(handle); + mpg123_exit(); +} + +Decoder mp3decoder = { + .init = mp3init, + .open = mp3open, + .bufsz = mp3bufsz, + .getfmt = mp3getfmt, + .read = mp3read, + .close = mp3close, + .exit = mp3exit + +}; diff --git a/playlist.c b/playlist.c @@ -0,0 +1,82 @@ +#include <sys/select.h> + +#include <limits.h> +#include <stdio.h> +#include <string.h> + +#include "sad.h" + +static Playlist playlist; + +void +initplaylist(void) +{ +} + +Song * +addplaylist(const char *path) +{ + Song *s; + static int rollingid = 0; + + s = &playlist.songs[playlist.nsongs]; + strncpy(s->path, path, sizeof(s->path)); + s->path[sizeof(s->path) - 1] = '\0'; + s->id = rollingid++; + s->fd = -1; + s->state = 0; + playlist.nsongs++; + return s; +} + +Song * +findsong(const char *path) +{ + Song *s; + int i; + + for (i = 0; i < playlist.nsongs; i++) { + s = &playlist.songs[i]; + if (!strcmp(s->path, path)) + return s; + } + return NULL; +} + +Song * +findsongid(int id) +{ + Song *s; + int i; + + for (i = 0; i < playlist.nsongs; i++) { + s = &playlist.songs[i]; + if (s->id == id) + return s; + } + return NULL; +} + +Song * +getcursong(void) +{ + return playlist.cursong; +} + +void +putcursong(Song *s) +{ + playlist.cursong = s; +} + +void +playlistdump(int fd) +{ + Song *s; + int i; + + for (i = 0; i < playlist.nsongs; i++) { + s = &playlist.songs[i]; + dprintf(fd, "%d: %s\n", s->id, s->path); + } +} diff --git a/sad.c b/sad.c @@ -0,0 +1,139 @@ +#include <sys/select.h> +#include <sys/socket.h> +#include <sys/un.h> + +#include <err.h> +#include <fcntl.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "sad.h" + +fd_set master; +fd_set rfds; +int fdmax; +Output *curoutput = &aooutput; +Decoder *curdecoder = &mp3decoder; + +int +servlisten(const char *name) +{ + struct sockaddr_un sa; + int listenfd, r, len; + + listenfd = socket(AF_UNIX, SOCK_STREAM, 0); + if (listenfd < 0) + err(1, "socket"); + + unlink(name); + + memset(&sa, 0, sizeof(sa)); + sa.sun_family = AF_UNIX; + strcpy(sa.sun_path, name); + + len = strlen(sa.sun_path) + sizeof(sa.sun_family); + r = bind(listenfd, (struct sockaddr *)&sa, len); + if (r < 0) + err(1, "bind"); + + r = listen(listenfd, 5); + if (r < 0) + err(1, "listen"); + + return listenfd; +} + +int +servaccept(int listenfd) +{ + struct sockaddr_un sa; + int clifd, len; + + len = sizeof(sa); + clifd = accept(listenfd, (struct sockaddr *)&sa, &len); + if (clifd < 0) + err(1, "accept"); + return clifd; +} + +void +doaudio(void) +{ + Song *s; + unsigned char *buf; + size_t bufsz; + long rate; + int channels, bits; + int n; + + s = getcursong(); + if (!s) + return; + + if (!FD_ISSET(s->fd, &rfds)) + return; + + switch (s->state) { + case READYTOPLAY: + curdecoder->open(s->path); + curdecoder->getfmt(&rate, &channels, &bits); + curoutput->putfmt(rate, channels, bits); + curoutput->open(); + s->state = PLAYING; + break; + case PLAYING: + bufsz = curdecoder->bufsz(); + buf = malloc(bufsz); + if (!buf) + err(1, "malloc"); + n = curdecoder->read(buf, bufsz); + if (n != 0) + curoutput->write(buf, n); + free(buf); + } +} + +int +main(void) +{ + int listenfd, clifd, n, i; + + FD_ZERO(&master); + FD_ZERO(&rfds); + + listenfd = servlisten("sock"); + FD_SET(listenfd, &master); + fdmax = listenfd; + + curoutput->init(); + curdecoder->init(); + + while (1) { + rfds = master; + n = select(fdmax + 1, &rfds, NULL, NULL, NULL); + if (n < 0) + err(1, "select"); + + doaudio(); + + for (i = 0; i <= fdmax; i++) { + if (!FD_ISSET(i, &rfds)) + continue; + if (i == listenfd) { + clifd = servaccept(listenfd); + FD_SET(clifd, &master); + if (clifd > fdmax) + fdmax = clifd; + } else { + if (docmd(i) < 0) { + close(i); + FD_CLR(i, &master); + } + } + } + } + return 0; +} diff --git a/sad.h b/sad.h @@ -0,0 +1,70 @@ +#define LEN(x) (sizeof (x) / sizeof *(x)) +#define VERSION "0.0" + +typedef struct { + char *name; + void (*fn)(int, int, char **); +} Cmd; + +enum { + NONE, + READYTOPLAY, + PLAYING, + PAUSED +}; + +typedef struct { + char path[PATH_MAX]; + int id; + int fd; + int state; +} Song; + +typedef struct { + Song songs[128]; + Song *cursong; + size_t nsongs; +} Playlist; + +typedef struct { + int (*init)(void); + int (*open)(const char *); + size_t (*bufsz)(void); + int (*getfmt)(long *, int *, int *); + int (*read)(void *, size_t); + int (*close)(void); + void (*exit)(void); +} Decoder; + +typedef struct { + int (*init)(void); + int (*putfmt)(long, int, int); + int (*open)(void); + int (*write)(void *, size_t); + int (*close)(void); + void (*exit)(void); +} Output; + +/* ao.c */ +extern Output aooutput; + +/* mp3.c */ +extern Decoder mp3decoder; + +/* playlist.c */ +Song *addplaylist(const char *); +Song *findsong(const char *); +Song *findsongid(int); +Song *getcursong(void); +void putcursong(Song *); + +/* sad.c */ +extern fd_set master; +extern fd_set rfds; +extern int fdmax; +extern Output *curoutput; +extern Decoder *curdecoder; + +/* tokenizer.c */ +int gettokens(char *, char **, int, char *); +int tokenize(char *, char **, int); diff --git a/tokenizer.c b/tokenizer.c @@ -0,0 +1,106 @@ +#include <string.h> + +static char qsep[] = " \t\r\n"; + +static char* +qtoken(char *s, char *sep) +{ + int quoting; + char *t; + + quoting = 0; + t = s; /* s is output string, t is input string */ + while(*t!='\0' && (quoting || strchr(sep, *t)==NULL)) { + if(*t != '\'') { + *s++ = *t++; + continue; + } + /* *t is a quote */ + if(!quoting) { + quoting = 1; + t++; + continue; + } + /* quoting and we're on a quote */ + if(t[1] != '\'') { + /* end of quoted section; absorb closing quote */ + t++; + quoting = 0; + continue; + } + /* doubled quote; fold one quote into two */ + t++; + *s++ = *t++; + } + if(*s != '\0') { + *s = '\0'; + if(t == s) + t++; + } + return t; +} + +static char* +etoken(char *t, char *sep) +{ + int quoting; + + /* move to end of next token */ + quoting = 0; + while(*t!='\0' && (quoting || strchr(sep, *t)==NULL)) { + if(*t != '\'') { + t++; + continue; + } + /* *t is a quote */ + if(!quoting) { + quoting = 1; + t++; + continue; + } + /* quoting and we're on a quote */ + if(t[1] != '\'') { + /* end of quoted section; absorb closing quote */ + t++; + quoting = 0; + continue; + } + /* doubled quote; fold one quote into two */ + t += 2; + } + return t; +} + +int +gettokens(char *s, char **args, int maxargs, char *sep) +{ + int nargs; + + for(nargs=0; nargs<maxargs; nargs++) { + while(*s!='\0' && strchr(sep, *s)!=NULL) + *s++ = '\0'; + if(*s == '\0') + break; + args[nargs] = s; + s = etoken(s, sep); + } + + return nargs; +} + +int +tokenize(char *s, char **args, int maxargs) +{ + int nargs; + + for(nargs=0; nargs<maxargs; nargs++) { + while(*s!='\0' && strchr(qsep, *s)!=NULL) + s++; + if(*s == '\0') + break; + args[nargs] = s; + s = qtoken(s, qsep); + } + + return nargs; +}