X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~mdw/git/disorder/blobdiff_plain/63761c193dfd5a34714db2e85382312c294cb85a..92db088e5b292c1180a090ed369b9851e933e610:/lib/uaudio-thread.c diff --git a/lib/uaudio-thread.c b/lib/uaudio-thread.c index 6bed4a8..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 * @@ -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); }