/* This program deviates /dev/dsp accesses into writes and reads of different soundcards using aplay and arecord. Compilation: g++ -o micsep.so micsep.cc -fpic -shared -ldl -pthread Usage: LD_PRELOAD=/path/to/micsep.so "$@" Where "$@" is your command line. Description: This library hooks up all access to /dev/dsp and replaces it with separate calls to aplay -Ddsp0 (for playback) and arecord -Ddsp0 (for recording). You can change the values by editing this source code if you wish. Idea: -Ddmix for playback, -Ddsnoop for recording. Purpose: To be able to use a mic attached to a different soundcard than the speakers are in, even if the application uses "full duplex" I/O with a single /dev/dsp file handle. Something that couldn't be done with simple aoss trickery. Author: Joel Yliluoma, 2005, http://iki.fi/bisqwit/ Notes: Should be reasonably thread-safe (Skype works!) */ #include #include #include #include #include #include #include #include #include #include /* The old function is found in libc.so.6 */ static struct LibcGet { void *f; LibcGet() : f(dlopen("libc.so.6",RTLD_NOW)) {} ~LibcGet() { dlclose(f); } void *operator() (const char *s) { return dlsym(f, s); } } caller; class mutex { pthread_mutex_t mut; public: mutex() { pthread_mutex_init(&mut, NULL); } ~mutex() { pthread_mutex_destroy(&mut); } void lock() { //fprintf(stderr, "pid %d: mutex lock\n", getpid()); pthread_mutex_lock(&mut); } void unlock() { //fprintf(stderr, "pid %d: unmutex lock\n", getpid()); pthread_mutex_unlock(&mut); } }; static class Device { FILE* in; FILE* out; int bits,stereo,rate; mutex mut; public: int fake_fd; bool write; bool read; Device(): fake_fd(-1), in(NULL), out(NULL) { } void SetBits(int b) { if(bits != b) { _Close(); } bits=b; } void SetStereo(int s) { if(stereo != s) { _Close(); } stereo=s; } void SetRate(int r) { if(rate != r) { _Close(); } rate=r; } void Init() { bits=8; stereo=0; rate=8000; } void Close() { _Close(); fake_fd=-1; } int Read(void* buf, unsigned n) { _Init(); if(in == NULL) { errno = EACCES; return -1; } int res = fread(buf,1,n, in); //fprintf(stderr, "read: b%d s%d r%d; n=%u, ret=%d\n", bits,stereo,rate, n,res); return res; } int Write(const void* buf, unsigned n) { _Init(); if(out == NULL) { errno = EACCES; return -1; } int res = fwrite(buf,1,n, out); //fprintf(stderr, "write: b%d s%d r%d; n=%u, ret=%d\n", bits,stereo,rate, n,res); return res; } void PrepareFork() { _Close(); } private: void _Close() { mut.lock(); if(in) { fprintf(stderr, "%d: Closing down 'in'\n", getpid()); pclose(in); in=NULL; } if(out) { fprintf(stderr, "%d: Closing down 'out'\n", getpid()); pclose(out); out=NULL; } mut.unlock(); } void _Init() { mut.lock(); if(!in && read) { #if 1 std::string cmd = "arecord -Ddsp0 "; if(!stereo) cmd += " -c1"; else cmd += " -c2"; if(bits == 8) cmd += " -fu8";else cmd += " -fS16_LE"; { char Buf[16]; sprintf(Buf, " -r%d", rate); cmd += Buf; } #else std::string cmd = "sh -c 'while mpg123 -s /WWW/kala/clock.mp3;do :;done'"; cmd += " | resample mr44100 '"; if(!stereo) cmd += 'm'; if(bits==8) cmd += 'b'; { char Buf[16]; sprintf(Buf, "r%d", rate); cmd += Buf; } cmd += "'"; #endif cmd = "LD_PRELOAD='' " + cmd + " 2>/dev/null"; fprintf(stderr, "%d: Launching 'in': %s\n", getpid(), cmd.c_str()); fflush(stderr); in = popen(cmd.c_str(), "r"); } if(!out && write) { std::string cmd = "aplay -Dhw "; if(!stereo) cmd += " -c1"; else cmd += " -c2"; if(bits == 8) cmd += " -fu8";else cmd += " -fS16_LE"; { char Buf[16]; sprintf(Buf, " -r%d", rate); cmd += Buf; } cmd = "LD_PRELOAD='' " + cmd + " 2>/dev/null"; fprintf(stderr, "%d: Launching 'out': %s\n", getpid(), cmd.c_str()); fflush(stderr); out = popen(cmd.c_str(), "w"); } mut.unlock(); } } dev; extern "C" { int open(const char* f, int mode, ...) { /* Find the real open function */ typedef int (*old_t)(const char*,int,...); static old_t old = (old_t)caller("open"); if(strcmp(f, "/dev/dsp") == 0) { int flags = mode&3; dev.Close(); dev.fake_fd = old("/dev/null", mode); dev.write = (flags==O_WRONLY || flags==O_RDWR); dev.read = (flags==O_RDONLY || flags==O_RDWR); dev.Init(); return dev.fake_fd; } if(mode & O_CREAT) { va_list args; va_start(args, mode); int p = va_arg(args, int); va_end(args); return old(f,mode,p); } return old(f,mode); } int close(int fd) { /* Find the real close function */ typedef int (*old_t)(int); static old_t old = (old_t)caller("close"); if(fd == dev.fake_fd) { old(dev.fake_fd); dev.Close(); return 0; } return old(fd); } int fork() { /* Find the real fork function */ typedef int (*old_t)(void); static old_t old = (old_t)caller("fork"); /* Do this to prevent ugly things happening when the * player creates subprocesses... */ dev.PrepareFork(); return old(); } int read(int fd, void *buf, unsigned count) { /* Find the real read function */ typedef int (*old_t)(int,void*,unsigned); static old_t old = (old_t)caller("read"); if(fd == dev.fake_fd) { return dev.Read(buf,count); } return old(fd,buf,count); } int write(int fd, const void *buf, unsigned count) { /* Find the real write function */ typedef int (*old_t)(int,const void*,unsigned); static old_t old = (old_t)caller("write"); if(fd == dev.fake_fd) { return dev.Write(buf,count); } return old(fd,buf,count); } int ioctl(int fd,unsigned req,void *p) { /* Find the real ioctl function */ typedef int (*old_t)(int,unsigned,void *); static old_t old = (old_t)caller("ioctl"); if(req == SNDCTL_DSP_SAMPLESIZE) { int* param = (int*)p; dev.SetBits(*param); return 0; } if(req == SNDCTL_DSP_STEREO) { int* param = (int*)p; dev.SetStereo(*param); return 0; } if(req == SNDCTL_DSP_SPEED) { int* param = (int*)p; dev.SetRate(*param); return 0; } if(req == SNDCTL_DSP_GETCAPS) { int* param = (int*)p; *param = DSP_CAP_DUPLEX; return 0; } if(req == SNDCTL_DSP_SETFRAGMENT) { int* param = (int*)p; int inused_frags = *param; return 0; } if(req == SNDCTL_DSP_GETOSPACE || req == SNDCTL_DSP_GETISPACE) { struct audio_buf_info *audiop = (struct audio_buf_info *)p; audiop->fragstotal = 16; audiop->fragsize = 4096; audiop->bytes = 16384; audiop->fragments = audiop->bytes / audiop->fragsize; return 0; } if(req == SNDCTL_DSP_RESET) { fprintf(stderr, "DSP_RESET unsupported\n"); return 0; } if(req == SNDCTL_DSP_SYNC) { fprintf(stderr, "DSP_SYNC unsupported\n"); return 0; } /* Call the real ioctl function */ return old(fd,req,p); } }