From: Jaroslav Kysela Date: Tue, 10 Feb 2004 13:40:36 +0000 (+0000) Subject: Added missing files X-Git-Tag: v1.0.3~33 X-Git-Url: https://git.alsa-project.org/?a=commitdiff_plain;h=5a747f9f7f6cebee9ed87e0b3e55a31e12c04014;p=alsa-oss.git Added missing files --- diff --git a/alsa/alsa-oss-emul.h b/alsa/alsa-oss-emul.h new file mode 100644 index 0000000..0c64400 --- /dev/null +++ b/alsa/alsa-oss-emul.h @@ -0,0 +1,84 @@ +#ifndef __ALSA_OSS_EMUL_H +/* + * OSS Redirector + * Copyright (c) by Jaroslav Kysela + * + * + * 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 index 0000000..2248739 --- /dev/null +++ b/alsa/mixer.c @@ -0,0 +1,576 @@ +/* + * OSS -> ALSA compatibility layer + * Copyright (c) by Abramo Bagnara + * + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 index 0000000..571e324 --- /dev/null +++ b/alsa/pcm.c @@ -0,0 +1,1324 @@ +/* + * OSS -> ALSA compatibility layer + * Copyright (c) by Abramo Bagnara + * + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; + } +}