]> git.alsa-project.org Git - alsa-oss.git/commitdiff
Added missing files
authorJaroslav Kysela <perex@perex.cz>
Tue, 10 Feb 2004 13:40:36 +0000 (13:40 +0000)
committerJaroslav Kysela <perex@perex.cz>
Tue, 10 Feb 2004 13:40:36 +0000 (13:40 +0000)
alsa/alsa-oss-emul.h [new file with mode: 0644]
alsa/mixer.c [new file with mode: 0644]
alsa/pcm.c [new file with mode: 0644]

diff --git a/alsa/alsa-oss-emul.h b/alsa/alsa-oss-emul.h
new file mode 100644 (file)
index 0000000..0c64400
--- /dev/null
@@ -0,0 +1,84 @@
+#ifndef __ALSA_OSS_EMUL_H
+/*
+ *  OSS Redirector
+ *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 95)
+#define NEW_MACRO_VARARGS
+#endif
+
+#if 1
+#define DEBUG_POLL
+#define DEBUG_SELECT
+#ifdef NEW_MACRO_VARARGS
+#define DEBUG(...) do { if (alsa_oss_debug) fprintf(stderr, __VA_ARGS__); } while (0)
+#else /* !NEW_MACRO_VARARGS */
+#define DEBUG(args...) do { if (alsa_oss_debug) fprintf(stderr, ##args); } while (0)
+#endif
+#else
+#ifdef NEW_MACRO_VARARGS
+#define DEBUG(...)
+#else /* !NEW_MACRO_VARARGS */
+#define DEBUG(args...)
+#endif
+#endif
+
+#define OSS_MAJOR              14
+#define OSS_DEVICE_MIXER       0
+#define OSS_DEVICE_SEQUENCER   1
+#define OSS_DEVICE_MIDI                2
+#define OSS_DEVICE_DSP         3
+#define OSS_DEVICE_AUDIO       4
+#define OSS_DEVICE_DSPW                5
+#define OSS_DEVICE_SNDSTAT     6
+#define OSS_DEVICE_MUSIC       8
+#define OSS_DEVICE_DMMIDI      9
+#define OSS_DEVICE_DMFM                10
+#define OSS_DEVICE_AMIXER      11
+#define OSS_DEVICE_ADSP                12
+#define OSS_DEVICE_AMIDI       13
+#define OSS_DEVICE_ADMMIDI     14
+
+#define OSS_WAIT_EVENT_READ    (1<<0)
+#define OSS_WAIT_EVENT_WRITE   (1<<1)
+#define OSS_WAIT_EVENT_ERROR   (1<<2)
+
+extern int lib_oss_pcm_open(const char *pathname, int flags, ...);
+extern int lib_oss_pcm_close(int fd);
+extern int lib_oss_pcm_nonblock(int fd, int nonblock);
+extern ssize_t lib_oss_pcm_read(int fd, void *buf, size_t count);
+extern ssize_t lib_oss_pcm_write(int fd, const void *buf, size_t count);
+extern void * lib_oss_pcm_mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
+extern int lib_oss_pcm_munmap(void *start, size_t length);
+extern int lib_oss_pcm_ioctl(int fd, unsigned long int request, ...);
+extern int lib_oss_pcm_select_prepare(int fd, fd_set *readfds, fd_set *writefds, fd_set *exceptfds);
+extern int lib_oss_pcm_select_result(int fd, fd_set *readfds, fd_set *writefds, fd_set *exceptfds);
+extern int lib_oss_pcm_poll_fds(int fd);
+extern int lib_oss_pcm_poll_prepare(int fd, struct pollfd *ufds);
+extern int lib_oss_pcm_poll_result(int fd, struct pollfd *ufds);
+
+extern int lib_oss_mixer_open(const char *pathname, int flags, ...);
+extern int lib_oss_mixer_close(int fd);
+extern int lib_oss_mixer_ioctl(int fd, unsigned long int request, ...);
+
+extern int alsa_oss_debug;
+extern snd_output_t *alsa_oss_debug_out;
+
+#endif /* __ALSA_OSS_EMUL_H */
diff --git a/alsa/mixer.c b/alsa/mixer.c
new file mode 100644 (file)
index 0000000..2248739
--- /dev/null
@@ -0,0 +1,576 @@
+/*
+ *  OSS -> ALSA compatibility layer
+ *  Copyright (c) by Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#define _GNU_SOURCE
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/poll.h>
+#include <sys/select.h>
+#include <sys/mman.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <dlfcn.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <errno.h>
+#include <assert.h>
+#include <linux/soundcard.h>
+#include <alsa/asoundlib.h>
+
+#include "alsa-oss-emul.h"
+
+typedef struct _oss_mixer {
+       int fileno;
+       snd_mixer_t *mix;
+       unsigned int modify_counter;
+       snd_mixer_elem_t *elems[SOUND_MIXER_NRDEVICES];
+       struct _oss_mixer *next;
+} oss_mixer_t;
+
+static oss_mixer_t *mixer_fds = NULL;
+
+static oss_mixer_t *look_for_fd(int fd)
+{
+       oss_mixer_t *result = mixer_fds;
+       while (result) {
+               if (result->fileno == fd)
+                       return result;
+               result = result->next;
+       }
+       return NULL;
+}
+
+static void insert_fd(oss_mixer_t *xfd)
+{
+       xfd->next = mixer_fds;
+       mixer_fds = xfd;
+}
+
+static void remove_fd(oss_mixer_t *xfd)
+{
+       oss_mixer_t *result = mixer_fds, *prev = NULL;
+       while (result) {
+               if (result == xfd) {
+                       if (prev == NULL)
+                               mixer_fds = xfd->next;
+                       else
+                               prev->next = xfd->next;
+                       return;
+               }
+               prev = result;
+               result = result->next;
+       }
+       assert(0);
+}
+
+static int oss_mixer_dev(const char *name, unsigned int index)
+{
+       static struct {
+               char *name;
+               unsigned int index;
+       } id[SOUND_MIXER_NRDEVICES] = {
+               [SOUND_MIXER_VOLUME] = { "Master", 0 },
+               [SOUND_MIXER_BASS] = { "Tone Control - Bass", 0 },
+               [SOUND_MIXER_TREBLE] = { "Tone Control - Treble", 0 },
+               [SOUND_MIXER_SYNTH] = { "Synth", 0 },
+               [SOUND_MIXER_PCM] = { "PCM", 0 },
+               [SOUND_MIXER_SPEAKER] = { "PC Speaker", 0 },
+               [SOUND_MIXER_LINE] = { "Line", 0 },
+               [SOUND_MIXER_MIC] = { "Mic", 0 },
+               [SOUND_MIXER_CD] = { "CD", 0 },
+               [SOUND_MIXER_IMIX] = { "Monitor Mix", 0 },
+               [SOUND_MIXER_ALTPCM] = { "PCM", 1 },
+               [SOUND_MIXER_RECLEV] = { "-- nothing --", 0 },
+               [SOUND_MIXER_IGAIN] = { "Capture", 0 },
+               [SOUND_MIXER_OGAIN] = { "Playback", 0 },
+               [SOUND_MIXER_LINE1] = { "Aux", 0 },
+               [SOUND_MIXER_LINE2] = { "Aux", 1 },
+               [SOUND_MIXER_LINE3] = { "Aux", 2 },
+               [SOUND_MIXER_DIGITAL1] = { "Digital", 0 },
+               [SOUND_MIXER_DIGITAL2] = { "Digital", 1 },
+               [SOUND_MIXER_DIGITAL3] = { "Digital", 2 },
+               [SOUND_MIXER_PHONEIN] = { "Phone", 0 },
+               [SOUND_MIXER_PHONEOUT] = { "Phone", 1 },
+               [SOUND_MIXER_VIDEO] = { "Video", 0 },
+               [SOUND_MIXER_RADIO] = { "Radio", 0 },
+               [SOUND_MIXER_MONITOR] = { "Monitor", 0 },
+       };
+       unsigned int k;
+       for (k = 0; k < SOUND_MIXER_NRDEVICES; ++k) {
+               if (index == id[k].index &&
+                   strcmp(name, id[k].name) == 0)
+                       return k;
+       }
+       return -1;
+}
+
+int lib_oss_mixer_close(int fd)
+{
+       int err, result = 0;
+       oss_mixer_t *mixer = look_for_fd(fd);
+       err = snd_mixer_close(mixer->mix);
+       if (err < 0)
+               result = err;
+       remove_fd(mixer);
+       free(mixer);
+       if (result < 0) {
+               errno = -result;
+               result = -1;
+       }
+       close(fd);
+       DEBUG("close(%d) -> %d", fd, result);
+       if (result < 0)
+               DEBUG("(errno=%d)\n", errno);
+       else
+               DEBUG("\n");
+       return 0;
+}
+
+static int oss_mixer_elem_callback(snd_mixer_elem_t *elem, unsigned int mask)
+{
+       oss_mixer_t *mixer = snd_mixer_elem_get_callback_private(elem);
+       if (mask == SND_CTL_EVENT_MASK_REMOVE) {
+               int idx = oss_mixer_dev(snd_mixer_selem_get_name(elem),
+                                       snd_mixer_selem_get_index(elem));
+               if (idx >= 0)
+                       mixer->elems[idx] = 0;
+               return 0;
+       }
+       if (mask & SND_CTL_EVENT_MASK_VALUE) {
+               mixer->modify_counter++;
+       }
+       return 0;
+}
+
+static int oss_mixer_callback(snd_mixer_t *mixer, unsigned int mask, 
+                             snd_mixer_elem_t *elem)
+{
+       if (mask & SND_CTL_EVENT_MASK_ADD) {
+               oss_mixer_t *mix = snd_mixer_get_callback_private(mixer);
+               int idx = oss_mixer_dev(snd_mixer_selem_get_name(elem),
+                                       snd_mixer_selem_get_index(elem));
+               if (idx >= 0) {
+                       mix->elems[idx] = elem;
+                       snd_mixer_selem_set_playback_volume_range(elem, 0, 100);
+                       snd_mixer_selem_set_capture_volume_range(elem, 0, 100);
+                       snd_mixer_elem_set_callback(elem, oss_mixer_elem_callback);
+                       snd_mixer_elem_set_callback_private(elem, mix);
+               }
+       }
+       return 0;
+}
+
+static int oss_mixer_open(int card, int device, int oflag, mode_t mode ATTRIBUTE_UNUSED)
+{
+       oss_mixer_t *mixer;
+       int fd = -1;
+       int result;
+       char name[64];
+
+       char *s = getenv("ALSA_OSS_DEBUG");
+       if (s) {
+               alsa_oss_debug = 1;
+               if (alsa_oss_debug_out == NULL) {
+                       if (snd_output_stdio_attach(&alsa_oss_debug_out, stderr, 0) < 0)
+                               alsa_oss_debug_out = NULL;
+               }
+       }
+       switch (device) {
+       case OSS_DEVICE_MIXER:
+               sprintf(name, "mixer%d", card);
+               break;
+       case OSS_DEVICE_AMIXER:
+               sprintf(name, "amixer%d", card);
+               break;
+       default:
+               errno = ENODEV;
+               return -1;
+       }
+       switch (oflag & O_ACCMODE) {
+       case O_RDONLY:
+       case O_WRONLY:
+       case O_RDWR:
+               break;
+       default:
+               errno = EINVAL;
+               return -1;
+       }
+       fd = open("/dev/null", oflag & O_ACCMODE);
+       assert(fd >= 0);
+       mixer = calloc(1, sizeof(oss_mixer_t));
+       if (!mixer) {
+               errno = -ENOMEM;
+               return -1;
+       }
+       result = snd_mixer_open(&mixer->mix, 0);
+       if (result < 0)
+               goto _error;
+       result = snd_mixer_attach(mixer->mix, name);
+       if (result < 0) {
+               /* try to open the default mixer as fallback */
+               if (card == 0)
+                       strcpy(name, "default");
+               else
+                       sprintf(name, "hw:%d", card);
+               result = snd_mixer_attach(mixer->mix, name);
+               if (result < 0)
+                       goto _error1;
+       }
+       result = snd_mixer_selem_register(mixer->mix, NULL, NULL);
+       if (result < 0)
+               goto _error1;
+       snd_mixer_set_callback(mixer->mix, oss_mixer_callback);
+       snd_mixer_set_callback_private(mixer->mix, mixer);
+       result = snd_mixer_load(mixer->mix);
+       if (result < 0)
+               goto _error1;
+       mixer->fileno = result;
+       insert_fd(mixer);
+       return fd;
+ _error1:
+       snd_mixer_close(mixer->mix);
+ _error:
+       close(fd);
+       errno = -result;
+       return -1;
+}
+
+static int oss_mixer_read_recsrc(oss_mixer_t *mixer, unsigned int *ret)
+{
+       unsigned int mask = 0;
+       unsigned int k;
+       int err = 0;
+       for (k = 0; k < SOUND_MIXER_NRDEVICES; k++) {
+               snd_mixer_elem_t *elem = mixer->elems[k];
+               if (elem && 
+                   snd_mixer_selem_has_capture_switch(elem)) {
+                       int sw;
+                       err = snd_mixer_selem_get_capture_switch(elem, SND_MIXER_SCHN_FRONT_LEFT, &sw);
+                       if (err < 0)
+                               break;
+                       if (sw)
+                               mask |= 1 << k;
+               }
+       }
+       *ret = mask;
+       return err;
+}
+
+
+int lib_oss_mixer_ioctl(int fd, unsigned long cmd, ...)
+{
+       int err = 0;
+       va_list args;
+       void *arg;
+       oss_mixer_t *mixer = look_for_fd(fd);
+       snd_mixer_t *mix;
+       unsigned int dev;
+
+       if (mixer == NULL) {
+               errno = ENODEV;
+               return -1;
+       }
+       mix = mixer->mix;
+       va_start(args, cmd);
+       arg = va_arg(args, void *);
+       va_end(args);
+       DEBUG("ioctl(%d, ", fd);
+       switch (cmd) {
+       case OSS_GETVERSION:
+               *(int*)arg = SOUND_VERSION;
+               DEBUG("OSS_GETVERSION, %p) -> [%d]\n", arg, *(int*)arg);
+               break;
+       case SOUND_MIXER_INFO:
+       {
+               mixer_info *info = arg;
+               snd_mixer_handle_events(mix);
+               strcpy(info->id, "alsa-oss");
+               strcpy(info->name, "alsa-oss");
+               info->modify_counter = mixer->modify_counter;
+               DEBUG("SOUND_MIXER_INFO, %p) -> {%s, %s, %d}\n", info, info->id, info->name, info->modify_counter);
+               break;
+       }
+       case SOUND_OLD_MIXER_INFO:
+       {
+               _old_mixer_info *info = arg;
+               strcpy(info->id, "alsa-oss");
+               strcpy(info->name, "alsa-oss");
+               DEBUG("SOUND_OLD_MIXER_INFO, %p) -> {%s, %s}\n", info, info->id, info->name);
+               break;
+       }
+       case SOUND_MIXER_WRITE_RECSRC:
+       {
+               unsigned int k, mask = *(unsigned int *) arg;
+               unsigned int old;
+               int excl = 0;
+               DEBUG("SOUND_MIXER_WRITE_RECSRC, %p) -> [%x]", arg, mask);
+               err = oss_mixer_read_recsrc(mixer, &old);
+               if (err < 0)
+                       break;
+               for (k = 0; k < SOUND_MIXER_NRDEVICES; k++) {
+                       snd_mixer_elem_t *elem = mixer->elems[k];
+                       if (elem && 
+                           snd_mixer_selem_has_capture_switch(elem)) {
+                               if (!excl &&
+                                   snd_mixer_selem_has_capture_switch_exclusive(elem) &&
+                                   mask & ~old) {
+                                       mask &= ~old;
+                                       excl = 1;
+                               }
+                               err = snd_mixer_selem_set_capture_switch_all(elem, !!(mask & 1 << k));
+                               if (err < 0)
+                                       break;
+                       }
+               }
+               if (err < 0)
+                       break;
+               goto __read_recsrc;
+       }
+       case SOUND_MIXER_READ_RECSRC:
+       {
+               unsigned int mask;
+               DEBUG("SOUND_MIXER_READ_RECSRC, %p) ->", arg);
+       __read_recsrc:
+               err = oss_mixer_read_recsrc(mixer, &mask);
+               *(int *)arg = mask;
+               DEBUG(" [%x]\n", mask);
+               break;
+       }
+       case SOUND_MIXER_READ_DEVMASK:
+       {
+               int k, mask = 0;
+               for (k = 0; k < SOUND_MIXER_NRDEVICES; k++) {
+                       snd_mixer_elem_t *elem = mixer->elems[k];
+                       if (elem && 
+                           snd_mixer_selem_has_playback_volume(elem))
+                               mask |= 1 << k;
+               }
+               *(int *)arg = mask;
+               DEBUG("SOUND_MIXER_READ_DEVMASK, %p) -> [%x]\n", arg, mask);
+               break;
+       }
+       case SOUND_MIXER_READ_RECMASK:
+       {
+               int k, mask = 0;
+               for (k = 0; k < SOUND_MIXER_NRDEVICES; k++) {
+                       snd_mixer_elem_t *elem = mixer->elems[k];
+                       if (elem &&
+                           snd_mixer_selem_has_capture_switch(elem))
+                               mask |= 1 << k;
+               }
+               *(int *)arg = mask;
+               DEBUG("SOUND_MIXER_READ_RECMASK, %p) -> [%x]\n", arg, mask);
+               break;
+       }
+       case SOUND_MIXER_READ_STEREODEVS:
+       {
+               int k, mask = 0;
+               for (k = 0; k < SOUND_MIXER_NRDEVICES; k++) {
+                       snd_mixer_elem_t *elem = mixer->elems[k];
+                       if (elem && 
+                           snd_mixer_selem_has_playback_volume(elem) &&
+                           !snd_mixer_selem_is_playback_mono(elem))
+                               mask |= 1 << k;
+               }
+               *(int *)arg = mask;
+               DEBUG("SOUND_MIXER_READ_STEREODEVS, %p) -> [%x]\n", arg, mask);
+               break;
+       }
+       case SOUND_MIXER_READ_CAPS:
+       {
+               int k;
+               *(int *)arg = 0;
+               for (k = 0; k < SOUND_MIXER_NRDEVICES; k++) {
+                       snd_mixer_elem_t *elem = mixer->elems[k];
+                       if (elem && 
+                           snd_mixer_selem_has_capture_switch_exclusive(elem)) {
+                               * (int*) arg = SOUND_CAP_EXCL_INPUT;
+                               break;
+                       }
+               }
+               DEBUG("SOUND_MIXER_READ_CAPS, %p) -> [%x]\n", arg, *(int*) arg);
+               break;
+       }
+       default:
+               if (cmd >= MIXER_WRITE(0) && cmd < MIXER_WRITE(SOUND_MIXER_NRDEVICES)) {
+                       snd_mixer_elem_t *elem;
+                       long lvol, rvol;
+                       dev = cmd & 0xff;
+                       lvol = *(int *)arg & 0xff;
+                       if (lvol > 100)
+                               lvol = 100;
+                       rvol = (*(int *)arg >> 8) & 0xff;
+                       if (rvol > 100)
+                               rvol = 100;
+                       DEBUG("SOUND_MIXER_WRITE[%d], %p) -> {%ld, %ld}", dev, arg, lvol, rvol);
+                       elem = mixer->elems[dev];
+                       if (!elem) {
+                               err = -EINVAL;
+                               break;
+                       }
+                       if (!snd_mixer_selem_has_playback_volume(elem)) {
+                               err = -EINVAL;
+                               break;
+                       }
+                       err = snd_mixer_selem_set_playback_volume(elem, SND_MIXER_SCHN_FRONT_LEFT, lvol);
+                       if (err < 0) 
+                               break;
+                       if (snd_mixer_selem_is_playback_mono(elem)) {
+                               if (snd_mixer_selem_has_playback_switch(elem))
+                                       err = snd_mixer_selem_set_playback_switch(elem, SND_MIXER_SCHN_FRONT_LEFT, lvol != 0);
+                               if (err < 0) 
+                                       break;
+                       } else {
+                               err = snd_mixer_selem_set_playback_volume(elem, SND_MIXER_SCHN_FRONT_RIGHT, rvol);
+                               if (err < 0) 
+                                       break;
+                               if (snd_mixer_selem_has_playback_switch(elem)) {
+                                       if (snd_mixer_selem_has_playback_switch_joined(elem))
+                                               err = snd_mixer_selem_set_playback_switch(elem, SND_MIXER_SCHN_FRONT_LEFT, lvol != 0 || rvol != 0);
+                                       else {
+                                               err = snd_mixer_selem_set_playback_switch(elem, SND_MIXER_SCHN_FRONT_LEFT, lvol != 0);
+                                               if (err < 0) 
+                                                       break;
+                                               err = snd_mixer_selem_set_playback_switch(elem, SND_MIXER_SCHN_FRONT_RIGHT, rvol != 0);
+                                               if (err < 0) 
+                                                       break;
+                                       }
+                               }
+                       }
+                       if (!snd_mixer_selem_has_capture_volume(elem))
+                               break;
+                       err = snd_mixer_selem_set_capture_volume(elem, SND_MIXER_SCHN_FRONT_LEFT, lvol);
+                       if (err < 0) 
+                               break;
+                       if (!snd_mixer_selem_is_capture_mono(elem)) {
+                               err = snd_mixer_selem_set_capture_volume(elem, SND_MIXER_SCHN_FRONT_RIGHT, rvol);
+                               if (err < 0) 
+                                       break;
+                       }
+                       goto __read;
+               }
+               if (cmd >= MIXER_READ(0) && cmd < MIXER_READ(SOUND_MIXER_NRDEVICES)) {
+                       snd_mixer_elem_t *elem;
+                       long lvol, rvol;
+                       int sw;
+                       dev = cmd & 0xff;
+                       DEBUG("SOUND_MIXER_READ[%d], %p) ->", dev, arg);
+               __read:
+                       elem = mixer->elems[dev];
+                       if (!elem) {
+                               err = -EINVAL;
+                               break;
+                       }
+                       if (!snd_mixer_selem_has_playback_volume(elem)) {
+                               err = -EINVAL;
+                               break;
+                       }
+                       err = snd_mixer_selem_get_playback_switch(elem, SND_MIXER_SCHN_FRONT_LEFT, &sw);
+                       if (err < 0) 
+                               break;
+                       if (sw) {
+                               err = snd_mixer_selem_get_playback_volume(elem, SND_MIXER_SCHN_FRONT_LEFT, &lvol);
+                               if (err < 0) 
+                                       break;
+                       } else
+                               lvol = 0;
+                       if (snd_mixer_selem_is_playback_mono(elem)) {
+                               rvol = lvol;
+                       } else {
+                               err = snd_mixer_selem_get_playback_switch(elem, SND_MIXER_SCHN_FRONT_RIGHT, &sw);
+                               if (err < 0) 
+                                       break;
+                               if (sw) {
+                                       err = snd_mixer_selem_get_playback_volume(elem, SND_MIXER_SCHN_FRONT_RIGHT, &rvol);
+                                       if (err < 0) 
+                                               break;
+                               } else
+                                       rvol = 0;
+                       }
+                       * (int*) arg = lvol | (rvol << 8);
+                       DEBUG("{%ld, %ld}\n", lvol, rvol);
+                       break;
+               }
+               DEBUG("%lx, %p)\n", cmd, arg);
+               err = -ENXIO;
+               break;
+       }
+       if (err >= 0)
+               return 0;
+       errno = -err;
+       return -1;
+}
+
+static void error_handler(const char *file ATTRIBUTE_UNUSED,
+                         int line ATTRIBUTE_UNUSED,
+                         const char *func ATTRIBUTE_UNUSED,
+                         int err ATTRIBUTE_UNUSED,
+                         const char *fmt ATTRIBUTE_UNUSED,
+                         ...)
+{
+       /* suppress the error message from alsa-lib */
+}
+
+int lib_oss_mixer_open(const char *file, int oflag, ...)
+{
+       int result;
+       int minor, card, device;
+       struct stat s;
+       mode_t mode;
+       va_list args;
+       va_start(args, oflag);
+       mode = va_arg(args, mode_t);
+       va_end(args);
+       result = stat(file, &s);
+       if (result < 0) {
+               if (!strncmp(file, "/dev/mixer", 10))
+                       minor = (atoi(file + 10) << 4) | OSS_DEVICE_MIXER;
+               else if (!strncmp(file, "/dev/amixer", 11))
+                       minor = (atoi(file + 11) << 4) | OSS_DEVICE_AMIXER;
+               else {
+                       errno = ENOENT;
+                       return -1;
+               }
+       } else {
+               if (!S_ISCHR(s.st_mode) || ((s.st_rdev >> 8) & 0xff) != OSS_MAJOR) {
+                       errno = ENOENT;
+                       return -1;
+               }
+               minor = s.st_rdev & 0xff;
+       }
+       if (! alsa_oss_debug)
+               snd_lib_error_set_handler(error_handler);
+       card = minor >> 4;
+       device = minor & 0x0f;
+       switch (device) {
+       case OSS_DEVICE_MIXER:
+       case OSS_DEVICE_AMIXER:
+               result = oss_mixer_open(card, device, oflag, mode);
+               DEBUG("open(\"%s\", %d, %d) -> %d\n", file, oflag, mode, result);
+               return result;
+       default:
+               errno = ENOENT;
+               return -1;
+       }
+}
diff --git a/alsa/pcm.c b/alsa/pcm.c
new file mode 100644 (file)
index 0000000..571e324
--- /dev/null
@@ -0,0 +1,1324 @@
+/*
+ *  OSS -> ALSA compatibility layer
+ *  Copyright (c) by Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#define _GNU_SOURCE
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/poll.h>
+#include <sys/select.h>
+#include <sys/mman.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <dlfcn.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <errno.h>
+#include <assert.h>
+#include <linux/soundcard.h>
+#include <alsa/asoundlib.h>
+
+#include "alsa-oss-emul.h"
+
+snd_pcm_uframes_t _snd_pcm_boundary(snd_pcm_t *pcm);
+snd_pcm_uframes_t _snd_pcm_mmap_hw_ptr(snd_pcm_t *pcm);
+
+int alsa_oss_debug = 0;
+snd_output_t *alsa_oss_debug_out = NULL;
+
+typedef struct {
+       snd_pcm_t *pcm;
+       size_t frame_bytes;
+       struct {
+               snd_pcm_uframes_t period_size;
+               snd_pcm_uframes_t buffer_size;
+               snd_pcm_uframes_t boundary;
+               snd_pcm_uframes_t old_hw_ptr;
+               size_t mmap_buffer_bytes;
+               size_t mmap_period_bytes;
+       } alsa;
+       struct {
+               snd_pcm_uframes_t period_size;
+               unsigned int periods;
+               snd_pcm_uframes_t buffer_size;
+               size_t bytes;
+       } oss;
+       unsigned int stopped:1;
+       void *mmap_buffer;
+       size_t mmap_bytes;
+       snd_pcm_channel_area_t *mmap_areas;
+       snd_pcm_uframes_t mmap_advance;
+} oss_dsp_stream_t;
+
+typedef struct {
+       unsigned int channels;
+       unsigned int rate;
+       unsigned int oss_format;
+       snd_pcm_format_t format;
+       unsigned int fragshift;
+       unsigned int maxfrags;
+       unsigned int subdivision;
+       oss_dsp_stream_t streams[2];
+} oss_dsp_t;
+
+typedef struct fd {
+       int fileno;
+       oss_dsp_t *dsp;
+       void *mmap_area;
+       struct fd *next;
+} fd_t;
+
+static fd_t *pcm_fds = NULL;
+
+
+static fd_t *look_for_fd(int fd)
+{
+       fd_t *result = pcm_fds;
+       while (result) {
+               if (result->fileno == fd)
+                       return result;
+               result = result->next;
+       }
+       return NULL;
+}
+
+static inline oss_dsp_t *look_for_dsp(int fd)
+{
+       fd_t *xfd = look_for_fd(fd);
+       return xfd ? xfd->dsp : NULL;
+}
+
+static inline oss_dsp_t *look_for_mmap_addr(void * addr)
+{
+       fd_t *result = pcm_fds;
+       while (result) {
+               if (result->mmap_area == addr)
+                       return result->dsp ? result->dsp : NULL;
+               result = result->next;
+       }
+       return NULL;
+}
+
+static void insert_fd(fd_t *xfd)
+{
+       xfd->next = pcm_fds;
+       pcm_fds = xfd;
+}
+
+static void remove_fd(fd_t *xfd)
+{
+       fd_t *result = pcm_fds, *prev = NULL;
+       while (result) {
+               if (result == xfd) {
+                       if (prev == NULL)
+                               pcm_fds = xfd->next;
+                       else
+                               prev->next = xfd->next;
+                       return;
+               }
+               prev = result;
+               result = result->next;
+       }
+       assert(0);
+}
+
+static unsigned int ld2(u_int32_t v)
+{
+       unsigned r = 0;
+
+       if (v >= 0x10000) {
+               v >>= 16;
+               r += 16;
+       }
+       if (v >= 0x100) {
+               v >>= 8;
+               r += 8;
+       }
+       if (v >= 0x10) {
+               v >>= 4;
+               r += 4;
+       }
+       if (v >= 4) {
+               v >>= 2;
+               r += 2;
+       }
+       if (v >= 2)
+               r++;
+       return r;
+}
+
+static snd_pcm_format_t oss_format_to_alsa(int format)
+{
+       switch (format) {
+       case AFMT_MU_LAW:       return SND_PCM_FORMAT_MU_LAW;
+       case AFMT_A_LAW:        return SND_PCM_FORMAT_A_LAW;
+       case AFMT_IMA_ADPCM:    return SND_PCM_FORMAT_IMA_ADPCM;
+       case AFMT_U8:           return SND_PCM_FORMAT_U8;
+       case AFMT_S16_LE:       return SND_PCM_FORMAT_S16_LE;
+       case AFMT_S16_BE:       return SND_PCM_FORMAT_S16_BE;
+       case AFMT_S8:           return SND_PCM_FORMAT_S8;
+       case AFMT_U16_LE:       return SND_PCM_FORMAT_U16_LE;
+       case AFMT_U16_BE:       return SND_PCM_FORMAT_U16_BE;
+       case AFMT_MPEG:         return SND_PCM_FORMAT_MPEG;
+       default:                return SND_PCM_FORMAT_U8;
+       }
+}
+
+static int alsa_format_to_oss(snd_pcm_format_t format)
+{
+       switch (format) {
+       case SND_PCM_FORMAT_MU_LAW:     return AFMT_MU_LAW;
+       case SND_PCM_FORMAT_A_LAW:      return AFMT_A_LAW;
+       case SND_PCM_FORMAT_IMA_ADPCM:  return AFMT_IMA_ADPCM;
+       case SND_PCM_FORMAT_U8:         return AFMT_U8;
+       case SND_PCM_FORMAT_S16_LE:     return AFMT_S16_LE;
+       case SND_PCM_FORMAT_S16_BE:     return AFMT_S16_BE;
+       case SND_PCM_FORMAT_S8:         return AFMT_S8;
+       case SND_PCM_FORMAT_U16_LE:     return AFMT_U16_LE;
+       case SND_PCM_FORMAT_U16_BE:     return AFMT_U16_BE;
+       case SND_PCM_FORMAT_MPEG:       return AFMT_MPEG;
+       default:                        return -EINVAL;
+       }
+}
+
+static int oss_dsp_hw_params(oss_dsp_t *dsp)
+{
+       int k;
+       for (k = 1; k >= 0; --k) {
+               oss_dsp_stream_t *str = &dsp->streams[k];
+               snd_pcm_t *pcm = str->pcm;
+               snd_pcm_hw_params_t *hw;
+               int err;
+               unsigned int rate, periods_min;
+               if (!pcm)
+                       continue;
+               str->frame_bytes = snd_pcm_format_physical_width(dsp->format) * dsp->channels / 8;
+               snd_pcm_hw_params_alloca(&hw);
+               snd_pcm_hw_params_any(pcm, hw);
+               dsp->format = oss_format_to_alsa(dsp->oss_format);
+
+               err = snd_pcm_hw_params_set_format(pcm, hw, dsp->format);
+               if (err < 0)
+                       return err;
+               err = snd_pcm_hw_params_set_channels(pcm, hw, dsp->channels);
+               if (err < 0)
+                       return err;
+               rate = dsp->rate;
+               err = snd_pcm_hw_params_set_rate_near(pcm, hw, &rate, 0);
+               if (err < 0)
+                       return err;
+#if 0
+               err = snd_pcm_hw_params_set_periods_integer(pcm, hw);
+               if (err < 0)
+                       return err;
+#endif
+
+               if (str->mmap_buffer) {
+                       snd_pcm_access_mask_t *mask;
+                       snd_pcm_access_mask_alloca(&mask);
+                       snd_pcm_access_mask_any(mask);
+                       snd_pcm_access_mask_set(mask, SND_PCM_ACCESS_MMAP_INTERLEAVED);
+                       snd_pcm_access_mask_set(mask, SND_PCM_ACCESS_MMAP_NONINTERLEAVED);
+                       snd_pcm_access_mask_set(mask, SND_PCM_ACCESS_MMAP_COMPLEX);
+                       err = snd_pcm_hw_params_set_access_mask(pcm, hw, mask);
+                       if (err < 0)
+                               return err;
+                       err = snd_pcm_hw_params_set_period_size(pcm, hw, str->alsa.mmap_period_bytes / str->frame_bytes, 0);
+                       if (err < 0)
+                               return err;
+                       err = snd_pcm_hw_params_set_buffer_size(pcm, hw, str->alsa.mmap_buffer_bytes / str->frame_bytes);
+                       if (err < 0)
+                               return err;
+                       err = snd_pcm_hw_params_set_access(pcm, hw, SND_PCM_ACCESS_MMAP_INTERLEAVED);
+                       if (err < 0)
+                               return err;
+               } else {
+                       err = snd_pcm_hw_params_set_access(pcm, hw, SND_PCM_ACCESS_RW_INTERLEAVED);
+                       if (err < 0)
+                               return err;
+                       periods_min = 2;
+                       err = snd_pcm_hw_params_set_periods_min(pcm, hw, &periods_min, 0);
+                       if (err < 0)
+                               return err;
+                       if (dsp->maxfrags > 0) {
+                               unsigned int periods_max = dsp->maxfrags;
+                               err = snd_pcm_hw_params_set_periods_max(pcm, hw,
+                                                                       &periods_max, 0);
+                               if (err < 0)
+                                       return err;
+                       }
+                       if (dsp->fragshift > 0) {
+                               snd_pcm_uframes_t s = (1 << dsp->fragshift) / str->frame_bytes;
+                               s *= 16;
+                               while (s >= 1024 && (err = snd_pcm_hw_params_set_buffer_size(pcm, hw, s)) < 0)
+                                       s /= 2;
+                               s = (1 << dsp->fragshift) / str->frame_bytes;
+                               while (s >= 256 && (err = snd_pcm_hw_params_set_period_size(pcm, hw, s, 0)) < 0)
+                                       s /= 2;
+                               if (err < 0) {
+                                       s = (1 << dsp->fragshift) / str->frame_bytes;
+                                       err = snd_pcm_hw_params_set_period_size_near(pcm, hw, &s, 0);
+                               }
+                       } else {
+                               snd_pcm_uframes_t s = 16, old_s;
+                               while (s * 2 < dsp->rate / 2) 
+                                       s *= 2;
+                               old_s = s = s / 2;
+                               while (s >= 1024 && (err = snd_pcm_hw_params_set_buffer_size(pcm, hw, s)) < 0)
+                                       s /= 2;
+                               s = old_s;
+                               while (s >= 256 && (err = snd_pcm_hw_params_set_period_size(pcm, hw, s, 0)) < 0)
+                                       s /= 2;
+                               if (err < 0) {
+                                       s = old_s;
+                                       err = snd_pcm_hw_params_set_period_size_near(pcm, hw, &s, 0);
+                               }
+                       }
+                       if (err < 0)
+                               return err;
+               }
+               err = snd_pcm_hw_params(pcm, hw);
+               if (err < 0)
+                       return err;
+#if 0
+               if (alsa_oss_debug)
+                       snd_pcm_dump_setup(pcm, stderr);
+#endif
+               if (err < 0)
+                       return err;
+               dsp->oss_format = alsa_format_to_oss(dsp->format);
+               err = snd_pcm_hw_params_get_period_size(hw, &str->alsa.period_size, 0);
+               if (err < 0)
+                       return err;
+               err = snd_pcm_hw_params_get_buffer_size(hw, &str->alsa.buffer_size);
+               if (err < 0)
+                       return err;
+               str->oss.buffer_size = 1 << ld2(str->alsa.buffer_size);
+               if (str->oss.buffer_size < str->alsa.buffer_size)
+                       str->oss.buffer_size *= 2;
+               str->oss.period_size = 1 << ld2(str->alsa.period_size);
+               if (str->oss.period_size < str->alsa.period_size)
+                       str->oss.period_size *= 2;
+               str->oss.periods = str->oss.buffer_size / str->oss.period_size;
+               if (str->mmap_areas)
+                       free(str->mmap_areas);
+               str->mmap_areas = NULL;
+               if (str->mmap_buffer) {
+                       unsigned int c;
+                       snd_pcm_channel_area_t *a;
+                       unsigned int bits_per_sample, bits_per_frame;
+                       str->mmap_areas = calloc(dsp->channels, sizeof(*str->mmap_areas));
+                       if (!str->mmap_areas)
+                               return -ENOMEM;
+                       bits_per_sample = snd_pcm_format_physical_width(dsp->format);
+                       bits_per_frame = bits_per_sample * dsp->channels;
+                       a = str->mmap_areas;
+                       for (c = 0; c < dsp->channels; c++, a++) {
+                               a->addr = str->mmap_buffer;
+                               a->first = bits_per_sample * c;
+                               a->step = bits_per_frame;
+                       }
+               }
+       }
+       return 0;
+}
+
+static int oss_dsp_sw_params(oss_dsp_t *dsp)
+{
+       int k;
+       for (k = 1; k >= 0; --k) {
+               oss_dsp_stream_t *str = &dsp->streams[k];
+               snd_pcm_t *pcm = str->pcm;
+               snd_pcm_sw_params_t *sw;
+               int err;
+               if (!pcm)
+                       continue;
+               snd_pcm_sw_params_alloca(&sw);
+               snd_pcm_sw_params_current(pcm, sw);
+               snd_pcm_sw_params_set_xfer_align(pcm, sw, 1);
+               snd_pcm_sw_params_set_start_threshold(pcm, sw, 
+                                                     str->stopped ? str->alsa.buffer_size + 1 :
+                                                     str->alsa.period_size);
+#if 1
+               snd_pcm_sw_params_set_stop_threshold(pcm, sw,
+                                                    str->mmap_buffer ? LONG_MAX :
+                                                    str->alsa.buffer_size);
+#else
+               snd_pcm_sw_params_set_stop_threshold(pcm, sw,
+                                                    LONG_MAX);
+               snd_pcm_sw_params_set_silence_threshold(pcm, sw,
+                                                      str->alsa.period_size);
+               snd_pcm_sw_params_set_silence_size(pcm, sw,
+                                                  str->alsa.period_size);
+#endif
+               err = snd_pcm_sw_params(pcm, sw);
+               if (err < 0)
+                       return err;
+               str->alsa.boundary = _snd_pcm_boundary(pcm);
+       }
+       return 0;
+}
+
+static int oss_dsp_params(oss_dsp_t *dsp)
+{
+       int err;
+       err = oss_dsp_hw_params(dsp);
+       if (err < 0) 
+               return err;
+       err = oss_dsp_sw_params(dsp);
+       if (err < 0) 
+               return err;
+#if 0
+       if (alsa_oss_debug && alsa_oss_debug_out) {
+               int k;
+               for (k = 1; k >= 0; --k) {
+                       oss_dsp_stream_t *str = &dsp->streams[k];
+                       if (str->pcm)
+                               snd_pcm_dump(str->pcm, alsa_oss_debug_out);
+               }
+       }
+#endif
+       return 0;
+}
+
+int lib_oss_pcm_close(int fd)
+{
+       int result = 0;
+       int k;
+       fd_t *xfd = look_for_fd(fd);
+       oss_dsp_t *dsp;
+       
+       if (xfd == NULL) {
+               errno = ENOENT;
+               return -1;
+       }
+       dsp = xfd->dsp;
+       for (k = 0; k < 2; ++k) {
+               int err;
+               oss_dsp_stream_t *str = &dsp->streams[k];
+               if (!str->pcm)
+                       continue;
+               if (k == SND_PCM_STREAM_PLAYBACK) {
+                       if (snd_pcm_state(str->pcm) != SND_PCM_STATE_OPEN)
+                               snd_pcm_drain(str->pcm);
+               }
+               err = snd_pcm_close(str->pcm);
+               if (err < 0)
+                       result = err;
+       }
+       remove_fd(xfd);
+       free(dsp);
+       free(xfd);
+       if (result < 0) {
+               errno = -result;
+               result = -1;
+       }
+       close(fd);
+       DEBUG("close(%d) -> %d", fd, result);
+       if (result < 0)
+               DEBUG("(errno=%d)\n", errno);
+       else
+               DEBUG("\n");
+       return 0;
+}
+
+static int oss_dsp_open(int card, int device, int oflag, mode_t mode)
+{
+       oss_dsp_t *dsp;
+       unsigned int pcm_mode = 0;
+       unsigned int streams, k;
+       int format = AFMT_MU_LAW;
+       int fd = -1;
+       fd_t *xfd;
+       int result;
+       char name[64];
+
+       char *s = getenv("ALSA_OSS_DEBUG");
+       if (s) {
+               alsa_oss_debug = 1;
+               if (alsa_oss_debug_out == NULL) {
+                       if (snd_output_stdio_attach(&alsa_oss_debug_out, stderr, 0) < 0)
+                               alsa_oss_debug_out = NULL;
+               }
+       }
+       switch (device) {
+       case OSS_DEVICE_DSP:
+               format = AFMT_U8;
+               sprintf(name, "dsp%d", card);
+               break;
+       case OSS_DEVICE_DSPW:
+               format = AFMT_S16_LE;
+               sprintf(name, "dspW%d", card);
+               break;
+       case OSS_DEVICE_AUDIO:
+               sprintf(name, "audio%d", card);
+               break;
+       case OSS_DEVICE_ADSP:
+               sprintf(name, "adsp%d", card);
+               break;
+       default:
+               errno = ENOENT;
+               return -1;
+       }
+       if (mode & O_NONBLOCK)
+               pcm_mode = SND_PCM_NONBLOCK;
+       switch (oflag & O_ACCMODE) {
+       case O_RDONLY:
+               streams = 1 << SND_PCM_STREAM_CAPTURE;
+               break;
+       case O_WRONLY:
+               streams = 1 << SND_PCM_STREAM_PLAYBACK;
+               break;
+       case O_RDWR:
+               streams = ((1 << SND_PCM_STREAM_PLAYBACK) | 
+                          (1 << SND_PCM_STREAM_CAPTURE));
+               break;
+       default:
+               errno = EINVAL;
+               return -1;
+       }
+       fd = open("/dev/null", oflag & O_ACCMODE);
+       if (fd < 0)
+               return -1;
+       xfd = calloc(1, sizeof(fd_t));
+       if (!xfd) {
+               close(fd);
+               errno = ENOMEM;
+               return -1;
+       }
+       dsp = calloc(1, sizeof(oss_dsp_t));
+       if (!dsp) {
+               close(fd);
+               free(xfd);
+               errno = ENOMEM;
+               return -1;
+       }
+       xfd->dsp = dsp;
+       dsp->channels = 1;
+       dsp->rate = 8000;
+       dsp->oss_format = format;
+       result = -EINVAL;
+       for (k = 0; k < 2; ++k) {
+               if (!(streams & (1 << k)))
+                       continue;
+               result = snd_pcm_open(&dsp->streams[k].pcm, name, k, pcm_mode);
+               if (result < 0)
+                       break;
+       }
+       if (result < 0) {
+               result = 0;
+               for (k = 0; k < 2; ++k) {
+                       if (dsp->streams[k].pcm) {
+                               snd_pcm_close(dsp->streams[k].pcm);
+                               dsp->streams[k].pcm = NULL;
+                       }
+               }
+               /* try to open the default pcm as fallback */
+               if (card == 0 && (device == OSS_DEVICE_DSP || device == OSS_DEVICE_AUDIO))
+                       strcpy(name, "default");
+               else
+                       sprintf(name, "plughw:%d", card);
+               for (k = 0; k < 2; ++k) {
+                       if (!(streams & (1 << k)))
+                               continue;
+                       result = snd_pcm_open(&dsp->streams[k].pcm, name, k, pcm_mode);
+                       if (result < 0)
+                               goto _error;
+               }
+       }
+       result = oss_dsp_params(dsp);
+       if (result < 0)
+               goto _error;
+       xfd->fileno = result;
+       insert_fd(xfd);
+       return fd;
+
+ _error:
+       close(fd);
+       errno = -result;
+       return -1;
+}
+
+ssize_t lib_oss_pcm_write(int fd, const void *buf, size_t n)
+{
+       ssize_t result;
+       oss_dsp_t *dsp = look_for_dsp(fd);
+       oss_dsp_stream_t *str;
+       snd_pcm_t *pcm;
+       snd_pcm_uframes_t frames;
+
+       if (dsp == NULL) {
+               errno = EBADFD;
+               result = -1;
+               goto _end;
+       }
+       str = &dsp->streams[SND_PCM_STREAM_PLAYBACK];
+       pcm = str->pcm;
+       if (!pcm) {
+               errno = EBADFD;
+               result = -1;
+               goto _end;
+       }
+       frames = n / str->frame_bytes;
+ _again:
+       result = snd_pcm_writei(pcm, buf, frames);
+       if (result == -EPIPE && 
+           snd_pcm_state(pcm) == SND_PCM_STATE_XRUN &&
+           (result = snd_pcm_prepare(pcm)) == 0)
+               goto _again;
+       if (result == -EPIPE && 
+           snd_pcm_state(pcm) == SND_PCM_STATE_SUSPENDED) {
+               while ((result = snd_pcm_resume(pcm)) == -EAGAIN)
+                       sleep(1);
+               if (result < 0 && (result = snd_pcm_prepare(pcm)) == 0)
+                       goto _again;
+       }
+       if (result < 0) {
+               errno = -result;
+               result = -1;
+               goto _end;
+       }
+       result *= str->frame_bytes;
+       str->oss.bytes += result;
+ _end:
+       DEBUG("write(%d, %p, %ld) -> %ld", fd, buf, (long)n, (long)result);
+       if (result < 0)
+               DEBUG("(errno=%d)\n", errno);
+       else
+               DEBUG("\n");
+       return result;
+}
+
+ssize_t lib_oss_pcm_read(int fd, void *buf, size_t n)
+{
+       ssize_t result;
+       oss_dsp_t *dsp = look_for_dsp(fd);
+       oss_dsp_stream_t *str;
+       snd_pcm_t *pcm;
+       snd_pcm_uframes_t frames;
+
+       if (dsp == NULL) {
+               errno = EBADFD;
+               result = -1;
+               goto _end;
+       }
+       str = &dsp->streams[SND_PCM_STREAM_CAPTURE];
+       pcm = str->pcm;
+       if (!pcm) {
+               errno = EBADFD;
+               result = -1;
+               goto _end;
+       }
+       frames = n / str->frame_bytes;
+ _again:
+       result = snd_pcm_readi(pcm, buf, frames);
+       if (result == -EPIPE && 
+           snd_pcm_state(pcm) == SND_PCM_STATE_XRUN &&
+           (result = snd_pcm_prepare(pcm)) == 0)
+               goto _again;
+       if (result == -EPIPE && 
+           snd_pcm_state(pcm) == SND_PCM_STATE_SUSPENDED) {
+               while ((result = snd_pcm_resume(pcm)) == -EAGAIN)
+                       sleep(1);
+               if (result < 0 && (result = snd_pcm_prepare(pcm)) == 0)
+                       goto _again;
+       }
+       if (result < 0) {
+               errno = -result;
+               result = -1;
+               goto _end;
+       }
+       result *= str->frame_bytes;
+       str->oss.bytes += result;
+ _end:
+       DEBUG("read(%d, %p, %ld) -> %ld", fd, buf, (long)n, (long)result);
+       if (result < 0)
+               DEBUG("(errno=%d)\n", errno);
+       else
+               DEBUG("\n");
+       return result;
+}
+
+#define USE_REWIND 1
+
+static void oss_dsp_mmap_update(oss_dsp_t *dsp, snd_pcm_stream_t stream,
+                               snd_pcm_sframes_t delay)
+{
+       oss_dsp_stream_t *str = &dsp->streams[stream];
+       snd_pcm_t *pcm = str->pcm;
+       snd_pcm_sframes_t err;
+       snd_pcm_uframes_t size;
+       const snd_pcm_channel_area_t *areas;
+       switch (stream) {
+       case SND_PCM_STREAM_PLAYBACK:
+               if (delay < 0) {
+                       snd_pcm_reset(pcm);
+                       str->mmap_advance -= delay;
+                       if (str->mmap_advance > dsp->rate / 10)
+                               str->mmap_advance = dsp->rate / 10;
+//                     fprintf(stderr, "mmap_advance=%ld\n", str->mmap_advance);
+               }
+#if USE_REWIND
+               err = snd_pcm_rewind(pcm, str->alsa.buffer_size);
+               if (err < 0)
+                       return;
+               size = str->mmap_advance;
+//             fprintf(stderr, "delay=%ld rewind=%ld forward=%ld offset=%ld\n",
+//                     delay, err, size, snd_pcm_mmap_offset(pcm));
+#else
+               size = str->mmap_advance - delay;
+#endif
+               while (size > 0) {
+                       snd_pcm_uframes_t ofs;
+                       snd_pcm_uframes_t frames = size;
+                       snd_pcm_mmap_begin(pcm, &areas, &ofs, &frames);
+//                     fprintf(stderr, "copy %ld %ld %d\n", ofs, frames, dsp->format);
+                       snd_pcm_areas_copy(areas, ofs, str->mmap_areas, ofs, 
+                                          dsp->channels, frames,
+                                          dsp->format);
+                       err = snd_pcm_mmap_commit(pcm, ofs, frames);
+                       assert(err == (snd_pcm_sframes_t) frames);
+                       size -= frames;
+               }
+               break;
+       case SND_PCM_STREAM_CAPTURE:
+               break;
+       }
+}
+
+int lib_oss_pcm_ioctl(int fd, unsigned long cmd, ...)
+{
+       int result, err = 0;
+       va_list args;
+       void *arg;
+       oss_dsp_t *dsp = look_for_dsp(fd);
+       oss_dsp_stream_t *str;
+       snd_pcm_t *pcm;
+
+       if (dsp == NULL) {
+               errno = EBADFD;
+               return -1;
+       }
+       va_start(args, cmd);
+       arg = va_arg(args, void *);
+       va_end(args);
+       DEBUG("ioctl(%d, ", fd);
+       switch (cmd) {
+       case OSS_GETVERSION:
+               *(int*)arg = SOUND_VERSION;
+               DEBUG("OSS_GETVERSION, %p) -> [%d]\n", arg, *(int*)arg);
+               break;
+       case SNDCTL_DSP_RESET:
+       {
+               int k;
+               DEBUG("SNDCTL_DSP_RESET)\n");
+               result = 0;
+               for (k = 0; k < 2; ++k) {
+                       str = &dsp->streams[k];
+                       pcm = str->pcm;
+                       if (!pcm)
+                               continue;
+                       err = snd_pcm_drop(pcm);
+                       if (err >= 0)
+                               err = snd_pcm_prepare(pcm);
+                       if (err < 0)
+                               result = err;
+                       str->oss.bytes = 0;
+               }
+               err = result;
+               break;
+       }
+       case SNDCTL_DSP_SYNC:
+       {
+               int k;
+               DEBUG("SNDCTL_DSP_SYNC)\n");
+               result = 0;
+               for (k = 0; k < 2; ++k) {
+                       str = &dsp->streams[k];
+                       pcm = str->pcm;
+                       if (!pcm)
+                               continue;
+                       err = snd_pcm_drain(pcm);
+                       if (err >= 0)
+                               err = snd_pcm_prepare(pcm);
+                       if (err < 0)
+                               result = err;
+                       
+               }
+               err = result;
+               break;
+       }
+       case SNDCTL_DSP_SPEED:
+               dsp->rate = *(int *)arg;
+               err = oss_dsp_params(dsp);
+               DEBUG("SNDCTL_DSP_SPEED, %p[%d]) -> [%d]\n", arg, *(int *)arg, dsp->rate);
+               *(int *)arg = dsp->rate;
+               break;
+       case SNDCTL_DSP_STEREO:
+               if (*(int *)arg)
+                       dsp->channels = 2;
+               else
+                       dsp->channels = 1;
+               err = oss_dsp_params(dsp);
+               DEBUG("SNDCTL_DSP_STEREO, %p[%d]) -> [%d]\n", arg, *(int *)arg, dsp->channels - 1);
+               *(int *)arg = dsp->channels - 1;
+               break;
+       case SNDCTL_DSP_CHANNELS:
+               dsp->channels = (*(int *)arg);
+               err = oss_dsp_params(dsp);
+               if (err < 0)
+                       break;
+               DEBUG("SNDCTL_DSP_CHANNELS, %p[%d]) -> [%d]\n", arg, *(int *)arg, dsp->channels);
+               *(int *)arg = dsp->channels;
+               break;
+       case SNDCTL_DSP_SETFMT:
+               if (*(int *)arg != AFMT_QUERY) {
+                       dsp->oss_format = *(int *)arg;
+                       err = oss_dsp_params(dsp);
+                       if (err < 0)
+                               break;
+               }
+               DEBUG("SNDCTL_DSP_SETFMT, %p[%d]) -> [%d]\n", arg, *(int *)arg, dsp->oss_format);
+               *(int *) arg = dsp->oss_format;
+               break;
+       case SNDCTL_DSP_GETBLKSIZE:
+               str = &dsp->streams[SND_PCM_STREAM_PLAYBACK];
+               if (!str->pcm)
+                       str = &dsp->streams[SND_PCM_STREAM_CAPTURE];
+               pcm = str->pcm;
+               *(int *) arg = str->oss.period_size * str->frame_bytes;
+               DEBUG("SNDCTL_DSP_GETBLKSIZE, %p) -> [%d]\n", arg, *(int *)arg);
+               break;
+       case SNDCTL_DSP_POST:
+               DEBUG("SNDCTL_DSP_POST)\n");
+               break;
+       case SNDCTL_DSP_SUBDIVIDE:
+               DEBUG("SNDCTL_DSP_SUBDIVIDE, %p[%d])\n", arg, *(int *)arg);
+               dsp->subdivision = *(int *)arg;
+               if (dsp->subdivision < 1)
+                       dsp->subdivision = 1;
+               err = oss_dsp_params(dsp);
+               break;
+       case SNDCTL_DSP_SETFRAGMENT:
+       {
+               DEBUG("SNDCTL_DSP_SETFRAGMENT, %p[%x])\n", arg, *(int *)arg);
+               dsp->fragshift = *(int *)arg & 0xffff;
+               if (dsp->fragshift < 4)
+                       dsp->fragshift = 4;
+               dsp->maxfrags = ((*(int *)arg) >> 16) & 0xffff;
+               if (dsp->maxfrags < 2)
+                       dsp->maxfrags = 2;
+               err = oss_dsp_params(dsp);
+               break;
+       }
+       case SNDCTL_DSP_GETFMTS:
+       {
+               *(int *)arg = (AFMT_MU_LAW | AFMT_A_LAW | AFMT_IMA_ADPCM | 
+                              AFMT_U8 | AFMT_S16_LE | AFMT_S16_BE | 
+                              AFMT_S8 | AFMT_U16_LE | AFMT_U16_BE);
+               DEBUG("SNDCTL_DSP_GETFMTS, %p) -> [%d]\n", arg, *(int *)arg);
+               break;
+       }
+       case SNDCTL_DSP_NONBLOCK:
+       {       
+               DEBUG("SNDCTL_DSP_NONBLOCK)\n");
+               return lib_oss_pcm_nonblock(fd, 1);
+       }
+       case SNDCTL_DSP_GETCAPS:
+       {
+               result = DSP_CAP_REALTIME | DSP_CAP_TRIGGER | DSP_CAP_MMAP;
+               if (dsp->streams[SND_PCM_STREAM_PLAYBACK].pcm && 
+                   dsp->streams[SND_PCM_STREAM_CAPTURE].pcm)
+                       result |= DSP_CAP_DUPLEX;
+               *(int*)arg = result;
+               DEBUG("SNDCTL_DSP_GETCAPS, %p) -> [%d]\n", arg, *(int*)arg);
+               break;
+       }
+       case SNDCTL_DSP_GETTRIGGER:
+       {
+               int s = 0;
+               pcm = dsp->streams[SND_PCM_STREAM_PLAYBACK].pcm;
+               if (pcm) {
+                       if (snd_pcm_state(pcm) == SND_PCM_STATE_RUNNING)
+                               s |= PCM_ENABLE_OUTPUT;
+               }
+               pcm = dsp->streams[SND_PCM_STREAM_CAPTURE].pcm;
+               if (pcm) {
+                       if (snd_pcm_state(pcm) == SND_PCM_STATE_RUNNING)
+                               s |= PCM_ENABLE_INPUT;
+               }
+               *(int*)arg = s;
+               DEBUG("SNDCTL_DSP_GETTRIGGER, %p) -> [%d]\n", arg, *(int*)arg);
+               break;
+       }               
+       case SNDCTL_DSP_SETTRIGGER:
+       {
+               DEBUG("SNDCTL_DSP_SETTRIGGER, %p[%d])\n", arg, *(int*)arg);
+               result = *(int*) arg;
+               str = &dsp->streams[SND_PCM_STREAM_CAPTURE];
+               pcm = str->pcm;
+               if (pcm) {
+                       if (result & PCM_ENABLE_INPUT) {
+                               if (str->stopped) {
+                                       str->stopped = 0;
+                                       err = oss_dsp_sw_params(dsp);
+                                       if (err < 0)
+                                               break;
+                                       err = snd_pcm_start(pcm);
+                                       if (err < 0)
+                                               break;
+                               }
+                       } else {
+                               if (!str->stopped) {
+                                       str->stopped = 1;
+                                       err = snd_pcm_drop(pcm);
+                                       if (err < 0)
+                                               break;
+                                       err = oss_dsp_sw_params(dsp);
+                                       if (err < 0)
+                                               break;
+                                       err = snd_pcm_prepare(pcm);
+                                       if (err < 0)
+                                               break;
+                               }
+                       }
+               }
+               str = &dsp->streams[SND_PCM_STREAM_PLAYBACK];
+               pcm = str->pcm;
+               if (pcm) {
+                       if (result & PCM_ENABLE_OUTPUT) {
+                               if (str->stopped) {
+                                       str->stopped = 0;
+                                       err = oss_dsp_sw_params(dsp);
+                                       if (err < 0)
+                                               break;
+                                       if (str->mmap_buffer) {
+                                               const snd_pcm_channel_area_t *areas;
+                                               snd_pcm_uframes_t offset;
+                                               snd_pcm_uframes_t size = str->alsa.buffer_size;
+                                               snd_pcm_mmap_begin(pcm, &areas, &offset, &size);
+                                               snd_pcm_areas_copy(areas, 0, str->mmap_areas, 0,
+                                                                  dsp->channels, size,
+                                                                  dsp->format);
+                                               snd_pcm_mmap_commit(pcm, offset, size);
+                                       }
+                                       err = snd_pcm_start(pcm);
+                                       if (err < 0)
+                                               break;
+                               }
+                       } else {
+                               if (!str->stopped) {
+                                       str->stopped = 1;
+                                       err = snd_pcm_drop(pcm);
+                                       if (err < 0)
+                                               break;
+                                       err = oss_dsp_sw_params(dsp);
+                                       if (err < 0)
+                                               break;
+                                       err = snd_pcm_prepare(pcm);
+                                       if (err < 0)
+                                               break;
+                               }
+                       }
+               }
+               break;
+       }
+       case SNDCTL_DSP_GETISPACE:
+       {
+               snd_pcm_sframes_t avail, delay;
+               snd_pcm_state_t state;
+               audio_buf_info *info = arg;
+               str = &dsp->streams[SND_PCM_STREAM_CAPTURE];
+               pcm = str->pcm;
+               if (!pcm) {
+                       err = -EINVAL;
+                       break;
+               }
+               state = snd_pcm_state(pcm);
+               if (state == SND_PCM_STATE_RUNNING) {
+                       snd_pcm_delay(pcm, &delay);
+                       if (str->mmap_buffer)
+                               oss_dsp_mmap_update(dsp, SND_PCM_STREAM_CAPTURE, delay);
+               }
+               avail = snd_pcm_avail_update(pcm);
+               if (avail < 0)
+                       avail = 0;
+               if ((snd_pcm_uframes_t)avail > str->oss.buffer_size)
+                       avail = str->oss.buffer_size;
+               info->fragsize = str->oss.period_size * str->frame_bytes;
+               info->fragstotal = str->oss.periods;
+               info->bytes = avail * str->frame_bytes;
+               info->fragments = avail / str->oss.period_size;
+               DEBUG("SNDCTL_DSP_GETISPACE, %p) -> {%d, %d, %d, %d}\n", arg,
+                     info->fragments,
+                     info->fragstotal,
+                     info->fragsize,
+                     info->bytes);
+               break;
+       }
+       case SNDCTL_DSP_GETOSPACE:
+       {
+               snd_pcm_sframes_t avail, delay;
+               snd_pcm_state_t state;
+               audio_buf_info *info = arg;
+               str = &dsp->streams[SND_PCM_STREAM_PLAYBACK];
+               pcm = str->pcm;
+               if (!pcm) {
+                       err = -EINVAL;
+                       break;
+               }
+               state = snd_pcm_state(pcm);
+               if (state == SND_PCM_STATE_RUNNING || 
+                   state == SND_PCM_STATE_DRAINING) {
+                       snd_pcm_delay(pcm, &delay);
+                       if (str->mmap_buffer)
+                               oss_dsp_mmap_update(dsp, SND_PCM_STREAM_PLAYBACK, delay);
+               }
+               avail = snd_pcm_avail_update(pcm);
+               if (avail < 0 || (snd_pcm_uframes_t)avail > str->oss.buffer_size)
+                       avail = str->oss.buffer_size;
+               info->fragsize = str->oss.period_size * str->frame_bytes;
+               info->fragstotal = str->oss.periods;
+               info->bytes = avail * str->frame_bytes;
+               info->fragments = avail / str->oss.period_size;
+               DEBUG("SNDCTL_DSP_GETOSPACE, %p) -> {%d %d %d %d}\n", arg,
+                     info->fragments,
+                     info->fragstotal,
+                     info->fragsize,
+                     info->bytes);
+               break;
+       }
+       case SNDCTL_DSP_GETIPTR:
+       {
+               snd_pcm_sframes_t delay = 0;
+               snd_pcm_uframes_t hw_ptr;
+               snd_pcm_state_t state;
+               count_info *info = arg;
+               str = &dsp->streams[SND_PCM_STREAM_CAPTURE];
+               pcm = str->pcm;
+               if (!pcm) {
+                       err = -EINVAL;
+                       break;
+               }
+               state = snd_pcm_state(pcm);
+               if (state == SND_PCM_STATE_RUNNING) {
+                       snd_pcm_delay(pcm, &delay);
+                       if (str->mmap_buffer)
+                               oss_dsp_mmap_update(dsp, SND_PCM_STREAM_CAPTURE, delay);
+               }
+               /* FIXME */
+               hw_ptr = _snd_pcm_mmap_hw_ptr(pcm);
+               info->bytes = hw_ptr;
+               info->bytes *= str->frame_bytes;
+               info->ptr = hw_ptr % str->oss.buffer_size;
+               info->ptr *= str->frame_bytes;
+               if (str->mmap_buffer) {
+                       ssize_t n = (hw_ptr / str->oss.period_size) - (str->alsa.old_hw_ptr / str->oss.period_size);
+                       if (n < 0)
+                               n += str->alsa.boundary / str->oss.period_size;
+                       info->blocks = n;
+                       str->alsa.old_hw_ptr = hw_ptr;
+               } else
+                       info->blocks = delay / str->oss.period_size;
+               DEBUG("SNDCTL_DSP_GETIPTR, %p) -> {%d %d %d}\n", arg,
+                     info->bytes,
+                     info->blocks,
+                     info->ptr);
+               break;
+       }
+       case SNDCTL_DSP_GETOPTR:
+       {
+               snd_pcm_sframes_t delay = 0;
+               snd_pcm_uframes_t hw_ptr;
+               snd_pcm_state_t state;
+               count_info *info = arg;
+               str = &dsp->streams[SND_PCM_STREAM_PLAYBACK];
+               pcm = str->pcm;
+               if (!pcm) {
+                       err = -EINVAL;
+                       break;
+               }
+               state = snd_pcm_state(pcm);
+               if (state == SND_PCM_STATE_RUNNING || 
+                   state == SND_PCM_STATE_DRAINING) {
+                       snd_pcm_delay(pcm, &delay);
+                       if (str->mmap_buffer)
+                               oss_dsp_mmap_update(dsp, SND_PCM_STREAM_PLAYBACK, delay);
+               }
+               /* FIXME */
+               hw_ptr = _snd_pcm_mmap_hw_ptr(pcm);
+               info->bytes = hw_ptr;
+               info->bytes *= str->frame_bytes;
+               info->ptr = hw_ptr % str->oss.buffer_size;
+               info->ptr *= str->frame_bytes;
+               if (str->mmap_buffer) {
+                       ssize_t n = (hw_ptr / str->oss.period_size) - (str->alsa.old_hw_ptr / str->oss.period_size);
+                       if (n < 0)
+                               n += str->alsa.boundary / str->oss.period_size;
+                       info->blocks = n;
+                       str->alsa.old_hw_ptr = hw_ptr;
+               } else
+                       info->blocks = delay / str->oss.period_size;
+               DEBUG("SNDCTL_DSP_GETOPTR, %p) -> {%d %d %d}\n", arg,
+                     info->bytes,
+                     info->blocks,
+                     info->ptr);
+               break;
+       }
+       case SNDCTL_DSP_GETODELAY:
+       {
+               snd_pcm_sframes_t delay = 0;
+               snd_pcm_state_t state;
+               str = &dsp->streams[SND_PCM_STREAM_PLAYBACK];
+               pcm = str->pcm;
+               if (!pcm) {
+                       err = -EINVAL;
+                       break;
+               }
+               state = snd_pcm_state(pcm);
+               if (state == SND_PCM_STATE_RUNNING || 
+                   state == SND_PCM_STATE_DRAINING) {
+                       snd_pcm_delay(pcm, &delay);
+                       if (str->mmap_buffer)
+                               oss_dsp_mmap_update(dsp, SND_PCM_STREAM_PLAYBACK, delay);
+               }
+               *(int *)arg = delay * str->frame_bytes;
+               DEBUG("SNDCTL_DSP_GETODELAY, %p) -> [%d]\n", arg, *(int*)arg); 
+               break;
+       }
+       case SNDCTL_DSP_SETDUPLEX:
+               DEBUG("SNDCTL_DSP_SETDUPLEX)\n"); 
+               break;
+       case SOUND_PCM_READ_RATE:
+       {
+               *(int *)arg = dsp->rate;
+               DEBUG("SOUND_PCM_READ_RATE, %p) -> [%d]\n", arg, *(int*)arg); 
+               break;
+       }
+       case SOUND_PCM_READ_CHANNELS:
+       {
+               *(int *)arg = dsp->channels;
+               DEBUG("SOUND_PCM_READ_CHANNELS, %p) -> [%d]\n", arg, *(int*)arg); 
+               break;
+       }
+       case SOUND_PCM_READ_BITS:
+       {
+               *(int *)arg = snd_pcm_format_width(dsp->format);
+               DEBUG("SOUND_PCM_READ_BITS, %p) -> [%d]\n", arg, *(int*)arg); 
+               break;
+       }
+       case SNDCTL_DSP_MAPINBUF:
+               DEBUG("SNDCTL_DSP_MAPINBUF)\n");
+               err = -EINVAL;
+               break;
+       case SNDCTL_DSP_MAPOUTBUF:
+               DEBUG("SNDCTL_DSP_MAPOUTBUF)\n");
+               err = -EINVAL;
+               break;
+       case SNDCTL_DSP_SETSYNCRO:
+               DEBUG("SNDCTL_DSP_SETSYNCRO)\n");
+               err = -EINVAL;
+               break;
+       case SOUND_PCM_READ_FILTER:
+               DEBUG("SOUND_PCM_READ_FILTER)\n");
+               err = -EINVAL;
+               break;
+       case SOUND_PCM_WRITE_FILTER:
+               DEBUG("SOUND_PCM_WRITE_FILTER)\n");
+               err = -EINVAL;
+               break;
+       default:
+               DEBUG("%lx, %p)\n", cmd, arg);
+               // return oss_mixer_ioctl(...);
+               err = -ENXIO;
+               break;
+       }
+       if (err >= 0)
+               return 0;
+       DEBUG("dsp ioctl error = %d\n", err);
+       errno = -err;
+       return -1;
+}
+
+int lib_oss_pcm_nonblock(int fd, int nonblock)
+{
+       oss_dsp_t *dsp = look_for_dsp(fd);
+       int k;
+
+       if (dsp == NULL) {
+               errno = EBADFD;
+               return -1;
+       }
+       for (k = 0; k < 2; ++k) {
+               snd_pcm_t *pcm = dsp->streams[k].pcm;
+               int err;
+               if (!pcm)
+                       continue;
+               err = snd_pcm_nonblock(pcm, nonblock);
+               if (err < 0) {
+                       errno = -err;
+                       return -1;
+               }
+       }
+       return 0;
+}
+
+void * lib_oss_pcm_mmap(void *addr ATTRIBUTE_UNUSED, size_t len ATTRIBUTE_UNUSED, int prot, int flags ATTRIBUTE_UNUSED, int fd, off_t offset ATTRIBUTE_UNUSED)
+{
+       int err;
+       void *result;
+       oss_dsp_t *dsp = look_for_dsp(fd);
+       oss_dsp_stream_t *str;
+
+       if (dsp == NULL) {
+               errno = -EBADFD;
+               return NULL;
+       }
+       switch (prot & (PROT_READ | PROT_WRITE)) {
+       case PROT_READ:
+               str = &dsp->streams[SND_PCM_STREAM_CAPTURE];
+               break;
+       case PROT_WRITE:
+               str = &dsp->streams[SND_PCM_STREAM_PLAYBACK];
+               break;
+       case PROT_READ | PROT_WRITE:
+               str = &dsp->streams[SND_PCM_STREAM_PLAYBACK];
+               if (!str->pcm)
+                       str = &dsp->streams[SND_PCM_STREAM_CAPTURE];
+               break;
+       default:
+               errno = EINVAL;
+               result = MAP_FAILED;
+               goto _end;
+       }
+       if (!str->pcm) {
+               errno = EBADFD;
+               result = MAP_FAILED;
+               goto _end;
+       }
+       assert(!str->mmap_buffer);
+       result = malloc(len);
+       if (!result) {
+               result = MAP_FAILED;
+               goto _end;
+       }
+       str->mmap_buffer = result;
+       str->mmap_bytes = len;
+       str->alsa.mmap_period_bytes = str->oss.period_size * str->frame_bytes;
+       str->alsa.mmap_buffer_bytes = str->oss.buffer_size * str->frame_bytes;
+       err = oss_dsp_params(dsp);
+       if (err < 0) {
+               free(result);
+               errno = -err;
+               result = MAP_FAILED;
+               goto _end;
+       }
+ _end:
+       DEBUG("mmap(%p, %lu, %d, %d, %d, %ld) -> %p\n", addr, (unsigned long)len, prot, flags, fd, offset, result);
+       return result;
+}
+
+int lib_oss_pcm_munmap(void *addr, size_t len)
+{
+       int err;
+       oss_dsp_t *dsp = look_for_mmap_addr(addr);
+       oss_dsp_stream_t *str;
+
+       if (dsp == NULL) {
+               errno = EBADFD;
+               return -1;
+       }
+       DEBUG("munmap(%p, %lu)\n", addr, (unsigned long)len);
+       str = &dsp->streams[SND_PCM_STREAM_PLAYBACK];
+       if (!str->pcm)
+               str = &dsp->streams[SND_PCM_STREAM_CAPTURE];
+       assert(str->mmap_buffer);
+       free(str->mmap_buffer);
+       str->mmap_buffer = 0;
+       str->mmap_bytes = 0;
+       err = oss_dsp_params(dsp);
+       if (err < 0) {
+               errno = -err;
+               return -1;
+       }
+       return 0;
+}
+
+static void error_handler(const char *file ATTRIBUTE_UNUSED,
+                         int line ATTRIBUTE_UNUSED,
+                         const char *func ATTRIBUTE_UNUSED,
+                         int err ATTRIBUTE_UNUSED,
+                         const char *fmt ATTRIBUTE_UNUSED,
+                         ...)
+{
+       /* suppress the error message from alsa-lib */
+}
+
+int lib_oss_pcm_open(const char *file, int oflag, ...)
+{
+       int result;
+       int minor, card, device;
+       struct stat s;
+       mode_t mode;
+       va_list args;
+       va_start(args, oflag);
+       mode = va_arg(args, mode_t);
+       va_end(args);
+       result = stat(file, &s);
+       if (result < 0) {
+               if (!strncmp(file, "/dev/dsp", 8))
+                       minor = (atoi(file + 8) << 4) | OSS_DEVICE_DSP;
+               else if (!strncmp(file, "/dev/dspW", 9))
+                       minor = (atoi(file + 9) << 4) | OSS_DEVICE_DSPW;
+               else if (!strncmp(file, "/dev/adsp", 9))
+                       minor = (atoi(file + 9) << 4) | OSS_DEVICE_ADSP;
+               else if (!strncmp(file, "/dev/audio", 10))
+                       minor = (atoi(file + 10) << 4) | OSS_DEVICE_AUDIO;
+               else {
+                       errno = ENOENT;
+                       return -1;
+               }
+       } else {
+               if (!S_ISCHR(s.st_mode) || ((s.st_rdev >> 8) & 0xff) != OSS_MAJOR) {
+                       errno = ENOENT;
+                       return -1;
+               }
+               minor = s.st_rdev & 0xff;
+       }
+       if (! alsa_oss_debug)
+               snd_lib_error_set_handler(error_handler);
+       card = minor >> 4;
+       device = minor & 0x0f;
+       switch (device) {
+       case OSS_DEVICE_DSP:
+       case OSS_DEVICE_DSPW:
+       case OSS_DEVICE_AUDIO:
+       case OSS_DEVICE_ADSP:
+               result = oss_dsp_open(card, device, oflag, mode);
+               DEBUG("open(\"%s\", %d, %d) -> %d\n", file, oflag, mode, result);
+               return result;
+       default:
+               errno = ENOENT;
+               return -1;
+       }
+}