commit a60e06ce7795add484ae86635c29152554c1cf6f
Author: sin <sin@2f30.org>
Date: Wed, 24 Dec 2014 14:33:20 +0000
Initial commit
Diffstat:
A | LICENSE | | | 13 | +++++++++++++ |
A | Makefile | | | 33 | +++++++++++++++++++++++++++++++++ |
A | PROTOCOL | | | 66 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | ao.c | | | 62 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | cmd.c | | | 221 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | mp3.c | | | 80 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | playlist.c | | | 82 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | sad.c | | | 139 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | sad.h | | | 70 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | 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;
+}