diff options
Diffstat (limited to 'esddsp.c')
-rw-r--r-- | esddsp.c | 821 |
1 files changed, 821 insertions, 0 deletions
diff --git a/esddsp.c b/esddsp.c new file mode 100644 index 0000000..d3c6ea9 --- /dev/null +++ b/esddsp.c @@ -0,0 +1,821 @@ +/* Evil evil evil hack to get OSS apps to cooperate with esd + * Copyright (C) 1998, 1999 Manish Singh <yosh@gimp.org> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* #define DSP_DEBUG */ + +/* This lets you run multiple instances of x11amp by setting the X11AMPNUM + environment variable. Only works on glibc2. + */ +/* #define MULTIPLE_X11AMP */ + +#if defined(__GNUC__) && !defined(__STRICT_ANSI__) + +#define _GNU_SOURCE 1 + +#ifdef DSP_DEBUG +#define DPRINTF(format, args...) printf(format, ## args) +#else +#define DPRINTF(format, args...) +#endif + +#include "config.h" + +#include <dlfcn.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <stdio.h> +#include <errno.h> +#include <pthread.h> + +#ifdef HAVE_MACHINE_SOUNDCARD_H +# include <machine/soundcard.h> +#else +# ifdef HAVE_SOUNDCARD_H +# include <soundcard.h> +# else +# include <sys/soundcard.h> +# endif +#endif + +#include "esd.h" + +/* BSDI has this functionality, but not define :() */ +#if defined(RTLD_NEXT) +#define REAL_LIBC RTLD_NEXT +#else +#define REAL_LIBC ((void *) -1L) +#endif + +#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__bsdi__) +typedef unsigned long request_t; +#else +typedef int request_t; +#endif + +static int sndfd = -1, mixfd = -1; +static int settings = 0, done = 0; + +static char *ident = NULL, *mixer = NULL; +static int use_mixer = 0; + +/* + * memory mapping emulation + */ +static int mmapemu = 0; +static count_info mmapemu_ocount; +static char *mmapemu_obuffer = NULL; +static int mmapemu_osize; +static long long mmapemu_bytes_per_sec; +struct timeval mmapemu_last_flush; + +#define MMAPEMU_FRAGSTOTAL 16 +#define MMAPEMU_FRAGSIZE ESD_BUF_SIZE + +#define OSS_VOLUME_BASE 50 + +#define ESD_VOL_TO_OSS(left, right) (short int) \ + (((OSS_VOLUME_BASE * (right) / ESD_VOLUME_BASE) << 8) | \ + (OSS_VOLUME_BASE * (left) / ESD_VOLUME_BASE)) + +#define OSS_VOL_TO_ESD_LEFT(vol) \ + (ESD_VOLUME_BASE * (vol & 0xff) / OSS_VOLUME_BASE) +#define OSS_VOL_TO_ESD_RIGHT(vol) \ + (ESD_VOLUME_BASE * ((vol >> 8) & 0xff) / OSS_VOLUME_BASE) + +static void +get_volume (int *left, int *right) +{ + int vol; + + if (read (mixfd, &vol, sizeof (vol)) != sizeof (vol)) + *left = *right = ESD_VOLUME_BASE; + else + { + *left = OSS_VOL_TO_ESD_LEFT (vol); + *right = OSS_VOL_TO_ESD_RIGHT (vol); + } +} + +static void +set_volume (int left, int right) +{ + int vol = ESD_VOL_TO_OSS (left, right); + + write (mixfd, &vol, sizeof (vol)); +} + + +pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; + +static void +dsp_init (void) +{ + pthread_mutex_lock(&mutex); + if (!ident) + { + const char *str; + + str = getenv ("ESDDSP_NAME"); + ident = malloc (ESD_NAME_MAX+1); + strncpy (ident, (str ? str : "esddsp"), ESD_NAME_MAX); + ident[ESD_NAME_MAX] = '\0'; + + str = getenv("ESDDSP_MMAP"); + mmapemu = str && !strcmp(str,"1"); + + if (getenv ("ESDDSP_MIXER")) + { + use_mixer = 1; + + str = getenv ("HOME"); + if (str) + { + mixer = malloc (strlen (str) + strlen (ident) + 10); + sprintf (mixer, "%s/.esddsp_%s", str, ident); + } + else + { + fprintf (stderr, "esddsp: can't get home directory\n"); + exit (1); + } + + DPRINTF ("mixer settings file: %s\n", mixer); + } + } + pthread_mutex_unlock(&mutex); +} + +static void +mix_init (int *esd, int *player) +{ + esd_info_t *all_info; + esd_player_info_t *player_info; + + if (*esd < 0 && (*esd = esd_open_sound (NULL)) < 0) + return; + + if (*player < 0) + { + all_info = esd_get_all_info (*esd); + if (all_info) + { + for (player_info = all_info->player_list; player_info; + player_info = player_info->next) + if (!strcmp(player_info->name, ident)) + { + *player = player_info->source_id; + break; + } + + esd_free_all_info (all_info); + } + } +} + +static void mmapemu_flush(void) +{ + struct timeval current_time; + long long usec_since_last_flush; + long long bytes_to_flush; + int frags_to_flush; + + gettimeofday(¤t_time, NULL); + usec_since_last_flush = 1000000 * (current_time.tv_sec - mmapemu_last_flush.tv_sec) + (current_time.tv_usec - mmapemu_last_flush.tv_usec); + + bytes_to_flush = (mmapemu_bytes_per_sec * usec_since_last_flush) / 1000000; + frags_to_flush = bytes_to_flush / MMAPEMU_FRAGSIZE; + + if (frags_to_flush > MMAPEMU_FRAGSTOTAL) + frags_to_flush = MMAPEMU_FRAGSTOTAL; + + if (frags_to_flush > 0) + mmapemu_last_flush = current_time; + + while (frags_to_flush-- > 0) + { + write(sndfd, &mmapemu_obuffer[mmapemu_ocount.ptr], MMAPEMU_FRAGSIZE); + mmapemu_ocount.ptr += MMAPEMU_FRAGSIZE; + mmapemu_ocount.ptr %= mmapemu_osize; + mmapemu_ocount.blocks++; + mmapemu_ocount.bytes += MMAPEMU_FRAGSIZE; + } +} + +static int +open_wrapper (int (*func) (const char *, int, mode_t), + const char *pathname, int flags, ...) +{ + va_list args; + mode_t mode; + + dsp_init (); + + va_start (args, flags); + if (sizeof (mode_t) < sizeof (int)) + mode = va_arg (args, int); + else + mode = va_arg (args, mode_t); + va_end (args); + + if (!strcmp (pathname, "/dev/dsp")) + { + if (!getenv ("ESPEAKER") && !mmapemu) + { + int ret; + + flags |= O_NONBLOCK; + if ((ret = (*func) (pathname, flags, mode)) >= 0) + return ret; + } + + DPRINTF ("hijacking /dev/dsp open, and taking it to esd...\n"); + settings = done = 0; + return (sndfd = esd_open_sound (NULL)); + } + else if (use_mixer && !strcmp (pathname, "/dev/mixer")) + { + DPRINTF ("hijacking /dev/mixer open, and taking it to esd...\n"); + return (mixfd = (*func) (mixer, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR)); + } + else + return (*func) (pathname, flags, mode); +} + +int +open (const char *pathname, int flags, ...) +{ + static int (*func) (const char *, int, mode_t) = NULL; + va_list args; + mode_t mode; + + DPRINTF ("open\n"); + + if (!func) + { + func = (int (*) (const char *, int, mode_t)) dlsym (REAL_LIBC, "open"); + if (!func) + { + fprintf(stderr, "esddsp: error: Cannot find symbol 'open'\n"); + errno = ENXIO; + return -1; + } + } + + va_start (args, flags); + mode = va_arg (args, mode_t); + va_end (args); + + return open_wrapper(func, pathname, flags, mode); +} + +int +open64 (const char *pathname, int flags, ...) +{ + static int (*func) (const char *, int, mode_t) = NULL; + va_list args; + mode_t mode; + + DPRINTF ("open64\n"); + + if (!func) + { + func = (int (*) (const char *, int, mode_t)) dlsym (REAL_LIBC, "open64"); + if (!func) + { + fprintf(stderr, "esddsp: error: Cannot find symbol 'open64'\n"); + errno = ENXIO; + return -1; + } + } + + va_start (args, flags); + mode = va_arg (args, mode_t); + va_end (args); + + return open_wrapper(func, pathname, flags, mode); +} + +static FILE * +fopen_wrapper (FILE * (*func) (const char *, const char *), + const char *pathname, const char *mode) +{ + dsp_init (); + + if (!strcmp (pathname, "/dev/dsp")) + { + if (!getenv ("ESPEAKER") && !mmapemu) + { + FILE *ret; + + if ((ret = (*func) (pathname, mode)) != NULL) + return ret; + } + + DPRINTF ("hijacking /dev/dsp fopen, and taking it to esd...\n"); + settings = done = 0; + return fdopen((sndfd = esd_open_sound (NULL)), mode); + } + else if (use_mixer && !strcmp (pathname, "/dev/mixer")) + { + FILE *ret; + DPRINTF ("hijacking /dev/mixer fopen, and taking it to esd...\n"); + + if ((ret = (*func) (pathname, mode)) != NULL) + { + mixfd = fileno(ret); + return ret; + } + else + return NULL; + } + else + return (*func) (pathname, mode); +} + +FILE * +fopen (const char *path, const char *mode) +{ + static FILE * (*func) (const char *, const char *) = NULL; + + DPRINTF ("fopen\n"); + + if (!func) + { + func = (FILE * (*) (const char *, const char *)) dlsym (REAL_LIBC, "fopen"); + if (!func) + { + fprintf(stderr, "esddsp: error: Cannot find symbol 'fopen'\n"); + return NULL; + } + } + + return fopen_wrapper(func, path, mode); +} + +FILE * +fopen64 (const char *path, const char *mode) +{ + static FILE * (*func) (const char *, const char *) = NULL; + + DPRINTF ("fopen64\n"); + + if (!func) + { + func = (FILE * (*) (const char *, const char *)) dlsym (REAL_LIBC, "fopen64"); + if (!func) + { + fprintf(stderr, "esddsp: error: Cannot find symbol 'fopen64'\n"); + return NULL; + } + } + + return fopen_wrapper(func, path, mode); +} + +static int +dspctl (int fd, request_t request, void *argp) +{ + static esd_format_t fmt = ESD_STREAM | ESD_PLAY | ESD_MONO; + static int speed; + + int *arg = (int *) argp; + + DPRINTF ("hijacking /dev/dsp ioctl, and sending it to esd " + "(%d : %x - %p)\n", fd, request, argp); + + switch (request) + { + case SNDCTL_DSP_RESET: + case SNDCTL_DSP_POST: + break; + + case SNDCTL_DSP_SETFMT: + fmt |= (*arg & 0x30) ? ESD_BITS16 : ESD_BITS8; + settings |= 1; + break; + + case SNDCTL_DSP_SPEED: + speed = *arg; + settings |= 2; + break; + + case SNDCTL_DSP_STEREO: + fmt &= ~ESD_MONO; + fmt |= (*arg) ? ESD_STEREO : ESD_MONO; + break; + + case SNDCTL_DSP_GETBLKSIZE: + if (mmapemu) + *arg = MMAPEMU_FRAGSIZE; + else + *arg = ESD_BUF_SIZE; + + break; + + case SNDCTL_DSP_GETFMTS: + *arg = 0x38; + break; + +#ifdef SNDCTL_DSP_GETCAPS + case SNDCTL_DSP_GETCAPS: + if (mmapemu) + *arg = DSP_CAP_MMAP | DSP_CAP_TRIGGER | DSP_CAP_REALTIME; + else + *arg = 0; + break; +#endif + + case SNDCTL_DSP_GETOSPACE: + case SNDCTL_DSP_GETISPACE: + { + audio_buf_info *bufinfo = (audio_buf_info *) argp; + if (mmapemu) + { + /* Number of fragments allocated for buffering */ + bufinfo->fragstotal = MMAPEMU_FRAGSTOTAL; + /* Size of a fragment in bytes (same as SNDCTL_DSP_GETBLKSIZE) */ + bufinfo->fragsize = MMAPEMU_FRAGSIZE; + /* Number of bytes that can be read or written immediately without blocking */ + bufinfo->bytes = MMAPEMU_FRAGSIZE; + /* Number of full fragments that can be read or written without blocking */ + bufinfo->fragments = 1; + } + else + { + /* Number of fragments allocated for buffering */ + bufinfo->fragstotal = 1; + /* Size of a fragment in bytes (same as SNDCTL_DSP_GETBLKSIZE) */ + bufinfo->fragsize = ESD_BUF_SIZE; + /* Number of bytes that can be read or written immediately without blocking */ + bufinfo->bytes = ESD_BUF_SIZE; + /* Number of full fragments that can be read or written without blocking */ + bufinfo->fragments = 1; + } + } + break; + + case SNDCTL_DSP_GETOPTR: + if (mmapemu) + { + DPRINTF("esddsp: mmapemu getoptr\n"); + mmapemu_flush(); + *((count_info *)arg) = mmapemu_ocount; + mmapemu_ocount.blocks = 0; + } + else + { + DPRINTF ("esddsp: SNDCTL_DSP_GETOPTR unsupported\n"); + } + break; + + default: + DPRINTF ("unhandled /dev/dsp ioctl (%x - %p)\n", request, argp); + break; + } + + if (settings == 3 && !done) + { + int proto = ESD_PROTO_STREAM_PLAY; + + done = 1; + + if (write (sndfd, &proto, sizeof (proto)) != sizeof (proto)) + return -1; + if (write (sndfd, &fmt, sizeof (fmt)) != sizeof (fmt)) + return -1; + if (write (sndfd, &speed, sizeof (speed)) != sizeof (speed)) + return -1; + if (write (sndfd, ident, ESD_NAME_MAX) != ESD_NAME_MAX) + return -1; + + if (mmapemu) + { + mmapemu_ocount.ptr=mmapemu_ocount.blocks=mmapemu_ocount.bytes=0; + mmapemu_bytes_per_sec = speed * ((fmt & ESD_BITS16) ? 2 : 1) * ((fmt & ESD_STEREO) ? 2 : 1); + gettimeofday(&mmapemu_last_flush, NULL); + } + + fmt = ESD_STREAM | ESD_PLAY | ESD_MONO; + speed = 0; + + if (use_mixer) + { + int esd = -1, player = -1; + int left, right; + + while (player < 0) + mix_init (&esd, &player); + + get_volume (&left, &right); + + DPRINTF ("panning %d - %d %d\n", player, left, right); + esd_set_stream_pan (esd, player, left, right); + + esd_close(esd); + } + } + + return 0; +} + +int +mixctl (int fd, request_t request, void *argp) +{ + static int esd = -1, player = -1; + static int left, right; + + int *arg = (int *) argp; + + DPRINTF ("hijacking /dev/mixer ioctl, and sending it to esd " + "(%d : %x - %p)\n", fd, request, argp); + + switch (request) + { + case SOUND_MIXER_READ_DEVMASK: + *arg = 5113; + break; + + case SOUND_MIXER_READ_PCM: + mix_init (&esd, &player); + + if (player > 0) + { + esd_info_t *all_info; + + all_info = esd_get_all_info (esd); + + esd_close(esd); + + if (all_info) + { + esd_player_info_t *player_info; + + for (player_info = all_info->player_list; player_info; + player_info = player_info->next) + if (player_info->source_id == player) + { + *arg = ESD_VOL_TO_OSS (player_info->left_vol_scale, + player_info->right_vol_scale); + } + + esd_free_all_info (all_info); + } + else + return -1; + } + else + { + get_volume (&left, &right); + esd_close(esd); + *arg = ESD_VOL_TO_OSS (left, right); + } + + break; + + case SOUND_MIXER_WRITE_PCM: + mix_init (&esd, &player); + + left = OSS_VOL_TO_ESD_LEFT (*arg); + right = OSS_VOL_TO_ESD_RIGHT (*arg); + + set_volume (left, right); + + if (player > 0) + { + DPRINTF ("panning %d - %d %d\n", player, left, right); + esd_set_stream_pan (esd, player, left, right); + } + + esd_close(esd); + + break; + + default: + DPRINTF ("unhandled /dev/mixer ioctl (%x - %p)\n", request, argp); + break; + } + + return 0; +} + +int +ioctl (int fd, request_t request, ...) +{ + static int (*func) (int, request_t, void *) = NULL; + va_list args; + void *argp; + + if (!func) + func = (int (*) (int, request_t, void *)) dlsym (REAL_LIBC, "ioctl"); + va_start (args, request); + argp = va_arg (args, void *); + va_end (args); + + if (fd == sndfd) + return dspctl (fd, request, argp); + else if (fd == mixfd) { + if(use_mixer) + return mixctl (fd, request, argp); + } else { /* (fd != sndfd && fd != mixfd) */ + return (*func) (fd, request, argp); + } + return 0; +} + +int +close (int fd) +{ + static int (*func) (int) = NULL; + + if (!func) + func = (int (*) (int)) dlsym (REAL_LIBC, "close"); + + if (fd == sndfd) + sndfd = -1; + else if (fd == mixfd) + mixfd = -1; + + return (*func) (fd); +} + +void * +mmap (void *start, size_t length, int prot, int flags, int fd, off_t offset) +{ + static void *(*func) (void *, size_t, int, int, int, off_t) = NULL; + + if (!func) + func = (void * (*) (void *, size_t, int, int, int, off_t)) dlsym (REAL_LIBC, "mmap"); + + if(fd != sndfd || sndfd == -1) + return (*func)(start,length,prot,flags,fd,offset); + else + { + DPRINTF ("esddsp: mmap - start = %x, length = %d, prot = %d\n", + start, length, prot); + DPRINTF (" flags = %d, fd = %d, offset = %d\n",flags, fd,offset); + if(mmapemu) + { + mmapemu_osize = length; + mmapemu_obuffer = malloc(length); + mmapemu_ocount.ptr = mmapemu_ocount.blocks = mmapemu_ocount.bytes = 0; + return mmapemu_obuffer; + } + else DPRINTF ("esddsp: /dev/dsp mmap (unsupported, try -m option)...\n"); + } + return (void *)-1; +} + +void * +mmap64 (void *start, size_t length, int prot, int flags, int fd, off64_t offset) +{ + static void *(*func) (void *, size_t, int, int, int, off64_t) = NULL; + + if (!func) + func = (void * (*) (void *, size_t, int, int, int, off64_t)) dlsym (REAL_LIBC, "mmap64"); + + if(fd != sndfd || sndfd == -1) + return (*func)(start,length,prot,flags,fd,offset); + else + { + DPRINTF ("esddsp: mmap64 - start = %x, length = %d, prot = %d\n", + start, length, prot); + DPRINTF (" flags = %d, fd = %d, offset = %lld\n",flags, fd,offset); + if(mmapemu) + { + mmapemu_osize = length; + mmapemu_obuffer = malloc(length); + mmapemu_ocount.ptr = mmapemu_ocount.blocks = mmapemu_ocount.bytes = 0; + return mmapemu_obuffer; + } + else DPRINTF ("esddsp: /dev/dsp mmap64 (unsupported, try -m option)...\n"); + } + return (void *)-1; +} + +int +munmap (void *start, size_t length) +{ + static int (*func) (void *, size_t) = NULL; + + if (!func) + func = (int (*) (void *, size_t)) dlsym (REAL_LIBC, "munmap"); + + if(start != mmapemu_obuffer || mmapemu_obuffer == 0) + return (*func)(start,length); + + DPRINTF ("esddsp: /dev/dsp munmap...\n"); + mmapemu_obuffer = NULL; + free(start); + + return 0; +} + +#ifdef MULTIPLE_X11AMP + +#include <socketbits.h> +#include <sys/param.h> +#include <sys/un.h> + +#define ENVSET "X11AMPNUM" + +int +unlink (const char *filename) +{ + static int (*func) (const char *) = NULL; + const char *num; + + if (!func) + func = (int (*) (const char *)) dlsym (REAL_LIBC, "unlink"); + + if (!strcmp (filename, "/tmp/X11Amp_CTRL") && (num = getenv (ENVSET))) + { + char buf[PATH_MAX] = "/tmp/X11Amp_CTRL"; + strncat (buf, num, (PATH_MAX - strlen(buf) - 1)); + return (*func) (buf); + } + else + return (*func) (filename); +} + +typedef int (*sa_func_t) (int, struct sockaddr *, int); + +static int +sockaddr_mangle (sa_func_t func, int fd, struct sockaddr *addr, int len) +{ + const char *num; + + if (!strcmp (((struct sockaddr_un *) addr)->sun_path, "/tmp/X11Amp_CTRL") + && (num = getenv(ENVSET))) + { + int ret; + char buf[PATH_MAX] = "/tmp/X11Amp_CTRL"; + + struct sockaddr_un *new_addr = malloc (len); + + strncat (buf, num, (PATH_MAX - strlen(buf) - 1)); + memcpy (new_addr, addr, len); + strncpy (new_addr->sun_path, buf, strlen(new_addr->sun_path)); + + ret = (*func) (fd, (struct sockaddr *) new_addr, len); + + free (new_addr); + return ret; + } + else + return (*func) (fd, addr, len); +} + +int +bind (int fd, struct sockaddr *addr, int len) +{ + static sa_func_t func = NULL; + + if (!func) + func = (sa_func_t) dlsym (REAL_LIBC, "bind"); + return sockaddr_mangle (func, fd, addr, len); +} + +int +connect (int fd, struct sockaddr *addr, int len) +{ + static sa_func_t func = NULL; + + if (!func) + func = (sa_func_t) dlsym (REAL_LIBC, "connect"); + return sockaddr_mangle (func, fd, addr, len); +} + +#endif /* MULTIPLE_X11AMP */ + +#else /* __GNUC__ */ + +void +nogcc (void) +{ + ident = NULL; +} + +#endif + |