ref: 87de3d34335e20fdb2ad2c471f89173aa6610193
dir: /sys/src/cmd/nusb/audio/audio.c/
#include <u.h> #include <libc.h> #include <fcall.h> #include <thread.h> #include <9p.h> #include "usb.h" enum { Paudio1 = 0x00, Paudio2 = 0x20, Csamfreq = 0x01, /* audio 1 */ Rsetcur = 0x01, /* audio 2 */ Rcur = 0x01, Rrange = 0x02, }; typedef struct Range Range; struct Range { uint min; uint max; }; typedef struct Aconf Aconf; struct Aconf { Ep *ep; int bits; int bps; /* subslot size (bytes per sample) */ int format; int channels; int terminal; Range *freq; int nfreq; /* audio 1 */ int controls; /* audio 2 */ int clock; }; int audiodelay = 1764; /* 40 ms */ int audiofreq = 44100; int audiochan = 2; int audiores = 16; char user[] = "audio"; Dev *audiodev = nil; Iface *audiocontrol = nil; Ep *audioepin = nil; Ep *audioepout = nil; Iface* findiface(Conf *conf, int class, int subclass, int id) { int i; Iface *iface; for(i = 0; i < nelem(conf->iface); i++){ iface = conf->iface[i]; if(iface == nil || Class(iface->csp) != class || Subclass(iface->csp) != subclass) continue; if(id == -1 || iface->id == id) return iface; } return nil; } Desc* findiad(Usbdev *u, int id, int csp) { int i; Desc *dd; uchar *b; for(i = 0; i < nelem(u->ddesc); i++){ dd = u->ddesc[i]; if(dd == nil || dd->data.bDescriptorType != 11 || dd->data.bLength != 8) continue; b = dd->data.bbytes; if(b[0] == id && b[0]+b[1] <= Niface && csp == CSP(b[2], b[3], b[4])) return dd; } return nil; } Desc* findacheader(Usbdev *u, Iface *ac) { Desc *dd; uchar *b; int i; for(i = 0; i < nelem(u->ddesc); i++){ dd = u->ddesc[i]; if(dd == nil || dd->iface != ac || dd->data.bDescriptorType != 0x24) continue; if(dd->data.bLength < 8 || dd->data.bbytes[0] != 1) continue; b = dd->data.bbytes; switch(Proto(ac->csp)){ case Paudio1: if(dd->data.bLength == 8+b[5]) return dd; break; case Paudio2: if(dd->data.bLength == 9) return dd; break; } } return nil; } Desc* findterminal(Usbdev *u, Iface *ac, int id) { Desc *dd; uchar *b; int i; for(i = 0; i < nelem(u->ddesc); i++){ dd = u->ddesc[i]; if(dd == nil || dd->iface != ac) continue; if(dd->data.bDescriptorType != 0x24 || dd->data.bLength < 4) continue; b = dd->data.bbytes; if(b[1] != id) continue; /* check descriptor length according to type and proto */ switch(b[0]<<16 | dd->data.bLength<<8 | Proto(ac->csp)){ case 0x020C00|Paudio1: case 0x030900|Paudio1: case 0x021100|Paudio2: case 0x030c00|Paudio2: return dd; } } return nil; } Desc* findclocksource(Usbdev *u, Iface *ac, int id) { Desc *dd; uchar *b; int i; for(i = 0; i < nelem(u->ddesc); i++){ dd = u->ddesc[i]; if(dd == nil || dd->iface != ac) continue; if(dd->data.bDescriptorType != 0x24 || dd->data.bLength != 8) continue; b = dd->data.bbytes; if(b[0] == 0x0A && b[1] == id) return dd; } return nil; } void parseasdesc1(Desc *dd, Aconf *c) { uchar *b; Range *f; b = dd->data.bbytes; switch(dd->data.bDescriptorType<<8 | b[0]){ case 0x2501: /* CS_ENDPOINT, EP_GENERAL */ if(dd->data.bLength != 7) return; c->controls = b[1]; break; case 0x2401: /* CS_INTERFACE, AS_GENERAL */ if(dd->data.bLength != 7) return; c->terminal = b[1]; c->format = GET2(&b[3]); break; case 0x2402: /* CS_INTERFACE, FORMAT_TYPE */ if(dd->data.bLength < 8 || b[1] != 1) return; c->channels = b[2]; c->bps = b[3]; c->bits = b[4]; if(b[5] == 0){ /* continuous frequency range */ c->nfreq = 1; c->freq = emallocz(sizeof(*f), 1); c->freq->min = b[6] | b[7]<<8 | b[8]<<16; c->freq->max = b[9] | b[10]<<8 | b[11]<<16; }else{ /* discrete sampling frequencies */ c->nfreq = b[5]; c->freq = emallocz(c->nfreq * sizeof(*f), 1); b += 6; for(f = c->freq; f < c->freq+c->nfreq; f++, b += 3){ f->min = b[0] | b[1]<<8 | b[2]<<16; f->max = f->min; } } break; } } void parseasdesc2(Desc *dd, Aconf *c) { uchar *b; b = dd->data.bbytes; switch(dd->data.bDescriptorType<<8 | b[0]){ case 0x2401: /* CS_INTERFACE, AS_GENERAL */ if(dd->data.bLength != 16 || b[3] != 1) return; c->terminal = b[1]; c->format = GET4(&b[4]); c->channels = b[8]; break; case 0x2402: /* CS_INTERFACE, FORMAT_TYPE */ if(dd->data.bLength != 6 || b[1] != 1) return; c->bps = b[2]; c->bits = b[3]; break; } } int setclock(Dev *d, Iface *ac, Aconf *c, int speed) { uchar b[4]; int index; switch(Proto(ac->csp)){ case Paudio1: if((c->controls & 1) == 0) break; b[0] = speed; b[1] = speed >> 8; b[2] = speed >> 16; index = c->ep->id & Epmax; if(c->ep->dir == Ein) index |= 0x80; return usbcmd(d, Rh2d|Rclass|Rep, Rsetcur, Csamfreq<<8, index, b, 3); case Paudio2: PUT4(b, speed); index = c->clock<<8 | ac->id; return usbcmd(d, Rh2d|Rclass|Riface, Rcur, Csamfreq<<8, index, b, 4); } return 0; } int getclockrange(Dev *d, Iface *ac, Aconf *c) { uchar b[2 + 32*12]; int i, n, rc; rc = usbcmd(d, Rd2h|Rclass|Riface, Rrange, Csamfreq<<8, c->clock<<8 | ac->id, b, sizeof(b)); if(rc < 0) return -1; if(rc < 2 || rc < 2 + (n = GET2(b))*12){ werrstr("invalid response"); return -1; } c->freq = emallocz(n, sizeof(Range)); c->nfreq = n; for(i = 0; i < n; i++) c->freq[i] = (Range){GET4(&b[2 + i*12]), GET4(&b[6 + i*12])}; return 0; } void parsestream(Dev *d, Iface *ac, int id) { Iface *as; Desc *dd; Ep *e; Aconf *c; uchar *b; int i; /* find AS interface */ as = findiface(d->usb->conf[0], Claudio, 2, id); /* enumerate through alt. settings */ for(; as != nil; as = as->next){ c = emallocz(sizeof(*c), 1); as->aux = c; /* find AS endpoint */ for(i = 0; i < nelem(as->ep); i++){ e = as->ep[i]; if(e != nil && e->type == Eiso && (e->attrib>>4 & 3) == Edata){ c->ep = e; break; } } if(c->ep == nil){ Skip: free(c); as->aux = nil; continue; } /* parse AS descriptors */ for(i = 0; i < nelem(d->usb->ddesc); i++){ dd = d->usb->ddesc[i]; if(dd == nil || dd->iface != as) continue; switch(Proto(ac->csp)){ case Paudio1: parseasdesc1(dd, c); break; case Paudio2: parseasdesc2(dd, c); break; } } if(Proto(ac->csp) == Paudio1) continue; dd = findterminal(d->usb, ac, c->terminal); if(dd == nil) goto Skip; b = dd->data.bbytes; switch(b[0]){ case 0x02: /* INPUT_TERMINAL */ c->clock = b[5]; break; case 0x03: /* OUTPUT_TERMINAL */ c->clock = b[6]; break; } dd = findclocksource(d->usb, ac, c->clock); if(dd == nil) goto Skip; b = dd->data.bbytes; /* check that clock has rw frequency control */ if((b[3] & 3) != 3) goto Skip; if(getclockrange(d, ac, c) != 0){ fprint(2, "getclockrange %d: %r", c->clock); goto Skip; } } } Dev* setupep(Dev *d, Iface *ac, Ep *e, int speed) { int dir = e->dir; Aconf *c; Range *f; for(;e != nil; e = e->next){ c = e->iface->aux; if(c == nil || e != c->ep || e->dir != dir) continue; if(c->format != 1 || c->bits != audiores || 8*c->bps != audiores || c->channels != audiochan) continue; for(f = c->freq; f != c->freq+c->nfreq; f++) if(speed >= f->min && speed <= f->max) goto Foundaltc; } werrstr("no altc found"); return nil; Foundaltc: if(setalt(d, e->iface) < 0) return nil; if(setclock(d, ac, c, speed) < 0){ werrstr("setclock: %r"); return nil; } if((d = openep(d, e)) == nil){ werrstr("openep: %r"); return nil; } devctl(d, "samplesz %d", audiochan*audiores/8); devctl(d, "sampledelay %d", audiodelay); devctl(d, "hz %d", speed); if(e->dir==Ein) devctl(d, "name audioin"); else devctl(d, "name audio"); return d; } void fsread(Req *r) { char *msg; msg = smprint("master 100 100\nspeed %d\ndelay %d\n", audiofreq, audiodelay); readstr(r, msg); respond(r, nil); free(msg); } void fswrite(Req *r) { char msg[256], *f[4]; int nf, speed; snprint(msg, sizeof(msg), "%.*s", utfnlen((char*)r->ifcall.data, r->ifcall.count), (char*)r->ifcall.data); nf = tokenize(msg, f, nelem(f)); if(nf < 2){ respond(r, "invalid ctl message"); return; } if(strcmp(f[0], "speed") == 0){ Dev *d; speed = atoi(f[1]); Setup: if((d = setupep(audiodev, audiocontrol, audioepout, speed)) == nil){ responderror(r); return; } closedev(d); if(audioepin != nil) if(d = setupep(audiodev, audiocontrol, audioepin, speed)) closedev(d); audiofreq = speed; } else if(strcmp(f[0], "delay") == 0){ audiodelay = atoi(f[1]); speed = audiofreq; goto Setup; } r->ofcall.count = r->ifcall.count; respond(r, nil); } Srv fs = { .read = fsread, .write = fswrite, }; void usage(void) { fprint(2, "%s devid\n", argv0); exits("usage"); } void main(int argc, char *argv[]) { char buf[32]; Dev *d, *ed; Desc *dd; Conf *conf; Iface *ac; Aconf *c; Ep *e; uchar *b; int i; ARGBEGIN { case 'D': chatty9p++; break; case 'd': usbdebug++; break; } ARGEND; if(argc == 0) usage(); if((d = getdev(*argv)) == nil) sysfatal("getdev: %r"); audiodev = d; conf = d->usb->conf[0]; ac = findiface(conf, Claudio, 1, -1); if(ac == nil) sysfatal("no audio control interface"); audiocontrol = ac; switch(Proto(ac->csp)){ case Paudio1: dd = findacheader(d->usb, ac); if(dd == nil) sysfatal("no audio control header"); b = dd->data.bbytes; for(i = 6; i < dd->data.bLength-2; i++) parsestream(d, ac, b[i]); break; case Paudio2: dd = findiad(d->usb, ac->id, CSP(Claudio, 0, Paudio2)); if(dd == nil) sysfatal("no audio function"); b = dd->data.bbytes; for(i = b[0]+1; i < b[0]+b[1]; i++) parsestream(d, ac, i); break; } for(i = 0; i < nelem(d->usb->ep); i++){ for(e = d->usb->ep[i]; e != nil; e = e->next){ c = e->iface->aux; if(c != nil && c->ep == e) break; } if(e == nil) continue; switch(e->dir){ case Ein: if(audioepin != nil) continue; audioepin = e; break; case Eout: if(audioepout != nil) continue; audioepout = e; break; } if((ed = setupep(d, ac, e, audiofreq)) == nil){ fprint(2, "setupep: %r\n"); if(e == audioepin) audioepin = nil; if(e == audioepout) audioepout = nil; continue; } closedev(ed); } if(audioepout == nil) sysfatal("no output stream found"); fs.tree = alloctree(user, "usb", DMDIR|0555, nil); createfile(fs.tree->root, "volume", user, 0666, nil); snprint(buf, sizeof buf, "%d.audio", audiodev->id); postsharesrv(&fs, nil, "usb", buf); exits(0); }