X-Git-Url: https://git.distorted.org.uk/~mdw/disorder/blobdiff_plain/50ae38dd3f0fa96b2b50cbb80c18ed7c5c01ec7b..544a9ec143de5670072bae8114834272ced98bcd:/server/speaker.c diff --git a/server/speaker.c b/server/speaker.c index b270f68..d3aea99 100644 --- a/server/speaker.c +++ b/server/speaker.c @@ -133,7 +133,6 @@ static struct track { static time_t last_report; /* when we last reported */ static int paused; /* pause status */ -static ao_sample_format pcm_format; /* current format if aodev != 0 */ static size_t bpf; /* bytes per frame */ static struct pollfd fds[NFDS]; /* if we need more than that */ static int fdno; /* fd number */ @@ -142,6 +141,7 @@ static size_t bufsize; /* buffer size */ /** @brief The current PCM handle */ static snd_pcm_t *pcm; static snd_pcm_uframes_t last_pcm_bufsize; /* last seen buffer size */ +static ao_sample_format pcm_format; /* current format if aodev != 0 */ #endif /** @brief Ready to send audio @@ -188,6 +188,15 @@ struct speaker_backend { * @c -1 terminates the list. */ int backend; + + /** @brief Flags + * + * Possible values + * - @ref FIXED_FORMAT + */ + unsigned flags; +/** @brief Lock to configured sample format */ +#define FIXED_FORMAT 0x0001 /** @brief Initialization * @@ -210,6 +219,19 @@ struct speaker_backend { * don't demand a single fixed sample format for the lifetime of the server). */ int (*activate)(void); + + /** @brief Play sound + * @param frames Number of frames to play + * @return Number of frames actually played + */ + size_t (*play)(size_t frames); + + /** @brief Deactivation + * + * Called to deactivate the sound device. This is the inverse of + * @c activate above. + */ + void (*deactivate)(void); }; /** @brief Selected backend */ @@ -352,16 +374,8 @@ static void soxargs(const char ***pp, char **qq, ao_sample_format *ao) { * 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)) { + if((backend->flags & FIXED_FORMAT) + && !formats_equal(&t->format, &config->sample_format)) { char argbuf[1024], *q = argbuf; const char *av[18], **pp = av; int soxpipe[2]; @@ -456,21 +470,8 @@ static int fill(struct track *t) { /** @brief Close the sound device */ static void idle(void) { D(("idle")); -#if API_ALSA - if(config->speaker_backend == BACKEND_ALSA && 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 + if(backend->deactivate) + backend->deactivate(); idled = 1; ready = 0; } @@ -561,11 +562,12 @@ static void fork_cmd(void) { } static void play(size_t frames) { - size_t avail_bytes, write_bytes, written_frames; + size_t avail_frames, avail_bytes, write_bytes, written_frames; ssize_t written_bytes; struct rtp_header header; struct iovec vec[2]; + /* Make sure the output device is activated */ if(activate()) { if(playing) forceplay = frames; @@ -588,43 +590,22 @@ static void play(size_t frames) { forceplay = 0; /* Figure out how many frames there are available to write */ if(playing->start + playing->used > playing->size) + /* The ring buffer is currently wrapped, only play up to the wrap point */ avail_bytes = playing->size - playing->start; else + /* The ring buffer is not wrapped, can play the lot */ avail_bytes = playing->used; + avail_frames = avail_bytes / bpf; + /* Only play up to the requested amount */ + if(avail_frames > frames) + avail_frames = frames; + if(!avail_frames) + return; switch(config->speaker_backend) { #if API_ALSA case BACKEND_ALSA: { - snd_pcm_sframes_t pcm_written_frames; - size_t avail_frames; - int err; - - avail_frames = avail_bytes / bpf; - if(avail_frames > frames) - avail_frames = frames; - if(!avail_frames) - return; - pcm_written_frames = snd_pcm_writei(pcm, - playing->buffer + playing->start, - avail_frames); - D(("actually play %zu frames, wrote %d", - avail_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; - case -EAGAIN: - return; - default: - fatal(0, "error calling snd_pcm_writei: %d", - (int)pcm_written_frames); - } - } - written_frames = pcm_written_frames; - written_bytes = written_frames * bpf; + written_frames = backend->play(avail_frames); break; } #endif @@ -759,6 +740,7 @@ static void play(size_t frames) { default: assert(!"reached"); } + written_bytes = written_frames * bpf; /* written_bytes and written_frames had better both be set and correct by * this point */ playing->start += written_bytes; @@ -913,6 +895,50 @@ error: } 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; +} + +/** @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 */ @@ -921,10 +947,14 @@ static void command_init(void) { fork_cmd(); } -/** @brief Command backend activation */ -static int command_activate(void) { +/** @brief Play to a subprocess */ +static size_t command_play(size_t frames) { + return frames; +} + +/** @brief Command/network backend activation */ +static int generic_activate(void) { if(!ready) { - pcm_format = config->sample_format; bufsize = 3 * FRAMES; bpf = bytes_per_frame(&config->sample_format); D(("acquired audio device")); @@ -1003,16 +1033,9 @@ static void network_init(void) { } } -/** @brief Network backend activation */ -static int network_activate(void) { - if(!ready) { - pcm_format = config->sample_format; - bufsize = 3 * FRAMES; - bpf = bytes_per_frame(&config->sample_format); - D(("acquired audio device")); - ready = 1; - } - return 0; +/** @brief Play over the network */ +static size_t network_play(size_t frames) { + return frames; } /** @brief Table of speaker backends */ @@ -1020,21 +1043,30 @@ static const struct speaker_backend backends[] = { #if API_ALSA { BACKEND_ALSA, + 0, alsa_init, - alsa_activate + alsa_activate, + alsa_play, + alsa_deactivate }, #endif { BACKEND_COMMAND, + FIXED_FORMAT, command_init, - command_activate + generic_activate, + command_play, + 0 /* deactivate */ }, { BACKEND_NETWORK, + FIXED_FORMAT, network_init, - network_activate + generic_activate, + network_play, + 0 /* deactivate */ }, - { -1, 0, 0 } + { -1, 0, 0, 0, 0, 0 } }; int main(int argc, char **argv) {