-#if API_ALSA
-/** @brief ALSA backend initialization */
-static void alsa_init(void) {
- info("selected ALSA backend");
-}
-
-/** @brief ALSA backend activation */
-static int alsa_activate(void) {
- /* If we need to change format then close the current device. */
- if(pcm && !formats_equal(&playing->format, &pcm_format))
- idle();
- if(!pcm) {
- snd_pcm_hw_params_t *hwparams;
- snd_pcm_sw_params_t *swparams;
- snd_pcm_uframes_t pcm_bufsize;
- int err;
- int sample_format = 0;
- unsigned rate;
-
- D(("snd_pcm_open"));
- if((err = snd_pcm_open(&pcm,
- config->device,
- SND_PCM_STREAM_PLAYBACK,
- SND_PCM_NONBLOCK))) {
- error(0, "error from snd_pcm_open: %d", err);
- goto error;
- }
- snd_pcm_hw_params_alloca(&hwparams);
- D(("set up hw params"));
- if((err = snd_pcm_hw_params_any(pcm, hwparams)) < 0)
- fatal(0, "error from snd_pcm_hw_params_any: %d", err);
- if((err = snd_pcm_hw_params_set_access(pcm, hwparams,
- SND_PCM_ACCESS_RW_INTERLEAVED)) < 0)
- fatal(0, "error from snd_pcm_hw_params_set_access: %d", err);
- switch(playing->format.bits) {
- case 8:
- sample_format = SND_PCM_FORMAT_S8;
- break;
- case 16:
- switch(playing->format.byte_format) {
- case AO_FMT_NATIVE: sample_format = SND_PCM_FORMAT_S16; break;
- case AO_FMT_LITTLE: sample_format = SND_PCM_FORMAT_S16_LE; break;
- case AO_FMT_BIG: sample_format = SND_PCM_FORMAT_S16_BE; break;
- error(0, "unrecognized byte format %d", playing->format.byte_format);
- goto fatal;
- }
- break;
- default:
- error(0, "unsupported sample size %d", playing->format.bits);
- goto fatal;
- }
- if((err = snd_pcm_hw_params_set_format(pcm, hwparams,
- sample_format)) < 0) {
- error(0, "error from snd_pcm_hw_params_set_format (%d): %d",
- sample_format, err);
- goto fatal;
- }
- rate = playing->format.rate;
- if((err = snd_pcm_hw_params_set_rate_near(pcm, hwparams, &rate, 0)) < 0) {
- error(0, "error from snd_pcm_hw_params_set_rate (%d): %d",
- playing->format.rate, err);
- goto fatal;
- }
- if(rate != (unsigned)playing->format.rate)
- info("want rate %d, got %u", playing->format.rate, rate);
- if((err = snd_pcm_hw_params_set_channels(pcm, hwparams,
- playing->format.channels)) < 0) {
- error(0, "error from snd_pcm_hw_params_set_channels (%d): %d",
- playing->format.channels, err);
- goto fatal;
- }
- bufsize = 3 * FRAMES;
- pcm_bufsize = bufsize;
- if((err = snd_pcm_hw_params_set_buffer_size_near(pcm, hwparams,
- &pcm_bufsize)) < 0)
- fatal(0, "error from snd_pcm_hw_params_set_buffer_size (%d): %d",
- 3 * FRAMES, err);
- if(pcm_bufsize != 3 * FRAMES && pcm_bufsize != last_pcm_bufsize)
- info("asked for PCM buffer of %d frames, got %d",
- 3 * FRAMES, (int)pcm_bufsize);
- last_pcm_bufsize = pcm_bufsize;
- if((err = snd_pcm_hw_params(pcm, hwparams)) < 0)
- fatal(0, "error calling snd_pcm_hw_params: %d", err);
- D(("set up sw params"));
- snd_pcm_sw_params_alloca(&swparams);
- if((err = snd_pcm_sw_params_current(pcm, swparams)) < 0)
- fatal(0, "error calling snd_pcm_sw_params_current: %d", err);
- if((err = snd_pcm_sw_params_set_avail_min(pcm, swparams, FRAMES)) < 0)
- fatal(0, "error calling snd_pcm_sw_params_set_avail_min %d: %d",
- FRAMES, err);
- if((err = snd_pcm_sw_params(pcm, swparams)) < 0)
- fatal(0, "error calling snd_pcm_sw_params: %d", err);
- pcm_format = playing->format;
- bpf = bytes_per_frame(&pcm_format);
- D(("acquired audio device"));
- log_params(hwparams, swparams);
- ready = 1;
- }
- return 0;
-fatal:
- abandon();
-error:
- /* We assume the error is temporary and that we'll retry in a bit. */
- if(pcm) {
- snd_pcm_close(pcm);
- pcm = 0;
- }
- return -1;
-}
-
-/** @brief Play via ALSA */
-static size_t alsa_play(size_t frames) {
- snd_pcm_sframes_t pcm_written_frames;
- int err;
-
- pcm_written_frames = snd_pcm_writei(pcm,
- playing->buffer + playing->start,
- frames);
- D(("actually play %zu frames, wrote %d",
- frames, (int)pcm_written_frames));
- if(pcm_written_frames < 0) {
- switch(pcm_written_frames) {
- case -EPIPE: /* underrun */
- error(0, "snd_pcm_writei reports underrun");
- if((err = snd_pcm_prepare(pcm)) < 0)
- fatal(0, "error calling snd_pcm_prepare: %d", err);
- return 0;
- case -EAGAIN:
- return 0;
- default:
- fatal(0, "error calling snd_pcm_writei: %d",
- (int)pcm_written_frames);
- }
- } else
- return pcm_written_frames;
-}
-
-static int alsa_slots, alsa_nslots = -1;
-
-/** @brief Fill in poll fd array for ALSA */
-static void alsa_beforepoll(void) {
- /* We send sample data to ALSA as fast as it can accept it, relying on
- * the fact that it has a relatively small buffer to minimize pause
- * latency. */
- int retry = 3, err;
-
- alsa_slots = fdno;
- do {
- retry = 0;
- alsa_nslots = snd_pcm_poll_descriptors(pcm, &fds[fdno], NFDS - fdno);
- if((alsa_nslots <= 0
- || !(fds[alsa_slots].events & POLLOUT))
- && snd_pcm_state(pcm) == SND_PCM_STATE_XRUN) {
- error(0, "underrun detected after call to snd_pcm_poll_descriptors()");
- if((err = snd_pcm_prepare(pcm)))
- fatal(0, "error calling snd_pcm_prepare: %d", err);
- } else
- break;
- } while(retry-- > 0);
- if(alsa_nslots >= 0)
- fdno += alsa_nslots;
-}
-
-/** @brief ALSA deactivation */
-static void alsa_deactivate(void) {
- if(pcm) {
- int err;
-
- if((err = snd_pcm_nonblock(pcm, 0)) < 0)
- fatal(0, "error calling snd_pcm_nonblock: %d", err);
- D(("draining pcm"));
- snd_pcm_drain(pcm);
- D(("closing pcm"));
- snd_pcm_close(pcm);
- pcm = 0;
- forceplay = 0;
- D(("released audio device"));
- }
-}
-#endif
-
-/** @brief Command backend initialization */
-static void command_init(void) {
- info("selected command backend");
- fork_cmd();
-}
-
-/** @brief Play to a subprocess */
-static size_t command_play(size_t frames) {
- size_t bytes = frames * bpf;
- int written_bytes;
-
- written_bytes = write(cmdfd, playing->buffer + playing->start, bytes);
- D(("actually play %zu bytes, wrote %d",
- bytes, written_bytes));
- if(written_bytes < 0) {
- switch(errno) {
- case EPIPE:
- error(0, "hmm, command died; trying another");
- fork_cmd();
- return 0;
- case EAGAIN:
- return 0;
- default:
- fatal(errno, "error writing to subprocess");
- }
- } else
- return written_bytes / bpf;
-}
-
-static int cmdfd_slot;
-
-/** @brief Update poll array for writing to subprocess */
-static void command_beforepoll(void) {
- /* We send sample data to the subprocess as fast as it can accept it.
- * This isn't ideal as pause latency can be very high as a result. */
- if(cmdfd >= 0)
- cmdfd_slot = addfd(cmdfd, POLLOUT);
-}
-
-/** @brief Command/network backend activation */
-static int generic_activate(void) {
- if(!ready) {
- bufsize = 3 * FRAMES;
- bpf = bytes_per_frame(&config->sample_format);
- D(("acquired audio device"));
- ready = 1;
- }
- return 0;
-}
-
-/** @brief Network backend initialization */
-static void network_init(void) {
- struct addrinfo *res, *sres;
- static const struct addrinfo pref = {
- 0,
- PF_INET,
- SOCK_DGRAM,
- IPPROTO_UDP,
- 0,
- 0,
- 0,
- 0
- };
- static const struct addrinfo prefbind = {
- AI_PASSIVE,
- PF_INET,
- SOCK_DGRAM,
- IPPROTO_UDP,
- 0,
- 0,
- 0,
- 0
- };
- static const int one = 1;
- int sndbuf, target_sndbuf = 131072;
- socklen_t len;
- char *sockname, *ssockname;
-
- res = get_address(&config->broadcast, &pref, &sockname);
- if(!res) exit(-1);
- if(config->broadcast_from.n) {
- sres = get_address(&config->broadcast_from, &prefbind, &ssockname);
- if(!sres) exit(-1);
- } else
- sres = 0;
- if((bfd = socket(res->ai_family,
- res->ai_socktype,
- res->ai_protocol)) < 0)
- fatal(errno, "error creating broadcast socket");
- if(setsockopt(bfd, SOL_SOCKET, SO_BROADCAST, &one, sizeof one) < 0)
- fatal(errno, "error setting SO_BROADCAST on broadcast socket");
- len = sizeof sndbuf;
- if(getsockopt(bfd, SOL_SOCKET, SO_SNDBUF,
- &sndbuf, &len) < 0)
- fatal(errno, "error getting SO_SNDBUF");
- if(target_sndbuf > sndbuf) {
- if(setsockopt(bfd, SOL_SOCKET, SO_SNDBUF,
- &target_sndbuf, sizeof target_sndbuf) < 0)
- error(errno, "error setting SO_SNDBUF to %d", target_sndbuf);
- else
- info("changed socket send buffer size from %d to %d",
- sndbuf, target_sndbuf);
- } else
- info("default socket send buffer is %d",
- sndbuf);
- /* We might well want to set additional broadcast- or multicast-related
- * options here */
- if(sres && bind(bfd, sres->ai_addr, sres->ai_addrlen) < 0)
- fatal(errno, "error binding broadcast socket to %s", ssockname);
- if(connect(bfd, res->ai_addr, res->ai_addrlen) < 0)
- fatal(errno, "error connecting broadcast socket to %s", sockname);
- /* Select an SSRC */
- gcry_randomize(&rtp_id, sizeof rtp_id, GCRY_STRONG_RANDOM);
- info("selected network backend, sending to %s", sockname);
- if(config->sample_format.byte_format != AO_FMT_BIG) {
- info("forcing big-endian sample format");
- config->sample_format.byte_format = AO_FMT_BIG;
- }
-}
-
-/** @brief Play over the network */
-static size_t network_play(size_t frames) {
- struct rtp_header header;
- struct iovec vec[2];
- size_t bytes = frames * bpf, written_frames;
- int written_bytes;
- /* We transmit using RTP (RFC3550) and attempt to conform to the internet
- * AVT profile (RFC3551). */
-
- if(idled) {
- /* There may have been a gap. Fix up the RTP time accordingly. */
- struct timeval now;
- uint64_t delta;
- uint64_t target_rtp_time;
-
- /* Find the current time */
- xgettimeofday(&now, 0);
- /* Find the number of microseconds elapsed since rtp_time=0 */
- delta = tvsub_us(now, rtp_time_0);
- assert(delta <= UINT64_MAX / 88200);
- target_rtp_time = (delta * playing->format.rate
- * playing->format.channels) / 1000000;
- /* Overflows at ~6 years uptime with 44100Hz stereo */
-
- /* rtp_time is the number of samples we've played. NB that we play
- * RTP_AHEAD_MS ahead of ourselves, so it may legitimately be ahead of
- * the value we deduce from time comparison.
- *
- * Suppose we have 1s track started at t=0, and another track begins to
- * play at t=2s. Suppose RTP_AHEAD_MS=1000 and 44100Hz stereo. In that
- * case we'll send 1s of audio as fast as we can, giving rtp_time=88200.
- * rtp_time stops at this point.
- *
- * At t=2s we'll have calculated target_rtp_time=176400. In this case we
- * set rtp_time=176400 and the player can correctly conclude that it
- * should leave 1s between the tracks.
- *
- * Suppose instead that the second track arrives at t=0.5s, and that
- * we've managed to transmit the whole of the first track already. We'll
- * have target_rtp_time=44100.
- *
- * The desired behaviour is to play the second track back to back with
- * first. In this case therefore we do not modify rtp_time.
- *
- * Is it ever right to reduce rtp_time? No; for that would imply
- * transmitting packets with overlapping timestamp ranges, which does not
- * make sense.
- */
- if(target_rtp_time > rtp_time) {
- /* More time has elapsed than we've transmitted samples. That implies
- * we've been 'sending' silence. */
- info("advancing rtp_time by %"PRIu64" samples",
- target_rtp_time - rtp_time);
- rtp_time = target_rtp_time;
- } else if(target_rtp_time < rtp_time) {
- const int64_t samples_ahead = ((uint64_t)RTP_AHEAD_MS
- * config->sample_format.rate
- * config->sample_format.channels
- / 1000);
-
- if(target_rtp_time + samples_ahead < rtp_time) {
- info("reversing rtp_time by %"PRIu64" samples",
- rtp_time - target_rtp_time);
- }