From: Richard Kettlewell Date: Mon, 24 Mar 2008 18:54:20 +0000 (+0000) Subject: Rewrite playrtp ALSA support. The result seems to be much more X-Git-Tag: 3.0~6 X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~mdw/git/disorder/commitdiff_plain/4dadf1a26a3a9b619f7d91cbc435c7d9429d64d3 Rewrite playrtp ALSA support. The result seems to be much more reliable, and it's certainly simpler. The speaker ALSA support would probably benefit from using the same backend at some point, and with a similar backend for OSS we'd be in a good position to more closely unify the support for the various different sound APIs. --- diff --git a/clients/playrtp-alsa.c b/clients/playrtp-alsa.c index 498b6e1..ac8ef7e 100644 --- a/clients/playrtp-alsa.c +++ b/clients/playrtp-alsa.c @@ -1,6 +1,6 @@ /* * This file is part of DisOrder. - * Copyright (C) 2007 Richard Kettlewell + * Copyright (C) 2008 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 @@ -19,6 +19,10 @@ */ /** @file clients/playrtp-alsa.c * @brief RTP player - ALSA support + * + * This has been rewritten to use the @ref alsabg.h interface and is therefore + * now closely modelled on @ref playrtp-coreaudio.c. Given a similar interface + * wrapping OSS the whole of playrtp could probably be greatly simplified. */ #include @@ -37,232 +41,63 @@ #include "vector.h" #include "heap.h" #include "playrtp.h" +#include "alsabg.h" -/** @brief PCM handle */ -static snd_pcm_t *pcm; - -/** @brief True when @ref pcm is up and running */ -static int playrtp_alsa_prepared = 1; - -static void playrtp_alsa_init(void) { - snd_pcm_hw_params_t *hwparams; - snd_pcm_sw_params_t *swparams; - /* Only support one format for now */ - const int sample_format = SND_PCM_FORMAT_S16_BE; - unsigned rate = 44100; - const int channels = 2; - const int samplesize = channels * sizeof(uint16_t); - snd_pcm_uframes_t pcm_bufsize = MAXSAMPLES * samplesize * 3; - /* If we can write more than this many samples we'll get a wakeup */ - const int avail_min = 256; - int err; - - /* Open ALSA */ - if((err = snd_pcm_open(&pcm, - device ? device : "default", - SND_PCM_STREAM_PLAYBACK, - SND_PCM_NONBLOCK))) - fatal(0, "error from snd_pcm_open: %d", err); - /* Set up 'hardware' parameters */ - snd_pcm_hw_params_alloca(&hwparams); - if((err = snd_pcm_hw_params_any(pcm, hwparams)) < 0) - fatal(0, "error from snd_pcm_hw_params_any: %d", err); - if((err = snd_pcm_hw_params_set_access(pcm, hwparams, - SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) - fatal(0, "error from snd_pcm_hw_params_set_access: %d", err); - if((err = snd_pcm_hw_params_set_format(pcm, hwparams, - sample_format)) < 0) - - fatal(0, "error from snd_pcm_hw_params_set_format (%d): %d", - sample_format, err); - if((err = snd_pcm_hw_params_set_rate_near(pcm, hwparams, &rate, 0)) < 0) - fatal(0, "error from snd_pcm_hw_params_set_rate (%d): %d", - rate, err); - if((err = snd_pcm_hw_params_set_channels(pcm, hwparams, - channels)) < 0) - fatal(0, "error from snd_pcm_hw_params_set_channels (%d): %d", - channels, err); - if((err = snd_pcm_hw_params_set_buffer_size_near(pcm, hwparams, - &pcm_bufsize)) < 0) - fatal(0, "error from snd_pcm_hw_params_set_buffer_size (%ld): %d", - (long)pcm_bufsize, err); - if((err = snd_pcm_hw_params(pcm, hwparams)) < 0) - fatal(0, "error calling snd_pcm_hw_params: %d", err); - /* Set up 'software' parameters */ - snd_pcm_sw_params_alloca(&swparams); - if((err = snd_pcm_sw_params_current(pcm, swparams)) < 0) - fatal(0, "error calling snd_pcm_sw_params_current: %d", err); - if((err = snd_pcm_sw_params_set_avail_min(pcm, swparams, avail_min)) < 0) - fatal(0, "error calling snd_pcm_sw_params_set_avail_min %d: %d", - avail_min, err); - /* Default start threshold is 1, which means that PCM starts as soon as we've - * written anything. Setting it to pcm_bufsize (around 15000) produces - * -EINVAL. 1024 is a guess... */ - if((err = snd_pcm_sw_params_set_start_threshold(pcm, swparams, 1024)) < 0) - fatal(0, "error calling snd_pcm_sw_params_set_start_threshold %d: %d", - 1024, err); - if((err = snd_pcm_sw_params(pcm, swparams)) < 0) - fatal(0, "error calling snd_pcm_sw_params: %d", err); -} - -/** @brief Wait until ALSA wants some audio */ -static void wait_alsa(void) { - struct pollfd fds[64]; - int nfds, err; - unsigned short events; - - for(;;) { - do { - if((nfds = snd_pcm_poll_descriptors(pcm, - fds, sizeof fds / sizeof *fds)) < 0) - fatal(0, "error calling snd_pcm_poll_descriptors: %d", nfds); - } while(poll(fds, nfds, -1) < 0 && errno == EINTR); - if((err = snd_pcm_poll_descriptors_revents(pcm, fds, nfds, &events))) - fatal(0, "error calling snd_pcm_poll_descriptors_revents: %d", err); - if(events & POLLOUT) - return; - } -} - -/** @brief Play some sound via ALSA - * @param s Pointer to sample data - * @param n Number of samples - * @return 0 on success, -1 on non-fatal error - */ -static int playrtp_alsa_writei(const void *s, size_t n) { - int err; - snd_pcm_sframes_t frames_written; - - /* Do the write */ - frames_written = snd_pcm_writei(pcm, s, n / 2); - if(frames_written < 0) { - /* Something went wrong */ - switch(frames_written) { - case -EAGAIN: - return 0; - case -EPIPE: - error(0, "error calling snd_pcm_writei: %ld", - (long)frames_written); - if((err = snd_pcm_recover(pcm, -EPIPE, 0)) < 0) { - error(0, "error calling snd_pcm_recover: %d", err); - return -1; - } - frames_written = snd_pcm_writei(pcm, s, n / 2); - if(frames_written == -EAGAIN) - return 0; - else if(frames_written < 0) { - error(0, "error retrying snd_pcm_writei: %ld", - (long)frames_written); - return -1; - } - break; - default: - fatal(0, "error calling snd_pcm_writei: %ld", - (long)frames_written); - } - } - /* Success */ - next_timestamp += frames_written * 2; - if(dump_buffer) { - snd_pcm_sframes_t count; - const int16_t *sp = s; - - for(count = 0; count < frames_written * 2; ++count) { - dump_buffer[dump_index++] = (int16_t)ntohs(*sp++); - dump_index %= dump_size; - } - } - return 0; -} +/** @brief Callback from alsa_bg_collect() */ +static int playrtp_alsa_supply(void *dst, + unsigned supply_nsamples) { + unsigned samples_available; -/** @brief Play the relevant part of a packet - * @param p Packet to play - * @return 0 on success, -1 on non-fatal error - */ -static int playrtp_alsa_play(const struct packet *p) { - return playrtp_alsa_writei(p->samples_raw + next_timestamp - p->timestamp, - (p->timestamp + p->nsamples) - next_timestamp); -} - -/** @brief Play some silence - * @param p Next packet or NULL - * @return 0 on success, -1 on non-fatal error - */ -static int playrtp_alsa_infill(const struct packet *p) { - static const uint16_t zeros[INFILL_SAMPLES]; - size_t samples_available = INFILL_SAMPLES; - - if(p && samples_available > p->timestamp - next_timestamp) - samples_available = p->timestamp - next_timestamp; - return playrtp_alsa_writei(zeros, samples_available); -} - -static void playrtp_alsa_enable(void){ - int err; - - if(!playrtp_alsa_prepared) { - if((err = snd_pcm_prepare(pcm))) - fatal(0, "error calling snd_pcm_prepare: %d", err); - playrtp_alsa_prepared = 1; - } -} - -/** @brief Reset ALSA state after we lost synchronization */ -static void playrtp_alsa_disable(int hard_reset) { - int err; - - if((err = snd_pcm_nonblock(pcm, 0))) - fatal(0, "error calling snd_pcm_nonblock: %d", err); - if(hard_reset) { - if((err = snd_pcm_drop(pcm))) - fatal(0, "error calling snd_pcm_drop: %d", err); + pthread_mutex_lock(&lock); + const struct packet *p = playrtp_next_packet(); + if(p && contains(p, next_timestamp)) { + /* This packet is ready to play */ + const uint32_t packet_end = p->timestamp + p->nsamples; + const uint32_t offset = next_timestamp - p->timestamp; + const uint16_t *src = (void *)(p->samples_raw + offset); + samples_available = packet_end - next_timestamp; + if(samples_available > supply_nsamples) + samples_available = supply_nsamples; + next_timestamp += samples_available; + memcpy(dst, src, samples_available * sizeof (int16_t)); + /* We don't bother junking the packet - that'll be dealt with next time + * round */ } else { - if((err = snd_pcm_drain(pcm))) { - error(0, "error calling snd_pcm_drain: %d", err); - if((err = snd_pcm_drop(pcm))) - fatal(0, "error calling snd_pcm_drop: %d", err); - } + /* No packet is ready to play (and there might be no packet at all) */ + samples_available = p ? p->timestamp - next_timestamp : supply_nsamples; + if(samples_available > supply_nsamples) + samples_available = supply_nsamples; + /*info("infill %d", samples_available);*/ + next_timestamp += samples_available; + /* Unlike Core Audio the buffer is not guaranteed to be 0-filled */ + memset(dst, 0, samples_available * sizeof (int16_t)); } - if((err = snd_pcm_nonblock(pcm, 1))) - fatal(0, "error calling snd_pcm_nonblock: %d", err); - playrtp_alsa_prepared = 0; + pthread_mutex_unlock(&lock); + return samples_available; } void playrtp_alsa(void) { - int escape; - const struct packet *p; - - playrtp_alsa_init(); + alsa_bg_init(device ? device : "default", + playrtp_alsa_supply); pthread_mutex_lock(&lock); for(;;) { /* Wait for the buffer to fill up a bit */ playrtp_fill_buffer(); - playrtp_alsa_enable(); - escape = 0; + /* Start playing now */ info("Playing..."); - /* Keep playing until the buffer empties out, or ALSA tells us to get - * lost */ - while((nsamples >= minbuffer - || (nsamples > 0 - && contains(pheap_first(&packets), next_timestamp))) - && !escape) { - /* Wait for ALSA to ask us for more data */ - pthread_mutex_unlock(&lock); - wait_alsa(); - pthread_mutex_lock(&lock); - /* ALSA is ready for more data, find something to play */ - p = playrtp_next_packet(); - /* Play it or play some silence */ - if(contains(p, next_timestamp)) - escape = playrtp_alsa_play(p); - else - escape = playrtp_alsa_infill(p); + next_timestamp = pheap_first(&packets)->timestamp; + active = 1; + alsa_bg_enable(); + /* Wait until the buffer empties out */ + while(nsamples >= minbuffer + || (nsamples > 0 + && contains(pheap_first(&packets), next_timestamp))) { + pthread_cond_wait(&cond, &lock); } + /* Stop playing for a bit until the buffer re-fills */ + alsa_bg_disable(); active = 0; - /* We stop playing for a bit until the buffer re-fills */ - pthread_mutex_unlock(&lock); - playrtp_alsa_disable(escape); - pthread_mutex_lock(&lock); + /* Go back round */ } } diff --git a/clients/playrtp.c b/clients/playrtp.c index 4f6e57a..b79e14d 100644 --- a/clients/playrtp.c +++ b/clients/playrtp.c @@ -330,8 +330,9 @@ static void *queue_thread(void attribute((unused)) *arg) { for(;;) { /* Get the next packet */ pthread_mutex_lock(&receive_lock); - while(!received_packets) + while(!received_packets) { pthread_cond_wait(&receive_cond, &receive_lock); + } p = received_packets; received_packets = p->next; if(!received_packets) @@ -429,8 +430,9 @@ static void *listen_thread(void attribute((unused)) *arg) { * out of order then we guarantee dropouts. But for now... */ if(nsamples >= maxbuffer) { pthread_mutex_lock(&lock); - while(nsamples >= maxbuffer) + while(nsamples >= maxbuffer) { pthread_cond_wait(&cond, &lock); + } pthread_mutex_unlock(&lock); } /* Add the packet to the receive queue */ @@ -453,8 +455,9 @@ void playrtp_fill_buffer(void) { while(nsamples) drop_first_packet(); info("Buffering..."); - while(nsamples < readahead) + while(nsamples < readahead) { pthread_cond_wait(&cond, &lock); + } next_timestamp = pheap_first(&packets)->timestamp; active = 1; } diff --git a/lib/Makefile.am b/lib/Makefile.am index 2331261..a7536d8 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -1,6 +1,6 @@ # # This file is part of DisOrder. -# Copyright (C) 2004, 2005, 2006, 2007 Richard Kettlewell +# Copyright (C) 2004-2008 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 @@ -24,6 +24,7 @@ noinst_PROGRAMS=test libdisorder_a_SOURCES=charset.c charset.h \ addr.c addr.h \ + alsabg.c alsabg.h \ authhash.c authhash.h \ basen.c basen.h \ base64.c base64.h \ diff --git a/lib/alsabg.c b/lib/alsabg.c new file mode 100644 index 0000000..61648f4 --- /dev/null +++ b/lib/alsabg.c @@ -0,0 +1,304 @@ +/* + * This file is part of DisOrder. + * Copyright (C) 2008 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ +/** @file alsabg.c + * @brief Background-thread interface to ALSA + */ + +#include + +#if HAVE_ALSA_ASOUNDLIB_H +#include "types.h" + +#include +#include + +#include "alsabg.h" +#include "log.h" + +/** @brief Output handle */ +static snd_pcm_t *pcm; + +/** @brief Called to get audio data */ +static alsa_bg_supply *supplyfn; + +static pthread_t alsa_bg_collect_tid, alsa_bg_play_tid; + +/** @brief Set to shut down the background threads */ +static int alsa_bg_shutdown = 0; + +/** @brief Number of channels (samples per frame) */ +#define CHANNELS 2 + +/** @brief Number of bytes per samples */ +#define BYTES_PER_SAMPLE 2 + +/** @brief Number of bytes per frame */ +#define BYTES_PER_FRAME (CHANNELS * BYTES_PER_SAMPLE) + +/** @brief Buffer size in bytes */ +#define BUFFER_BYTES 65536 + +/** @brief Buffer size in frames */ +#define BUFFER_FRAMES (BUFFER_BYTES / BYTES_PER_FRAME) + +/** @brief Buffer size in samples */ +#define BUFFER_SAMPLES (BUFFER_BYTES / BYTES_PER_SAMPLE) + +/** @brief Audio buffer */ +static uint8_t alsa_bg_buffer[BUFFER_BYTES]; + +/** @brief First playable byte in audio buffer */ +static unsigned alsa_bg_start; + +/** @brief Number of playable bytes in audio buffer */ +static unsigned alsa_bg_count; + +/** @brief Current enable status */ +static int alsa_bg_enabled; + +/** @brief Lock protecting audio buffer pointers */ +static pthread_mutex_t alsa_bg_lock = PTHREAD_MUTEX_INITIALIZER; + +/** @brief Signaled when buffer contents changes */ +static pthread_cond_t alsa_bg_cond = PTHREAD_COND_INITIALIZER; + +/** @brief Call a pthread_ function and fatal() on exit */ +#define ep(x) do { \ + int prc; \ + \ + if((prc = (x))) \ + fatal(prc, "%s", #x); \ +} while(0) + +/** @brief Data collection thread + * + * This thread collects audio data to play and stores it in the ring + * buffer. + */ +static void *alsa_bg_collect(void attribute((unused)) *arg) { + unsigned avail_start, avail_count; + int count; + + ep(pthread_mutex_lock(&alsa_bg_lock)); + for(;;) { + /* If we're shutting down, quit straight away */ + if(alsa_bg_shutdown) + break; + /* While we're disabled or the buffer is full, just wait */ + if(!alsa_bg_enabled || alsa_bg_count == BUFFER_BYTES) { + ep(pthread_cond_wait(&alsa_bg_cond, &alsa_bg_lock)); + continue; + } + /* Figure out where and how big the gap we can write into is */ + avail_start = alsa_bg_start + alsa_bg_count; + if(avail_start < BUFFER_BYTES) + avail_count = BUFFER_BYTES - avail_start; + else { + avail_start %= BUFFER_BYTES; + avail_count = alsa_bg_start - avail_start; + } + assert(avail_start < BUFFER_BYTES); + assert(avail_count <= BUFFER_BYTES); + assert(avail_count + alsa_bg_count <= BUFFER_BYTES); + ep(pthread_mutex_unlock(&alsa_bg_lock)); + count = supplyfn(alsa_bg_buffer + avail_start, + avail_count / BYTES_PER_SAMPLE); + ep(pthread_mutex_lock(&alsa_bg_lock)); + alsa_bg_count += count * BYTES_PER_SAMPLE; + assert(alsa_bg_start < BUFFER_BYTES); + assert(alsa_bg_count <= BUFFER_BYTES); + ep(pthread_cond_signal(&alsa_bg_cond)); + } + ep(pthread_mutex_unlock(&alsa_bg_lock)); + return 0; +} + +/** @brief Playback thread + * + * This thread reads audio data out of the ring buffer and plays it back + */ +static void *alsa_bg_play(void attribute((unused)) *arg) { + int prepared = 1, err; + int start, nbytes, nframes, rframes; + + ep(pthread_mutex_lock(&alsa_bg_lock)); + for(;;) { + /* If we're shutting down, quit straight away */ + if(alsa_bg_shutdown) + break; + /* Wait for some data to be available. (If we are marked disabled + * we keep on playing what we've got.) */ + if(alsa_bg_count == 0) { + if(prepared) { + if((err = snd_pcm_drain(pcm))) + fatal(0, "snd_pcm_drain: %d", err); + prepared = 0; + } + ep(pthread_cond_wait(&alsa_bg_cond, &alsa_bg_lock)); + continue; + } + /* Calculate how much we can play */ + start = alsa_bg_start; + if(start + alsa_bg_count <= BUFFER_BYTES) + nbytes = alsa_bg_count; + else + nbytes = BUFFER_BYTES - start; + /* Limit how much of the buffer we play. The effect is that we return from + * _writei earlier, and therefore free up more buffer space to read fresh + * data into. /2 works fine, /4 is just conservative. /1 (i.e. abolishing + * the heuristic) produces noticably noisy output. */ + if(nbytes > BUFFER_BYTES / 4) + nbytes = BUFFER_BYTES / 4; + assert((unsigned)nbytes <= alsa_bg_count); + nframes = nbytes / BYTES_PER_FRAME; + ep(pthread_mutex_unlock(&alsa_bg_lock)); + /* Make sure the PCM is prepared */ + if(!prepared) { + if((err = snd_pcm_prepare(pcm))) + fatal(0, "snd_pcm_prepare: %d", err); + prepared = 1; + } + /* Play what we can */ + rframes = snd_pcm_writei(pcm, alsa_bg_buffer + start, nframes); + ep(pthread_mutex_lock(&alsa_bg_lock)); + if(rframes < 0) { + error(0, "snd_pcm_writei: %d", rframes); + switch(rframes) { + case -EPIPE: + if((err = snd_pcm_recover(pcm, -EPIPE, 0))) + fatal(0, "snd_pcm_recover: %d", err); + break; + } + } else { + const int rbytes = rframes * BYTES_PER_FRAME; + /*fprintf(stderr, "%5d -> %5d\n", nbytes, rbytes);*/ + /* Update the buffer pointers */ + alsa_bg_count -= rbytes; + alsa_bg_start += rbytes; + if(alsa_bg_start >= BUFFER_BYTES) + alsa_bg_start -= BUFFER_BYTES; + assert(alsa_bg_start < BUFFER_BYTES); + assert(alsa_bg_count <= BUFFER_BYTES); + /* Let the collector know we've opened up some space */ + ep(pthread_cond_signal(&alsa_bg_cond)); + } + } + ep(pthread_mutex_unlock(&alsa_bg_lock)); + return 0; +} + +/** @brief Enable ALSA play */ +void alsa_bg_enable(void) { + ep(pthread_mutex_lock(&alsa_bg_lock)); + alsa_bg_enabled = 1; + ep(pthread_cond_broadcast(&alsa_bg_cond)); + ep(pthread_mutex_unlock(&alsa_bg_lock)); +} + +/** @brief Disable ALSA play */ +void alsa_bg_disable(void) { + ep(pthread_mutex_lock(&alsa_bg_lock)); + alsa_bg_enabled = 0; + ep(pthread_cond_broadcast(&alsa_bg_cond)); + ep(pthread_mutex_unlock(&alsa_bg_lock)); +} + +/** @brief Initialize background ALSA playback + * @param device Target device or NULL to use default + * @param supply Function to call to get audio data to play + * + * Playback is not initially enabled; see alsa_bg_enable(). When playback is + * enabled, @p supply will be called in a background thread to request audio + * data. It should return in a timely manner, but playback happens from a + * further thread and delays in @p supply will not delay transfer of data to + * the sound device (provided it doesn't actually run out). + */ +void alsa_bg_init(const char *device, + alsa_bg_supply *supply) { + snd_pcm_hw_params_t *hwparams; + /* Only support one format for now */ + const int sample_format = SND_PCM_FORMAT_S16_BE; + unsigned rate = 44100; + int err; + + if((err = snd_pcm_open(&pcm, + device ? device : "default", + SND_PCM_STREAM_PLAYBACK, + 0))) + fatal(0, "error from snd_pcm_open: %d", err); + /* Set up 'hardware' parameters */ + snd_pcm_hw_params_alloca(&hwparams); + if((err = snd_pcm_hw_params_any(pcm, hwparams)) < 0) + fatal(0, "error from snd_pcm_hw_params_any: %d", err); + if((err = snd_pcm_hw_params_set_access(pcm, hwparams, + SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) + fatal(0, "error from snd_pcm_hw_params_set_access: %d", err); + if((err = snd_pcm_hw_params_set_format(pcm, hwparams, + sample_format)) < 0) + + fatal(0, "error from snd_pcm_hw_params_set_format (%d): %d", + sample_format, err); + if((err = snd_pcm_hw_params_set_rate_near(pcm, hwparams, &rate, 0)) < 0) + fatal(0, "error from snd_pcm_hw_params_set_rate (%d): %d", + rate, err); + if((err = snd_pcm_hw_params_set_channels(pcm, hwparams, + CHANNELS)) < 0) + fatal(0, "error from snd_pcm_hw_params_set_channels (%d): %d", + CHANNELS, err); + if((err = snd_pcm_hw_params(pcm, hwparams)) < 0) + fatal(0, "error calling snd_pcm_hw_params: %d", err); + + /* Record the audio supply function */ + supplyfn = supply; + + /* Create the audio output thread */ + alsa_bg_shutdown = 0; + alsa_bg_enabled = 0; + ep(pthread_create(&alsa_bg_collect_tid, 0, alsa_bg_collect, 0)); + ep(pthread_create(&alsa_bg_play_tid, 0, alsa_bg_play, 0)); +} + +void alsa_bg_close(void) { + void *r; + + /* Notify background threads that we're shutting down */ + ep(pthread_mutex_lock(&alsa_bg_lock)); + alsa_bg_enabled = 0; + alsa_bg_shutdown = 1; + ep(pthread_cond_signal(&alsa_bg_cond)); + ep(pthread_mutex_unlock(&alsa_bg_lock)); + /* Join background threads when they're done */ + ep(pthread_join(alsa_bg_collect_tid, &r)); + ep(pthread_join(alsa_bg_play_tid, &r)); + /* Close audio device */ + snd_pcm_close(pcm); + pcm = 0; +} + +#endif + +/* +Local Variables: +c-basic-offset:2 +comment-column:40 +fill-column:79 +indent-tabs-mode:nil +End: +*/ diff --git a/lib/alsabg.h b/lib/alsabg.h new file mode 100644 index 0000000..0ef9c59 --- /dev/null +++ b/lib/alsabg.h @@ -0,0 +1,60 @@ +/* + * This file is part of DisOrder. + * Copyright (C) 2008 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ +/** @file alsabg.h + * @brief Background-thread interface to ALSA + * + * This wraps ALSA with an interface which calls back to the client from a + * thread. It's not intended for completely general use, just what DisOrder + * needs. + */ + +#ifndef ALSABG_H +#define ALSABG_H + +/** @brief Supply audio callback + * @param dst Where to write audio data + * @param nsamples Number of samples to write + * + * This function should write up to @p *nsamples samples of data at + * @p dst, and return the number of samples written, or -1 if some error + * occurred. It will be called in a background thread. + */ +typedef int alsa_bg_supply(void *dst, + unsigned nsamples); + +void alsa_bg_init(const char *device, + alsa_bg_supply *supply); + +void alsa_bg_enable(void); + +void alsa_bg_disable(void); + +void alsa_bg_close(void); + +#endif /* ALSABG_H */ + +/* +Local Variables: +c-basic-offset:2 +comment-column:40 +fill-column:79 +indent-tabs-mode:nil +End: +*/