#include #include #include #include "dat.h" #include "fns.h" #include "linux.h" enum { FREQUENCY = 44100, CHANNELS = 2, DELAY = 100, FRAGSIZE = 4096, }; typedef struct Chan Chan; typedef struct DSP DSP; struct Chan { ulong phase; int last; }; struct DSP { Ufile; int channels; /* number of channels (2 for stereo) */ int freq; /* frequency of sound stream */ int rfreq; /* frequency of /dev/audio */ uchar *buf; /* resampling */ ulong nbuf; Chan chan[CHANNELS]; vlong time; /* time point of the last sample in device buffer */ ulong written; /* number of bytes written to dsp */ ulong written2; /* same as written, will be reset on every GETOPTR ioctl */ }; static int closedsp(Ufile *file) { DSP *dsp = (DSP*)file; trace("dsp: closedsp"); free(dsp->buf); close(dsp->fd); return 0; } static int polldsp(Ufile *, void *) { return POLLOUT; } static int readdsp(Ufile *, void *, int, vlong) { return 0; /* not implemented */ } static int resample(Chan *c, uchar *src, uchar *dst, int sstep, int dstep, ulong delta, ulong count) { int last, val, out; ulong phase, pos; uchar *dp, *sp; dp = dst; last = val = c->last; phase = c->phase; pos = phase >> 16; while(pos < count){ sp = src + sstep*pos; val = sp[0] | (sp[1] << 8); val = (val & 0x7FFF) - (val & 0x8000); if(pos){ sp -= sstep; last = sp[0] | (sp[1] << 8); last = (last & 0x7FFF) - (last & 0x8000); } out = last + (((val - last) * (phase & 0xFFFF)) >> 16); dp[0] = out; dp[1] = out >> 8; dp += dstep; phase += delta; pos = phase >> 16; } c->last = val; if(delta < 0x10000){ c->phase = phase & 0xFFFF; } else { c->phase = phase - (count << 16); } return (dp - dst) / dstep; } static int convertout(DSP *dsp, uchar *buf, int len, uchar **out) { int ret, ch; ulong count, delta; /* no conversion required? */ if(dsp->freq == dsp->rfreq && dsp->channels == CHANNELS){ *out = buf; return len; } /* * delta is the number of input samples to * produce one output sample. scaled by 16 bit to * get fractional part. */ delta = ((ulong)dsp->freq << 16) / dsp->rfreq; count = len / (2 * dsp->channels); /* * get maximum required size of output bufer. this is not exact! * number of output samples depends on phase! */ ret = (((count << 16) + delta-1) / delta) * 2*CHANNELS; if(ret > dsp->nbuf){ free(dsp->buf); dsp->buf = kmalloc(ret); dsp->nbuf = ret; } for(ch=0; ch < CHANNELS; ch++) ret = resample(dsp->chan + ch, buf + 2*(ch % dsp->channels), dsp->buf + 2*ch, 2*dsp->channels, 2*CHANNELS, delta, count); *out = dsp->buf; return ret * 2*CHANNELS; } static int writedsp(Ufile *file, void *buf, int len, vlong) { DSP *dsp = (DSP*)file; vlong now; int ret, diff; uchar *out; if((ret = convertout(dsp, buf, len, &out)) <= 0) return ret; if((ret = write(dsp->fd, out, ret)) < 0) return mkerror(); now = nsec(); if(dsp->time < now){ dsp->time = now; dsp->written = 0; dsp->written2 = 0; } else { diff = (dsp->time - now) / 1000000; if(diff > DELAY) sleep(diff - DELAY); } dsp->time += ((1000000000LL) * ret / (dsp->rfreq * 2*CHANNELS)); dsp->written += len; dsp->written2 += len; return len; } enum { AFMT_S16_LE = 0x10, }; static int ioctldsp(Ufile *file, int cmd, void *arg) { DSP *dsp = (DSP*)file; int ret, i; vlong now; static int counter; ret = 0; switch(cmd){ default: trace("dsp: unknown ioctl %lux %p", (ulong)cmd, arg); ret = -ENOTTY; break; case 0xC004500A: trace("dsp: SNDCTL_DSP_SETFRAGMENT(%lux)", *(ulong*)arg); break; case 0xC0045004: trace("dsp: SNDCTL_DSP_GETBLKSIZE"); *((int*)arg) = FRAGSIZE; break; case 0x800c5011: trace("dsp: SNDCTL_DSP_GETIPTR"); ret = -EPERM; break; case 0x800c5012: trace("dsp: SNDCTL_DSP_GETOPTR"); ((int*)arg)[0] = dsp->written; // Total # of bytes processed ((int*)arg)[1] = dsp->written2 / FRAGSIZE; // # of fragment transitions since last time dsp->written2 = 0; ((int*)arg)[2] = 0; // Current DMA pointer value break; case 0x8010500D: trace("dsp: SNDCTL_DSG_GETISPACE"); ret = -EPERM; break; case 0x8010500C: trace("dsp: SNDCTL_DSP_GETOSPACE"); i = (2 * dsp->channels) * ((dsp->freq*DELAY)/1000); ((int*)arg)[1] = i / FRAGSIZE; // fragstot ((int*)arg)[2] = FRAGSIZE; // fragsize now = nsec(); if(now < dsp->time){ i -= ((2 * dsp->channels) * (((dsp->time - now) * (vlong)dsp->freq) / 1000000000)); if(i < 0) i = 0; } ((int*)arg)[0] = i / FRAGSIZE; // available fragment count ((int*)arg)[3] = i; // available space in bytes break; case 0x8004500B: trace("dsp: SNDCTL_DSP_GETFMTS(%d)", *(int*)arg); *(int*)arg = AFMT_S16_LE; break; case 0x8004500F: trace("dsp: SNDCTL_DSP_GETCAPS"); *(int*)arg = 0x400; break; case 0xC0045005: trace("dsp: SNDCTL_DSP_SETFMT(%d)", *(int*)arg); *(int*)arg = AFMT_S16_LE; break; case 0xC0045006: trace("dsp: SOUND_PCM_WRITE_CHANNELS(%d)", *(int*)arg); dsp->channels = *(int*)arg; break; case 0xC0045003: trace("dsp: SNDCTL_DSP_STEREO(%d)", *(int*)arg); dsp->channels = 2; *(int*)arg = 1; break; case 0xC0045002: trace("dsp: SNDCTL_DSP_SPEED(%d)", *(int*)arg); dsp->freq = *(int*)arg; for(i=0; ichan[i].phase = 0; dsp->chan[i].last = 0; } break; case 0x00005000: trace("dsp: SNDCTL_DSP_RESET"); break; case 0x00005001: trace("dsp: SNDCTL_DSP_SYNC"); break; } return ret; } static int getaudiofreq(void) { int ret, n, fd; char buf[1024]; ret = FREQUENCY; if((fd = open("/dev/volume", OREAD)) < 0) return ret; if((n = read(fd, buf, sizeof(buf)-1)) > 0){ char *p; buf[n] = 0; if(p = strstr(buf, "speed out ")) ret = atoi(p + 10); } close(fd); return ret; } int opendsp(char *path, int mode, int, Ufile **pf) { DSP *dsp; int freq; int fd; if(strcmp(path, "/dev/dsp")==0 || strcmp(path, "/dev/dsp0")==0){ if((fd = open("/dev/audio", OWRITE)) < 0) return mkerror(); freq = getaudiofreq(); dsp = mallocz(sizeof(DSP), 1); dsp->ref = 1; dsp->mode = mode; dsp->dev = DSPDEV; dsp->fd = fd; dsp->path = kstrdup(path); dsp->rfreq = freq; dsp->freq = freq; dsp->channels = CHANNELS; *pf = dsp; return 0; } return -ENOENT; } static int fstatdsp(Ufile *f, Ustat *s) { s->mode = 0666 | S_IFCHR; s->uid = current->uid; s->gid = current->gid; s->ino = hashpath(f->path); s->size = 0; return 0; }; static int statdsp(char *path, int , Ustat *s) { if(strcmp(path, "/dev/dsp")==0 || strcmp(path, "/dev/dsp0")==0){ s->mode = 0666 | S_IFCHR; s->uid = current->uid; s->gid = current->gid; s->ino = hashpath(path); s->size = 0; return 0; } return -ENOENT; } static Udev dspdev = { .open = opendsp, .read = readdsp, .write = writedsp, .poll = polldsp, .close = closedsp, .ioctl = ioctldsp, .stat = statdsp, .fstat = fstatdsp, }; void dspdevinit(void) { devtab[DSPDEV] = &dspdev; fsmount(&dspdev, "/dev/dsp"); fsmount(&dspdev, "/dev/dsp0"); }