X-Git-Url: https://git.distorted.org.uk/~mdw/disorder/blobdiff_plain/0763e1f402fe7bb6c2b7618efb4838810c7ff0e6..544a9ec143de5670072bae8114834272ced98bcd:/server/speaker.c?ds=sidebyside diff --git a/server/speaker.c b/server/speaker.c index bbaedb9..d3aea99 100644 --- a/server/speaker.c +++ b/server/speaker.c @@ -219,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 */ @@ -457,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; } @@ -562,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; @@ -589,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 @@ -760,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; @@ -914,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 */ @@ -922,8 +947,13 @@ 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) { bufsize = 3 * FRAMES; bpf = bytes_per_frame(&config->sample_format); @@ -1003,15 +1033,9 @@ static void network_init(void) { } } -/** @brief Network backend activation */ -static int network_activate(void) { - if(!ready) { - 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 */ @@ -1021,22 +1045,28 @@ static const struct speaker_backend backends[] = { 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, 0 } + { -1, 0, 0, 0, 0, 0 } }; int main(int argc, char **argv) {