X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~mdw/git/disorder/blobdiff_plain/dc450d306ec47916c28a11b84d9757f0e1921460..3496051ff14c92ced851b6febd9b4f4bfea4b10c:/server/speaker.c diff --git a/server/speaker.c b/server/speaker.c index 2c742f7..a6c5f43 100644 --- a/server/speaker.c +++ b/server/speaker.c @@ -1,6 +1,7 @@ /* * This file is part of DisOrder * Copyright (C) 2005, 2006, 2007 Richard Kettlewell + * Portions (C) 2007 Mark Wooding * * 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 @@ -117,6 +118,8 @@ static const struct option options[] = { { "config", required_argument, 0, 'c' }, { "debug", no_argument, 0, 'd' }, { "no-debug", no_argument, 0, 'D' }, + { "syslog", no_argument, 0, 's' }, + { "no-syslog", no_argument, 0, 'S' }, { 0, 0, 0, 0 } }; @@ -129,6 +132,7 @@ static void help(void) { " --version, -V Display version number\n" " --config PATH, -c PATH Set configuration file\n" " --debug, -d Turn on debugging\n" + " --[no-]syslog Force logging\n" "\n" "Speaker process for DisOrder. Not intended to be run\n" "directly.\n"); @@ -192,7 +196,7 @@ static void destroy(struct track *t) { * main loop whenever the track's file descriptor is readable, assuming the * buffer has not reached the maximum allowed occupancy. */ -static int fill(struct track *t) { +static int speaker_fill(struct track *t) { size_t where, left; int n; @@ -215,9 +219,12 @@ static int fill(struct track *t) { if(n == 0) { D(("fill %s: eof detected", t->id)); t->eof = 1; + t->playable = 1; return -1; } t->used += n; + if(t->used == sizeof t->buffer) + t->playable = 1; } return 0; } @@ -277,6 +284,17 @@ static void maybe_finished(void) { abandon(); } +/** @brief Return nonzero if we want to play some audio + * + * We want to play audio if there is a current track; and it is not paused; and + * it is playable according to the rules for @ref track::playable. + */ +static int playable(void) { + return playing + && !paused + && playing->playable; +} + /** @brief Play up to @p frames frames of audio * * It is always safe to call this function. @@ -287,15 +305,15 @@ static void maybe_finished(void) { * - If there is not enough audio to play then it play what is available. * * If there are not enough frames to play then whatever is available is played - * instead. It is up to mainloop() to ensure that play() is not called when - * unreasonably only an small amounts of data is available to play. + * instead. It is up to mainloop() to ensure that speaker_play() is not called + * when unreasonably only an small amounts of data is available to play. */ -static void play(size_t frames) { +static void speaker_play(size_t frames) { size_t avail_frames, avail_bytes, written_frames; ssize_t written_bytes; - /* Make sure there's a track to play and it is not pasued */ - if(!playing || paused) + /* Make sure there's a track to play and it is not paused */ + if(!playable()) return; /* Make sure the output device is open */ if(device_state != device_open) { @@ -333,6 +351,11 @@ static void play(size_t frames) { * empty) wrap it back to the start. */ if(!playing->used || playing->start == (sizeof playing->buffer)) playing->start = 0; + /* If the buffer emptied out mark the track as unplayably */ + if(!playing->used && !playing->eof) { + error(0, "track buffer emptied"); + playing->playable = 0; + } frames -= written_frames; return; } @@ -372,26 +395,20 @@ int addfd(int fd, int events) { /** @brief Table of speaker backends */ static const struct speaker_backend *backends[] = { -#if API_ALSA +#if HAVE_ALSA_ASOUNDLIB_H &alsa_backend, #endif &command_backend, &network_backend, +#if HAVE_COREAUDIO_AUDIOHARDWARE_H + &coreaudio_backend, +#endif +#if HAVE_SYS_SOUNDCARD_H + &oss_backend, +#endif 0 }; -/** @brief Return nonzero if we want to play some audio - * - * We want to play audio if there is a current track; and it is not paused; and - * there are at least @ref FRAMES frames of audio to play, or we are in sight - * of the end of the current track. - */ -static int playable(void) { - return playing - && !paused - && (playing->used >= FRAMES || playing->eof); -} - /** @brief Main event loop */ static void mainloop(void) { struct track *t; @@ -426,7 +443,7 @@ static void mainloop(void) { * instead, but the post-poll code will cope even if it's * device_closed. */ if(device_state == device_open) - backend->beforepoll(); + backend->beforepoll(&timeout); } /* If any other tracks don't have a full buffer, try to read sample data * from them. We do this last of all, so that if we run out of slots, @@ -451,15 +468,15 @@ static void mainloop(void) { /* We want to play some audio */ if(device_state == device_open) { if(backend->ready()) - play(3 * FRAMES); + speaker_play(3 * FRAMES); } else { /* We must be in _closed or _error, and it should be the latter, but we * cope with either. * - * We most likely timed out, so now is a good time to retry. play() - * knows to re-activate the device if necessary. + * We most likely timed out, so now is a good time to retry. + * speaker_play() knows to re-activate the device if necessary. */ - play(3 * FRAMES); + speaker_play(3 * FRAMES); } } /* Perhaps a connection has arrived */ @@ -470,6 +487,7 @@ static void mainloop(void) { char id[24]; if((fd = accept(listenfd, (struct sockaddr *)&addr, &addrlen)) >= 0) { + blocking(fd); if(read(fd, &l, sizeof l) < 4) { error(errno, "reading length from inbound connection"); xclose(fd); @@ -485,7 +503,7 @@ static void mainloop(void) { t = findtrack(id, 1/*create*/); write(fd, "", 1); /* write an ack */ if(t->fd != -1) { - error(0, "got a connection for a track that already has one"); + error(0, "%s: already got a connection", id); xclose(fd); } else { nonblock(fd); @@ -512,9 +530,9 @@ static void mainloop(void) { error(0, "cannot play track because no connection arrived"); playing = t; /* We attempt to play straight away rather than going round the loop. - * play() is clever enough to perform any activation that is + * speaker_play() is clever enough to perform any activation that is * required. */ - play(3 * FRAMES); + speaker_play(3 * FRAMES); report(); break; case SM_PAUSE: @@ -528,7 +546,7 @@ static void mainloop(void) { paused = 0; /* As for SM_PLAY we attempt to play straight away. */ if(playing) - play(3 * FRAMES); + speaker_play(3 * FRAMES); } report(); break; @@ -561,7 +579,7 @@ static void mainloop(void) { if(t->fd != -1 && t->slot != -1 && (fds[t->slot].revents & (POLLIN | POLLHUP))) - fill(t); + speaker_fill(t); /* Maybe we finished playing a track somewhere in the above */ maybe_finished(); /* If we don't need the sound device for now then close it for the benefit @@ -575,25 +593,28 @@ static void mainloop(void) { } int main(int argc, char **argv) { - int n; + int n, logsyslog = !isatty(2); struct sockaddr_un addr; static const int one = 1; + struct speaker_message sm; + const char *d; set_progname(argv); if(!setlocale(LC_CTYPE, "")) fatal(errno, "error calling setlocale"); - while((n = getopt_long(argc, argv, "hVc:dD", options, 0)) >= 0) { + while((n = getopt_long(argc, argv, "hVc:dDSs", options, 0)) >= 0) { switch(n) { case 'h': help(); case 'V': version(); 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"); } } - if(getenv("DISORDER_DEBUG_SPEAKER")) debugging = 1; - /* If stderr is a TTY then log there, otherwise to syslog. */ - if(!isatty(2)) { + if((d = getenv("DISORDER_DEBUG_SPEAKER"))) debugging = atoi(d); + if(logsyslog) { openlog(progname, LOG_PID, LOG_DAEMON); log_default = &log_syslog; } @@ -632,6 +653,9 @@ int main(int argc, char **argv) { xlisten(listenfd, 128); nonblock(listenfd); info("listening on %s", addr.sun_path); + memset(&sm, 0, sizeof sm); + sm.type = SM_READY; + speaker_send(1, &sm); mainloop(); info("stopped (parent terminated)"); exit(0);