chiark / gitweb /
Uniform audio backend for Core Audio. There is an untested OSS one
authorRichard Kettlewell <rjk@greenend.org.uk>
Sat, 28 Feb 2009 17:50:53 +0000 (17:50 +0000)
committerRichard Kettlewell <rjk@greenend.org.uk>
Sat, 28 Feb 2009 17:50:53 +0000 (17:50 +0000)
and a placeholder RTP one lurking around too.

disorder-playrtp now uses the uniform audio interface (and is
considerably simplified as a result).

12 files changed:
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
lib/Makefile.am
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.c [new file with mode: 0644]
lib/uaudio.h [new file with mode: 0644]

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
 
 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)
 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..f1eaebcc61a95823b14ba04dc2e3d559c8589a44 100644 (file)
@@ -79,6 +79,7 @@
 #include "playrtp.h"
 #include "inputline.h"
 #include "version.h"
 #include "playrtp.h"
 #include "inputline.h"
 #include "version.h"
+#include "uaudio.h"
 
 #define readahead linux_headers_are_borked
 
 
 #define readahead linux_headers_are_borked
 
@@ -94,7 +95,6 @@ static int rtpfd;
 static FILE *logfp;
 
 /** @brief Output device */
 static FILE *logfp;
 
 /** @brief Output device */
-const char *device;
 
 /** @brief Minimum low watermark
  *
 
 /** @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;
 
 /** @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 */
 /** @brief Backend to play with */
-static void (*backend)(void) = DEFAULT_PLAYRTP_BACKEND;
+static const struct uaudio *backend;
 
 HEAP_DEFINE(pheap, struct packet *, lt_packet);
 
 
 HEAP_DEFINE(pheap, struct packet *, lt_packet);
 
@@ -479,29 +471,6 @@ struct packet *playrtp_next_packet(void) {
   return 0;
 }
 
   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"
 /* display usage message and terminate */
 static void help(void) {
   xprintf("Usage:\n"
@@ -529,6 +498,66 @@ static void help(void) {
   exit(0);
 }
 
   exit(0);
 }
 
+static size_t playrtp_callback(int16_t *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 * sizeof *buffer);
+  }
+  /* Debug dump */
+  if(dump_buffer) {
+    for(size_t i = 0; i < samples; ++i) {
+      dump_buffer[dump_index++] = 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;
 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;
   };
   union any_sockaddr mgroup;
   const char *dumpfile = 0;
+  const char *device = 0;
+  pthread_t ltid;
 
   static const struct addrinfo prefs = {
     .ai_flags = AI_PASSIVE,
 
   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");
 
   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();
   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 '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
 #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      
 #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;
 #endif
     case 'C': configfile = optarg; break;
     case 's': control_socket = optarg; break;
@@ -710,7 +742,36 @@ int main(int argc, char **argv) {
       fatal(errno, "mapping %s", dumpfile);
     info("dumping to %s", dumpfile);
   }
       fatal(errno, "mapping %s", dumpfile);
     info("dumping to %s", dumpfile);
   }
-  play_rtp();
+  /* Choose output device */
+  if(device)
+    uaudio_set("device", device);
+  /* Set up output */
+  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;
 }
 
   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);
 
 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;
 extern struct packet *received_packets;
 extern struct packet **received_tail;
 extern pthread_mutex_t receive_lock;
index 9df1dfdce0cf0e146d81539989325899bd9a2795..4ac8cbddc00fe4246895ebf1793927fc6d6768d7 100644 (file)
@@ -1,6 +1,6 @@
 #
 # This file is part of DisOrder.
 #
 # 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
 #
 # 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,8 @@ libdisorder_a_SOURCES=charset.c charset.h             \
        $(TRACKDB) trackdb.h trackdb-int.h              \
        trackname.c trackorder.c trackname.h            \
        tracksort.c                                     \
        $(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    \
        url.h url.c                                     \
        user.h user.c                                   \
        unicode.h unicode.c                             \
        url.h url.c                                     \
        user.h user.c                                   \
        unicode.h unicode.c                             \
diff --git a/lib/uaudio-coreaudio.c b/lib/uaudio-coreaudio.c
new file mode 100644 (file)
index 0000000..129a66e
--- /dev/null
@@ -0,0 +1,162 @@
+/*
+ * 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 */
+      int16_t input[1024], *ptr = input;
+      /* How many samples we'll ask for */
+      const int ask = nsamples > 1024 ? 1024 : (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);
+      }
+    }
+    /* 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;
+
+  coreaudio_callback = callback;
+  coreaudio_userdata = userdata;
+  device = uaudio_get("device");
+  coreaudio_adid = coreaudio_getdevice(device);
+  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));
+  if(asbd.mFormatID != kAudioFormatLinearPCM)
+    disorder_fatal(0, "audio device does not support kAudioFormatLinearPCM");
+  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..06ffb73
--- /dev/null
@@ -0,0 +1,189 @@
+/*
+ * 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 <pthread.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+#include <arpa/inet.h>
+
+#include "mem.h"
+#include "log.h"
+#include "syscalls.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;
+static int oss_bufsize;
+
+static const char *const oss_options[] = {
+  "device",
+  NULL
+};
+
+static void *oss_thread(void *userdata) {
+  int16_t *buffer;
+  pthread_mutex_lock(&oss_lock);
+  buffer = xmalloc(XXX);
+  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_bufsizeXXX, userdata);;
+      if(write(oss_fd, buffer, XXX) < 0)
+        fatal(errno, "error playing audio");
+      // TODO short writes!
+    }
+    oss_going = 0;
+    pthread_cond_signal(&oss_cond);
+  }
+  pthread_mutex_unlock(&oss_lock);
+  return NULL;
+}
+
+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");
+}
+
+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);
+}
+
+static void oss_activate(void) {
+  pthread_mutex_lock(&oss_lock);
+  if(!oss_activated) {
+    const char *device = uaudio_get(&uadiuo_oss, "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
+    /* 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(ossfd, SNDCTL_DSP_STEREO, &stereo) < 0) {
+      error(errno, "error calling ioctl SNDCTL_DSP_STEREO %d", stereo);
+      goto failed;
+    }
+    /* 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);
+    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);
+  }
+  pthread_mutex_unlock(&oss_lock);
+}
+
+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);
+}
+
+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.c b/lib/uaudio.c
new file mode 100644 (file)
index 0000000..aa00f66
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * 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 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 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..6b0dea1
--- /dev/null
@@ -0,0 +1,110 @@
+/*
+ * 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
+
+/** @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,
+                               size_t max_samples,
+                               void *userdata);
+
+/** @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(const char *name, const char *value);
+const char *uaudio_get(const char *name);
+
+#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:
+*/