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