chiark / gitweb /
12a47dc9e37cdeb34227553a848cd7b9bdb70f9c
[disorder] / lib / uaudio-command.c
1 /*
2  * This file is part of DisOrder.
3  * Copyright (C) 2005, 2006, 2007, 2009 Richard Kettlewell
4  * Portions (C) 2007 Mark Wooding
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  * 
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 /** @file lib/uaudio-command.c
20  * @brief Support for commmand backend */
21 #include "common.h"
22
23 #include <errno.h>
24 #include <unistd.h>
25
26 #include "syscalls.h"
27 #include "log.h"
28 #include "mem.h"
29 #include "wstat.h"
30 #include "uaudio.h"
31
32 /** @brief Pipe to subprocess */
33 static int command_fd;
34
35 /** @brief Child process ID */
36 static pid_t command_pid;
37
38 static const char *const command_options[] = {
39   "command",
40   NULL
41 };
42
43 /** @brief Close pipe and wait for subprocess to terminate */
44 static void command_wait(void) {
45   int w;
46   pid_t rc;
47   char *ws;
48
49   close(command_fd);
50   while((rc = waitpid(command_pid, &w, 0) < 0 && errno == EINTR))
51     ;
52   if(rc < 0)
53     fatal(errno, "waitpid");
54   if(w) {
55     ws = wstat(w);
56     error(0, "command subprocess %s", ws);
57     xfree(ws);
58   }
59 }
60
61 /** @brief Create subprocess */ 
62 static void command_open(void) {
63   int pfd[2];
64   const char *command;
65
66   if(!(command = uaudio_get("command")))
67     fatal(0, "'command' not set");
68   xpipe(pfd);
69   command_pid = xfork();
70   if(!command_pid) {
71     exitfn = _exit;
72     signal(SIGPIPE, SIG_DFL);
73     xdup2(pfd[0], 0);
74     close(pfd[0]);
75     close(pfd[1]);
76     /* TODO it would be nice to set some environment variables given the sample
77      * format.  The original intended model is that you adapt DisOrder to the
78      * command you run but it'd be nice to support the opposite. */
79     execl("/bin/sh", "sh", "-c", command, (char *)0);
80     fatal(errno, "error executing /bin/sh");
81   }
82   close(pfd[0]);
83   command_pid = pfd[1];
84 }
85
86 /** @brief Send audio data to subprocess */
87 static size_t command_play(void *buffer, size_t nsamples) {
88   const size_t bytes = nsamples * uaudio_sample_size;
89   int written = write(command_fd, buffer, bytes);
90   if(written < 0) {
91     switch(errno) {
92     case EINTR:
93       return 0;                 /* will retry */
94     case EPIPE:
95       error(0, "audio command subprocess terminated");
96       command_wait();
97       command_open();
98       return 0;                 /* will retry */
99     default:
100       fatal(errno, "error writing to audio command subprocess");
101     }
102   }
103   return written / uaudio_sample_size;
104 }
105
106 static void command_start(uaudio_callback *callback,
107                       void *userdata) {
108   command_open();
109   uaudio_thread_start(callback,
110                       userdata,
111                       command_play,
112                       uaudio_channels,
113                       4096 / uaudio_sample_size);
114 }
115
116 static void command_stop(void) {
117   uaudio_thread_stop();
118   command_wait();
119 }
120
121 static void command_activate(void) {
122   uaudio_thread_activate();
123 }
124
125 static void command_deactivate(void) {
126   uaudio_thread_deactivate();
127 }
128
129 const struct uaudio uaudio_command = {
130   .name = "command",
131   .options = command_options,
132   .start = command_start,
133   .stop = command_stop,
134   .activate = command_activate,
135   .deactivate = command_deactivate
136 };
137
138 /*
139 Local Variables:
140 c-basic-offset:2
141 comment-column:40
142 fill-column:79
143 indent-tabs-mode:nil
144 End:
145 */