chiark / gitweb /
Fix mis-merged trackdb_open().
[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 #include "configuration.h"
38
39 /** @brief Pipe to subprocess */
40 static int command_fd;
41
42 /** @brief Child process ID */
43 static pid_t command_pid;
44
45 /** @brief Whether to suspend on pause */
46 static int command_suspend_on_pause;
47
48 static const char *const command_options[] = {
49   "command",
50   "pause-mode",
51   NULL
52 };
53
54 /** @brief Close pipe and wait for subprocess to terminate */
55 static void command_wait(void) {
56   int w;
57   pid_t rc;
58   char *ws;
59
60   close(command_fd);
61   while((rc = waitpid(command_pid, &w, 0) < 0 && errno == EINTR))
62     ;
63   if(rc < 0)
64     fatal(errno, "waitpid");
65   if(w) {
66     ws = wstat(w);
67     error(0, "command subprocess %s", ws);
68     xfree(ws);
69   }
70 }
71
72 /** @brief Create subprocess */ 
73 static void command_open(void) {
74   int pfd[2];
75   const char *command;
76
77   if(!(command = uaudio_get("command", NULL)))
78     fatal(0, "'command' not set");
79   xpipe(pfd);
80   command_pid = xfork();
81   if(!command_pid) {
82     exitfn = _exit;
83     signal(SIGPIPE, SIG_DFL);
84     xdup2(pfd[0], 0);
85     close(pfd[0]);
86     close(pfd[1]);
87     /* TODO it would be nice to set some environment variables given the sample
88      * format.  The original intended model is that you adapt DisOrder to the
89      * command you run but it'd be nice to support the opposite. */
90     execl("/bin/sh", "sh", "-c", command, (char *)0);
91     fatal(errno, "error executing /bin/sh");
92   }
93   close(pfd[0]);
94   command_fd = pfd[1];
95 }
96
97 /** @brief Send audio data to subprocess */
98 static size_t command_play(void *buffer, size_t nsamples, unsigned flags) {
99   uaudio_schedule_sync();
100   /* If we're pausing and want that to be represented by stopping writing, we
101    * just pretend */
102   if((flags & UAUDIO_PAUSED) && command_suspend_on_pause)
103     return nsamples;
104   const size_t bytes = nsamples * uaudio_sample_size;
105   int written = write(command_fd, buffer, bytes);
106   if(written < 0) {
107     switch(errno) {
108     case EINTR:
109       return 0;                 /* will retry */
110     case EPIPE:
111       error(0, "audio command subprocess terminated");
112       command_wait();
113       command_open();
114       return 0;                 /* will retry */
115     default:
116       fatal(errno, "error writing to audio command subprocess");
117     }
118   }
119   /* TODO what if we write a partial sample? Actually reasonably unlikely but
120    * not impossible.  Maybe someone who actually uses this backend should sort
121    * it out. */
122   const size_t written_samples = written / uaudio_sample_size;
123   uaudio_schedule_sent(written_samples);
124   return written_samples;
125 }
126
127 static void command_start(uaudio_callback *callback,
128                       void *userdata) {
129   const char *pausemode = uaudio_get("pause-mode", "silence");
130   unsigned flags = 0;
131
132   if(!strcmp(pausemode, "silence"))
133     command_suspend_on_pause = 0;
134   else if(!strcmp(pausemode, "suspend"))
135     command_suspend_on_pause = 1;
136   else
137     fatal(0, "unknown pause mode '%s'", pausemode);
138   command_open();
139   uaudio_schedule_init();
140   uaudio_thread_start(callback,
141                       userdata,
142                       command_play,
143                       uaudio_channels,
144                       4096 / uaudio_sample_size,
145                       flags);
146 }
147
148 static void command_stop(void) {
149   uaudio_thread_stop();
150   command_wait();
151 }
152
153 static void command_configure(void) {
154   uaudio_set("command", config->speaker_command);
155   uaudio_set("pause-mode", config->pause_mode);
156 }
157
158 const struct uaudio uaudio_command = {
159   .name = "command",
160   .options = command_options,
161   .start = command_start,
162   .stop = command_stop,
163   .activate = uaudio_thread_activate,
164   .deactivate = uaudio_thread_deactivate,
165   .configure = command_configure,
166 };
167
168 /*
169 Local Variables:
170 c-basic-offset:2
171 comment-column:40
172 fill-column:79
173 indent-tabs-mode:nil
174 End:
175 */