X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~mdw/git/disorder/blobdiff_plain/60432d3dc3d2e182ed80f2329f3160894643d334..417ef7de00568897d331d282478d93b323574f8c:/server/normalize.c diff --git a/server/normalize.c b/server/normalize.c index 28faa81..0f97a2f 100644 --- a/server/normalize.c +++ b/server/normalize.c @@ -1,47 +1,32 @@ /* * This file is part of DisOrder - * Copyright (C) 2007 Richard Kettlewell + * Copyright (C) 2007-2009 Richard Kettlewell * - * This program is free software; you can redistribute it and/or modify + * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or + * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 - * USA + * along with this program. If not, see . */ /** @file server/normalize.c * @brief Convert "raw" format output to the configured format * - * Currently we invoke sox even for trivial conversions such as byte-swapping. - * Ideally we would do all conversion including resampling in this one process - * and eliminate the dependency on sox. + * If libsamplerate is available then resample_convert() is used to do all + * conversions. If not then we invoke sox (even for trivial conversions such + * as byte-swapping). The sox support might be removed in a future version. */ -#include -#include "types.h" - -#include -#include -#include -#include -#include -#include -#include -#include +#include "disorder-server.h" +#include "resample.h" -#include "syscalls.h" -#include "log.h" -#include "configuration.h" -#include "speaker-protocol.h" -#include "defs.h" +static char buffer[1024 * 1024]; static const struct option options[] = { { "help", no_argument, 0, 'h' }, @@ -71,20 +56,12 @@ static void help(void) { exit(0); } -/* display version number and terminate */ -static void version(void) { - xprintf("%s", disorder_version_string); - xfclose(stdout); - exit(0); -} - /** @brief Copy bytes from one file descriptor to another * @param infd File descriptor read from * @param outfd File descriptor to write to * @param n Number of bytes to copy */ static void copy(int infd, int outfd, size_t n) { - char buffer[4096]; ssize_t written; while(n > 0) { @@ -94,21 +71,22 @@ static void copy(int infd, int outfd, size_t n) { if(errno == EINTR) continue; else - fatal(errno, "read error"); + disorder_fatal(errno, "read error"); } if(readden == 0) - fatal(0, "unexpected EOF"); + disorder_fatal(0, "unexpected EOF"); n -= readden; written = 0; while(written < readden) { const ssize_t w = write(outfd, buffer + written, readden - written); if(w < 0) - fatal(errno, "write error"); + disorder_fatal(errno, "write error"); written += w; } } } +#if !HAVE_SAMPLERATE_H static void soxargs(const char ***pp, char **qq, const struct stream_header *header) { *(*pp)++ = "-t.raw"; @@ -127,7 +105,7 @@ static void soxargs(const char ***pp, char **qq, case 16: *(*pp)++ = "-w"; break; case 32: *(*pp)++ = "-l"; break; case 64: *(*pp)++ = "-d"; break; - default: fatal(0, "cannot handle sample size %d", header->bits); + default: disorder_fatal(0, "cannot handle sample size %d", header->bits); } break; case 1: @@ -138,80 +116,182 @@ static void soxargs(const char ***pp, char **qq, case ENDIAN_LITTLE: *(*pp)++ = "-L"; break; } if(header->bits % 8) - fatal(0, "cannot handle sample size %d", header->bits); + disorder_fatal(0, "cannot handle sample size %d", header->bits); *qq += sprintf((char *)(*(*pp)++ = *qq), "-%d", header->bits / 8) + 1; break; default: - fatal(0, "unknown sox_generation %ld", config->sox_generation); + disorder_fatal(0, "unknown sox_generation %ld", config->sox_generation); } } +#else +static void converted(uint8_t *bytes, + size_t nbytes, + void attribute((unused)) *cd) { + /*syslog(LOG_INFO, "out: %02x %02x %02x %02x", + bytes[0], + bytes[1], + bytes[2], + bytes[3]);*/ + while(nbytes > 0) { + ssize_t n = write(1, bytes, nbytes); + if(n < 0) + disorder_fatal(errno, "writing to stdout"); + bytes += n; + nbytes -= n; + } +} +#endif int main(int argc, char attribute((unused)) **argv) { struct stream_header header, latest_format; - int n, p[2], outfd = -1, logsyslog = !isatty(2); + int n, outfd = -1, logsyslog = !isatty(2), rs_in_use = 0; pid_t pid = -1; + struct resampler rs[1]; set_progname(argv); if(!setlocale(LC_CTYPE, "")) - fatal(errno, "error calling setlocale"); + disorder_fatal(errno, "error calling setlocale"); while((n = getopt_long(argc, argv, "hVc:dDSs", options, 0)) >= 0) { switch(n) { case 'h': help(); - case 'V': version(); + case 'V': version("disorder-normalize"); case 'c': configfile = optarg; break; case 'd': debugging = 1; break; case 'D': debugging = 0; break; case 'S': logsyslog = 0; break; case 's': logsyslog = 1; break; - default: fatal(0, "invalid option"); + default: disorder_fatal(0, "invalid option"); } } - if(config_read(1)) - fatal(0, "cannot read configuration"); + if(config_read(1, NULL)) + disorder_fatal(0, "cannot read configuration"); if(logsyslog) { openlog(progname, LOG_PID, LOG_DAEMON); log_default = &log_syslog; } memset(&latest_format, 0, sizeof latest_format); for(;;) { + /* Read one header */ n = 0; while((size_t)n < sizeof header) { int r = read(0, (char *)&header + n, sizeof header - n); if(r < 0) { if(errno != EINTR) - fatal(errno, "error reading header"); + disorder_fatal(errno, "error reading header"); } else if(r == 0) { if(n) - fatal(0, "EOF reading header"); + disorder_fatal(0, "EOF reading header"); break; } else n += r; } if(!n) break; + D(("NEW HEADER: %"PRIu32" bytes %"PRIu32"Hz %"PRIu8" channels %"PRIu8" bits %"PRIu8" endian", + header.nbytes, header.rate, header.channels, header.bits, header.endian)); /* Sanity check the header */ if(header.rate < 100 || header.rate > 1000000) - fatal(0, "implausible rate %"PRId32"Hz (%#"PRIx32")", - header.rate, header.rate); + disorder_fatal(0, "implausible rate %"PRId32"Hz (%#"PRIx32")", + header.rate, header.rate); if(header.channels < 1 || header.channels > 2) - fatal(0, "unsupported channel count %d", header.channels); + disorder_fatal(0, "unsupported channel count %d", header.channels); if(header.bits % 8 || !header.bits || header.bits > 64) - fatal(0, "unsupported sample size %d bits", header.bits); + disorder_fatal(0, "unsupported sample size %d bits", header.bits); if(header.endian != ENDIAN_BIG && header.endian != ENDIAN_LITTLE) - fatal(0, "unsupported byte order %x", header.bits); + disorder_fatal(0, "unsupported byte order %d", header.endian); /* Skip empty chunks regardless of their alleged format */ if(header.nbytes == 0) continue; /* If the format has changed we stop/start the converter */ +#if HAVE_SAMPLERATE_H + /* We have libsamplerate */ + if(formats_equal(&header, &config->sample_format)) + /* If the format is already correct then we just write out the data */ + copy(0, 1, header.nbytes); + else { + /* If we have a resampler active already check it is suitable and destroy + * it if not */ + if(rs_in_use) { + D(("call resample_close")); + resample_close(rs); + rs_in_use = 0; + } + /*syslog(LOG_INFO, "%d/%d/%d/%d/%d -> %d/%d/%d/%d/%d", + header.bits, + header.channels, + header.rate, + 1, + header.endian, + config->sample_format.bits, + config->sample_format.channels, + config->sample_format.rate, + 1, + config->sample_format.endian);*/ + if(!rs_in_use) { + /* Create a suitable resampler. */ + D(("call resample_init")); + resample_init(rs, + header.bits, + header.channels, + header.rate, + 1, /* signed */ + header.endian, + config->sample_format.bits, + config->sample_format.channels, + config->sample_format.rate, + 1, /* signed */ + config->sample_format.endian); + latest_format = header; + rs_in_use = 1; + /* TODO speaker protocol does not record signedness of samples. It's + * assumed that they are always signed. This should be fixed in the + * future (and the sample format syntax extended in a compatible + * way). */ + } + /* Feed data through the resampler */ + size_t used = 0, left = header.nbytes; + while(used || left) { + if(left) { + size_t limit = (sizeof buffer) - used; + if(limit > left) + limit = left; + ssize_t r = read(0, buffer + used, limit); + if(r < 0) + disorder_fatal(errno, "reading from stdin"); + if(r == 0) + disorder_fatal(0, "unexpected EOF"); + left -= r; + used += r; + //syslog(LOG_INFO, "read %zd bytes", r); + D(("read %zd bytes", r)); + } + /*syslog(LOG_INFO, " in: %02x %02x %02x %02x", + (uint8_t)buffer[0], + (uint8_t)buffer[1], + (uint8_t)buffer[2], + (uint8_t)buffer[3]);*/ + D(("calling resample_convert used=%zu !left=%d", used, !left)); + const size_t consumed = resample_convert(rs, + (uint8_t *)buffer, used, + !left, + converted, 0); + //syslog(LOG_INFO, "used=%zu consumed=%zu", used, consumed); + D(("consumed=%zu", consumed)); + memmove(buffer, buffer + consumed, used - consumed); + used -= consumed; + } + } +#else + /* We do not have libsamplerate. We will use sox instead. */ if(!formats_equal(&header, &latest_format)) { if(pid != -1) { /* There's a running converter, stop it */ xclose(outfd); if(waitpid(pid, &n, 0) < 0) - fatal(errno, "error calling waitpid"); + disorder_fatal(errno, "error calling waitpid"); if(n) - fatal(0, "sox failed: %#x", n); + disorder_fatal(0, "sox failed: %#x", n); pid = -1; outfd = -1; } @@ -227,6 +307,7 @@ int main(int argc, char attribute((unused)) **argv) { *pp++ = "-"; /* stdout */ *pp = 0; /* This pipe will be sox's stdin */ + int p[2]; xpipe(p); if(!(pid = xfork())) { exitfn = _exit; @@ -234,7 +315,7 @@ int main(int argc, char attribute((unused)) **argv) { xclose(p[0]); xclose(p[1]); execvp(av[0], (char **)av); - fatal(errno, "sox"); + disorder_fatal(errno, "sox"); } xclose(p[0]); outfd = p[1]; @@ -246,16 +327,19 @@ int main(int argc, char attribute((unused)) **argv) { } /* Convert or copy this chunk */ copy(0, outfd, header.nbytes); +#endif } if(outfd != -1) xclose(outfd); if(pid != -1) { /* There's still a converter running */ if(waitpid(pid, &n, 0) < 0) - fatal(errno, "error calling waitpid"); + disorder_fatal(errno, "error calling waitpid"); if(n) - fatal(0, "sox failed: %#x", n); + disorder_fatal(0, "sox failed: %#x", n); } + if(rs_in_use) + resample_close(rs); return 0; }