chiark / gitweb /
move speaker declarations to speaker.h
[disorder] / server / speaker.c
index 994c4b40f73aec6647f87f374ab08ace09087bb3..172671b0ec8d994ad8042899f667b297bb8f1421 100644 (file)
@@ -18,7 +18,7 @@
  * USA
  */
 /** @file server/speaker.c
- * @brief Speaker processs
+ * @brief Speaker process
  *
  * This program is responsible for transmitting a single coherent audio stream
  * to its destination (over the network, to some sound API, to some
 #include "log.h"
 #include "defs.h"
 #include "mem.h"
-#include "speaker.h"
+#include "speaker-protocol.h"
 #include "user.h"
 #include "addr.h"
 #include "timeval.h"
 #include "rtp.h"
+#include "speaker.h"
 
 #if API_ALSA
 #include <alsa/asoundlib.h>
 #endif
 
-#ifdef WORDS_BIGENDIAN
-# define MACHINE_AO_FMT AO_FMT_BIG
-#else
-# define MACHINE_AO_FMT AO_FMT_LITTLE
-#endif
-
-/** @brief How many seconds of input to buffer
- *
- * While any given connection has this much audio buffered, no more reads will
- * be issued for that connection.  The decoder will have to wait.
- */
-#define BUFFER_SECONDS 5
-
-#define FRAMES 4096                     /* Frame batch size */
-
-/** @brief Bytes to send per network packet
- *
- * Don't make this too big or arithmetic will start to overflow.
- */
-#define NETWORK_BYTES (1024+sizeof(struct rtp_header))
-
-/** @brief Maximum RTP playahead (ms) */
-#define RTP_AHEAD_MS 1000
+/** @brief Linked list of all prepared tracks */
+struct track *tracks;
 
-/** @brief Maximum number of FDs to poll for */
-#define NFDS 256
-
-/** @brief Track structure
- *
- * Known tracks are kept in a linked list.  Usually there will be at most two
- * of these but rearranging the queue can cause there to be more.
- */
-static struct track {
-  struct track *next;                   /* next track */
-  int fd;                               /* input FD */
-  char id[24];                          /* ID */
-  size_t start, used;                   /* start + bytes used */
-  int eof;                              /* input is at EOF */
-  int got_format;                       /* got format yet? */
-  ao_sample_format format;              /* sample format */
-  unsigned long long played;            /* number of frames played */
-  char *buffer;                         /* sample buffer */
-  size_t size;                          /* sample buffer size */
-  int slot;                             /* poll array slot */
-} *tracks, *playing;                    /* all tracks + playing track */
+/** @brief Playing track, or NULL */
+struct track *playing;
 
 static time_t last_report;              /* when we last reported */
 static int paused;                      /* pause status */
@@ -159,9 +120,25 @@ static ao_sample_format pcm_format;     /* current format if aodev != 0 */
  */
 static int ready;
 
-static int forceplay;                   /* frames to force play */
-static int cmdfd = -1;                  /* child process input */
-static int bfd = -1;                    /* broadcast FD */
+/** @brief Frames to force-play
+ *
+ * If this is nonzero, and playing is enabled, then the main loop will attempt
+ * to play this many frames without checking whether the output device is
+ * ready.
+ */
+static int forceplay;
+
+/** @brief Pipe to subprocess
+ *
+ * This is the file descriptor to write to for @ref BACKEND_COMMAND.
+ */
+static int cmdfd = -1;
+
+/** @brief Network socket
+ *
+ * This is the file descriptor to write to for @ref BACKEND_NETWORK.
+ */
+static int bfd = -1;
 
 /** @brief RTP timestamp
  *
@@ -183,70 +160,21 @@ static uint64_t rtp_time;
  */
 static struct timeval rtp_time_0;
 
-static uint16_t rtp_seq;                /* frame sequence number */
-static uint32_t rtp_id;                 /* RTP SSRC */
-static int idled;                       /* set when idled */
-static int audio_errors;                /* audio error counter */
+/** @brief RTP packet sequence number */
+static uint16_t rtp_seq;
 
-/** @brief Structure of a backend */
-struct speaker_backend {
-  /** @brief Which backend this is
-   *
-   * @c -1 terminates the list.
-   */
-  int backend;
+/** @brief RTP SSRC */
+static uint32_t rtp_id;
 
-  /** @brief Flags
-   *
-   * Possible values
-   * - @ref FIXED_FORMAT
-   */
-  unsigned flags;
-/** @brief Lock to configured sample format */
-#define FIXED_FORMAT 0x0001
-  
-  /** @brief Initialization
-   *
-   * Called once at startup.  This is responsible for one-time setup
-   * operations, for instance opening a network socket to transmit to.
-   *
-   * When writing to a native sound API this might @b not imply opening the
-   * native sound device - that might be done by @c activate below.
-   */
-  void (*init)(void);
-
-  /** @brief Activation
-   * @return 0 on success, non-0 on error
-   *
-   * Called to activate the output device.
-   *
-   * After this function succeeds, @ref ready should be non-0.  As well as
-   * opening the audio device, this function is responsible for reconfiguring
-   * if it necessary to cope with different samples formats (for backends that
-   * 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 Set when idled
+ *
+ * This is set when the sound device is deliberately closed by idle().
+ * @ref ready is set to 0 at the same time.
+ */
+static int idled;                       /* set when idled */
 
-  /** @brief Called before poll()
-   *
-   * Called before the call to poll().  Should call addfd() to update the FD
-   * array and stash the slot number somewhere safe.
-   */
-  void (*beforepoll)(void);
-};
+/** @brief Error counter */
+static int audio_errors;
 
 /** @brief Selected backend */
 static const struct speaker_backend *backend;
@@ -431,7 +359,9 @@ static void enable_translation(struct track *t) {
  * @param t Pointer to track
  * @return 0 on success, -1 on EOF
  *
- * This is effectively the read callback on @c t->fd.
+ * This is effectively the read callback on @c t->fd.  It is called from the
+ * 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) {
   size_t where, left;
@@ -481,7 +411,12 @@ static int fill(struct track *t) {
   return 0;
 }
 
-/** @brief Close the sound device */
+/** @brief Close the sound device
+ *
+ * This is called to deactivate the output device when pausing, and also by the
+ * ALSA backend when changing encoding (in which case the sound device will be
+ * immediately reactivated).
+ */
 static void idle(void) {
   D(("idle"));
   if(backend->deactivate)
@@ -547,7 +482,13 @@ static int activate(void) {
   return backend->activate();
 }
 
-/* Check to see whether the current track has finished playing */
+/** @brief Check whether the current track has finished
+ *
+ * The current track is determined to have finished either if the input stream
+ * eded before the format could be determined (i.e. it is malformed) or the
+ * input is at end of file and there is less than a frame left unplayed.  (So
+ * it copes with decoders that crash mid-frame.)
+ */
 static void maybe_finished(void) {
   if(playing
      && playing->eof
@@ -556,6 +497,7 @@ static void maybe_finished(void) {
     abandon();
 }
 
+/** @brief Start the subprocess for @ref BACKEND_COMMAND */
 static void fork_cmd(void) {
   pid_t cmdpid;
   int pfd[2];
@@ -575,6 +517,7 @@ static void fork_cmd(void) {
   D(("forked cmd %d, fd = %d", cmdpid, cmdfd));
 }
 
+/** @brief Play up to @p frames frames of audio */
 static void play(size_t frames) {
   size_t avail_frames, avail_bytes, written_frames;
   ssize_t written_bytes;
@@ -824,6 +767,25 @@ static void alsa_beforepoll(void) {
     fdno += alsa_nslots;
 }
 
+/** @brief Process poll() results for ALSA */
+static int alsa_afterpoll(void) {
+  int err;
+
+  if(alsa_slots != -1) {
+    unsigned short alsa_revents;
+    
+    if((err = snd_pcm_poll_descriptors_revents(pcm,
+                                               &fds[alsa_slots],
+                                               alsa_nslots,
+                                               &alsa_revents)) < 0)
+      fatal(0, "error calling snd_pcm_poll_descriptors_revents: %d", err);
+    if(alsa_revents & (POLLOUT | POLLERR))
+      play(3 * FRAMES);
+    return 0;
+  } else
+    return 1;
+}
+
 /** @brief ALSA deactivation */
 static void alsa_deactivate(void) {
   if(pcm) {
@@ -881,6 +843,16 @@ static void command_beforepoll(void) {
     cmdfd_slot = addfd(cmdfd, POLLOUT);
 }
 
+/** @brief Process poll() results for subprocess play */
+static int command_afterpoll(void) {
+  if(cmdfd_slot != -1) {
+    if(fds[cmdfd_slot].revents & (POLLOUT | POLLERR))
+      play(3 * FRAMES);
+    return 0;
+  } else
+    return -1;
+}
+
 /** @brief Command/network backend activation */
 static int generic_activate(void) {
   if(!ready) {
@@ -1100,6 +1072,16 @@ static void network_beforepoll(void) {
     bfd_slot = addfd(bfd, POLLOUT);
 }
 
+/** @brief Process poll() results for network play */
+static int network_afterpoll(void) {
+  if(bfd_slot != -1) {
+    if(fds[bfd_slot].revents & (POLLOUT | POLLERR))
+      play(3 * FRAMES);
+    return 0;
+  } else
+    return 1;
+}
+
 /** @brief Table of speaker backends */
 static const struct speaker_backend backends[] = {
 #if API_ALSA
@@ -1110,7 +1092,8 @@ static const struct speaker_backend backends[] = {
     alsa_activate,
     alsa_play,
     alsa_deactivate,
-    alsa_beforepoll
+    alsa_beforepoll,
+    alsa_afterpoll
   },
 #endif
   {
@@ -1120,7 +1103,8 @@ static const struct speaker_backend backends[] = {
     generic_activate,
     command_play,
     0,                                  /* deactivate */
-    command_beforepoll
+    command_beforepoll,
+    command_afterpoll
   },
   {
     BACKEND_NETWORK,
@@ -1129,57 +1113,59 @@ static const struct speaker_backend backends[] = {
     generic_activate,
     network_play,
     0,                                  /* deactivate */
-    network_beforepoll
+    network_beforepoll,
+    network_afterpoll
   },
-  { -1, 0, 0, 0, 0, 0, 0 }
+  { -1, 0, 0, 0, 0, 0, 0, 0 }           /* end of list */
 };
 
-int main(int argc, char **argv) {
-  int n, fd, stdin_slot, poke, timeout;
+/** @brief Main event loop
+ *
+ * This has grown in a rather bizarre and ad-hoc way is very sensitive to
+ * changes...
+ *
+ * Firstly the loop is terminated when the parent process exits.  Therefore the
+ * speaker process has the same lifetime as the main server.  This and the
+ * reading of data from decoders is comprehensible enough.
+ *
+ * The playing of audio is more complicated however.
+ *
+ * On the first run through when a track is ready to be played, @ref ready and
+ * @ref forceplay will both be zero.  Therefore @c beforepoll is not called.
+ *
+ * @c afterpoll on the other hand @b is called and will return nonzero.  The
+ * result is that we call @c play(0).  This will call activate(), setting
+ * @ref ready nonzero, but otherwise has no immediate effect.
+ *
+ * We then deal with stdin and the decoders.
+ *
+ * We then reach the second place we might play some audio.  @ref forceplay is
+ * 0 so nothing happens here again.
+ *
+ * On the next iteration through however @ref ready is nonzero, and @ref
+ * forceplay is 0, so we call @c beforepoll.  After the @c poll() we call @c
+ * afterpoll and actually get some audio played.
+ *
+ * This is surely @b far more complicated than it needs to be!
+ *
+ * If at any call to play(), activate() fails, or if there aren't enough bytes
+ * in the buffer to satisfy the request, then @ref forceplay is set non-0.  On
+ * the next pass through the event loop @c beforepoll is not called.  This
+ * means that (if none of the other FDs trigger) the @c poll() call will block
+ * for up to a second.  @c afterpoll will return nonzero, since @c beforepoll
+ * wasn't called, and consequently play() is called with @ref forceplay as its
+ * argument.
+ *
+ * The effect is to attempt to restart playing audio - including the activate()
+ * step, which may have failed at the previous attempt - at least once a second
+ * after an error has disabled it.  The delay prevents busy-waiting on whatever
+ * condition has rendered the audio device uncooperative.
+ */
+static void mainloop(void) {
   struct track *t;
   struct speaker_message sm;
-#if API_ALSA
-  int err;
-#endif
+  int n, fd, stdin_slot, poke, timeout;
 
-  set_progname(argv);
-  if(!setlocale(LC_CTYPE, "")) fatal(errno, "error calling setlocale");
-  while((n = getopt_long(argc, argv, "hVc:dD", 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;
-    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)) {
-    openlog(progname, LOG_PID, LOG_DAEMON);
-    log_default = &log_syslog;
-  }
-  if(config_read()) fatal(0, "cannot read configuration");
-  /* ignore SIGPIPE */
-  signal(SIGPIPE, SIG_IGN);
-  /* reap kids */
-  signal(SIGCHLD, reap);
-  /* set nice value */
-  xnice(config->nice_speaker);
-  /* change user */
-  become_mortal();
-  /* make sure we're not root, whatever the config says */
-  if(getuid() == 0 || geteuid() == 0) fatal(0, "do not run as root");
-  /* identify the backend used to play */
-  for(n = 0; backends[n].backend != -1; ++n)
-    if(backends[n].backend == config->speaker_backend)
-      break;
-  if(backends[n].backend == -1)
-    fatal(0, "unsupported backend %d", config->speaker_backend);
-  backend = &backends[n];
-  /* backend-specific initialization */
-  backend->init();
   while(getppid() != 1) {
     fdno = 0;
     /* Always ready for commands from the main server. */
@@ -1192,7 +1178,9 @@ int main(int argc, char **argv) {
       playing->slot = -1;
     /* If forceplay is set then wait until it succeeds before waiting on the
      * sound device. */
+#if API_ALSA
     alsa_slots = -1;
+#endif
     cmdfd_slot = -1;
     bfd_slot = -1;
     /* By default we will wait up to a second before thinking about current
@@ -1218,39 +1206,7 @@ int main(int argc, char **argv) {
       fatal(errno, "error calling poll");
     }
     /* Play some sound before doing anything else */
-    poke = 0;
-    switch(config->speaker_backend) {
-#if API_ALSA
-    case BACKEND_ALSA:
-      if(alsa_slots != -1) {
-        unsigned short alsa_revents;
-        
-        if((err = snd_pcm_poll_descriptors_revents(pcm,
-                                                   &fds[alsa_slots],
-                                                   alsa_nslots,
-                                                   &alsa_revents)) < 0)
-          fatal(0, "error calling snd_pcm_poll_descriptors_revents: %d", err);
-        if(alsa_revents & (POLLOUT | POLLERR))
-          play(3 * FRAMES);
-      } else
-        poke = 1;
-      break;
-#endif
-    case BACKEND_COMMAND:
-      if(cmdfd_slot != -1) {
-        if(fds[cmdfd_slot].revents & (POLLOUT | POLLERR))
-          play(3 * FRAMES);
-      } else
-        poke = 1;
-      break;
-    case BACKEND_NETWORK:
-      if(bfd_slot != -1) {
-        if(fds[bfd_slot].revents & (POLLOUT | POLLERR))
-          play(3 * FRAMES);
-      } else
-        poke = 1;
-      break;
-    }
+    poke = backend->afterpoll();
     if(poke) {
       /* Some attempt to play must have failed */
       if(playing && !paused)
@@ -1333,6 +1289,50 @@ int main(int argc, char **argv) {
     if(time(0) > last_report)
       report();
   }
+}
+
+int main(int argc, char **argv) {
+  int n;
+
+  set_progname(argv);
+  if(!setlocale(LC_CTYPE, "")) fatal(errno, "error calling setlocale");
+  while((n = getopt_long(argc, argv, "hVc:dD", 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;
+    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)) {
+    openlog(progname, LOG_PID, LOG_DAEMON);
+    log_default = &log_syslog;
+  }
+  if(config_read()) fatal(0, "cannot read configuration");
+  /* ignore SIGPIPE */
+  signal(SIGPIPE, SIG_IGN);
+  /* reap kids */
+  signal(SIGCHLD, reap);
+  /* set nice value */
+  xnice(config->nice_speaker);
+  /* change user */
+  become_mortal();
+  /* make sure we're not root, whatever the config says */
+  if(getuid() == 0 || geteuid() == 0) fatal(0, "do not run as root");
+  /* identify the backend used to play */
+  for(n = 0; backends[n].backend != -1; ++n)
+    if(backends[n].backend == config->speaker_backend)
+      break;
+  if(backends[n].backend == -1)
+    fatal(0, "unsupported backend %d", config->speaker_backend);
+  backend = &backends[n];
+  /* backend-specific initialization */
+  backend->init();
+  mainloop();
   info("stopped (parent terminated)");
   exit(0);
 }