Commit | Line | Data |
---|---|---|
6d2d327c RK |
1 | /* |
2 | * This file is part of DisOrder | |
bfdadcc9 | 3 | * Copyright (C) 2007-2009 Richard Kettlewell |
6d2d327c | 4 | * |
e7eb3a27 | 5 | * This program is free software: you can redistribute it and/or modify |
6d2d327c | 6 | * it under the terms of the GNU General Public License as published by |
e7eb3a27 | 7 | * the Free Software Foundation, either version 3 of the License, or |
6d2d327c RK |
8 | * (at your option) any later version. |
9 | * | |
e7eb3a27 RK |
10 | * This program is distributed in the hope that it will be useful, |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
13 | * GNU General Public License for more details. | |
14 | * | |
6d2d327c | 15 | * You should have received a copy of the GNU General Public License |
e7eb3a27 | 16 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
6d2d327c | 17 | */ |
717ba987 | 18 | /** @file server/normalize.c |
6d2d327c RK |
19 | * @brief Convert "raw" format output to the configured format |
20 | * | |
bfdadcc9 RK |
21 | * If libsamplerate is available then resample_convert() is used to do all |
22 | * conversions. If not then we invoke sox (even for trivial conversions such | |
23 | * as byte-swapping). The sox support might be removed in a future version. | |
6d2d327c RK |
24 | */ |
25 | ||
05b75f8d | 26 | #include "disorder-server.h" |
bfdadcc9 RK |
27 | #include "resample.h" |
28 | ||
29 | static char buffer[1024 * 1024]; | |
0ca6d097 RK |
30 | |
31 | static const struct option options[] = { | |
32 | { "help", no_argument, 0, 'h' }, | |
33 | { "version", no_argument, 0, 'V' }, | |
34 | { "config", required_argument, 0, 'c' }, | |
35 | { "debug", no_argument, 0, 'd' }, | |
36 | { "no-debug", no_argument, 0, 'D' }, | |
37 | { "syslog", no_argument, 0, 's' }, | |
38 | { "no-syslog", no_argument, 0, 'S' }, | |
39 | { 0, 0, 0, 0 } | |
40 | }; | |
41 | ||
42 | /* display usage message and terminate */ | |
43 | static void help(void) { | |
44 | xprintf("Usage:\n" | |
45 | " disorder-normalize [OPTIONS]\n" | |
46 | "Options:\n" | |
47 | " --help, -h Display usage message\n" | |
48 | " --version, -V Display version number\n" | |
49 | " --config PATH, -c PATH Set configuration file\n" | |
50 | " --debug, -d Turn on debugging\n" | |
51 | " --[no-]syslog Force logging\n" | |
52 | "\n" | |
53 | "Audio format normalizer for DisOrder. Not intended to be run\n" | |
54 | "directly.\n"); | |
55 | xfclose(stdout); | |
56 | exit(0); | |
57 | } | |
58 | ||
6d2d327c RK |
59 | /** @brief Copy bytes from one file descriptor to another |
60 | * @param infd File descriptor read from | |
61 | * @param outfd File descriptor to write to | |
62 | * @param n Number of bytes to copy | |
63 | */ | |
64 | static void copy(int infd, int outfd, size_t n) { | |
62234e45 | 65 | ssize_t written; |
6d2d327c RK |
66 | |
67 | while(n > 0) { | |
62234e45 | 68 | const ssize_t readden = read(infd, buffer, |
69 | n > sizeof buffer ? sizeof buffer : n); | |
70 | if(readden < 0) { | |
6d2d327c RK |
71 | if(errno == EINTR) |
72 | continue; | |
73 | else | |
2e9ba080 | 74 | disorder_fatal(errno, "read error"); |
6d2d327c | 75 | } |
62234e45 | 76 | if(readden == 0) |
2e9ba080 | 77 | disorder_fatal(0, "unexpected EOF"); |
62234e45 | 78 | n -= readden; |
79 | written = 0; | |
80 | while(written < readden) { | |
81 | const ssize_t w = write(outfd, buffer + written, readden - written); | |
6d2d327c | 82 | if(w < 0) |
2e9ba080 | 83 | disorder_fatal(errno, "write error"); |
62234e45 | 84 | written += w; |
6d2d327c RK |
85 | } |
86 | } | |
87 | } | |
88 | ||
bfdadcc9 | 89 | #if !HAVE_SAMPLERATE_H |
6d2d327c RK |
90 | static void soxargs(const char ***pp, char **qq, |
91 | const struct stream_header *header) { | |
92 | *(*pp)++ = "-t.raw"; | |
93 | *(*pp)++ = "-s"; | |
94 | *qq += sprintf((char *)(*(*pp)++ = *qq), "-r%d", header->rate) + 1; | |
95 | *qq += sprintf((char *)(*(*pp)++ = *qq), "-c%d", header->channels) + 1; | |
96 | /* sox 12.17.9 insists on -b etc; CVS sox insists on -<n> etc; both are | |
97 | * deployed! */ | |
98 | switch(config->sox_generation) { | |
99 | case 0: | |
100 | if(header->bits != 8 | |
101 | && header->endian != ENDIAN_NATIVE) | |
102 | *(*pp)++ = "-x"; | |
103 | switch(header->bits) { | |
104 | case 8: *(*pp)++ = "-b"; break; | |
105 | case 16: *(*pp)++ = "-w"; break; | |
106 | case 32: *(*pp)++ = "-l"; break; | |
107 | case 64: *(*pp)++ = "-d"; break; | |
2e9ba080 | 108 | default: disorder_fatal(0, "cannot handle sample size %d", header->bits); |
6d2d327c RK |
109 | } |
110 | break; | |
111 | case 1: | |
112 | if(header->bits != 8 | |
113 | && header->endian != ENDIAN_NATIVE) | |
114 | switch(header->endian) { | |
115 | case ENDIAN_BIG: *(*pp)++ = "-B"; break; | |
116 | case ENDIAN_LITTLE: *(*pp)++ = "-L"; break; | |
117 | } | |
118 | if(header->bits % 8) | |
2e9ba080 | 119 | disorder_fatal(0, "cannot handle sample size %d", header->bits); |
6d2d327c RK |
120 | *qq += sprintf((char *)(*(*pp)++ = *qq), "-%d", header->bits / 8) + 1; |
121 | break; | |
122 | default: | |
2e9ba080 | 123 | disorder_fatal(0, "unknown sox_generation %ld", config->sox_generation); |
6d2d327c RK |
124 | } |
125 | } | |
bfdadcc9 RK |
126 | #else |
127 | static void converted(uint8_t *bytes, | |
128 | size_t nbytes, | |
129 | void attribute((unused)) *cd) { | |
130 | /*syslog(LOG_INFO, "out: %02x %02x %02x %02x", | |
131 | bytes[0], | |
132 | bytes[1], | |
133 | bytes[2], | |
134 | bytes[3]);*/ | |
135 | while(nbytes > 0) { | |
136 | ssize_t n = write(1, bytes, nbytes); | |
137 | if(n < 0) | |
138 | disorder_fatal(errno, "writing to stdout"); | |
139 | bytes += n; | |
140 | nbytes -= n; | |
141 | } | |
142 | } | |
143 | #endif | |
6d2d327c RK |
144 | |
145 | int main(int argc, char attribute((unused)) **argv) { | |
146 | struct stream_header header, latest_format; | |
bfdadcc9 | 147 | int n, outfd = -1, logsyslog = !isatty(2), rs_in_use = 0; |
6d2d327c | 148 | pid_t pid = -1; |
bfdadcc9 | 149 | struct resampler rs[1]; |
6d2d327c RK |
150 | |
151 | set_progname(argv); | |
152 | if(!setlocale(LC_CTYPE, "")) | |
2e9ba080 | 153 | disorder_fatal(errno, "error calling setlocale"); |
0ca6d097 RK |
154 | while((n = getopt_long(argc, argv, "hVc:dDSs", options, 0)) >= 0) { |
155 | switch(n) { | |
156 | case 'h': help(); | |
3fbdc96d | 157 | case 'V': version("disorder-normalize"); |
0ca6d097 RK |
158 | case 'c': configfile = optarg; break; |
159 | case 'd': debugging = 1; break; | |
160 | case 'D': debugging = 0; break; | |
161 | case 'S': logsyslog = 0; break; | |
162 | case 's': logsyslog = 1; break; | |
2e9ba080 | 163 | default: disorder_fatal(0, "invalid option"); |
0ca6d097 RK |
164 | } |
165 | } | |
02ba7921 | 166 | if(config_read(1, NULL)) |
2e9ba080 | 167 | disorder_fatal(0, "cannot read configuration"); |
0ca6d097 | 168 | if(logsyslog) { |
6d2d327c RK |
169 | openlog(progname, LOG_PID, LOG_DAEMON); |
170 | log_default = &log_syslog; | |
171 | } | |
172 | memset(&latest_format, 0, sizeof latest_format); | |
173 | for(;;) { | |
bfdadcc9 | 174 | /* Read one header */ |
e7fd1612 RK |
175 | n = 0; |
176 | while((size_t)n < sizeof header) { | |
177 | int r = read(0, (char *)&header + n, sizeof header - n); | |
178 | ||
179 | if(r < 0) { | |
180 | if(errno != EINTR) | |
2e9ba080 | 181 | disorder_fatal(errno, "error reading header"); |
e99d42b1 | 182 | } else if(r == 0) { |
183 | if(n) | |
2e9ba080 | 184 | disorder_fatal(0, "EOF reading header"); |
e99d42b1 | 185 | break; |
186 | } else | |
e7fd1612 RK |
187 | n += r; |
188 | } | |
24fc5e58 | 189 | if(!n) |
190 | break; | |
c57b3a9e RK |
191 | D(("NEW HEADER: %"PRIu32" bytes %"PRIu32"Hz %"PRIu8" channels %"PRIu8" bits %"PRIu8" endian", |
192 | header.nbytes, header.rate, header.channels, header.bits, header.endian)); | |
6d2d327c RK |
193 | /* Sanity check the header */ |
194 | if(header.rate < 100 || header.rate > 1000000) | |
2e9ba080 RK |
195 | disorder_fatal(0, "implausible rate %"PRId32"Hz (%#"PRIx32")", |
196 | header.rate, header.rate); | |
6d2d327c | 197 | if(header.channels < 1 || header.channels > 2) |
2e9ba080 | 198 | disorder_fatal(0, "unsupported channel count %d", header.channels); |
6d2d327c | 199 | if(header.bits % 8 || !header.bits || header.bits > 64) |
2e9ba080 | 200 | disorder_fatal(0, "unsupported sample size %d bits", header.bits); |
6d2d327c | 201 | if(header.endian != ENDIAN_BIG && header.endian != ENDIAN_LITTLE) |
2d0a6606 | 202 | disorder_fatal(0, "unsupported byte order %d", header.endian); |
6d2d327c RK |
203 | /* Skip empty chunks regardless of their alleged format */ |
204 | if(header.nbytes == 0) | |
205 | continue; | |
206 | /* If the format has changed we stop/start the converter */ | |
bfdadcc9 RK |
207 | #if HAVE_SAMPLERATE_H |
208 | /* We have libsamplerate */ | |
209 | if(formats_equal(&header, &config->sample_format)) | |
210 | /* If the format is already correct then we just write out the data */ | |
211 | copy(0, 1, header.nbytes); | |
212 | else { | |
213 | /* If we have a resampler active already check it is suitable and destroy | |
214 | * it if not */ | |
c57b3a9e RK |
215 | if(rs_in_use) { |
216 | D(("call resample_close")); | |
bfdadcc9 RK |
217 | resample_close(rs); |
218 | rs_in_use = 0; | |
219 | } | |
220 | /*syslog(LOG_INFO, "%d/%d/%d/%d/%d -> %d/%d/%d/%d/%d", | |
221 | header.bits, | |
222 | header.channels, | |
223 | header.rate, | |
224 | 1, | |
225 | header.endian, | |
226 | config->sample_format.bits, | |
227 | config->sample_format.channels, | |
228 | config->sample_format.rate, | |
229 | 1, | |
230 | config->sample_format.endian);*/ | |
231 | if(!rs_in_use) { | |
232 | /* Create a suitable resampler. */ | |
c57b3a9e | 233 | D(("call resample_init")); |
bfdadcc9 RK |
234 | resample_init(rs, |
235 | header.bits, | |
236 | header.channels, | |
237 | header.rate, | |
238 | 1, /* signed */ | |
239 | header.endian, | |
240 | config->sample_format.bits, | |
241 | config->sample_format.channels, | |
242 | config->sample_format.rate, | |
243 | 1, /* signed */ | |
244 | config->sample_format.endian); | |
245 | latest_format = header; | |
246 | rs_in_use = 1; | |
247 | /* TODO speaker protocol does not record signedness of samples. It's | |
248 | * assumed that they are always signed. This should be fixed in the | |
249 | * future (and the sample format syntax extended in a compatible | |
250 | * way). */ | |
251 | } | |
252 | /* Feed data through the resampler */ | |
253 | size_t used = 0, left = header.nbytes; | |
254 | while(used || left) { | |
255 | if(left) { | |
256 | size_t limit = (sizeof buffer) - used; | |
257 | if(limit > left) | |
258 | limit = left; | |
259 | ssize_t r = read(0, buffer + used, limit); | |
260 | if(r < 0) | |
261 | disorder_fatal(errno, "reading from stdin"); | |
262 | if(r == 0) | |
263 | disorder_fatal(0, "unexpected EOF"); | |
264 | left -= r; | |
265 | used += r; | |
266 | //syslog(LOG_INFO, "read %zd bytes", r); | |
c57b3a9e | 267 | D(("read %zd bytes", r)); |
bfdadcc9 RK |
268 | } |
269 | /*syslog(LOG_INFO, " in: %02x %02x %02x %02x", | |
270 | (uint8_t)buffer[0], | |
271 | (uint8_t)buffer[1], | |
272 | (uint8_t)buffer[2], | |
273 | (uint8_t)buffer[3]);*/ | |
c57b3a9e | 274 | D(("calling resample_convert used=%zu !left=%d", used, !left)); |
bfdadcc9 RK |
275 | const size_t consumed = resample_convert(rs, |
276 | (uint8_t *)buffer, used, | |
277 | !left, | |
278 | converted, 0); | |
279 | //syslog(LOG_INFO, "used=%zu consumed=%zu", used, consumed); | |
c57b3a9e | 280 | D(("consumed=%zu", consumed)); |
bfdadcc9 RK |
281 | memmove(buffer, buffer + consumed, used - consumed); |
282 | used -= consumed; | |
283 | } | |
284 | } | |
285 | #else | |
286 | /* We do not have libsamplerate. We will use sox instead. */ | |
6d2d327c RK |
287 | if(!formats_equal(&header, &latest_format)) { |
288 | if(pid != -1) { | |
289 | /* There's a running converter, stop it */ | |
290 | xclose(outfd); | |
291 | if(waitpid(pid, &n, 0) < 0) | |
2e9ba080 | 292 | disorder_fatal(errno, "error calling waitpid"); |
6d2d327c | 293 | if(n) |
2e9ba080 | 294 | disorder_fatal(0, "sox failed: %#x", n); |
6d2d327c RK |
295 | pid = -1; |
296 | outfd = -1; | |
297 | } | |
298 | if(!formats_equal(&header, &config->sample_format)) { | |
299 | const char *av[32], **pp = av; | |
300 | char argbuf[1024], *q = argbuf; | |
301 | ||
302 | /* Input format doesn't match target, need to start a converter */ | |
303 | *pp++ = "sox"; | |
304 | soxargs(&pp, &q, &header); | |
305 | *pp++ = "-"; /* stdin */ | |
306 | soxargs(&pp, &q, &config->sample_format); | |
307 | *pp++ = "-"; /* stdout */ | |
308 | *pp = 0; | |
309 | /* This pipe will be sox's stdin */ | |
bfdadcc9 | 310 | int p[2]; |
6d2d327c RK |
311 | xpipe(p); |
312 | if(!(pid = xfork())) { | |
313 | exitfn = _exit; | |
314 | xdup2(p[0], 0); | |
315 | xclose(p[0]); | |
316 | xclose(p[1]); | |
317 | execvp(av[0], (char **)av); | |
2e9ba080 | 318 | disorder_fatal(errno, "sox"); |
6d2d327c RK |
319 | } |
320 | xclose(p[0]); | |
321 | outfd = p[1]; | |
322 | } else | |
323 | /* Input format matches output, can just copy bytes */ | |
324 | outfd = 1; | |
325 | /* Remember current format for next iteration */ | |
326 | latest_format = header; | |
327 | } | |
328 | /* Convert or copy this chunk */ | |
329 | copy(0, outfd, header.nbytes); | |
bfdadcc9 | 330 | #endif |
6d2d327c RK |
331 | } |
332 | if(outfd != -1) | |
333 | xclose(outfd); | |
60432d3d RK |
334 | if(pid != -1) { |
335 | /* There's still a converter running */ | |
336 | if(waitpid(pid, &n, 0) < 0) | |
2e9ba080 | 337 | disorder_fatal(errno, "error calling waitpid"); |
60432d3d | 338 | if(n) |
2e9ba080 | 339 | disorder_fatal(0, "sox failed: %#x", n); |
60432d3d | 340 | } |
bfdadcc9 RK |
341 | if(rs_in_use) |
342 | resample_close(rs); | |
6d2d327c RK |
343 | return 0; |
344 | } | |
345 | ||
346 | /* | |
347 | Local Variables: | |
348 | c-basic-offset:2 | |
349 | comment-column:40 | |
350 | fill-column:79 | |
351 | indent-tabs-mode:nil | |
352 | End: | |
353 | */ |