chiark / gitweb /
log: more general error message formatting
[disorder] / lib / uaudio-thread.c
index 6bed4a8012f2bf62ba7e30f6da14b75115425aa1..113932f1e3004e09d7303abe7506506de6811026 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * This file is part of DisOrder.
- * Copyright (C) 2009 Richard Kettlewell
+ * Copyright (C) 2009, 2013 Richard Kettlewell
  *
  * 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
@@ -25,6 +25,8 @@
 #include "uaudio.h"
 #include "log.h"
 #include "mem.h"
+#include "syscalls.h"
+#include "timeval.h"
 
 /** @brief Number of buffers
  *
@@ -75,7 +77,6 @@ static uaudio_callback *uaudio_thread_collect_callback;
 static uaudio_playcallback *uaudio_thread_play_callback;
 static void *uaudio_thread_userdata;
 static int uaudio_thread_started;
-static int uaudio_thread_activated;
 static int uaudio_thread_collecting;
 static pthread_mutex_t uaudio_thread_lock = PTHREAD_MUTEX_INITIALIZER;
 static pthread_cond_t uaudio_thread_cond = PTHREAD_COND_INITIALIZER;
@@ -86,6 +87,9 @@ static size_t uaudio_thread_min;
 /** @brief Maximum number of samples per chunk */
 static size_t uaudio_thread_max;
 
+/** @brief Set when activated, clear when paused */
+static int uaudio_thread_activated;
+
 /** @brief Return number of buffers currently in use */
 static int uaudio_buffers_used(void) {
   return (uaudio_collect_buffer - uaudio_play_buffer) % UAUDIO_THREAD_BUFFERS;
@@ -107,8 +111,7 @@ static void *uaudio_collect_thread_fn(void attribute((unused)) *arg) {
     /* We are definitely active now */
     uaudio_thread_collecting = 1;
     pthread_cond_broadcast(&uaudio_thread_cond);
-    while(uaudio_thread_activated
-          || (uaudio_thread_flags & UAUDIO_THREAD_FAKE_PAUSE)) {
+    while(uaudio_thread_activated) {
       if(uaudio_buffers_used() < UAUDIO_THREAD_BUFFERS - 1) {
         /* At least one buffer is available.  We release the lock while
          * collecting data so that other already-filled buffers can be played
@@ -127,9 +130,6 @@ static void *uaudio_collect_thread_fn(void attribute((unused)) *arg) {
                uaudio_thread_max - b->nsamples,
                uaudio_thread_userdata);
           }
-        } else if(uaudio_thread_flags & UAUDIO_THREAD_FAKE_PAUSE) {
-          memset(b->samples, 0, uaudio_thread_min * uaudio_sample_size);
-          b->nsamples += uaudio_thread_min;
         }
         pthread_mutex_lock(&uaudio_thread_lock);
         /* Advance to next buffer */
@@ -147,6 +147,44 @@ static void *uaudio_collect_thread_fn(void attribute((unused)) *arg) {
   return NULL;
 }
 
+static size_t uaudio_play_samples(void *buffer, size_t samples, unsigned flags) {
+  static struct timespec base;
+  static int64_t frames_supplied;
+  struct timespec now;
+  struct timespec delay_ts;
+  double target, delay;
+
+  if(!base.tv_sec)
+    xgettime(CLOCK_MONOTONIC, &base);
+  samples = uaudio_thread_play_callback(buffer, samples, flags);
+  frames_supplied += samples / uaudio_channels;
+  /* Set target to the approximate point at which we run out of buffered audio.
+   * If no buffer size has been specified, use 1/16th of a second. */
+  target = (frames_supplied - (uaudio_buffer ? uaudio_buffer : uaudio_rate / 16))
+    / (double)uaudio_rate + ts_to_double(base);
+  for(;;) {
+    xgettime(CLOCK_MONOTONIC, &now);
+    delay = target - ts_to_double(now);
+    if(delay <= 0) {
+      //putc('.', stderr);
+      break;
+    }
+    //putc('!', stderr);
+    /*
+    fprintf(stderr, "frames supplied %ld (%lds) base %f target %f now %f want delay %g\n", 
+            frames_supplied,
+            frames_supplied / uaudio_rate,
+            ts_to_double(base),
+            target, 
+            ts_to_double(now),
+            delay);
+    */
+    delay_ts = double_to_ts(delay);
+    xnanosleep(&delay_ts, NULL);
+  }
+  return samples;
+}
+
 /** @brief Background thread for audio playing 
  *
  * This thread plays data as long as there is something to play.  So the
@@ -154,9 +192,22 @@ static void *uaudio_collect_thread_fn(void attribute((unused)) *arg) {
  */
 static void *uaudio_play_thread_fn(void attribute((unused)) *arg) {
   int resync = 1;
+  unsigned last_flags = 0;
+  unsigned char zero[uaudio_thread_max * uaudio_sample_size];
+  memset(zero, 0, sizeof zero);
 
-  pthread_mutex_lock(&uaudio_thread_lock);
   while(uaudio_thread_started) {
+    // If we're paused then just play silence
+    if(!uaudio_thread_activated) {
+      pthread_mutex_unlock(&uaudio_thread_lock);
+      unsigned flags = UAUDIO_PAUSED;
+      if(last_flags & UAUDIO_PLAYING)
+        flags |= UAUDIO_PAUSE;
+      uaudio_play_samples(zero, uaudio_thread_max, last_flags = flags);
+      /* We expect the play callback to block for a reasonable period */
+      pthread_mutex_lock(&uaudio_thread_lock);
+      continue;
+    }
     const int used = uaudio_buffers_used();
     int go;
 
@@ -171,10 +222,15 @@ static void *uaudio_play_thread_fn(void attribute((unused)) *arg) {
       pthread_mutex_unlock(&uaudio_thread_lock);
       //fprintf(stderr, "P%d.", uaudio_play_buffer);
       size_t played = 0;
-      while(played < b->nsamples)
-        played += uaudio_thread_play_callback((char *)b->samples
-                                              + played * uaudio_sample_size,
-                                              b->nsamples - played);
+      while(played < b->nsamples) {
+        unsigned flags = UAUDIO_PLAYING;
+        if(last_flags & UAUDIO_PAUSED)
+          flags |= UAUDIO_RESUME;
+        played += uaudio_play_samples((char *)b->samples
+                                      + played * uaudio_sample_size,
+                                      b->nsamples - played,
+                                      last_flags = flags);
+      }
       pthread_mutex_lock(&uaudio_thread_lock);
       /* Move to next buffer */
       uaudio_play_buffer = (1 + uaudio_play_buffer) % UAUDIO_THREAD_BUFFERS;
@@ -198,6 +254,7 @@ static void *uaudio_play_thread_fn(void attribute((unused)) *arg) {
  * @param playcallback Callback to play audio data
  * @param min Minimum number of samples to play in a chunk
  * @param max Maximum number of samples to play in a chunk
+ * @param flags Flags (not currently used)
  *
  * @p callback will be called multiple times in quick succession if necessary
  * to gather at least @p min samples.  Equally @p playcallback may be called
@@ -218,6 +275,7 @@ void uaudio_thread_start(uaudio_callback *callback,
   uaudio_thread_max = max;
   uaudio_thread_flags = flags;
   uaudio_thread_started = 1;
+  uaudio_thread_activated = 0;
   for(int n = 0; n < UAUDIO_THREAD_BUFFERS; ++n)
     uaudio_buffers[n].samples = xcalloc_noptr(uaudio_thread_max,
                                               uaudio_sample_size);
@@ -226,12 +284,12 @@ void uaudio_thread_start(uaudio_callback *callback,
                          NULL,
                          uaudio_collect_thread_fn,
                          NULL)))
-    fatal(e, "pthread_create");
+    disorder_fatal(e, "pthread_create");
   if((e = pthread_create(&uaudio_play_thread,
                          NULL,
                          uaudio_play_thread_fn,
                          NULL)))
-    fatal(e, "pthread_create");
+    disorder_fatal(e, "pthread_create");
 }
 
 /** @brief Shut down background threads for audio processing */
@@ -254,20 +312,14 @@ void uaudio_thread_activate(void) {
   pthread_mutex_lock(&uaudio_thread_lock);
   uaudio_thread_activated = 1;
   pthread_cond_broadcast(&uaudio_thread_cond);
-  while(!uaudio_thread_collecting)
-    pthread_cond_wait(&uaudio_thread_cond, &uaudio_thread_lock);
   pthread_mutex_unlock(&uaudio_thread_lock);
 }
 
 /** @brief Deactivate audio output */
 void uaudio_thread_deactivate(void) {
   pthread_mutex_lock(&uaudio_thread_lock);
-  uaudio_thread_activated = 0;
+  uaudio_thread_activated = 0; 
   pthread_cond_broadcast(&uaudio_thread_cond);
-  if(!(uaudio_thread_flags & UAUDIO_THREAD_FAKE_PAUSE)) {
-    while(uaudio_thread_collecting || uaudio_buffers_used())
-      pthread_cond_wait(&uaudio_thread_cond, &uaudio_thread_lock);
-  }
   pthread_mutex_unlock(&uaudio_thread_lock);
 }