From 5db8461a362f14017d276216a78c848929eb81f7 Mon Sep 17 00:00:00 2001 Message-Id: <5db8461a362f14017d276216a78c848929eb81f7.1717070736.git.mdw@distorted.org.uk> From: Mark Wooding Date: Sat, 19 Jan 2013 18:40:11 +0000 Subject: [PATCH] uaudio: fake blocking for play callbacks Organization: Straylight/Edgeware From: Richard Kettlewell This satisfies timing expectations in the face of very large downstream buffers. --- lib/Makefile.am | 4 ++-- lib/syscalls.c | 7 +++++- lib/syscalls.h | 4 +++- lib/syscallsrt.c | 45 +++++++++++++++++++++++++++++++++++++ lib/timeval.h | 54 ++++++++++++++++++++++++++++++++++++++++++++- lib/uaudio-thread.c | 53 ++++++++++++++++++++++++++++++++++++++------ lib/uaudio.c | 5 ++++- lib/uaudio.h | 3 ++- 8 files changed, 161 insertions(+), 14 deletions(-) create mode 100644 lib/syscallsrt.c diff --git a/lib/Makefile.am b/lib/Makefile.am index 4df927f..241c71e 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -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 \ diff --git a/lib/syscalls.c b/lib/syscalls.c index e13922e..b894ca3 100644 --- a/lib/syscalls.c +++ b/lib/syscalls.c @@ -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 #include #include +#include #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 diff --git a/lib/syscalls.h b/lib/syscalls.h index e45125a..af60335 100644 --- a/lib/syscalls.h +++ b/lib/syscalls.h @@ -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 index 0000000..314c206 --- /dev/null +++ b/lib/syscallsrt.c @@ -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 . + */ +/** @file lib/syscallsrt.c + * @brief Error-checking library call wrappers + */ +#include "common.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#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: +*/ diff --git a/lib/timeval.h b/lib/timeval.h index fe2906c..e1e64da 100644 --- a/lib/timeval.h +++ b/lib/timeval.h @@ -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 #include +#include 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 */ /* diff --git a/lib/uaudio-thread.c b/lib/uaudio-thread.c index e42980c..113932f 100644 --- a/lib/uaudio-thread.c +++ b/lib/uaudio-thread.c @@ -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 */ diff --git a/lib/uaudio.c b/lib/uaudio.c index e793045..343290d 100644 --- a/lib/uaudio.c +++ b/lib/uaudio.c @@ -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 diff --git a/lib/uaudio.h b/lib/uaudio.h index 6592d53..709979e 100644 --- a/lib/uaudio.h +++ b/lib/uaudio.h @@ -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 -- [mdw]