chiark / gitweb /
Uniform audio command back end now rate limited.
[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  *
22  * We use the code in @ref lib/uaudio-schedule.c to ensure that we write at
23  * approximately the 'real' rate.  For disorder-playrtp this isn't very useful
24  * (thought it might reduce the size of various buffers downstream of us) but
25  * when run from the speaker it means that pausing stands a chance of working.
26  */
27 #include "common.h"
28
29 #include <errno.h>
30 #include <unistd.h>
31
32 #include "syscalls.h"
33 #include "log.h"
34 #include "mem.h"
35 #include "wstat.h"
36 #include "uaudio.h"
37
38 /** @brief Pipe to subprocess */
39 static int command_fd;
40
41 /** @brief Child process ID */
42 static pid_t command_pid;
43
44 static const char *const command_options[] = {
45   "command",
46   NULL
47 };
48
49 /** @brief Close pipe and wait for subprocess to terminate */
50 static void command_wait(void) {
51   int w;
52   pid_t rc;
53   char *ws;
54
55   close(command_fd);
56   while((rc = waitpid(command_pid, &w, 0) < 0 && errno == EINTR))
57     ;
58   if(rc < 0)
59     fatal(errno, "waitpid");
60   if(w) {
61     ws = wstat(w);
62     error(0, "command subprocess %s", ws);
63     xfree(ws);
64   }
65 }
66
67 /** @brief Create subprocess */ 
68 static void command_open(void) {
69   int pfd[2];
70   const char *command;
71
72   if(!(command = uaudio_get("command")))
73     fatal(0, "'command' not set");
74   xpipe(pfd);
75   command_pid = xfork();
76   if(!command_pid) {
77     exitfn = _exit;
78     signal(SIGPIPE, SIG_DFL);
79     xdup2(pfd[0], 0);
80     close(pfd[0]);
81     close(pfd[1]);
82     /* TODO it would be nice to set some environment variables given the sample
83      * format.  The original intended model is that you adapt DisOrder to the
84      * command you run but it'd be nice to support the opposite. */
85     execl("/bin/sh", "sh", "-c", command, (char *)0);
86     fatal(errno, "error executing /bin/sh");
87   }
88   close(pfd[0]);
89   command_fd = pfd[1];
90 }
91
92 /** @brief Send audio data to subprocess */
93 static size_t command_play(void *buffer, size_t nsamples) {
94   uaudio_schedule_synchronize();
95   const size_t bytes = nsamples * uaudio_sample_size;
96   int written = write(command_fd, buffer, bytes);
97   if(written < 0) {
98     switch(errno) {
99     case EINTR:
100       return 0;                 /* will retry */
101     case EPIPE:
102       error(0, "audio command subprocess terminated");
103       command_wait();
104       command_open();
105       return 0;                 /* will retry */
106     default:
107       fatal(errno, "error writing to audio command subprocess");
108     }
109   }
110   const size_t written_samples = written / uaudio_sample_size;
111   uaudio_schedule_update(written_samples);
112   return written_samples;
113 }
114
115 static void command_start(uaudio_callback *callback,
116                       void *userdata) {
117   command_open();
118   uaudio_schedule_init();
119   uaudio_thread_start(callback,
120                       userdata,
121                       command_play,
122                       uaudio_channels,
123                       4096 / uaudio_sample_size);
124 }
125
126 static void command_stop(void) {
127   uaudio_thread_stop();
128   command_wait();
129 }
130
131 static void command_activate(void) {
132   uaudio_schedule_reactivated = 1;
133   uaudio_thread_activate();
134 }
135
136 static void command_deactivate(void) {
137   uaudio_thread_deactivate();
138 }
139
140 const struct uaudio uaudio_command = {
141   .name = "command",
142   .options = command_options,
143   .start = command_start,
144   .stop = command_stop,
145   .activate = command_activate,
146   .deactivate = command_deactivate
147 };
148
149 /*
150 Local Variables:
151 c-basic-offset:2
152 comment-column:40
153 fill-column:79
154 indent-tabs-mode:nil
155 End:
156 */