diff options
Diffstat (limited to 'artsc/artsdsp.c')
-rw-r--r-- | artsc/artsdsp.c | 675 |
1 files changed, 675 insertions, 0 deletions
diff --git a/artsc/artsdsp.c b/artsc/artsdsp.c new file mode 100644 index 0000000..368b15f --- /dev/null +++ b/artsc/artsdsp.c @@ -0,0 +1,675 @@ +/* Evil evil evil hack to get OSS apps to cooperate with artsd + * This is based on the original esddsp, which esd uses to do the same. + * + * Copyright (C) 1998 Manish Singh <yosh@gimp.org> + * Copyright (C) 2000 Stefan Westerfeld <stefan@space.twc.de> (aRts port) + * + * 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 _GNU_SOURCE 1 + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#ifdef HAVE_SYS_SOUNDCARD_H +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <stdarg.h> +#include <sys/types.h> +#include <sys/soundcard.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +/* #include <sys/mman.h> */ +#include <fcntl.h> + +#include <artsc.h> +#include <dlfcn.h> + +#include "arts_export.h" +/* + * NOTE: + * + * To truly support non-blocking I/O, there is some stuff missing. Namely, + * select should be trapped and redirected, as well as the means for making + * a stream non-blocking and so on. Maybe poll, too. + * + * Currently, only apps that are doing blocking I/O will probably work right. + */ + +/** + * the stream status: sndfd is -1 when unused, otherwise it is a useless fd + * which points to /dev/null, to ensure compatibility with more weird + * operations on streams + * + * settings contains what has already been set (speed, bits, channels), and + * is 7 when all of these are true + * + * stream contains an aRts stream or 0 + */ +static int sndfd = -1; +static int settings; +static int arts_init_done = 0; +static arts_stream_t stream = 0; +static arts_stream_t record_stream = 0; +static int bits = 0; +static int speed = 0; +static int channels = 0; +static int frags; + +#if defined(HAVE_IOCTL_INT_INT_DOTS) +typedef int ioctl_request_t; +#elif defined(HAVE_IOCTL_INT_ULONG_DOTS) +typedef unsigned long ioctl_request_t; +#elif defined(HAVE_IOCTL_INT_ULONGINT_DOTS) +typedef unsigned long int ioctl_request_t; +#else +#error "unknown ioctl type (check config.h, adapt configure test)..." +#endif + +/* + * memory mapping emulation + */ +static int mmapemu = 0; +static count_info mmapemu_ocount; +static char *mmapemu_obuffer = 0; +static int mmapemu_osize = 0; + +/* + * original C library functions + */ +typedef int (*orig_open_ptr)(const char *pathname, int flags, ...); +typedef int (*orig_close_ptr)(int fd); +typedef int (*orig_ioctl_ptr)(int fd, ioctl_request_t request, ...); +typedef ssize_t (*orig_write_ptr)(int fd, const void *buf, size_t count); +typedef ssize_t (*orig_read_ptr)(int fd, void *buf, size_t count); +typedef void* (*orig_mmap_ptr)(void *start, size_t length, int prot, + int flags, int fd, off_t offset); +typedef int (*orig_munmap_ptr)(void *start, size_t length); +typedef FILE* (*orig_fopen_ptr)(const char *path, const char *mode); +typedef int (*orig_access_ptr)(const char *pathname, int mode); + +static orig_open_ptr orig_open; +static orig_close_ptr orig_close; +static orig_ioctl_ptr orig_ioctl; +static orig_write_ptr orig_write; +static orig_read_ptr orig_read; +static orig_mmap_ptr orig_mmap; +static orig_munmap_ptr orig_munmap; +static orig_fopen_ptr orig_fopen; +static orig_access_ptr orig_access; + +static int artsdsp_debug = 0; +static int artsdsp_init = 0; + +void *mmap(void *start, size_t length, int prot, int flags, + int fd, off_t offset); +int munmap(void *start, size_t length); +#define CHECK_INIT() if(!artsdsp_init) artsdsp_doinit(); + +/* + * Initialization - maybe this should be either be a startup only called + * routine, or use pthread locks to prevent strange effects in multithreaded + * use (however it seems highly unlikely that an application would create + * multiple threads before even using one of redirected the system functions + * once). + */ + +static void artsdsp_doinit() +{ + const char *env; + artsdsp_init = 1; + + /* debugging? */ + env = getenv("ARTSDSP_VERBOSE"); + artsdsp_debug = env && !strcmp(env,"1"); + + /* mmap emulation? */ + env = getenv("ARTSDSP_MMAP"); + mmapemu = env && !strcmp(env,"1"); + + /* resolve original symbols */ + orig_open = (orig_open_ptr)dlsym(RTLD_NEXT,"open"); + orig_close = (orig_close_ptr)dlsym(RTLD_NEXT,"close"); + orig_write = (orig_write_ptr)dlsym(RTLD_NEXT,"write"); + orig_read = (orig_read_ptr)dlsym(RTLD_NEXT,"read"); + orig_ioctl = (orig_ioctl_ptr)dlsym(RTLD_NEXT,"ioctl"); + orig_mmap = (orig_mmap_ptr)dlsym(RTLD_NEXT,"mmap"); + orig_munmap = (orig_munmap_ptr)dlsym(RTLD_NEXT,"munmap"); + orig_fopen = (orig_fopen_ptr)dlsym(RTLD_NEXT,"fopen"); + orig_access = (orig_access_ptr)dlsym(RTLD_NEXT,"access"); +} + +static void +#ifdef __GNUC__ + __attribute__( ( format ( printf, 1, 2 ) ) ) +#endif +artsdspdebug(const char *fmt,...) +{ + CHECK_INIT(); + + if(artsdsp_debug) + { + va_list ap; + va_start(ap, fmt); + (void) vfprintf(stderr, fmt, ap); + va_end(ap); + } +} + +static void mmapemu_flush() +{ + int space; + + if (mmapemu_osize == 0) { + return; + } + + space = arts_stream_get(stream, ARTS_P_BUFFER_SPACE); + artsdspdebug("space = %d\n",space); + while(space >= 4096) + { + arts_write(stream,&mmapemu_obuffer[mmapemu_ocount.ptr],4096); + space -= 4096; + mmapemu_ocount.ptr += 4096; + mmapemu_ocount.ptr %= mmapemu_osize; + mmapemu_ocount.blocks++; + mmapemu_ocount.bytes += 4096; + } +} + +/* returns 1 if the filename points to a sound device */ +static int is_sound_device(const char *pathname) +{ + if(!pathname) return 0; + if(strcmp(pathname,"/dev/dsp") == 0) return 1; + if(strcmp(pathname,"/dev/sound/dsp") == 0) return 1; + return 0; +} + +int open (const char *pathname, int flags, ...) +{ + va_list args; + mode_t mode = 0; + + CHECK_INIT(); + + /* + * After the documentation, va_arg is not safe if there is no argument to + * get "random errors will occur", so only get it in case O_CREAT is set, + * and hope that passing 0 to the orig_open function in all other cases + * will work. + */ + va_start(args,flags); + if(flags & O_CREAT) { + /* The compiler will select one of these at compile-tyime if -O is used. + * Otherwise, it may be deferred until runtime. + */ + if (sizeof(int) >= sizeof(mode_t)) { + mode = va_arg(args, int); + } else { + mode = va_arg(args, mode_t); + } + } + va_end(args); + + if (!is_sound_device(pathname)) /* original open for anything but sound */ + return orig_open (pathname, flags, mode); + + settings = 0; + frags = 0; + stream = 0; + record_stream = 0; + + artsdspdebug ("aRts: hijacking /dev/dsp open...\n"); + + sndfd = orig_open("/dev/null",flags,mode); + if(sndfd >= 0) + { + if(!arts_init_done) { + int rc = arts_init(); + if(rc < 0) { + artsdspdebug("error on aRts init: %s\n", arts_error_text(rc)); + orig_close(sndfd); + sndfd = -1; + return orig_open (pathname, flags, mode); + } + else arts_init_done = 1; + } + } + + /* success */ + return sndfd; +} + +int ioctl (int fd, ioctl_request_t request, ...) +{ + int space, size, latency, odelay; + + /* + * FreeBSD needs ioctl with varargs. However I have no idea how to "forward" + * the variable args ioctl to the orig_ioctl routine. So I expect the ioctl + * to have exactly one pointer-like parameter and forward that, hoping that + * it works + */ + va_list args; + void *argp; + va_start(args,request); + argp = va_arg(args, void *); + va_end(args); + + CHECK_INIT(); + + if (fd != sndfd ) + return orig_ioctl (fd, request, argp); + else if (sndfd != -1) + { + int *arg = (int *) argp; + artsdspdebug("aRts: hijacking /dev/dsp ioctl (%d : %x - %p)\n", + ( int ) fd, ( int ) request, ( void* ) argp); + + switch (request) + { + struct audio_buf_info *audiop; +#ifdef SNDCTL_DSP_RESET + case SNDCTL_DSP_RESET: /* _SIO ('P', 0) */ + artsdspdebug("aRts: SNDCTL_DSP_RESET unsupported\n"); + break; +#endif + +#ifdef SNDCTL_DSP_SYNC + case SNDCTL_DSP_SYNC: /* _SIO ('P', 1) */ + artsdspdebug("aRts: SNDCTL_DSP_SYNC unsupported\n"); + break; +#endif + +#ifdef SNDCTL_DSP_SPEED + case SNDCTL_DSP_SPEED: /* _SIOWR('P', 2, int) */ + speed = *arg; + settings |= 2; + break; +#endif + +#ifdef SNDCTL_DSP_STEREO + case SNDCTL_DSP_STEREO: /* _SIOWR('P', 3, int) */ + channels = (*arg)?2:1; + settings |= 4; + break; +#endif + +#ifdef SNDCTL_DSP_GETBLKSIZE + case SNDCTL_DSP_GETBLKSIZE: /* _SIOWR('P', 4, int) */ + if(mmapemu) + *arg = 4096; + else if(stream) + *arg = arts_stream_get(stream,ARTS_P_PACKET_SIZE); + else + { + int fragSize = frags & 0x7fff; + if(fragSize > 1 && fragSize < 16) + *arg = (1 << fragSize); + else + *arg = 16384; /* no idea ;-) */ + } + break; +#endif + +#ifdef SNDCTL_DSP_SETFMT + case SNDCTL_DSP_SETFMT: /* _SIOWR('P',5, int) */ + bits = (*arg & 0x30) ? 16 : 8; + settings |= 1; + break; +#endif + +#ifdef SNDCTL_DSP_CHANNELS + case SNDCTL_DSP_CHANNELS: /* _SIOWR('P', 6, int) */ + channels = (*arg); + settings |= 4; + break; +#endif + +#ifdef SOUND_PCM_WRITE_FILTER + case SOUND_PCM_WRITE_FILTER: /* _SIOWR('P', 7, int) */ + artsdspdebug("aRts: SNDCTL_DSP_WRITE_FILTER(%d) unsupported\n",*arg); + break; +#endif + +#ifdef SNDCTL_DSP_POST + case SNDCTL_DSP_POST: /* _SIO ('P', 8) */ + artsdspdebug("aRts: SNDCTL_DSP_POST unsupported\n"); + break; +#endif + +#ifdef SNDCTL_DSP_SUBDIVIDE + case SNDCTL_DSP_SUBDIVIDE: /* _SIOWR('P', 9, int) */ + artsdspdebug("aRts: SNDCTL_DSP_SUBDIVIDE(%d) unsupported\n",*arg); + break; +#endif + +#ifdef SNDCTL_DSP_SETFRAGMENT + case SNDCTL_DSP_SETFRAGMENT: /* _SIOWR('P',10, int) */ + artsdspdebug("aRts: SNDCTL_DSP_SETFRAGMENT(%x) partially supported\n", + *arg); + frags = *arg; + break; +#endif + +#ifdef SNDCTL_DSP_GETFMTS + case SNDCTL_DSP_GETFMTS: /* _SIOR ('P',11, int) */ + *arg = 8 | 16; + break; +#endif + +#if defined(SNDCTL_DSP_GETOSPACE) && defined(SNDCTL_DSP_GETISPACE) + case SNDCTL_DSP_GETOSPACE: /* _SIOR ('P',12, audio_buf_info) */ + case SNDCTL_DSP_GETISPACE: /* _SIOR ('P',13, audio_buf_info) */ + audiop = argp; + if(mmapemu) + { + audiop->fragstotal = 16; + audiop->fragsize = 4096; + audiop->bytes = 0; /* FIXME: is this right? */ + audiop->fragments = 0; + } + else + { + audiop->fragstotal = + stream?arts_stream_get(stream, ARTS_P_PACKET_COUNT):10; + audiop->fragsize = + stream?arts_stream_get(stream, ARTS_P_PACKET_SIZE):16384; + audiop->bytes = + stream?arts_stream_get(stream, ARTS_P_BUFFER_SPACE):16384; + audiop->fragments = audiop->bytes / audiop->fragsize; + } + break; +#endif + +#ifdef SNDCTL_DSP_NONBLOCK + case SNDCTL_DSP_NONBLOCK: /* _SIO ('P',14) */ + artsdspdebug("aRts: SNDCTL_DSP_NONBLOCK unsupported\n"); + break; +#endif + +#ifdef SNDCTL_DSP_GETCAPS + case SNDCTL_DSP_GETCAPS: /* _SIOR ('P',15, int) */ + if(mmapemu) + *arg = DSP_CAP_MMAP | DSP_CAP_TRIGGER | DSP_CAP_REALTIME | DSP_CAP_DUPLEX; + else + *arg = DSP_CAP_DUPLEX; + break; +#endif + +#ifdef SNDCTL_DSP_GETTRIGGER + case SNDCTL_DSP_GETTRIGGER: /* _SIOR ('P',16, int) */ + artsdspdebug("aRts: SNDCTL_DSP_GETTRIGGER unsupported\n"); + break; +#endif + +#ifdef SNDCTL_DSP_SETTRIGGER + case SNDCTL_DSP_SETTRIGGER: /* _SIOW ('P',16, int) */ + artsdspdebug("aRts: SNDCTL_DSP_SETTRIGGER unsupported\n"); + break; +#endif + +#ifdef SNDCTL_DSP_GETIPTR + case SNDCTL_DSP_GETIPTR: /* _SIOR ('P',17, count_info) */ + artsdspdebug("aRts: SNDCTL_DSP_GETIPTR unsupported\n"); + break; +#endif + +#ifdef SNDCTL_DSP_GETOPTR + case SNDCTL_DSP_GETOPTR: /* _SIOR ('P',18, count_info) */ + if(mmapemu) + { + mmapemu_flush(); + *((count_info *)arg) = mmapemu_ocount; + mmapemu_ocount.blocks = 0; + } + else + { + artsdspdebug("aRts: SNDCTL_DSP_GETOPTR unsupported\n"); + } + break; +#endif + +#ifdef SNDCTL_DSP_MAPINBUF + case SNDCTL_DSP_MAPINBUF: /* _SIOR ('P', 19, buffmem_desc) */ + artsdspdebug("aRts: SNDCTL_DSP_MAPINBUF unsupported\n"); + break; +#endif + +#ifdef SNDCTL_DSP_MAPOUTBUF + case SNDCTL_DSP_MAPOUTBUF: /* _SIOR ('P', 20, buffmem_desc) */ + artsdspdebug("aRts: SNDCTL_DSP_MAPOUTBUF unsupported\n"); + break; +#endif + +#ifdef SNDCTL_DSP_SETSYNCRO + case SNDCTL_DSP_SETSYNCRO: /* _SIO ('P', 21) */ + artsdspdebug("aRts: SNDCTL_DSP_SETSYNCHRO unsupported\n"); + break; +#endif + +#ifdef SNDCTL_DSP_SETDUPLEX + case SNDCTL_DSP_SETDUPLEX: /* _SIO ('P', 22) */ + artsdspdebug("aRts: SNDCTL_DSP_SETDUPLEX unsupported\n"); + break; +#endif + +#ifdef SNDCTL_DSP_GETODELAY + case SNDCTL_DSP_GETODELAY: /* _SIOR ('P', 23, int) */ + space = arts_stream_get(stream, ARTS_P_BUFFER_SPACE); + size = arts_stream_get(stream, ARTS_P_BUFFER_SIZE); + latency = arts_stream_get(stream, ARTS_P_SERVER_LATENCY); + odelay = size - space + (latency * speed * bits * channels) / 8000; + artsdspdebug("aRts: SNDCTL_DSP_GETODELAY returning %d\n", odelay); + *arg = odelay; + break; +#endif + + default: + artsdspdebug("aRts: unhandled /dev/dsp ioctl (%lx - %p)\n",request, argp); + break; + } + + if (settings == 7 && !stream) + { + const char *name = getenv("ARTSDSP_NAME"); + int fragSize = (frags & 0x7fff); + int fragCount = (frags & 0x7fff0000) >> 16; + + artsdspdebug ("aRts: creating stream...\n"); + stream = arts_play_stream(speed,bits,channels,name?name:"artsdsp"); + + if(fragSize > 1 && fragSize < 16) + { + /* + * if fragment settings are way too large (unrealistic), we + * will assume that the user didn't mean it, and let the C API + * choose a convenient number >= 3 + */ + if(fragCount < 2 || fragCount > 8192 + || (fragCount * (1 << fragSize)) > 128*1024) + { + frags = 0x00030000+fragSize; + } + + arts_stream_set(stream,ARTS_P_PACKET_SETTINGS,frags); + } + if(mmapemu) + { + arts_stream_set(stream,ARTS_P_PACKET_SETTINGS,0x0002000c); + mmapemu_ocount.ptr=mmapemu_ocount.blocks=mmapemu_ocount.bytes=0; + artsdspdebug("aRts: total latency = %dms, buffer latency = %dms\n", + arts_stream_get(stream,ARTS_P_TOTAL_LATENCY), + arts_stream_get(stream, ARTS_P_BUFFER_TIME)); + } + } + + return 0; + } + + return 0; +} + +int close(int fd) +{ + CHECK_INIT(); + + if (fd != sndfd) + return orig_close (fd); + else if (sndfd != -1) + { + artsdspdebug ("aRts: /dev/dsp close...\n"); + if(stream) + { + arts_close_stream(stream); + stream = 0; + } + if (record_stream) + { + arts_close_stream(record_stream); + record_stream = 0; + } + if(mmapemu && mmapemu_obuffer) + { + free(mmapemu_obuffer); + mmapemu_obuffer = 0; + } + + /* + * there are problems with library unloading in conjunction with X11, + * (Arts::X11GlobalComm), so we don't unload stuff again here + */ + + /* arts_free(); */ + + orig_close(sndfd); + sndfd = -1; + } + return 0; +} + +int access(const char *pathname, int mode) +{ + CHECK_INIT(); + + if (!is_sound_device(pathname)) /* original open for anything but sound */ + return orig_access (pathname, mode); + else + artsdspdebug ("aRts: /dev/dsp access...\n"); + return 0; +} + +ssize_t write (int fd, const void *buf, size_t count) +{ + CHECK_INIT(); + + if(fd != sndfd) + return orig_write(fd,buf,count); + else if(sndfd != -1) + { + artsdspdebug ("aRts: /dev/dsp write...\n"); + if(stream != 0) + { + return arts_write(stream,buf,count); + } + } + return 0; +} + +ssize_t read (int fd, void *buf, size_t count) +{ + CHECK_INIT(); + + if (fd != sndfd) + return orig_read(fd,buf,count); + else if (sndfd == -1) + return 0; + + if (!record_stream) + record_stream = arts_record_stream(speed, bits, channels, "artsdsp"); + artsdspdebug ( "aRts: /dev/dsp read...\n" ); + return arts_read(record_stream, buf, count); +} + +void *mmap(void *start, size_t length, int prot, int flags, + int fd, off_t offset) +{ + CHECK_INIT(); + + if(fd != sndfd || sndfd == -1) + return orig_mmap(start,length,prot,flags,fd,offset); + else + { + artsdspdebug ("aRts: mmap - start = %p, length = %zd, prot = %d\n", + start, length, prot); + artsdspdebug (" flags = %d, fd = %d, offset = %ld\n",flags, fd,offset); + + if(mmapemu && length > 0) + { + mmapemu_osize = length; + mmapemu_obuffer = malloc(length); + mmapemu_ocount.ptr = mmapemu_ocount.blocks = mmapemu_ocount.bytes = 0; + return mmapemu_obuffer; + } + else artsdspdebug ("aRts: /dev/dsp mmap (unsupported, try -m option)...\n"); + } + return (void *)-1; +} + +int munmap(void *start, size_t length) +{ + CHECK_INIT(); + + if(start != mmapemu_obuffer || mmapemu_obuffer == 0) + return orig_munmap(start,length); + + artsdspdebug ("aRts: /dev/dsp munmap...\n"); + mmapemu_obuffer = 0; + free(start); + + return 0; +} + +#ifdef HAVE_ARTSDSP_STDIO_EMU + +/* + * use #include rather than linking, because we want to keep everything + * static inside artsdsp to be sure that we don't override application symbols + */ +#include "stdioemu.c" + +FILE* fopen(const char *path, const char *mode) +{ + CHECK_INIT(); + + if (!is_sound_device(path)) /* original open for anything but sound */ + return orig_fopen (path, mode); + + artsdspdebug ("aRts: hijacking /dev/dsp fopen...\n"); + + return fake_fopen(path, mode); +} +#endif + +#endif +/* + * vim:ts=4 + */ |