chiark / gitweb /
Merge from uniform audio branch. disorder-playrtp now uses the uaudio
authorRichard Kettlewell <rjk@greenend.org.uk>
Sun, 1 Mar 2009 13:09:43 +0000 (13:09 +0000)
committerRichard Kettlewell <rjk@greenend.org.uk>
Sun, 1 Mar 2009 13:09:43 +0000 (13:09 +0000)
code for OSS, ALSA and Core Audio and works on Linux, FreeBSD and MacOS.
A few build and documentation fixes come along with these changes.

17 files changed:
.bzrignore
clients/Makefile.am
clients/playrtp-alsa.c [deleted file]
clients/playrtp-coreaudio.c [deleted file]
clients/playrtp-oss.c [deleted file]
clients/playrtp.c
clients/playrtp.h
configure.ac
doc/disorder-playrtp.1.in
lib/Makefile.am
lib/uaudio-alsa.c [new file with mode: 0644]
lib/uaudio-coreaudio.c [new file with mode: 0644]
lib/uaudio-oss.c [new file with mode: 0644]
lib/uaudio-rtp.c [new file with mode: 0644]
lib/uaudio-thread.c [new file with mode: 0644]
lib/uaudio.c [new file with mode: 0644]
lib/uaudio.h [new file with mode: 0644]

index 022ddca92cb36f792c31caa02f3abba5bf6a4cd5..e5fb4931a6fb37405c8362b20d4775d78f79eaed 100644 (file)
@@ -200,3 +200,4 @@ doc/disorder_preferences.5.html
 plugins/index.html
 doc/disorder-choose.8
 doc/disorder-choose.8.html
+config.aux/compile
index 91d444887c95d3651ff67c45a4c852db9b2ed5d0..5456bd1c8ee4f34458dbf4b9d6fa9e3490e49c7e 100644 (file)
@@ -33,8 +33,7 @@ disorderfm_SOURCES=disorderfm.c \
 disorderfm_LDADD=$(LIBOBJS) ../lib/libdisorder.a $(LIBGC) $(LIBICONV)
 disorderfm_DEPENDENCIES=$(LIBOBJS) ../lib/libdisorder.a
 
-disorder_playrtp_SOURCES=playrtp.c playrtp.h playrtp-mem.c \
-                        playrtp-alsa.c playrtp-coreaudio.c playrtp-oss.c
+disorder_playrtp_SOURCES=playrtp.c playrtp.h playrtp-mem.c
 disorder_playrtp_LDADD=$(LIBOBJS) ../lib/libdisorder.a \
        $(LIBASOUND) $(LIBPCRE) $(LIBICONV) $(LIBGCRYPT) $(COREAUDIO) \
        $(LIBDB) $(LIBPTHREAD)
diff --git a/clients/playrtp-alsa.c b/clients/playrtp-alsa.c
deleted file mode 100644 (file)
index a407b2f..0000000
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * 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 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 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 "common.h"
-
-#if HAVE_ALSA_ASOUNDLIB_H 
-
-#include <poll.h>
-#include <alsa/asoundlib.h>
-#include <pthread.h>
-#include <arpa/inet.h>
-
-#include "mem.h"
-#include "log.h"
-#include "vector.h"
-#include "heap.h"
-#include "playrtp.h"
-#include "alsabg.h"
-
-/** @brief Callback from alsa_bg_collect() */
-static int playrtp_alsa_supply(void *dst,
-                               unsigned supply_nsamples) {
-  unsigned samples_available;
-  const struct packet *p;
-
-  pthread_mutex_lock(&lock);
-  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 {
-    /* 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));
-  }
-  pthread_mutex_unlock(&lock);
-  return samples_available;
-}
-
-void playrtp_alsa(void) {
-  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();
-    /* Start playing now */
-    info("Playing...");
-    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;
-    /* Go back round */
-  }
-}
-
-#endif
-
-/*
-Local Variables:
-c-basic-offset:2
-comment-column:40
-fill-column:79
-indent-tabs-mode:nil
-End:
-*/
diff --git a/clients/playrtp-coreaudio.c b/clients/playrtp-coreaudio.c
deleted file mode 100644 (file)
index dae7027..0000000
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * This file is part of DisOrder.
- * Copyright (C) 2007 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 clients/playrtp-coreaudio.c
- * @brief RTP player - Core Audio support
- */
-
-#include "common.h"
-
-#if HAVE_COREAUDIO_AUDIOHARDWARE_H
-#include <pthread.h>
-
-#include "mem.h"
-#include "log.h"
-#include "vector.h"
-#include "heap.h"
-#include "playrtp.h"
-#include "coreaudio.h"
-
-/** @brief Callback from Core Audio */
-static OSStatus adioproc
-    (AudioDeviceID attribute((unused)) inDevice,
-     const AudioTimeStamp attribute((unused)) *inNow,
-     const AudioBufferList attribute((unused)) *inInputData,
-     const AudioTimeStamp attribute((unused)) *inInputTime,
-     AudioBufferList *outOutputData,
-     const AudioTimeStamp attribute((unused)) *inOutputTime,
-     void attribute((unused)) *inClientData) {
-  UInt32 nbuffers = outOutputData->mNumberBuffers;
-  AudioBuffer *ab = outOutputData->mBuffers;
-  uint32_t samples_available;
-
-  pthread_mutex_lock(&lock);
-  while(nbuffers > 0) {
-    float *samplesOut = ab->mData;
-    size_t samplesOutLeft = ab->mDataByteSize / sizeof (float);
-
-    while(samplesOutLeft > 0) {
-      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 *ptr = (void *)(p->samples_raw + offset);
-
-        samples_available = packet_end - next_timestamp;
-        if(samples_available > samplesOutLeft)
-          samples_available = samplesOutLeft;
-        next_timestamp += samples_available;
-        samplesOutLeft -= samples_available;
-        if(dump_buffer) {
-          size_t n;
-
-          for(n = 0; n < samples_available; ++n) {
-            dump_buffer[dump_index++] = (int16_t)ntohs(ptr[n]);
-            dump_index %= dump_size;
-          }
-        }
-        while(samples_available-- > 0)
-          *samplesOut++ = (int16_t)ntohs(*ptr++) * (0.5 / 32767);
-        /* We don't bother junking the packet - that'll be dealt with next time
-         * round */
-      } else {
-        /* No packet is ready to play (and there might be no packet at all) */
-        samples_available = p ? p->timestamp - next_timestamp
-                              : samplesOutLeft;
-        if(samples_available > samplesOutLeft)
-          samples_available = samplesOutLeft;
-        //info("infill by %"PRIu32, samples_available);
-        /* Conveniently the buffer is 0 to start with */
-        next_timestamp += samples_available;
-        samplesOut += samples_available;
-        samplesOutLeft -= samples_available;
-        if(dump_buffer) {
-          size_t n;
-
-          for(n = 0; n < samples_available; ++n) {
-            dump_buffer[dump_index++] = 0;
-            dump_index %= dump_size;
-          }
-        }
-      }
-    }
-    ++ab;
-    --nbuffers;
-  }
-  pthread_mutex_unlock(&lock);
-  return 0;
-}
-
-void playrtp_coreaudio(void) {
-  OSStatus status;
-  UInt32 propertySize;
-  AudioDeviceID adid;
-  AudioStreamBasicDescription asbd;
-
-  /* If this looks suspiciously like libao's macosx driver there's an
-   * excellent reason for that... */
-
-  /* TODO report errors as strings not numbers */
-  /* Identify the device to use */
-  adid = coreaudio_getdevice(device);
-  propertySize = sizeof asbd;
-  status = AudioDeviceGetProperty(adid, 0, false,
-                                 kAudioDevicePropertyStreamFormat,
-                                 &propertySize, &asbd);
-  if(status)
-    fatal(0, "AudioHardwareGetProperty: %d", (int)status);
-  D(("mSampleRate       %f", asbd.mSampleRate));
-  D(("mFormatID         %08lx", asbd.mFormatID));
-  D(("mFormatFlags      %08lx", asbd.mFormatFlags));
-  D(("mBytesPerPacket   %08lx", asbd.mBytesPerPacket));
-  D(("mFramesPerPacket  %08lx", asbd.mFramesPerPacket));
-  D(("mBytesPerFrame    %08lx", asbd.mBytesPerFrame));
-  D(("mChannelsPerFrame %08lx", asbd.mChannelsPerFrame));
-  D(("mBitsPerChannel   %08lx", asbd.mBitsPerChannel));
-  D(("mReserved         %08lx", asbd.mReserved));
-  if(asbd.mFormatID != kAudioFormatLinearPCM)
-    fatal(0, "audio device does not support kAudioFormatLinearPCM");
-  status = AudioDeviceAddIOProc(adid, adioproc, 0);
-  if(status)
-    fatal(0, "AudioDeviceAddIOProc: %d", (int)status);
-  pthread_mutex_lock(&lock);
-  for(;;) {
-    /* Wait for the buffer to fill up a bit */
-    playrtp_fill_buffer();
-    /* Start playing now */
-    info("Playing...");
-    next_timestamp = pheap_first(&packets)->timestamp;
-    active = 1;
-    status = AudioDeviceStart(adid, adioproc);
-    if(status)
-      fatal(0, "AudioDeviceStart: %d", (int)status);
-    /* 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 */
-    status = AudioDeviceStop(adid, adioproc);
-    if(status)
-      fatal(0, "AudioDeviceStop: %d", (int)status);
-    active = 0;
-    /* Go back round */
-  }
-}
-
-#endif
-
-/*
-Local Variables:
-c-basic-offset:2
-comment-column:40
-fill-column:79
-indent-tabs-mode:nil
-End:
-*/
diff --git a/clients/playrtp-oss.c b/clients/playrtp-oss.c
deleted file mode 100644 (file)
index 4b0ab83..0000000
+++ /dev/null
@@ -1,278 +0,0 @@
-/*
- * This file is part of DisOrder.
- * Copyright (C) 2007 Richard Kettlewell
- * Portions copyright (C) 2007 Ross Younger
- *
- * 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 clients/playrtp-oss.c
- * @brief RTP player - OSS and empeg support
- */
-
-#include "common.h"
-
-#if HAVE_SYS_SOUNDCARD_H || EMPEG_HOST
-
-#include <poll.h>
-#include <sys/ioctl.h>
-#if !EMPEG_HOST
-#include <sys/soundcard.h>
-#endif
-#include <pthread.h>
-#include <fcntl.h>
-#include <unistd.h>
-#include <errno.h>
-#include <arpa/inet.h>
-
-#include "mem.h"
-#include "log.h"
-#include "vector.h"
-#include "heap.h"
-#include "syscalls.h"
-#include "playrtp.h"
-
-/** @brief /dev/dsp (or whatever) */
-static int playrtp_oss_fd = -1;
-
-/** @brief Audio buffer */
-static char *playrtp_oss_buffer;
-
-/** @brief Size of @ref playrtp_oss_buffer in bytes */
-static int playrtp_oss_bufsize;
-
-/** @brief Number of bytes used in @ref playrtp_oss_buffer */
-static int playrtp_oss_bufused;
-
-/** @brief Open and configure the OSS audio device */
-static void playrtp_oss_enable(void) {
-  if(playrtp_oss_fd == -1) {
-#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. */
-    if(!device)
-      device = "/dev/audio";
-    if((playrtp_oss_fd = open(device, O_WRONLY)) < 0)
-      fatal(errno, "error opening %s", device);
-    playrtp_oss_bufsize = 4608;
-#else
-    int rate = 44100, stereo = 1, format = AFMT_S16_BE;
-    if(!device) {
-      if(access("/dev/dsp", W_OK) == 0)
-       device = "/dev/dsp";
-      else if(access("/dev/audio", W_OK) == 0)
-       device = "/dev/audio";
-      else
-       fatal(0, "cannot determine default audio device");
-    }
-    if((playrtp_oss_fd = open(device, O_WRONLY)) < 0)
-      fatal(errno, "error opening %s", device);
-    if(ioctl(playrtp_oss_fd, SNDCTL_DSP_SETFMT, &format) < 0)
-      fatal(errno, "ioctl SNDCTL_DSP_SETFMT");
-    if(ioctl(playrtp_oss_fd, SNDCTL_DSP_STEREO, &stereo) < 0)
-      fatal(errno, "ioctl SNDCTL_DSP_STEREO");
-    if(ioctl(playrtp_oss_fd, SNDCTL_DSP_SPEED, &rate) < 0)
-      fatal(errno, "ioctl SNDCTL_DSP_SPEED");
-    if(rate != 44100)
-      error(0, "asking for 44100Hz, got %dHz", rate);
-    if(ioctl(playrtp_oss_fd, SNDCTL_DSP_GETBLKSIZE, &playrtp_oss_bufsize) < 0)
-      fatal(errno, "ioctl SNDCTL_DSP_GETBLKSIZE");
-    info("OSS buffer size %d", playrtp_oss_bufsize);
-#endif
-    playrtp_oss_buffer = xmalloc(playrtp_oss_bufsize);
-    playrtp_oss_bufused = 0;
-    nonblock(playrtp_oss_fd);
-  }
-}
-
-/** @brief Flush the OSS output buffer
- * @return 0 on success, non-0 on error
- */
-static int playrtp_oss_flush(void) {
-  int nbyteswritten;
-
-  if(!playrtp_oss_bufused)
-    return 0;                           /* nothing to do */
-  /* 0 out the unused portion of the buffer */
-  memset(playrtp_oss_buffer + playrtp_oss_bufused, 0,
-         playrtp_oss_bufsize - playrtp_oss_bufused);
-#if EMPEG_HOST 
-  /* empeg audio driver insists on native-endian samples */
-  {
-    uint16_t *ptr,
-      *const limit = (uint16_t *)(playrtp_oss_buffer + playrtp_oss_bufused);
-
-    for(ptr = (uint16_t *)playrtp_oss_buffer; ptr < limit; ++ptr)
-      *ptr = ntohs(*ptr);
-  }
-#endif
-  for(;;) {
-    nbyteswritten = write(playrtp_oss_fd,
-                          playrtp_oss_buffer, playrtp_oss_bufsize);
-    if(nbyteswritten < 0) {
-      switch(errno) {
-      case EINTR:
-        break;                          /* try again */
-      case EAGAIN:
-        return 0;                       /* try later */
-      default:
-        error(errno, "error writing to %s", device);
-        return -1;
-      }
-    } else {
-      if(nbyteswritten < playrtp_oss_bufsize)
-        error(0, "%s: short write (%d/%d)",
-              device, nbyteswritten, playrtp_oss_bufsize);
-      if(dump_buffer) {
-        int count;
-        const int16_t *sp = (const int16_t *)playrtp_oss_buffer;
-        
-        for(count = 0; count < playrtp_oss_bufsize; count += sizeof(int16_t)) {
-          dump_buffer[dump_index++] = (int16_t)ntohs(*sp++);
-          dump_index %= dump_size;
-        }
-      }
-      playrtp_oss_bufused = 0;
-      return 0;
-    }
-  }
-}
-
-/** @brief Wait until the audio device can accept more data */
-static void playrtp_oss_wait(void) {
-  struct pollfd fds[1];
-  int n;
-
-  do {
-    fds[0].fd = playrtp_oss_fd;
-    fds[0].events = POLLOUT;
-    while((n = poll(fds, 1, -1)) < 0 && errno == EINTR)
-      ;
-    if(n < 0)
-      fatal(errno, "calling poll");
-  } while(!(fds[0].revents & (POLLOUT|POLLERR)));
-}
-
-/** @brief Close the OSS output device
- * @param hard If nonzero, drop pending data
- */
-static void playrtp_oss_disable(int hard) {
-  if(hard) {
-#if !EMPEG_HOST
-    /* No SNDCTL_DSP_ ioctls on empeg */
-    if(ioctl(playrtp_oss_fd, SNDCTL_DSP_RESET, 0) < 0)
-      error(errno, "ioctl SNDCTL_DSP_RESET");
-#endif
-  } else
-    playrtp_oss_flush();
-  xclose(playrtp_oss_fd);
-  playrtp_oss_fd = -1;
-  free(playrtp_oss_buffer);
-  playrtp_oss_buffer = 0;
-}
-
-/** @brief Write samples to OSS output device
- * @param data Pointer to sample data
- * @param samples Number of samples
- * @return 0 on success, non-0 on error
- */
-static int playrtp_oss_write(const char *data, size_t samples) {
-  long bytes = samples * sizeof(int16_t);
-  while(bytes > 0) {
-    int n = playrtp_oss_bufsize - playrtp_oss_bufused;
-
-    if(n > bytes)
-      n = bytes;
-    memcpy(playrtp_oss_buffer + playrtp_oss_bufused, data, n);
-    bytes -= n;
-    data += n;
-    playrtp_oss_bufused += n;
-    if(playrtp_oss_bufused == playrtp_oss_bufsize)
-      if(playrtp_oss_flush())
-        return -1;
-  }
-  next_timestamp += samples;
-  return 0;
-}
-
-/** @brief Play some data from packet @p p
- *
- * @p p is assumed to contain @ref next_timestamp.
- */
-static int playrtp_oss_play(const struct packet *p) {
-  return playrtp_oss_write
-    ((const char *)(p->samples_raw + next_timestamp - p->timestamp),
-     (p->timestamp + p->nsamples) - next_timestamp);
-}
-
-/** @brief Play some silence before packet @p p
- *
- * @p p is assumed to be entirely before @ref next_timestamp.
- */
-static int playrtp_oss_infill(const struct packet *p) {
-  static const char zeros[INFILL_SAMPLES * sizeof(int16_t)];
-  size_t samples_available = INFILL_SAMPLES;
-
-  if(p && samples_available > p->timestamp - next_timestamp)
-    samples_available = p->timestamp - next_timestamp;
-  return playrtp_oss_write(zeros, samples_available);
-}
-
-/** @brief OSS backend for playrtp */
-void playrtp_oss(void) {
-  int escape;
-  const struct packet *p;
-
-  pthread_mutex_lock(&lock);
-  for(;;) {
-    /* Wait for the buffer to fill up a bit */
-    playrtp_fill_buffer();
-    playrtp_oss_enable();
-    escape = 0;
-    info("Playing...");
-    /* Keep playing until the buffer empties out, we get an error */
-    while((nsamples >= minbuffer
-          || (nsamples > 0
-              && contains(pheap_first(&packets), next_timestamp)))
-         && !escape) {
-      /* Wait until we can play more */
-      pthread_mutex_unlock(&lock);
-      playrtp_oss_wait();
-      pthread_mutex_lock(&lock);
-      /* Device 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_oss_play(p);
-      else
-       escape = playrtp_oss_infill(p);
-    }
-    active = 0;
-    /* We stop playing for a bit until the buffer re-fills */
-    pthread_mutex_unlock(&lock);
-    playrtp_oss_disable(escape);
-    pthread_mutex_lock(&lock);
-  }
-}
-
-#endif
-
-/*
-Local Variables:
-c-basic-offset:2
-comment-column:40
-fill-column:79
-indent-tabs-mode:nil
-End:
-*/
index aca1532f52f657da8248f1d3fdf7dcb6c757fff5..f5103a30865c486a5b60f5c92525620d7e84b641 100644 (file)
@@ -79,6 +79,7 @@
 #include "playrtp.h"
 #include "inputline.h"
 #include "version.h"
+#include "uaudio.h"
 
 #define readahead linux_headers_are_borked
 
@@ -94,7 +95,6 @@ static int rtpfd;
 static FILE *logfp;
 
 /** @brief Output device */
-const char *device;
 
 /** @brief Minimum low watermark
  *
@@ -168,16 +168,8 @@ pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
 /** @brief Condition variable signalled whenever @ref packets is changed */
 pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
 
-#if DEFAULT_BACKEND == BACKEND_ALSA
-# define DEFAULT_PLAYRTP_BACKEND playrtp_alsa
-#elif DEFAULT_BACKEND == BACKEND_OSS
-# define DEFAULT_PLAYRTP_BACKEND playrtp_oss
-#elif DEFAULT_BACKEND == BACKEND_COREAUDIO
-# define DEFAULT_PLAYRTP_BACKEND playrtp_coreaudio
-#endif
-
 /** @brief Backend to play with */
-static void (*backend)(void) = DEFAULT_PLAYRTP_BACKEND;
+static const struct uaudio *backend;
 
 HEAP_DEFINE(pheap, struct packet *, lt_packet);
 
@@ -407,7 +399,7 @@ static void *listen_thread(void attribute((unused)) *arg) {
     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) */
@@ -479,33 +471,10 @@ struct packet *playrtp_next_packet(void) {
   return 0;
 }
 
-/** @brief Play an RTP stream
- *
- * This is the guts of the program.  It is responsible for:
- * - starting the listening thread
- * - opening the audio device
- * - reading ahead to build up a buffer
- * - arranging for audio to be played
- * - detecting when the buffer has got too small and re-buffering
- */
-static void play_rtp(void) {
-  pthread_t ltid;
-  int err;
-
-  /* We receive and convert audio data in a background thread */
-  if((err = pthread_create(&ltid, 0, listen_thread, 0)))
-    fatal(err, "pthread_create listen_thread");
-  /* We have a second thread to add received packets to the queue */
-  if((err = pthread_create(&ltid, 0, queue_thread, 0)))
-    fatal(err, "pthread_create queue_thread");
-  /* The rest of the work is backend-specific */
-  backend();
-}
-
 /* display usage message and terminate */
 static void help(void) {
   xprintf("Usage:\n"
-         "  disorder-playrtp [OPTIONS] ADDRESS [PORT]\n"
+         "  disorder-playrtp [OPTIONS] [[ADDRESS] PORT]\n"
          "Options:\n"
           "  --device, -D DEVICE     Output device\n"
           "  --min, -m FRAMES        Buffer low water mark\n"
@@ -529,6 +498,66 @@ static void help(void) {
   exit(0);
 }
 
+static size_t playrtp_callback(void *buffer,
+                               size_t max_samples,
+                               void attribute((unused)) *userdata) {
+  size_t samples;
+
+  pthread_mutex_lock(&lock);
+  /* Get the next packet, junking any that are now in the past */
+  const struct packet *p = playrtp_next_packet();
+  if(p && contains(p, next_timestamp)) {
+    /* This packet is ready to play; the desired next timestamp points
+     * somewhere into it. */
+
+    /* Timestamp of end of packet */
+    const uint32_t packet_end = p->timestamp + p->nsamples;
+
+    /* Offset of desired next timestamp into current packet */
+    const uint32_t offset = next_timestamp - p->timestamp;
+
+    /* Pointer to audio data */
+    const uint16_t *ptr = (void *)(p->samples_raw + offset);
+
+    /* Compute number of samples left in packet, limited to output buffer
+     * size */
+    samples = packet_end - next_timestamp;
+    if(samples > max_samples)
+      samples = max_samples;
+
+    /* Copy into buffer, converting to native endianness */
+    size_t i = samples;
+    int16_t *bufptr = buffer;
+    while(i > 0) {
+      *bufptr++ = (int16_t)ntohs(*ptr++);
+      --i;
+    }
+    /* We don't junk the packet here; a subsequent call to
+     * playrtp_next_packet() will dispose of it (if it's actually done with). */
+  } else {
+    /* There is no suitable packet.  We introduce 0s up to the next packet, or
+     * to fill the buffer if there's no next packet or that's too many.  The
+     * comparison with max_samples deals with the otherwise troubling overflow
+     * case. */
+    samples = p ? p->timestamp - next_timestamp : max_samples;
+    if(samples > max_samples)
+      samples = max_samples;
+    //info("infill by %zu", samples);
+    memset(buffer, 0, samples * uaudio_sample_size);
+  }
+  /* Debug dump */
+  if(dump_buffer) {
+    for(size_t i = 0; i < samples; ++i) {
+      dump_buffer[dump_index++] = ((int16_t *)buffer)[i];
+      dump_index %= dump_size;
+    }
+  }
+  /* Advance timestamp */
+  next_timestamp += samples;
+  pthread_mutex_unlock(&lock);
+  return samples;
+}
+
 int main(int argc, char **argv) {
   int n, err;
   struct addrinfo *res;
@@ -548,6 +577,8 @@ int main(int argc, char **argv) {
   };
   union any_sockaddr mgroup;
   const char *dumpfile = 0;
+  const char *device = 0;
+  pthread_t ltid;
 
   static const struct addrinfo prefs = {
     .ai_flags = AI_PASSIVE,
@@ -558,6 +589,7 @@ int main(int argc, char **argv) {
 
   mem_init();
   if(!setlocale(LC_CTYPE, "")) fatal(errno, "error calling setlocale");
+  backend = uaudio_apis[0];
   while((n = getopt_long(argc, argv, "hVdD:m:b:x:L:R:M:aocC:r", options, 0)) >= 0) {
     switch(n) {
     case 'h': help();
@@ -570,13 +602,13 @@ int main(int argc, char **argv) {
     case 'L': logfp = fopen(optarg, "w"); break;
     case 'R': target_rcvbuf = atoi(optarg); break;
 #if HAVE_ALSA_ASOUNDLIB_H
-    case 'a': backend = playrtp_alsa; break;
+    case 'a': backend = &uaudio_alsa; break;
 #endif
 #if HAVE_SYS_SOUNDCARD_H || EMPEG_HOST
-    case 'o': backend = playrtp_oss; break;
+    case 'o': backend = &uaudio_oss; break;
 #endif
 #if HAVE_COREAUDIO_AUDIOHARDWARE_H      
-    case 'c': backend = playrtp_coreaudio; break;
+    case 'c': backend = &uaudio_coreaudio; break;
 #endif
     case 'C': configfile = optarg; break;
     case 's': control_socket = optarg; break;
@@ -710,7 +742,40 @@ int main(int argc, char **argv) {
       fatal(errno, "mapping %s", dumpfile);
     info("dumping to %s", dumpfile);
   }
-  play_rtp();
+  /* Choose output device */
+  if(device)
+    uaudio_set("device", device);
+  /* 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(&ltid, 0, listen_thread, 0)))
+    fatal(err, "pthread_create listen_thread");
+  /* We have a second thread to add received packets to the queue */
+  if((err = pthread_create(&ltid, 0, queue_thread, 0)))
+    fatal(err, "pthread_create queue_thread");
+  pthread_mutex_lock(&lock);
+  for(;;) {
+    /* Wait for the buffer to fill up a bit */
+    playrtp_fill_buffer();
+    /* Start playing now */
+    info("Playing...");
+    next_timestamp = pheap_first(&packets)->timestamp;
+    active = 1;
+    backend->activate();
+    /* 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 */
+    backend->deactivate();
+    active = 0;
+    /* Go back round */
+  }
   return 0;
 }
 
index 900ff50dc3f8e418d1a2127d9689129dc69a0b83..d5caa4c1c66cdeaae135f63fb46557f3c6af4b3f 100644 (file)
@@ -128,7 +128,6 @@ void playrtp_free_packet(struct packet *p);
 void playrtp_fill_buffer(void);
 struct packet *playrtp_next_packet(void);
 
-extern const char *device;
 extern struct packet *received_packets;
 extern struct packet **received_tail;
 extern pthread_mutex_t receive_lock;
index 765123aafd708ec9ac238e4597591bc3eb075906..62b05844c1db64b9a3f027e92f3903f7906c322f 100644 (file)
@@ -41,6 +41,7 @@ want_coreaudio=yes
 
 # Checks for programs.
 AC_PROG_CC
+AM_PROG_CC_C_O
 AC_PROG_AWK
 AC_SET_MAKE
 if test "x$GCC" = xyes; then
index b8147ad0a76c83305422fab2bf6de2e32f6ef41a..440604ffae32f47bbf3a5690db58304ee440d6f3 100644 (file)
@@ -1,5 +1,5 @@
 .\"
-.\" Copyright (C) 2007, 2008 Richard Kettlewell
+.\" Copyright (C) 2007-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
@@ -21,21 +21,21 @@ disorder-playrtp \- play DisOrder network broadcasts
 .B disorder\-playrtp
 .RI [ OPTIONS ]
 .RB [ \-\- ]
-.RI [[ GROUP ]
+.RI [[ ADDRESS ]
 .IR PORT ]
 .SH DESCRIPTION
 \fBdisorder\-playrtp\fR plays a network broadcast sent from the specified
 address.
 .PP
-If neither a group nor port are specified then the local DisOrder
+If neither an address nor port are specified then the local DisOrder
 configuration is consulted to find the server and the server is asked where the
 RTP stream is.
 .PP
 If just a port is specified then the RTP stream is assumed to be unicast or
 broadcast to that port.
 .PP
-If a group and a port are specified then the RTP stream is assumed to be
-multicast to that group and port.
+If an address and a port are specified then the RTP stream is assumed to be
+multicast to that group address and port.
 .SH OPTIONS
 The default sound API is the first of the ones listed below that are available.
 Usually this implies ALSA under Linux and Core Audio under OS X.
index 9df1dfdce0cf0e146d81539989325899bd9a2795..fe6a6617d3e937060b2970d3f5d002f2139cedb2 100644 (file)
@@ -1,6 +1,6 @@
 #
 # This file is part of DisOrder.
-# Copyright (C) 2004-2008 Richard Kettlewell
+# Copyright (C) 2004-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
@@ -82,6 +82,10 @@ libdisorder_a_SOURCES=charset.c charset.h            \
        $(TRACKDB) trackdb.h trackdb-int.h              \
        trackname.c trackorder.c trackname.h            \
        tracksort.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                             \
diff --git a/lib/uaudio-alsa.c b/lib/uaudio-alsa.c
new file mode 100644 (file)
index 0000000..343d1aa
--- /dev/null
@@ -0,0 +1,147 @@
+/*
+ * 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:
+*/
diff --git a/lib/uaudio-coreaudio.c b/lib/uaudio-coreaudio.c
new file mode 100644 (file)
index 0000000..8cde8c3
--- /dev/null
@@ -0,0 +1,203 @@
+/*
+ * 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-coreaudio.c
+ * @brief Support for Core Audio backend */
+#include "common.h"
+
+#if HAVE_COREAUDIO_AUDIOHARDWARE_H
+
+#include "coreaudio.h"
+#include "uaudio.h"
+#include "mem.h"
+#include "log.h"
+#include "syscalls.h"
+
+/** @brief Callback to request sample data */
+static uaudio_callback *coreaudio_callback;
+
+/** @brief Userdata for @ref coreaudio_callback */
+static void *coreaudio_userdata;
+
+/** @brief Core Audio device ID */
+static AudioDeviceID coreaudio_adid;
+
+/** @brief Core Audio option names */
+static const char *const coreaudio_options[] = {
+  "device",
+  NULL
+};
+
+/** @brief Callback from Core Audio
+ *
+ * Core Audio demands floating point samples but we provide integers.
+ * So there is a conversion step in here.
+ */
+static OSStatus coreaudio_adioproc
+    (AudioDeviceID attribute((unused)) inDevice,
+     const AudioTimeStamp attribute((unused)) *inNow,
+     const AudioBufferList attribute((unused)) *inInputData,
+     const AudioTimeStamp attribute((unused)) *inInputTime,
+     AudioBufferList *outOutputData,
+     const AudioTimeStamp attribute((unused)) *inOutputTime,
+     void attribute((unused)) *inClientData) {
+  /* Number of buffers we must fill */
+  unsigned nbuffers = outOutputData->mNumberBuffers;
+  /* Pointer to buffer to fill */
+  AudioBuffer *ab = outOutputData->mBuffers;
+  
+  while(nbuffers > 0) {
+    /* Where to store converted sample data */
+    float *samples = ab->mData;
+    /* Number of samples left to fill */
+    size_t nsamples = ab->mDataByteSize / sizeof (float);
+
+    while(nsamples > 0) {
+      /* Integer-format input buffer */
+      unsigned char input[1024];
+      const size_t maxsamples = sizeof input / uaudio_sample_size;
+      /* How many samples we'll ask for */
+      const size_t ask = nsamples > maxsamples ? maxsamples : 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;
+      if(uaudio_signed) {
+        if(uaudio_bits == 16) {
+          const int16_t *ptr = (int16_t *)input;
+          while(got > 0) {
+            --got;
+            *samples++ = *ptr++ * (0.5 / 32767);
+          }
+        } else {
+          const int8_t *ptr = (int8_t *)input;
+          while(got > 0) {
+            --got;
+            *samples++ = *ptr++ * (0.5 / 127);
+          }
+        }
+      } else {
+        if(uaudio_bits == 16) {
+          const uint16_t *ptr = (uint16_t *)input;
+          while(got > 0) {
+            --got;
+            *samples++ = ((int)*ptr++ - 32768) * (0.5 / 32767);
+          }
+        } else {
+          const uint8_t *ptr = (uint8_t *)input;
+          while(got > 0) {
+            --got;
+            *samples++ = ((int)*ptr++ - 128) * (0.5 / 127);
+          }
+        }
+      }
+    }
+    /* Move on to the next buffer */
+    ++ab;
+    --nbuffers;
+  }
+  return 0;
+}
+
+static void coreaudio_start(uaudio_callback *callback,
+                            void *userdata) {
+  OSStatus status;
+  UInt32 propertySize;
+  AudioStreamBasicDescription asbd;
+  const char *device;
+
+  if(uaudio_bits != 8 && uaudio_bits != 16)
+    disorder_fatal(0, "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,
+                                 &propertySize, &asbd);
+  if(status)
+    coreaudio_fatal(status, "AudioHardwareGetProperty");
+  D(("mSampleRate       %f", asbd.mSampleRate));
+  D(("mFormatID         %08lx", asbd.mFormatID));
+  D(("mFormatFlags      %08lx", asbd.mFormatFlags));
+  D(("mBytesPerPacket   %08lx", asbd.mBytesPerPacket));
+  D(("mFramesPerPacket  %08lx", asbd.mFramesPerPacket));
+  D(("mBytesPerFrame    %08lx", asbd.mBytesPerFrame));
+  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 != (unsigned)uaudio_channels) {
+    disorder_fatal(0, "want %dHz %d channels "
+                      "but got %gHz %"PRIu32" channels",
+                   uaudio_rate,
+                   uaudio_channels,
+                   (double)asbd.mSampleRate,
+                   (uint32_t)asbd.mChannelsPerFrame);
+  }
+  /* Add a collector callback */
+  status = AudioDeviceAddIOProc(coreaudio_adid, coreaudio_adioproc, 0);
+  if(status)
+    coreaudio_fatal(status, "AudioDeviceAddIOProc");
+}
+
+static void coreaudio_stop(void) {
+}
+
+static void coreaudio_activate(void) {
+  OSStatus status;
+
+  status = AudioDeviceStart(coreaudio_adid, coreaudio_adioproc);
+  if(status)
+    coreaudio_fatal(status, "AudioDeviceStart");
+}
+
+static void coreaudio_deactivate(void) {
+  OSStatus status;
+
+  status = AudioDeviceStop(coreaudio_adid, coreaudio_adioproc);
+  if(status)
+    coreaudio_fatal(status, "AudioDeviceStop");
+}
+
+const struct uaudio uaudio_coreaudio = {
+  .name = "coreaudio",
+  .options = coreaudio_options,
+  .start = coreaudio_start,
+  .stop = coreaudio_stop,
+  .activate = coreaudio_activate,
+  .deactivate = coreaudio_deactivate
+};
+
+#endif
+
+/*
+Local Variables:
+c-basic-offset:2
+comment-column:40
+fill-column:79
+indent-tabs-mode:nil
+End:
+*/
diff --git a/lib/uaudio-oss.c b/lib/uaudio-oss.c
new file mode 100644 (file)
index 0000000..7bdfb35
--- /dev/null
@@ -0,0 +1,150 @@
+/*
+ * 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-oss.c
+ * @brief Support for OSS backend */
+#include "common.h"
+
+#if HAVE_SYS_SOUNDCARD_H || EMPEG_HOST
+
+#if HAVE_SYS_SOUNDCARD_H
+# include <sys/soundcard.h>
+#endif
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include "mem.h"
+#include "log.h"
+#include "uaudio.h"
+
+#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
+
+static int oss_fd = -1;
+
+static const char *const oss_options[] = {
+  "device",
+  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;
+}
+
+/** @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";
+#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
+  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) {
+  uaudio_thread_deactivate();
+  close(oss_fd);
+  oss_fd = -1;
+}
+  
+static void oss_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); 
+#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) {
+  uaudio_thread_stop();
+}
+
+const struct uaudio uaudio_oss = {
+  .name = "oss",
+  .options = oss_options,
+  .start = oss_start,
+  .stop = oss_stop,
+  .activate = oss_activate,
+  .deactivate = oss_deactivate
+};
+
+#endif
+
+/*
+Local Variables:
+c-basic-offset:2
+comment-column:40
+fill-column:79
+indent-tabs-mode:nil
+End:
+*/
diff --git a/lib/uaudio-rtp.c b/lib/uaudio-rtp.c
new file mode 100644 (file)
index 0000000..1bbfa9c
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * 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-oss.c
+ * @brief Support for RTP network play backend */
+#include "common.h"
+
+#include <pthread.h>
+
+#include "uaudio.h"
+#include "mem.h"
+#include "log.h"
+#include "syscalls.h"
+
+static const char *const rtp_options[] = {
+  NULL
+};
+
+static void rtp_start(uaudio_callback *callback,
+                      void *userdata) {
+  (void)callback;
+  (void)userdata;
+  /* TODO */
+}
+
+static void rtp_stop(void) {
+  /* TODO */
+}
+
+static void rtp_activate(void) {
+  /* TODO */
+}
+
+static void rtp_deactivate(void) {
+  /* TODO */
+}
+
+const struct uaudio uaudio_rtp = {
+  .name = "rtp",
+  .options = rtp_options,
+  .start = rtp_start,
+  .stop = rtp_stop,
+  .activate = rtp_activate,
+  .deactivate = rtp_deactivate
+};
+
+/*
+Local Variables:
+c-basic-offset:2
+comment-column:40
+fill-column:79
+indent-tabs-mode:nil
+End:
+*/
diff --git a/lib/uaudio-thread.c b/lib/uaudio-thread.c
new file mode 100644 (file)
index 0000000..3253a1d
--- /dev/null
@@ -0,0 +1,268 @@
+/*
+ * 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:
+*/
diff --git a/lib/uaudio.c b/lib/uaudio.c
new file mode 100644 (file)
index 0000000..f18af09
--- /dev/null
@@ -0,0 +1,120 @@
+/*
+ * 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.c
+ * @brief Uniform audio interface
+ */
+
+#include "common.h"
+#include "uaudio.h"
+#include "hash.h"
+#include "mem.h"
+
+/** @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)
+    uaudio_options = hash_new(sizeof(char *));
+  value = xstrdup(value);
+  hash_add(uaudio_options, name, &value, HASH_INSERT_OR_REPLACE);
+}
+
+/** @brief Set a uaudio option */
+const char *uaudio_get(const char *name) {
+  const char *value = (uaudio_options ?
+                       *(char **)hash_find(uaudio_options, name)
+                       : NULL);
+  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.
+ *
+ * The first one will be used as a default, so putting ALSA before OSS
+ * constitutes a policy decision.
+ */
+const struct uaudio *uaudio_apis[] = {
+#if HAVE_COREAUDIO_AUDIOHARDWARE_H
+  &uaudio_coreaudio,
+#endif  
+#if HAVE_ALSA_ASOUNDLIB_H
+  &uaudio_alsa,
+#endif
+#if HAVE_SYS_SOUNDCARD_H || EMPEG_HOST
+  &uaudio_oss,
+#endif
+  &uaudio_rtp,
+  NULL,
+};
+
+/*
+Local Variables:
+c-basic-offset:2
+comment-column:40
+fill-column:79
+indent-tabs-mode:nil
+End:
+*/
diff --git a/lib/uaudio.h b/lib/uaudio.h
new file mode 100644 (file)
index 0000000..ac88b3f
--- /dev/null
@@ -0,0 +1,132 @@
+/*
+ * 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.h
+ * @brief Uniform audio interface
+ */
+
+#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
+ */
+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 */
+  const char *name;
+
+  /** @brief List of options, terminated by NULL */
+  const char *const *options;
+
+  /** @brief Do slow setup
+   * @param ua Handle returned by uaudio_open()
+   * @param callback Called for audio data
+   * @param userdata Passed to @p callback
+   *
+   * This does resource-intensive setup for the output device.
+   *
+   * For instance it might open mixable audio devices or network sockets.  It
+   * will create any background thread required.  However, it must not exclude
+   * other processes from outputting sound.
+   */
+  void (*start)(uaudio_callback *callback,
+                void *userdata);
+
+  /** @brief Tear down
+   * @param ua Handle returned by uaudio_open()
+   *
+   * This undoes the effect of @c start.
+   */
+  void (*stop)(void);
+
+  /** @brief Enable output
+   *
+   * A background thread will start calling @c callback as set by @c
+   * start and playing the audio data received from it.
+   */
+  void (*activate)(void);
+
+  /** @brief Disable output
+   *
+   * The background thread will stop calling @c callback.
+   */
+  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;
+#endif
+
+#if HAVE_ALSA_ASOUNDLIB_H
+extern const struct uaudio uaudio_alsa;
+#endif
+
+#if HAVE_SYS_SOUNDCARD_H || EMPEG_HOST
+extern const struct uaudio uaudio_oss;
+#endif
+
+extern const struct uaudio uaudio_rtp;
+
+extern const struct uaudio *uaudio_apis[];
+
+#endif /* UAUDIO_H */
+
+/*
+Local Variables:
+c-basic-offset:2
+comment-column:40
+fill-column:79
+indent-tabs-mode:nil
+End:
+*/