chiark / gitweb /
uaudio: fake blocking for play callbacks
authorRichard Kettlewell <rjk@terraraq.org.uk>
Sat, 19 Jan 2013 18:40:11 +0000 (18:40 +0000)
committerRichard Kettlewell <rjk@terraraq.org.uk>
Sat, 19 Jan 2013 18:40:11 +0000 (18:40 +0000)
This satisfies timing expectations in the face of very large downstream
buffers.

lib/Makefile.am
lib/syscalls.c
lib/syscalls.h
lib/syscallsrt.c [new file with mode: 0644]
lib/timeval.h
lib/uaudio-thread.c
lib/uaudio.c
lib/uaudio.h

index 4df927f084759c6cd4d8041d049a9d22df706d9c..241c71e732b91d3dbb4a4e573f2fdd7179f4f0b1 100644 (file)
@@ -1,6 +1,6 @@
 #
 # This file is part of DisOrder.
-# Copyright (C) 2004-2010 Richard Kettlewell
+# Copyright (C) 2004-2010, 2012, 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
@@ -76,7 +76,7 @@ libdisorder_a_SOURCES=charset.c charsetf.c charset.h  \
        speaker-protocol.c speaker-protocol.h           \
        split.c split.h                                 \
        strptime.c strptime.h                           \
-       syscalls.c syscalls.h                           \
+       syscalls.c syscallsrt.c syscalls.h              \
        common.h                                        \
        table.c table.h                                 \
        timeval.h                                       \
index e13922e2d69a616fa6e280a51d15b64f7ea0b5c7..b894ca39bfb80885917dc6bd903c9d47f9c1ae63 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * This file is part of DisOrder.
- * Copyright (C) 2004, 2005, 2007, 2008 Richard Kettlewell
+ * Copyright (C) 2004, 2005, 2007, 2008, 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
@@ -27,6 +27,7 @@
 #include <sys/socket.h>
 #include <sys/time.h>
 #include <signal.h>
+#include <time.h>
 
 #include "syscalls.h"
 #include "log.h"
@@ -159,6 +160,10 @@ time_t xtime(time_t *whenp) {
   return tv.tv_sec;
 }
 
+void xnanosleep(const struct timespec *req, struct timespec *rem) {
+  mustnotbeminus1("nanosleep", nanosleep(req, rem));
+}
+
 /*
 Local Variables:
 c-basic-offset:2
index e45125a8473449666cf9b86058470a1c34421c7e..af60335d83796810b9ab3fd4a9c873b509bbdc17 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * This file is part of DisOrder.
- * Copyright (C) 2004, 2005, 2007, 2008 Richard Kettlewell
+ * Copyright (C) 2004, 2005, 2007-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
@@ -49,6 +49,8 @@ void xfclose(FILE *);
 int xnice(int);
 void xgettimeofday(struct timeval *, struct timezone *);
 time_t xtime(time_t *when);
+void xgettime(clockid_t clk_id, struct timespec *tp);
+void xnanosleep(const struct timespec *req, struct timespec *rem);
 /* the above all call @fatal@ if the system call fails */
 
 void nonblock(int fd);
diff --git a/lib/syscallsrt.c b/lib/syscallsrt.c
new file mode 100644 (file)
index 0000000..314c206
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * This file is part of DisOrder.
+ * Copyright (C) 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
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+/** @file lib/syscallsrt.c
+ * @brief Error-checking library call wrappers
+ */
+#include "common.h"
+
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <signal.h>
+#include <time.h>
+
+#include "syscalls.h"
+#include "log.h"
+#include "printf.h"
+
+void xgettime(clockid_t clk_id, struct timespec *tp) {
+  mustnotbeminus1("clock_gettime", clock_gettime(clk_id, tp));
+}
+
+/*
+Local Variables:
+c-basic-offset:2
+comment-column:40
+End:
+*/
index fe2906c7d7566836d1d97f973309bd67d35f1b5c..e1e64da2031a57512a79baea49c09161185a1e0c 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * This file is part of DisOrder.
- * Copyright (C) 2007-2009 Richard Kettlewell
+ * Copyright (C) 2007-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
@@ -23,6 +23,7 @@
 
 #include <time.h>
 #include <sys/time.h>
+#include <math.h>
 
 static inline struct timeval tvsub(const struct timeval a,
                                    const struct timeval b) {
@@ -99,6 +100,57 @@ static inline int tvle(const struct timeval *a, const struct timeval *b) {
   return !tvgt(a, b);
 }
 
+/** @brief Return the sum of two timespecs */
+static inline struct timespec tsadd(const struct timespec a,
+                                    const struct timespec b) {
+  struct timespec r;
+
+  r.tv_sec = a.tv_sec + b.tv_sec;
+  r.tv_nsec = a.tv_nsec + b.tv_nsec;
+  if(r.tv_nsec < 0) {
+    r.tv_nsec += 1000000;
+    r.tv_sec--;
+  }
+  if(r.tv_nsec > 999999) {
+    r.tv_nsec -= 1000000;
+    r.tv_sec++;
+  }
+  return r;
+}
+
+/** @brief Subtract one timespec from another */
+static inline struct timespec tssub(const struct timespec a,
+                                    const struct timespec b) {
+  struct timespec r;
+
+  r.tv_sec = a.tv_sec - b.tv_sec;
+  r.tv_nsec = a.tv_nsec - b.tv_nsec;
+  if(r.tv_nsec < 0) {
+    r.tv_nsec += 1000000;
+    r.tv_sec--;
+  }
+  if(r.tv_nsec > 999999) {
+    r.tv_nsec -= 1000000;
+    r.tv_sec++;
+  }
+  return r;
+}
+
+/** @brief Convert a timespec to a double */
+static inline double ts_to_double(const struct timespec ts) {
+  return ts.tv_sec + ts.tv_nsec / 1000000000.0;
+}
+
+/** @brief Convert a double to a timespec */
+static inline struct timespec double_to_ts(double n) {
+  double i, f;
+  struct timespec r;
+  f = modf(n, &i);
+  r.tv_sec = i;
+  r.tv_nsec = 1000000000 * f;
+  return r;
+}
+
 #endif /* TIMEVAL_H */
 
 /*
index e42980c211cd10cc22c95cb6a81c7f7586d3598e..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
  *
@@ -145,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
@@ -163,8 +203,7 @@ static void *uaudio_play_thread_fn(void attribute((unused)) *arg) {
       unsigned flags = UAUDIO_PAUSED;
       if(last_flags & UAUDIO_PLAYING)
         flags |= UAUDIO_PAUSE;
-      uaudio_thread_play_callback(zero, uaudio_thread_max,
-                                  last_flags = flags);
+      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;
@@ -187,10 +226,10 @@ static void *uaudio_play_thread_fn(void attribute((unused)) *arg) {
         unsigned flags = UAUDIO_PLAYING;
         if(last_flags & UAUDIO_PAUSED)
           flags |= UAUDIO_RESUME;
-        played += uaudio_thread_play_callback((char *)b->samples
-                                              + played * uaudio_sample_size,
-                                              b->nsamples - played,
-                                              last_flags = flags);
+        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 */
index e793045ce77c995cb2f13ef52e4ae736e6168cdd..343290df1fb68006d30731f1f431d3d4343d5f33 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
@@ -41,6 +41,9 @@ int uaudio_channels;
 /** @brief Whether samples are signed or unsigned */
 int uaudio_signed;
 
+/** @brief Frames of buffer to tolerate inside chosen API */
+int uaudio_buffer;
+
 /** @brief Sample size in bytes
  *
  * NB one sample is a single point sample; up to @c uaudio_channels samples may
index 6592d5353a4c43380781e3bfe2f2373f2a7a8efe..709979ec544acc80bffc9f2af6739128710f6bc6 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
@@ -28,6 +28,7 @@ extern int uaudio_bits;
 extern int uaudio_channels;
 extern int uaudio_signed;
 extern size_t uaudio_sample_size;
+extern int uaudio_buffer;
 
 /** @brief Callback to get audio data
  * @param buffer Where to put audio data