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 */ | |
16bf32dc | 43 | static void attribute((noreturn)) help(void) { |
0ca6d097 RK |
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 | } | |
477b12ff | 166 | config_per_user = 0; |
02ba7921 | 167 | if(config_read(1, NULL)) |
2e9ba080 | 168 | disorder_fatal(0, "cannot read configuration"); |
0ca6d097 | 169 | if(logsyslog) { |
6d2d327c RK |
170 | openlog(progname, LOG_PID, LOG_DAEMON); |
171 | log_default = &log_syslog; | |
172 | } | |
173 | memset(&latest_format, 0, sizeof latest_format); | |
174 | for(;;) { | |
bfdadcc9 | 175 | /* Read one header */ |
e7fd1612 RK |
176 | n = 0; |
177 | while((size_t)n < sizeof header) { | |
178 | int r = read(0, (char *)&header + n, sizeof header - n); | |
179 | ||
180 | if(r < 0) { | |
181 | if(errno != EINTR) | |
2e9ba080 | 182 | disorder_fatal(errno, "error reading header"); |
e99d42b1 | 183 | } else if(r == 0) { |
184 | if(n) | |
2e9ba080 | 185 | disorder_fatal(0, "EOF reading header"); |
e99d42b1 | 186 | break; |
187 | } else | |
e7fd1612 RK |
188 | n += r; |
189 | } | |
24fc5e58 | 190 | if(!n) |
191 | break; | |
c57b3a9e RK |
192 | D(("NEW HEADER: %"PRIu32" bytes %"PRIu32"Hz %"PRIu8" channels %"PRIu8" bits %"PRIu8" endian", |
193 | header.nbytes, header.rate, header.channels, header.bits, header.endian)); | |
6d2d327c RK |
194 | /* Sanity check the header */ |
195 | if(header.rate < 100 || header.rate > 1000000) | |
2e9ba080 RK |
196 | disorder_fatal(0, "implausible rate %"PRId32"Hz (%#"PRIx32")", |
197 | header.rate, header.rate); | |
6d2d327c | 198 | if(header.channels < 1 || header.channels > 2) |
2e9ba080 | 199 | disorder_fatal(0, "unsupported channel count %d", header.channels); |
6d2d327c | 200 | if(header.bits % 8 || !header.bits || header.bits > 64) |
2e9ba080 | 201 | disorder_fatal(0, "unsupported sample size %d bits", header.bits); |
6d2d327c | 202 | if(header.endian != ENDIAN_BIG && header.endian != ENDIAN_LITTLE) |
2d0a6606 | 203 | disorder_fatal(0, "unsupported byte order %d", header.endian); |
6d2d327c RK |
204 | /* Skip empty chunks regardless of their alleged format */ |
205 | if(header.nbytes == 0) | |
206 | continue; | |
207 | /* If the format has changed we stop/start the converter */ | |
bfdadcc9 RK |
208 | #if HAVE_SAMPLERATE_H |
209 | /* We have libsamplerate */ | |
210 | if(formats_equal(&header, &config->sample_format)) | |
211 | /* If the format is already correct then we just write out the data */ | |
212 | copy(0, 1, header.nbytes); | |
213 | else { | |
214 | /* If we have a resampler active already check it is suitable and destroy | |
215 | * it if not */ | |
c57b3a9e RK |
216 | if(rs_in_use) { |
217 | D(("call resample_close")); | |
bfdadcc9 RK |
218 | resample_close(rs); |
219 | rs_in_use = 0; | |
220 | } | |
221 | /*syslog(LOG_INFO, "%d/%d/%d/%d/%d -> %d/%d/%d/%d/%d", | |
222 | header.bits, | |
223 | header.channels, | |
224 | header.rate, | |
225 | 1, | |
226 | header.endian, | |
227 | config->sample_format.bits, | |
228 | config->sample_format.channels, | |
229 | config->sample_format.rate, | |
230 | 1, | |
231 | config->sample_format.endian);*/ | |
232 | if(!rs_in_use) { | |
233 | /* Create a suitable resampler. */ | |
c57b3a9e | 234 | D(("call resample_init")); |
bfdadcc9 RK |
235 | resample_init(rs, |
236 | header.bits, | |
237 | header.channels, | |
238 | header.rate, | |
239 | 1, /* signed */ | |
240 | header.endian, | |
241 | config->sample_format.bits, | |
242 | config->sample_format.channels, | |
243 | config->sample_format.rate, | |
244 | 1, /* signed */ | |
245 | config->sample_format.endian); | |
246 | latest_format = header; | |
247 | rs_in_use = 1; | |
248 | /* TODO speaker protocol does not record signedness of samples. It's | |
249 | * assumed that they are always signed. This should be fixed in the | |
250 | * future (and the sample format syntax extended in a compatible | |
251 | * way). */ | |
252 | } | |
253 | /* Feed data through the resampler */ | |
254 | size_t used = 0, left = header.nbytes; | |
255 | while(used || left) { | |
256 | if(left) { | |
257 | size_t limit = (sizeof buffer) - used; | |
258 | if(limit > left) | |
259 | limit = left; | |
260 | ssize_t r = read(0, buffer + used, limit); | |
261 | if(r < 0) | |
262 | disorder_fatal(errno, "reading from stdin"); | |
263 | if(r == 0) | |
264 | disorder_fatal(0, "unexpected EOF"); | |
265 | left -= r; | |
266 | used += r; | |
267 | //syslog(LOG_INFO, "read %zd bytes", r); | |
c57b3a9e | 268 | D(("read %zd bytes", r)); |
bfdadcc9 RK |
269 | } |
270 | /*syslog(LOG_INFO, " in: %02x %02x %02x %02x", | |
271 | (uint8_t)buffer[0], | |
272 | (uint8_t)buffer[1], | |
273 | (uint8_t)buffer[2], | |
274 | (uint8_t)buffer[3]);*/ | |
c57b3a9e | 275 | D(("calling resample_convert used=%zu !left=%d", used, !left)); |
bfdadcc9 RK |
276 | const size_t consumed = resample_convert(rs, |
277 | (uint8_t *)buffer, used, | |
278 | !left, | |
279 | converted, 0); | |
280 | //syslog(LOG_INFO, "used=%zu consumed=%zu", used, consumed); | |
c57b3a9e | 281 | D(("consumed=%zu", consumed)); |
bfdadcc9 RK |
282 | memmove(buffer, buffer + consumed, used - consumed); |
283 | used -= consumed; | |
284 | } | |
285 | } | |
286 | #else | |
287 | /* We do not have libsamplerate. We will use sox instead. */ | |
6d2d327c RK |
288 | if(!formats_equal(&header, &latest_format)) { |
289 | if(pid != -1) { | |
290 | /* There's a running converter, stop it */ | |
291 | xclose(outfd); | |
292 | if(waitpid(pid, &n, 0) < 0) | |
2e9ba080 | 293 | disorder_fatal(errno, "error calling waitpid"); |
6d2d327c | 294 | if(n) |
2e9ba080 | 295 | disorder_fatal(0, "sox failed: %#x", n); |
6d2d327c RK |
296 | pid = -1; |
297 | outfd = -1; | |
298 | } | |
299 | if(!formats_equal(&header, &config->sample_format)) { | |
300 | const char *av[32], **pp = av; | |
301 | char argbuf[1024], *q = argbuf; | |
302 | ||
303 | /* Input format doesn't match target, need to start a converter */ | |
304 | *pp++ = "sox"; | |
305 | soxargs(&pp, &q, &header); | |
306 | *pp++ = "-"; /* stdin */ | |
307 | soxargs(&pp, &q, &config->sample_format); | |
308 | *pp++ = "-"; /* stdout */ | |
309 | *pp = 0; | |
310 | /* This pipe will be sox's stdin */ | |
bfdadcc9 | 311 | int p[2]; |
6d2d327c RK |
312 | xpipe(p); |
313 | if(!(pid = xfork())) { | |
314 | exitfn = _exit; | |
315 | xdup2(p[0], 0); | |
316 | xclose(p[0]); | |
317 | xclose(p[1]); | |
318 | execvp(av[0], (char **)av); | |
2e9ba080 | 319 | disorder_fatal(errno, "sox"); |
6d2d327c RK |
320 | } |
321 | xclose(p[0]); | |
322 | outfd = p[1]; | |
323 | } else | |
324 | /* Input format matches output, can just copy bytes */ | |
325 | outfd = 1; | |
326 | /* Remember current format for next iteration */ | |
327 | latest_format = header; | |
328 | } | |
329 | /* Convert or copy this chunk */ | |
330 | copy(0, outfd, header.nbytes); | |
bfdadcc9 | 331 | #endif |
6d2d327c RK |
332 | } |
333 | if(outfd != -1) | |
334 | xclose(outfd); | |
60432d3d RK |
335 | if(pid != -1) { |
336 | /* There's still a converter running */ | |
337 | if(waitpid(pid, &n, 0) < 0) | |
2e9ba080 | 338 | disorder_fatal(errno, "error calling waitpid"); |
60432d3d | 339 | if(n) |
2e9ba080 | 340 | disorder_fatal(0, "sox failed: %#x", n); |
60432d3d | 341 | } |
bfdadcc9 RK |
342 | if(rs_in_use) |
343 | resample_close(rs); | |
6d2d327c RK |
344 | return 0; |
345 | } | |
346 | ||
347 | /* | |
348 | Local Variables: | |
349 | c-basic-offset:2 | |
350 | comment-column:40 | |
351 | fill-column:79 | |
352 | indent-tabs-mode:nil | |
353 | End: | |
354 | */ |