if(header.mpt & 0x80)
p->flags |= IDLE;
switch(header.mpt & 0x7F) {
- case 10:
+ case 10: /* L16 */
p->nsamples = (n - sizeof header) / sizeof(uint16_t);
break;
/* TODO support other RFC3551 media types (when the speaker does) */
exit(0);
}
-static size_t playrtp_callback(int16_t *buffer,
+static size_t playrtp_callback(void *buffer,
size_t max_samples,
void attribute((unused)) *userdata) {
size_t samples;
if(samples > max_samples)
samples = max_samples;
//info("infill by %zu", samples);
- memset(buffer, 0, samples * sizeof *buffer);
+ memset(buffer, 0, samples * uaudio_sample_size);
}
/* Debug dump */
if(dump_buffer) {
for(size_t i = 0; i < samples; ++i) {
- dump_buffer[dump_index++] = buffer[i];
+ dump_buffer[dump_index++] = ((int16_t *)buffer)[i];
dump_index %= dump_size;
}
}
/* Choose output device */
if(device)
uaudio_set("device", device);
- /* Set up output */
+ /* Set up output. Currently we only support L16 so there's no harm setting
+ * the format before we know what it is! */
+ uaudio_set_format(44100/*Hz*/, 2/*channels*/,
+ 16/*bits/channel*/, 1/*signed*/);
backend->start(playrtp_callback, NULL);
/* We receive and convert audio data in a background thread */
if((err = pthread_create(<id, 0, listen_thread, 0)))
/* Wait until the buffer empties out */
while(nsamples >= minbuffer
|| (nsamples > 0
- && contains(pheap_first(&packets), next_timestamp)))
+ && contains(pheap_first(&packets), next_timestamp))) {
pthread_cond_wait(&cond, &lock);
+ }
/* Stop playing for a bit until the buffer re-fills */
backend->deactivate();
active = 0;
$(TRACKDB) trackdb.h trackdb-int.h \
trackname.c trackorder.c trackname.h \
tracksort.c \
- uaudio.c uaudio.h \
- uaudio-oss.c uaudio-coreaudio.c uaudio-rtp.c \
+ uaudio.c uaudio-thread.c uaudio.h \
+ uaudio-oss.c uaudio-alsa.c \
+ uaudio-coreaudio.c \
+ uaudio-rtp.c \
url.h url.c \
user.h user.c \
unicode.h unicode.c \
--- /dev/null
+/*
+ * This file is part of DisOrder.
+ * Copyright (C) 2009 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/uaudio-alsa.c
+ * @brief Support for ALSA backend */
+#include "common.h"
+
+#if HAVE_ALSA_ASOUNDLIB_H
+
+#include <alsa/asoundlib.h>
+
+#include "mem.h"
+#include "log.h"
+#include "uaudio.h"
+
+/** @brief The current PCM handle */
+static snd_pcm_t *alsa_pcm;
+
+static const char *const alsa_options[] = {
+ "device",
+ NULL
+};
+
+/** @brief Actually play sound via ALSA */
+static size_t alsa_play(void *buffer, size_t samples) {
+ int err;
+ /* ALSA wants 'frames', where frame = several concurrently played samples */
+ const snd_pcm_uframes_t frames = samples / uaudio_channels;
+
+ snd_pcm_sframes_t rc = snd_pcm_writei(alsa_pcm, buffer, frames);
+ if(rc < 0) {
+ switch(rc) {
+ case -EPIPE:
+ if((err = snd_pcm_prepare(alsa_pcm)))
+ fatal(0, "error calling snd_pcm_prepare: %d", err);
+ return 0;
+ case -EAGAIN:
+ return 0;
+ default:
+ fatal(0, "error calling snd_pcm_writei: %d", (int)rc);
+ }
+ }
+ return rc * uaudio_channels;
+}
+
+/** @brief Open the ALSA sound device */
+static void alsa_open(void) {
+ const char *device = uaudio_get("device");
+ int err;
+
+ if(!device || !*device)
+ device = "default";
+ if((err = snd_pcm_open(&alsa_pcm,
+ device,
+ SND_PCM_STREAM_PLAYBACK,
+ 0)))
+ fatal(0, "error from snd_pcm_open: %d", err);
+ snd_pcm_hw_params_t *hwparams;
+ snd_pcm_hw_params_alloca(&hwparams);
+ if((err = snd_pcm_hw_params_any(alsa_pcm, hwparams)) < 0)
+ fatal(0, "error from snd_pcm_hw_params_any: %d", err);
+ if((err = snd_pcm_hw_params_set_access(alsa_pcm, hwparams,
+ SND_PCM_ACCESS_RW_INTERLEAVED)) < 0)
+ fatal(0, "error from snd_pcm_hw_params_set_access: %d", err);
+ int sample_format;
+ if(uaudio_bits == 16)
+ sample_format = uaudio_signed ? SND_PCM_FORMAT_S16 : SND_PCM_FORMAT_U16;
+ else
+ sample_format = uaudio_signed ? SND_PCM_FORMAT_S8 : SND_PCM_FORMAT_U8;
+ if((err = snd_pcm_hw_params_set_format(alsa_pcm, hwparams,
+ sample_format)) < 0)
+ fatal(0, "error from snd_pcm_hw_params_set_format (%d): %d",
+ sample_format, err);
+ unsigned rate = uaudio_rate;
+ if((err = snd_pcm_hw_params_set_rate_near(alsa_pcm, hwparams, &rate, 0)) < 0)
+ fatal(0, "error from snd_pcm_hw_params_set_rate_near (%d): %d",
+ rate, err);
+ if((err = snd_pcm_hw_params_set_channels(alsa_pcm, hwparams,
+ uaudio_channels)) < 0)
+ fatal(0, "error from snd_pcm_hw_params_set_channels (%d): %d",
+ uaudio_channels, err);
+ if((err = snd_pcm_hw_params(alsa_pcm, hwparams)) < 0)
+ fatal(0, "error calling snd_pcm_hw_params: %d", err);
+
+}
+
+static void alsa_activate(void) {
+ uaudio_thread_activate();
+}
+
+static void alsa_deactivate(void) {
+ uaudio_thread_deactivate();
+}
+
+static void alsa_start(uaudio_callback *callback,
+ void *userdata) {
+ if(uaudio_channels != 1 && uaudio_channels != 2)
+ fatal(0, "asked for %d channels but only support 1 or 2",
+ uaudio_channels);
+ if(uaudio_bits != 8 && uaudio_bits != 16)
+ fatal(0, "asked for %d bits/channel but only support 8 or 16",
+ uaudio_bits);
+ alsa_open();
+ uaudio_thread_start(callback, userdata, alsa_play,
+ 32 / uaudio_sample_size,
+ 4096 / uaudio_sample_size);
+}
+
+static void alsa_stop(void) {
+ uaudio_thread_stop();
+ snd_pcm_close(alsa_pcm);
+ alsa_pcm = 0;
+}
+
+const struct uaudio uaudio_alsa = {
+ .name = "alsa",
+ .options = alsa_options,
+ .start = alsa_start,
+ .stop = alsa_stop,
+ .activate = alsa_activate,
+ .deactivate = alsa_deactivate
+};
+
+#endif
+
+/*
+Local Variables:
+c-basic-offset:2
+comment-column:40
+fill-column:79
+indent-tabs-mode:nil
+End:
+*/
while(nsamples > 0) {
/* Integer-format input buffer */
- int16_t input[1024], *ptr = input;
+ unsigned char input[1024];
+ const int maxsamples = sizeof input / uaudio_sample_size;
/* How many samples we'll ask for */
- const int ask = nsamples > 1024 ? 1024 : (int)nsamples;
+ const int ask = nsamples > maxsamples ? maxsamples : (int)nsamples;
/* How many we get */
int got;
got = coreaudio_callback(input, ask, coreaudio_userdata);
/* Convert the samples and store in the output buffer */
nsamples -= got;
- while(got > 0) {
- --got;
- *samples++ = *ptr++ * (0.5 / 32767);
+ if(uaudio_signed) {
+ if(uaudio_bits == 16) {
+ const int16_t *ptr = input;
+ while(got > 0) {
+ --got;
+ *samples++ = *ptr++ * (0.5 / 32767);
+ }
+ } else {
+ const int8_t *ptr = input;
+ while(got > 0) {
+ --got;
+ *samples++ = *ptr++ * (0.5 / 127);
+ }
+ }
+ } else {
+ if(uaudio_bits == 16) {
+ const uint16_t *ptr = input;
+ while(got > 0) {
+ --got;
+ *samples++ = ((int)*ptr++ - 32768) * (0.5 / 32767);
+ }
+ } else {
+ const uint8_t *ptr = input;
+ while(got > 0) {
+ --got;
+ *samples++ = ((int)*ptr++ - 128) * (0.5 / 127);
+ }
+ }
}
}
/* Move on to the next buffer */
AudioStreamBasicDescription asbd;
const char *device;
+ if(uaudio_bits != 8 && uaudio_bits != 16)
+ disorder_fatal("asked for %d bits/channel but only support 8 and 16",
+ uaudio_bits);
coreaudio_callback = callback;
coreaudio_userdata = userdata;
device = uaudio_get("device");
coreaudio_adid = coreaudio_getdevice(device);
+ /* Get the device properties */
propertySize = sizeof asbd;
status = AudioDeviceGetProperty(coreaudio_adid, 0, false,
kAudioDevicePropertyStreamFormat,
D(("mChannelsPerFrame %08lx", asbd.mChannelsPerFrame));
D(("mBitsPerChannel %08lx", asbd.mBitsPerChannel));
D(("mReserved %08lx", asbd.mReserved));
+ /* Check that everything adds up */
if(asbd.mFormatID != kAudioFormatLinearPCM)
disorder_fatal(0, "audio device does not support kAudioFormatLinearPCM");
+ if(asbd.mSampleRate != uaudio_rate
+ || asbd.mChannelsPerFrame != uaudio_channels) {
+ disorder_fatal(0, "want %dHz %d channels "
+ "but got %"PRIu32"Hz %"PRIu32" channels",
+ uaudio_rate,
+ uaudio_channels,
+ asbd.mSampleRate,
+ asbd.mChannelsPerFrame);
+ }
+ /* Add a collector callback */
status = AudioDeviceAddIOProc(coreaudio_adid, coreaudio_adioproc, 0);
if(status)
coreaudio_fatal(status, "AudioDeviceAddIOProc");
# include <sys/soundcard.h>
#endif
#include <sys/ioctl.h>
-#include <pthread.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
-#include <arpa/inet.h>
#include "mem.h"
#include "log.h"
-#include "syscalls.h"
#include "uaudio.h"
-#include "configuration.h"
-static int oss_fd = -1;
-static pthread_t oss_thread;
-static uaudio_callback *oss_callback;
-static int oss_started;
-static int oss_activated;
-static int oss_going;
-static pthread_mutex_t oss_lock = PTHREAD_MUTEX_INITIALIZER;
-static pthread_cond_t oss_cond = PTHREAD_COND_INITIALIZER;
+#ifndef AFMT_U16_NE
+# if BYTE_ORDER == BIG_ENDIAN
+# define AFMT_U16_NE AFMT_U16_BE
+# else
+# define AFMT_U16_NE AFMT_U16_LE
+# endif
+#endif
-/** @brief Buffer size in bytes */
-static int oss_bufsize;
+static int oss_fd = -1;
static const char *const oss_options[] = {
"device",
NULL
};
-static void *oss_thread_fn(void *userdata) {
- int16_t *buffer;
- pthread_mutex_lock(&oss_lock);
- buffer = xmalloc(oss_bufsize);
- while(oss_started) {
- while(oss_started && !oss_activated)
- pthread_cond_wait(&oss_cond, &oss_lock);
- if(!oss_started)
- break;
- /* We are definitely active now */
- oss_going = 1;
- pthread_cond_signal(&oss_cond);
- while(oss_activated) {
- const int nsamples = oss_callback(buffer,
- oss_bufsize / sizeof(int16_t),
- userdata);
- const int nbytes = nsamples * sizeof(int16_t);
- int written = 0;
-
- while(written < nsamples) {
- const int rc = write(oss_fd, buffer + written, nbytes - written);
- if(rc < 0)
- fatal(errno, "error playing audio");
- written += rc;
- }
- }
- oss_going = 0;
- pthread_cond_signal(&oss_cond);
- }
- pthread_mutex_unlock(&oss_lock);
- return NULL;
+/** @brief Actually play sound via OSS */
+static size_t oss_play(void *buffer, size_t samples) {
+ const size_t bytes = samples * uaudio_sample_size;
+ int rc = write(oss_fd, buffer, bytes);
+ if(rc < 0)
+ fatal(errno, "error writing to sound device");
+ return rc / uaudio_sample_size;
}
-static void oss_activate(void) {
- pthread_mutex_lock(&oss_lock);
- if(!oss_activated) {
- const char *device = uaudio_get("device");
+/** @brief Open the OSS sound device */
+static void oss_open(void) {
+ const char *device = uaudio_get("device");
#if EMPEG_HOST
- if(!device || !*device || !strcmp(device, "default")) {
- device "/dev/audio";
+ if(!device || !*device || !strcmp(device, "default"))
+ device "/dev/audio";
#else
- if(!device || !*device || !strcmp(device, "default")) {
- if(access("/dev/dsp", W_OK) == 0)
- device = "/dev/dsp";
- else
- device = "/dev/audio";
- }
-#endif
- if((oss_fd = open(device, O_WRONLY, 0)) < 0)
- fatal(errno, "error opening %s", device);
-#if EMPEG_HOST
- /* empeg audio driver only knows /dev/audio, only supports the equivalent
- * of AFMT_S16_NE, has a fixed buffer size, and does not support the
- * SNDCTL_ ioctls. */
- oss_bufsize = 4608;
-#else
- int stereo = (config->sample_format.channels == 2), format, rate;
- if(ioctl(oss_fd, SNDCTL_DSP_STEREO, &stereo) < 0)
- fatal(errno, "error calling ioctl SNDCTL_DSP_STEREO %d", stereo);
- /* TODO we need to think about where we decide this */
- if(config->sample_format.bits == 8)
- format = AFMT_U8;
- else if(config->sample_format.bits == 16)
- format = (config->sample_format.endian == ENDIAN_LITTLE
- ? AFMT_S16_LE : AFMT_S16_BE);
+ if(!device || !*device || !strcmp(device, "default")) {
+ if(access("/dev/dsp", W_OK) == 0)
+ device = "/dev/dsp";
else
- fatal(0, "unsupported sample_format for oss backend");
- if(ioctl(oss_fd, SNDCTL_DSP_SETFMT, &format) < 0)
- fatal(errno, "error calling ioctl SNDCTL_DSP_SETFMT %#x", format);
- rate = config->sample_format.rate;
- if(ioctl(oss_fd, SNDCTL_DSP_SPEED, &rate) < 0)
- fatal(errno, "error calling ioctl SNDCTL_DSP_SPEED %d", rate);
- if((unsigned)rate != config->sample_format.rate)
- error(0, "asked for %luHz, got %dHz",
- (unsigned long)config->sample_format.rate, rate);
- if(ioctl(oss_fd, SNDCTL_DSP_GETBLKSIZE, &oss_bufsize) < 0) {
- error(errno, "ioctl SNDCTL_DSP_GETBLKSIZE");
- oss_bufsize = 2048; /* guess */
- }
-#endif
- oss_activated = 1;
- pthread_cond_signal(&oss_cond);
- while(!oss_going)
- pthread_cond_wait(&oss_cond, &oss_lock);
+ device = "/dev/audio";
}
- pthread_mutex_unlock(&oss_lock);
+#endif
+ if((oss_fd = open(device, O_WRONLY, 0)) < 0)
+ fatal(errno, "error opening %s", device);
+#if !EMPEG_HOST
+ int stereo = (uaudio_channels == 2), format;
+ if(ioctl(oss_fd, SNDCTL_DSP_STEREO, &stereo) < 0)
+ fatal(errno, "error calling ioctl SNDCTL_DSP_STEREO %d", stereo);
+ if(uaudio_bits == 16)
+ format = uaudio_signed ? AFMT_S16_NE : AFMT_U16_NE;
+ else
+ format = uaudio_signed ? AFMT_S8 : AFMT_U8;
+ if(ioctl(oss_fd, SNDCTL_DSP_SETFMT, &format) < 0)
+ fatal(errno, "error calling ioctl SNDCTL_DSP_SETFMT %#x", format);
+ int rate = uaudio_rate;
+ if(ioctl(oss_fd, SNDCTL_DSP_SPEED, &rate) < 0)
+ fatal(errno, "error calling ioctl SNDCTL_DSP_SPEED %d", rate);
+ if(rate != uaudio_rate)
+ error(0, "asked for %dHz, got %dHz", uaudio_rate, rate);
+#endif
+}
+
+static void oss_activate(void) {
+ oss_open();
+ uaudio_thread_activate();
}
static void oss_deactivate(void) {
- pthread_mutex_lock(&oss_lock);
- if(oss_activated) {
- oss_activated = 0;
- pthread_cond_signal(&oss_cond);
- while(oss_going)
- pthread_cond_wait(&oss_cond, &oss_lock);
- close(oss_fd);
- oss_fd = -1;
- }
- pthread_mutex_unlock(&oss_lock);
+ uaudio_thread_deactivate();
+ close(oss_fd);
+ oss_fd = -1;
}
static void oss_start(uaudio_callback *callback,
void *userdata) {
- int e;
- oss_callback = callback;
- if((e = pthread_create(&oss_thread,
- NULL,
- oss_thread_fn,
- userdata)))
- fatal(e, "pthread_create");
+ if(uaudio_channels != 1 && uaudio_channels != 2)
+ fatal(0, "asked for %d channels but only support 1 or 2",
+ uaudio_channels);
+ if(uaudio_bits != 8 && uaudio_bits != 16)
+ fatal(0, "asked for %d bits/channel but only support 8 or 16",
+ uaudio_bits);
+#if EMPEG_HOST
+ /* Very specific buffer size requirements here apparently */
+ uaudio_thread_start(callback, userdata, oss_play,
+ 4608 / uaudio_sample_size,
+ 4608 / uaudio_sample_size);
+#else
+ /* We could SNDCTL_DSP_GETBLKSIZE but only when the device is already open,
+ * which is kind of inconvenient. We go with 1-4Kbyte for now. */
+ uaudio_thread_start(callback, userdata, oss_play,
+ 32 / uaudio_sample_size,
+ 4096 / uaudio_sample_size);
+#endif
}
static void oss_stop(void) {
- void *result;
-
- oss_deactivate();
- pthread_mutex_lock(&oss_lock);
- oss_started = 0;
- pthread_cond_signal(&oss_cond);
- pthread_mutex_unlock(&oss_lock);
- pthread_join(oss_thread, &result);
+ uaudio_thread_stop();
}
const struct uaudio uaudio_oss = {
--- /dev/null
+/*
+ * This file is part of DisOrder.
+ * Copyright (C) 2009 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/uaudio-thread.c
+ * @brief Background thread for audio processing */
+#include "common.h"
+
+#include <pthread.h>
+#include <unistd.h>
+
+#include "uaudio.h"
+#include "log.h"
+#include "mem.h"
+
+/** @brief Number of buffers
+ *
+ * Must be at least 2 and should normally be at least 3. We maintain multiple
+ * buffers so that we can read new data into one while the previous is being
+ * played.
+ */
+#define UAUDIO_THREAD_BUFFERS 4
+
+/** @brief Buffer data structure */
+struct uaudio_buffer {
+ /** @brief Pointer to sample data */
+ void *samples;
+
+ /** @brief Count of samples */
+ size_t nsamples;
+};
+
+/** @brief Input buffers
+ *
+ * This is actually a ring buffer, managed by @ref uaudio_collect_buffer and
+ * @ref uaudio_play_buffer.
+ *
+ * Initially both pointers are 0. Whenever the pointers are equal, we
+ * interpreted this as meaning that there is no data stored at all. A
+ * consequence of this is that maximal occupancy is when the collect point is
+ * just before the play point, so at least one buffer is always empty (hence it
+ * being good for @ref UAUDIO_THREAD_BUFFERS to be at least 3).
+ */
+static struct uaudio_buffer uaudio_buffers[UAUDIO_THREAD_BUFFERS];
+
+/** @brief Buffer to read into */
+static unsigned uaudio_collect_buffer;
+
+/** @brief Buffer to play from */
+static unsigned uaudio_play_buffer;
+
+/** @brief Collection thread ID */
+static pthread_t uaudio_collect_thread;
+
+/** @brief Playing thread ID */
+static pthread_t uaudio_play_thread;
+
+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 Minimum number of samples per chunk */
+static size_t uaudio_thread_min;
+
+/** @brief Maximum number of samples per chunk */
+static size_t uaudio_thread_max;
+
+/** @brief Return number of buffers currently in use */
+static int uaudio_buffers_used(void) {
+ return (uaudio_collect_buffer - uaudio_play_buffer) % UAUDIO_THREAD_BUFFERS;
+}
+
+/** @brief Background thread for audio collection
+ *
+ * Collects data while activated and communicates its status via @ref
+ * uaudio_thread_collecting.
+ */
+static void *uaudio_collect_thread_fn(void attribute((unused)) *arg) {
+ pthread_mutex_lock(&uaudio_thread_lock);
+ while(uaudio_thread_started) {
+ /* Wait until we're activatd */
+ if(!uaudio_thread_activated) {
+ pthread_cond_wait(&uaudio_thread_cond, &uaudio_thread_lock);
+ continue;
+ }
+ /* We are definitely active now */
+ uaudio_thread_collecting = 1;
+ pthread_cond_broadcast(&uaudio_thread_cond);
+ 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
+ * without delay. */
+ struct uaudio_buffer *const b = &uaudio_buffers[uaudio_collect_buffer];
+ pthread_mutex_unlock(&uaudio_thread_lock);
+ //fprintf(stderr, "C%d.", uaudio_collect_buffer);
+
+ /* 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);
+ }
+ pthread_mutex_lock(&uaudio_thread_lock);
+ /* Advance to next buffer */
+ uaudio_collect_buffer = (1 + uaudio_collect_buffer) % UAUDIO_THREAD_BUFFERS;
+ /* Awaken player */
+ pthread_cond_broadcast(&uaudio_thread_cond);
+ } else
+ /* No space, wait for player */
+ pthread_cond_wait(&uaudio_thread_cond, &uaudio_thread_lock);
+ }
+ uaudio_thread_collecting = 0;
+ pthread_cond_broadcast(&uaudio_thread_cond);
+ }
+ pthread_mutex_unlock(&uaudio_thread_lock);
+ return NULL;
+}
+
+/** @brief Background thread for audio playing
+ *
+ * This thread plays data as long as there is something to play. So the
+ * buffers will drain to empty before deactivation completes.
+ */
+static void *uaudio_play_thread_fn(void attribute((unused)) *arg) {
+ int resync = 1;
+
+ pthread_mutex_lock(&uaudio_thread_lock);
+ while(uaudio_thread_started) {
+ const int used = uaudio_buffers_used();
+ int go;
+
+ if(resync)
+ go = (used == UAUDIO_THREAD_BUFFERS - 1);
+ else
+ go = (used > 0);
+ if(go) {
+ /* At least one buffer is filled. We release the lock while playing so
+ * that more collection can go on. */
+ struct uaudio_buffer *const b = &uaudio_buffers[uaudio_play_buffer];
+ 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);
+ pthread_mutex_lock(&uaudio_thread_lock);
+ /* Move to next buffer */
+ uaudio_play_buffer = (1 + uaudio_play_buffer) % UAUDIO_THREAD_BUFFERS;
+ /* Awaken collector */
+ pthread_cond_broadcast(&uaudio_thread_cond);
+ resync = 0;
+ } else {
+ /* Insufficient data to play, wait for collector */
+ pthread_cond_wait(&uaudio_thread_cond, &uaudio_thread_lock);
+ /* (Still) re-synchronizing */
+ resync = 1;
+ }
+ }
+ pthread_mutex_unlock(&uaudio_thread_lock);
+ return NULL;
+}
+
+/** @brief Create background threads for audio processing
+ * @param callback Callback to collect audio data
+ * @param userdata Passed to @p callback
+ * @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
+ *
+ * @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
+ * repeatedly in quick succession to play however much was received in a single
+ * chunk.
+ */
+void uaudio_thread_start(uaudio_callback *callback,
+ void *userdata,
+ uaudio_playcallback *playcallback,
+ size_t min,
+ size_t max) {
+ 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_started = 1;
+ for(int n = 0; n < UAUDIO_THREAD_BUFFERS; ++n)
+ uaudio_buffers[n].samples = xcalloc(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");
+ if((e = pthread_create(&uaudio_play_thread,
+ NULL,
+ uaudio_play_thread_fn,
+ NULL)))
+ fatal(e, "pthread_create");
+}
+
+/** @brief Shut down background threads for audio processing */
+void uaudio_thread_stop(void) {
+ void *result;
+
+ pthread_mutex_lock(&uaudio_thread_lock);
+ uaudio_thread_activated = 0;
+ uaudio_thread_started = 0;
+ pthread_cond_broadcast(&uaudio_thread_cond);
+ pthread_mutex_unlock(&uaudio_thread_lock);
+ pthread_join(uaudio_collect_thread, &result);
+ pthread_join(uaudio_play_thread, &result);
+ for(int n = 0; n < UAUDIO_THREAD_BUFFERS; ++n)
+ xfree(uaudio_buffers[n].samples);
+}
+
+/** @brief Activate audio output */
+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;
+ 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);
+}
+
+/*
+Local Variables:
+c-basic-offset:2
+comment-column:40
+fill-column:79
+indent-tabs-mode:nil
+End:
+*/
/** @brief Options for chosen uaudio API */
static hash *uaudio_options;
+/** @brief Sample rate (Hz) */
+int uaudio_rate;
+
+/** @brief Bits per channel */
+int uaudio_bits;
+
+/** @brief Number of channels */
+int uaudio_channels;
+
+/** @brief Whether samples are signed or unsigned */
+int uaudio_signed;
+
+/** @brief Sample size in bytes
+ *
+ * NB one sample is a single point sample; up to @c uaudio_channels samples may
+ * play at the same time through different speakers. Thus this value is
+ * independent of @ref uaudio_channels.
+ */
+size_t uaudio_sample_size;
+
/** @brief Set a uaudio option */
void uaudio_set(const char *name, const char *value) {
if(!uaudio_options)
return value ? xstrdup(value) : NULL;
}
+/** @brief Set sample format
+ * @param rate Sample rate in KHz
+ * @param channels Number of channels (i.e. 2 for stereo)
+ * @param bits Number of bits per channel (typically 8 or 16)
+ * @param signed_ True for signed samples, false for unsigned
+ *
+ * Sets @ref uaudio_rate, @ref uaudio_channels, @ref uaudio_bits, @ref
+ * uaudio_signed and @ref uaudio_sample_size.
+ *
+ * Currently there is no way to specify non-native endian formats even if the
+ * underlying API can conveniently handle them. Actually this would be quite
+ * convenient for playrtp, so it might be added at some point.
+ *
+ * Not all APIs can support all sample formats. Generally the @c start
+ * function will do some error checking but some may be deferred to the point
+ * the device is opened (which might be @c activate).
+ */
+void uaudio_set_format(int rate, int channels, int bits, int signed_) {
+ uaudio_rate = rate;
+ uaudio_channels = channels;
+ uaudio_bits = bits;
+ uaudio_signed = signed_;
+ uaudio_sample_size = bits / CHAR_BIT;
+}
+
/** @brief List of known APIs
*
* Terminated by a null pointer.
#ifndef UAUDIO_H
#define UAUDIO_H
+extern int uaudio_rate;
+extern int uaudio_bits;
+extern int uaudio_channels;
+extern int uaudio_signed;
+extern size_t uaudio_sample_size;
+
/** @brief Callback to get audio data
* @param buffer Where to put audio data
* @param max_samples How many samples to supply
* @param userdata As passed to uaudio_open()
* @return Number of samples filled
- *
- * One sample is a single 16-bit signed value.
*/
-typedef size_t uaudio_callback(int16_t *buffer,
+typedef size_t uaudio_callback(void *buffer,
size_t max_samples,
void *userdata);
+/** @brief Callback to play audio data
+ * @param buffer Pointer to audio buffer
+ * @param samples Number of samples to play
+ * @return Number of samples played
+ *
+ * Used with uaudio_thread_start() etc.
+ */
+typedef size_t uaudio_playcallback(void *buffer, size_t samples);
+
/** @brief Audio API definition */
struct uaudio {
/** @brief Name of this API */
void (*deactivate)(void);
};
-
+
+void uaudio_set_format(int rate, int channels, int samplesize, int signed_);
void uaudio_set(const char *name, const char *value);
const char *uaudio_get(const char *name);
+void uaudio_thread_start(uaudio_callback *callback,
+ void *userdata,
+ uaudio_playcallback *playcallback,
+ size_t min,
+ size_t max);
+void uaudio_thread_stop(void);
+void uaudio_thread_activate(void);
+void uaudio_thread_deactivate(void);
#if HAVE_COREAUDIO_AUDIOHARDWARE_H
extern const struct uaudio uaudio_coreaudio;