+/** @brief Return true if A and B denote identical libao formats, else false */
+static int formats_equal(const ao_sample_format *a,
+ const ao_sample_format *b) {
+ return (a->bits == b->bits
+ && a->rate == b->rate
+ && a->channels == b->channels
+ && a->byte_format == b->byte_format);
+}
+
+/** @brief Compute arguments to sox */
+static void soxargs(const char ***pp, char **qq, ao_sample_format *ao) {
+ int n;
+
+ *(*pp)++ = "-t.raw";
+ *(*pp)++ = "-s";
+ *(*pp)++ = *qq; n = sprintf(*qq, "-r%d", ao->rate); *qq += n + 1;
+ *(*pp)++ = *qq; n = sprintf(*qq, "-c%d", ao->channels); *qq += n + 1;
+ /* sox 12.17.9 insists on -b etc; CVS sox insists on -<n> etc; both are
+ * deployed! */
+ switch(config->sox_generation) {
+ case 0:
+ if(ao->bits != 8
+ && ao->byte_format != AO_FMT_NATIVE
+ && ao->byte_format != MACHINE_AO_FMT) {
+ *(*pp)++ = "-x";
+ }
+ switch(ao->bits) {
+ case 8: *(*pp)++ = "-b"; break;
+ case 16: *(*pp)++ = "-w"; break;
+ case 32: *(*pp)++ = "-l"; break;
+ case 64: *(*pp)++ = "-d"; break;
+ default: fatal(0, "cannot handle sample size %d", (int)ao->bits);
+ }
+ break;
+ case 1:
+ switch(ao->byte_format) {
+ case AO_FMT_NATIVE: break;
+ case AO_FMT_BIG: *(*pp)++ = "-B"; break;
+ case AO_FMT_LITTLE: *(*pp)++ = "-L"; break;
+ }
+ *(*pp)++ = *qq; n = sprintf(*qq, "-%d", ao->bits/8); *qq += n + 1;
+ break;
+ }
+}
+
+/** @brief Enable format translation
+ *
+ * If necessary, replaces a tracks inbound file descriptor with one connected
+ * to a sox invocation, which performs the required translation.
+ */
+static void enable_translation(struct track *t) {
+ switch(config->speaker_backend) {
+ case BACKEND_COMMAND:
+ case BACKEND_NETWORK:
+ /* These backends need a specific sample format */
+ break;
+ case BACKEND_ALSA:
+ /* ALSA can cope */
+ return;
+ }
+ if(!formats_equal(&t->format, &config->sample_format)) {
+ char argbuf[1024], *q = argbuf;
+ const char *av[18], **pp = av;
+ int soxpipe[2];
+ pid_t soxkid;
+
+ *pp++ = "sox";
+ soxargs(&pp, &q, &t->format);
+ *pp++ = "-";
+ soxargs(&pp, &q, &config->sample_format);
+ *pp++ = "-";
+ *pp++ = 0;
+ if(debugging) {
+ for(pp = av; *pp; pp++)
+ D(("sox arg[%d] = %s", pp - av, *pp));
+ D(("end args"));
+ }
+ xpipe(soxpipe);
+ soxkid = xfork();
+ if(soxkid == 0) {
+ signal(SIGPIPE, SIG_DFL);
+ xdup2(t->fd, 0);
+ xdup2(soxpipe[1], 1);
+ fcntl(0, F_SETFL, fcntl(0, F_GETFL) & ~O_NONBLOCK);
+ close(soxpipe[0]);
+ close(soxpipe[1]);
+ close(t->fd);
+ execvp("sox", (char **)av);
+ _exit(1);
+ }
+ D(("forking sox for format conversion (kid = %d)", soxkid));
+ close(t->fd);
+ close(soxpipe[1]);
+ t->fd = soxpipe[0];
+ t->format = config->sample_format;
+ ready = 0;
+ }
+}
+
+/** @brief Read data into a sample buffer
+ * @param t Pointer to track
+ * @return 0 on success, -1 on EOF
+ *
+ * This is effectively the read callback on @c t->fd.
+ */