/*
* 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
#include "uaudio.h"
#include "log.h"
#include "mem.h"
+#include "syscalls.h"
+#include "timeval.h"
/** @brief Number of buffers
*
/** @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;
/** @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;
/* 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 */
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
*/
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;
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;
* @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
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(uaudio_thread_max, uaudio_sample_size);
+ uaudio_buffers[n].samples = xcalloc_noptr(uaudio_thread_max,
+ uaudio_sample_size);
uaudio_collect_buffer = uaudio_play_buffer = 0;
if((e = pthread_create(&uaudio_collect_thread,
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 */
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);
}