chiark / gitweb /
(Slightly scrappy) new playlist box
[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
b1f6ca8c
RK
45/** @brief Whether to suspend on pause */
46static int command_suspend_on_pause;
47
e8c185c3
RK
48static const char *const command_options[] = {
49 "command",
287ad384 50 "pause-mode",
e8c185c3
RK
51 NULL
52};
53
54/** @brief Close pipe and wait for subprocess to terminate */
55static 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)
2e9ba080 64 disorder_fatal(errno, "waitpid");
e8c185c3
RK
65 if(w) {
66 ws = wstat(w);
2e9ba080 67 disorder_error(0, "command subprocess %s", ws);
e8c185c3
RK
68 xfree(ws);
69 }
70}
71
72/** @brief Create subprocess */
73static void command_open(void) {
74 int pfd[2];
75 const char *command;
76
b50cfb8a 77 if(!(command = uaudio_get("command", NULL)))
2e9ba080 78 disorder_fatal(0, "'command' not set");
e8c185c3
RK
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);
2e9ba080 91 disorder_fatal(errno, "error executing /bin/sh");
e8c185c3
RK
92 }
93 close(pfd[0]);
e03eb69e 94 command_fd = pfd[1];
e8c185c3
RK
95}
96
97/** @brief Send audio data to subprocess */
b1f6ca8c
RK
98static 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;
e8c185c3
RK
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:
2e9ba080 111 disorder_error(0, "audio command subprocess terminated");
e8c185c3
RK
112 command_wait();
113 command_open();
114 return 0; /* will retry */
115 default:
2e9ba080 116 disorder_fatal(errno, "error writing to audio command subprocess");
e8c185c3
RK
117 }
118 }
b1f6ca8c
RK
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. */
ec57f6c9 122 const size_t written_samples = written / uaudio_sample_size;
b1f6ca8c 123 uaudio_schedule_sent(written_samples);
ec57f6c9 124 return written_samples;
e8c185c3
RK
125}
126
127static void command_start(uaudio_callback *callback,
128 void *userdata) {
287ad384
RK
129 const char *pausemode = uaudio_get("pause-mode", "silence");
130 unsigned flags = 0;
131
132 if(!strcmp(pausemode, "silence"))
b1f6ca8c 133 command_suspend_on_pause = 0;
287ad384 134 else if(!strcmp(pausemode, "suspend"))
b1f6ca8c 135 command_suspend_on_pause = 1;
287ad384 136 else
2e9ba080 137 disorder_fatal(0, "unknown pause mode '%s'", pausemode);
e8c185c3 138 command_open();
ec57f6c9 139 uaudio_schedule_init();
e8c185c3
RK
140 uaudio_thread_start(callback,
141 userdata,
142 command_play,
143 uaudio_channels,
63761c19 144 4096 / uaudio_sample_size,
287ad384 145 flags);
e8c185c3
RK
146}
147
148static void command_stop(void) {
149 uaudio_thread_stop();
150 command_wait();
151}
152
ba70caca
RK
153static void command_configure(void) {
154 uaudio_set("command", config->speaker_command);
f75ab9d3 155 uaudio_set("pause-mode", config->pause_mode);
ba70caca
RK
156}
157
e8c185c3
RK
158const struct uaudio uaudio_command = {
159 .name = "command",
160 .options = command_options,
161 .start = command_start,
162 .stop = command_stop,
b1f6ca8c
RK
163 .activate = uaudio_thread_activate,
164 .deactivate = uaudio_thread_deactivate,
ba70caca 165 .configure = command_configure,
e8c185c3
RK
166};
167
168/*
169Local Variables:
170c-basic-offset:2
171comment-column:40
172fill-column:79
173indent-tabs-mode:nil
174End:
175*/