chiark / gitweb /
Merge from disorder.dev.
[disorder] / lib / uaudio-oss.c
index 06ffb73..d0c9062 100644 (file)
 # 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 <time.h>
 
 #include "mem.h"
 #include "log.h"
-#include "syscalls.h"
+#include "uaudio.h"
+#include "configuration.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
+
+/* documentation does not match implementation! */
+#ifndef SOUND_MIXER_READ
+# define SOUND_MIXER_READ(x) MIXER_READ(x)
+#endif
+#ifndef SOUND_MIXER_WRITE
+# define SOUND_MIXER_WRITE(x) MIXER_WRITE(x)
+#endif
 
 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 int oss_mixer_fd = -1;
+static int oss_mixer_channel;
 
 static const char *const oss_options[] = {
   "device",
+  "mixer-device",
+  "mixer-channel",
   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);
+/** @brief Open the OSS sound device */
+static void oss_open(void) {
+  const char *device = uaudio_get("device", NULL);
+
+#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
+}
+
+/** @brief Close the OSS sound device */
+static void oss_close(void) {
+  if(oss_fd != -1) {
+    close(oss_fd);
+    oss_fd = -1;
+  }
+}
+
+/** @brief Actually play sound via OSS */
+static size_t oss_play(void *buffer, size_t samples, unsigned flags) {
+  /* cf uaudio-alsa.c:alsa-play() */
+  if(flags & UAUDIO_PAUSED) {
+    if(flags & UAUDIO_PAUSE)
+      oss_close();
+    if(samples > 64)
+      samples /= 2;
+    const uint64_t ns = ((uint64_t)samples * 1000000000
+                         / (uaudio_rate * uaudio_channels));
+    struct timespec ts[1];
+    ts->tv_sec = ns / 1000000000;
+    ts->tv_nsec = ns % 1000000000;
+    while(nanosleep(ts, ts) < 0 && errno == EINTR)
+      ;
+    return samples;
   }
-  pthread_mutex_unlock(&oss_lock);
-  return NULL;
+  if(flags & UAUDIO_RESUME)
+    oss_open();
+  const size_t bytes = samples * uaudio_sample_size;
+  int rc = write(oss_fd, buffer, bytes);
+  if(rc < 0)
+    fatal(errno, "error writing to sound device");
+  return rc / uaudio_sample_size;
 }
 
 static void oss_start(uaudio_callback *callback,
                       void *userdata) {
-  int e;
-  oss_callback = callback;
-  if((e = pthread_create(&oss_thread,
-                         NULL,
-                         oss_thread_fn,
-                         userdata)))
-    fatal(e, "pthread_create");
+  if(uaudio_channels != 1 && uaudio_channels != 2)
+    fatal(0, "asked for %d channels but only support 1 or 2",
+          uaudio_channels); 
+  if(uaudio_bits != 8 && uaudio_bits != 16)
+    fatal(0, "asked for %d bits/channel but only support 8 or 16",
+          uaudio_bits); 
+#if EMPEG_HOST
+  /* Very specific buffer size requirements here apparently */
+  uaudio_thread_start(callback, userdata, oss_play, 
+                      4608 / uaudio_sample_size,
+                      4608 / uaudio_sample_size,
+                      0);
+#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,
+                      0);
+#endif
 }
 
 static void oss_stop(void) {
-  void *result;
-
-  oss_deactivate();
-  pthread_mutex_lock(&oss_lock);
-  oss_started = 0;
-  pthread_cond_signal(&oss_cond);
-  pthread_mutex_unlock(&oss_lock);
-  pthread_join(oss_thread, &result);
+  uaudio_thread_stop();
+  oss_close();                          /* might not have been paused */
 }
 
-static void oss_activate(void) {
-  pthread_mutex_lock(&oss_lock);
-  if(!oss_activated) {
-    const char *device = uaudio_get(&uadiuo_oss, "device");
+/** @brief Channel names */
+static const char *oss_channels[] = SOUND_DEVICE_NAMES;
 
-#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);
+static int oss_mixer_find_channel(const char *channel) {
+  if(!channel[strspn(channel, "0123456789")])
+    return atoi(channel);
+  else {
+    for(unsigned n = 0; n < sizeof oss_channels / sizeof *oss_channels; ++n)
+      if(!strcmp(oss_channels[n], channel))
+       return n;
+    return -1;
+  }
+}  
+
+static void oss_open_mixer(void) {
+  const char *mixer = uaudio_get("mixer-device", "/dev/mixer");
+  /* TODO infer mixer-device from device */
+  if((oss_mixer_fd = open(mixer, O_RDWR, 0)) < 0)
+    fatal(errno, "error opening %s", mixer);
+  const char *channel = uaudio_get("mixer-channel", "pcm");
+  oss_mixer_channel = oss_mixer_find_channel(channel);
+  if(oss_mixer_channel < 0)
+    fatal(0, "no such channel as '%s'", channel);
+}
+
+static void oss_close_mixer(void) {
+  close(oss_mixer_fd);
+  oss_mixer_fd = -1;
+}
+
+static void oss_get_volume(int *left, int *right) {
+  int r;
+
+  *left = *right = 0;
+  if(ioctl(oss_mixer_fd, SOUND_MIXER_READ(oss_mixer_channel), &r) < 0)
+    error(errno, "error getting volume");
+  else {
+    *left = r & 0xff;
+    *right = (r >> 8) & 0xff;
   }
-  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;
+static void oss_set_volume(int *left, int *right) {
+  int r =  (*left & 0xff) + (*right & 0xff) * 256;
+  if(ioctl(oss_mixer_fd, SOUND_MIXER_WRITE(oss_mixer_channel), &r) == -1)
+    error(errno, "error setting volume");
+  else if(ioctl(oss_mixer_fd, SOUND_MIXER_READ(oss_mixer_channel), &r) < 0)
+    error(errno, "error getting volume");
+  else {
+    *left = r & 0xff;
+    *right = (r >> 8) & 0xff;
   }
-  pthread_mutex_unlock(&oss_lock);
+}
+
+static void oss_configure(void) {
+  uaudio_set("device", config->device);
+  uaudio_set("mixer-device", config->mixer);
+  uaudio_set("mixer-channel", config->channel);
 }
 
 const struct uaudio uaudio_oss = {
@@ -173,8 +225,13 @@ const struct uaudio uaudio_oss = {
   .options = oss_options,
   .start = oss_start,
   .stop = oss_stop,
-  .activate = oss_activate,
-  .deactivate = oss_deactivate
+  .activate = uaudio_thread_activate,
+  .deactivate = uaudio_thread_deactivate,
+  .open_mixer = oss_open_mixer,
+  .close_mixer = oss_close_mixer,
+  .get_volume = oss_get_volume,
+  .set_volume = oss_set_volume,
+  .configure = oss_configure,
 };
 
 #endif