/* 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 * Copyright (C) 2000 Stefan Westerfeld (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 #endif #ifdef HAVE_SYS_SOUNDCARD_H #include #include #include #include #include #include #include #include #include /* #include */ #include #include #include #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 */