X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~mdw/git/disorder/blobdiff_plain/8e93ddd109ce3cc88ffaa80e8f5e578808adb1a2..refs/tags/5.1:/lib/uaudio-thread.c diff --git a/lib/uaudio-thread.c b/lib/uaudio-thread.c index cd0d727..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 * @@ -68,11 +70,13 @@ static pthread_t uaudio_collect_thread; /** @brief Playing thread ID */ static pthread_t uaudio_play_thread; +/** @brief Flags */ +static unsigned uaudio_thread_flags; + 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; @@ -83,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; @@ -115,12 +122,14 @@ static void *uaudio_collect_thread_fn(void attribute((unused)) *arg) { /* Keep on trying until we get the minimum required amount of data */ b->nsamples = 0; - while(b->nsamples < uaudio_thread_min) { - b->nsamples += uaudio_thread_collect_callback - ((char *)b->samples - + b->nsamples * uaudio_sample_size, - uaudio_thread_max - b->nsamples, - uaudio_thread_userdata); + if(uaudio_thread_activated) { + while(b->nsamples < uaudio_thread_min) { + b->nsamples += uaudio_thread_collect_callback + ((char *)b->samples + + b->nsamples * uaudio_sample_size, + uaudio_thread_max - b->nsamples, + uaudio_thread_userdata); + } } pthread_mutex_lock(&uaudio_thread_lock); /* Advance to next buffer */ @@ -138,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 @@ -145,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; @@ -162,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; @@ -189,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 @@ -199,14 +265,17 @@ void uaudio_thread_start(uaudio_callback *callback, void *userdata, uaudio_playcallback *playcallback, size_t min, - size_t max) { + size_t max, + unsigned flags) { int e; uaudio_thread_collect_callback = callback; uaudio_thread_userdata = userdata; uaudio_thread_play_callback = playcallback; uaudio_thread_min = min; 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); @@ -215,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 */ @@ -243,19 +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); - - while(uaudio_thread_collecting || uaudio_buffers_used()) - pthread_cond_wait(&uaudio_thread_cond, &uaudio_thread_lock); pthread_mutex_unlock(&uaudio_thread_lock); }