diff 1b51d5683a5d8adcde03bbd277e6331f23c2f723 uncommitted --- a/sys/src/cmd/nusb/audio/audio.c +++ b/sys/src/cmd/nusb/audio/audio.c @@ -3,6 +3,8 @@ #include #include #include <9p.h> +#include +#pragma varargck type "!" Pcmdesc #include "usb.h" enum { @@ -13,12 +15,35 @@ /* audio 1 */ Rsetcur = 0x01, + Rgetcur = 0x81, + Rgetmin, + Rgetmax, + Rgetres, /* audio 2 */ Rcur = 0x01, Rrange = 0x02, + + /* feature unit control values */ + Cmute = 1, + Cvolume = 2, + Cagc = 7, + Cbassboost = 9, + Cloudness = 10, + Cnum, }; +#define Conlycur (1<data.bLength != 7) return; c->terminal = b[1]; - c->format = GET2(&b[3]); + c->fmt = tofmt(GET2(&b[3])); break; case 0x2402: /* CS_INTERFACE, FORMAT_TYPE */ @@ -223,7 +275,7 @@ if(dd->data.bLength != 16 || b[3] != 1) return; c->terminal = b[1]; - c->format = GET4(&b[4]); + c->fmt = tofmt(GET4(&b[4])); c->channels = b[8]; break; @@ -281,10 +333,143 @@ return 0; } +int +setvalue(Dev *d, Iface *ac, int r, uchar id, uchar hi, uchar lo, short value, int sz) +{ + uchar b[2]; + + if(sz == 1) + b[0] = value; + else + PUT2(b, value); + return usbcmd(d, Rh2d|Rclass|Riface, r, hi<<8 | lo, id<<8 | ac->id, b, sz); +} + +int +getvalue(Dev *d, Iface *ac, int r, uchar id, uchar hi, uchar lo, short *value) +{ + uchar b[2]; + int rc; + + *value = 0; + rc = usbcmd(d, Rd2h|Rclass|Riface, r, hi<<8 | lo, id<<8 | ac->id, b, sizeof(b)); + if(rc < 0) + return -1; + if(rc < 1){ + werrstr("invalid response"); + return -1; + } + *value = rc > 1 ? GET2(b) : b[0]; + return 0; +} + +int +getvalues(Dev *d, Iface *ac, Ctrl *c) +{ + char *s; + int onlycur; + + onlycur = Conlycur & (1<cs); + if(((s = "cur") && getvalue(d, ac, Rgetcur, c->id, c->cs, c->ch, &c->cur) < 0) || + (!onlycur && ( + ((s = "min") && getvalue(d, ac, Rgetmin, c->id, c->cs, c->ch, &c->min) < 0) || + ((s = "max") && getvalue(d, ac, Rgetmax, c->id, c->cs, c->ch, &c->max) < 0) || + ((s = "res") && getvalue(d, ac, Rgetres, c->id, c->cs, c->ch, &c->res) < 0)))){ + fprint(2, "getvalue: %s: %s: %r\n", ctrlname[c->cs], s); + return -1; + } + if(onlycur){ + c->min = 0; + c->max = 1; + c->res = 1; + }else if(c->res < 1){ + fprint(2, "getvalue: %s: invalid res: %d\n", ctrlname[c->cs], c->res); + return -1; + } + return 0; +} + +int +cmpctrl(void *a_, void *b_) +{ + Ctrl *a, *b; + a = a_; + b = b_; + if(a->cs != b->cs) + return a->cs - b->cs; + if(a->id != b->id) + return a->id - b->id; + /* same type on the same unit - group those */ + a->group = 1; + b->group = 1; + return a->ch - b->ch; +} + void +findcontrols(Dev *d, Iface *ac) +{ + Desc *dd; + uchar *b; + Ctrl *c; + int i, k, m, n, x; + uchar cs, ch; + + for(i = 0; i < nelem(d->usb->ddesc); i++){ + dd = d->usb->ddesc[i]; + if(dd == nil) + continue; + b = dd->data.bbytes; + switch(dd->data.bDescriptorType<<8 | b[0]){ + case 0x2406: /* CS_INTERFACE, FEATURE_UNIT */ + if(dd->data.bLength < 8 || nctrl >= nelem(ctrl) || (n = b[3]) < 1) + continue; + for(k = 0; k < nctrl; k++) + if(ctrl[k].id == b[1]) + break; + if(k < nctrl) + break; + for(k = 4, ch = 0; k <= dd->data.bLength-2-n-1; k += n, ch++){ + x = (n > 1 ? GET2(b+k) : b[k]) & (Call>>1); + if(x == 0) + continue; + m = 1; + for(cs = 1; cs < Cnum && nctrl < nelem(ctrl); cs++, m <<= 1){ + if((m & x) == 0) + continue; + c = &ctrl[nctrl++]; + c->cs = cs; + c->ch = ch; + c->id = b[1]; + getvalues(d, ac, c); + } + } + break; + } + } + qsort(ctrl, nctrl, sizeof(Ctrl), cmpctrl); +} + +void +parseterminal2(Dev *d, Iface *ac, Desc *dd, Aconf *c) +{ + uchar *b; + + USED(d, ac); + 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; + } +} + +void parsestream(Dev *d, Iface *ac, int id) { - Iface *as; + Iface *as, *zb; Desc *dd; Ep *e; Aconf *c; @@ -294,6 +479,9 @@ /* find AS interface */ as = findiface(d->usb->conf[0], Claudio, 2, id); + /* find zero-bandwidth setting, if any */ + for(zb = as; zb != nil && zb->alt != 0; zb = zb->next); + /* enumerate through alt. settings */ for(; as != nil; as = as->next){ c = emallocz(sizeof(*c), 1); @@ -313,6 +501,7 @@ as->aux = nil; continue; } + c->zb = zb; /* parse AS descriptors */ for(i = 0; i < nelem(d->usb->ddesc); i++){ @@ -335,15 +524,7 @@ 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; - } + parseterminal2(d, ac, dd, c); dd = findclocksource(d->usb, ac, c->clock); if(dd == nil) @@ -353,62 +534,82 @@ if((b[3] & 3) != 3) goto Skip; if(getclockrange(d, ac, c) != 0){ - fprint(2, "getclockrange %d: %r", c->clock); + fprint(2, "getclockrange %d: %r\n", c->clock); goto Skip; } } } +int +fmtcmp(Pcmdesc *a, Pcmdesc *b) +{ + if(a->bits != b->bits) + return a->bits - b->bits; + if(a->channels != b->channels) + return a->channels - b->channels; + if(a->fmt != b->fmt){ + if(a->fmt == L's' || a->fmt == L'f') + return 1; + if(b->fmt == L's' || b->fmt == L'f') + return -1; + return a->fmt - b->fmt; + } + return a->rate - b->rate; +} + Dev* -setupep(Dev *d, Iface *ac, Ep *e, int *speed, int force) +setupep(Dev *d, Iface *ac, Ep *e, Pcmdesc *fmt, int exact) { - int dir = e->dir; + int n, r, dir; Aconf *c, *bestc; + Ep *beste, *ep; Range *f; - Ep *beste; - int closest, sp; + dir = e->dir; + ep = e; bestc = nil; beste = nil; - closest = 1<<30; - sp = *speed; + r = -1; for(;e != nil; e = e->next){ c = e->iface->aux; - if(c == nil || e != c->ep || e->dir != dir) + if(c == nil || e != c->ep || e->dir != dir || c->fmt == 0 || c->bits != 8*c->bps) 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(sp >= f->min && sp <= f->max) - goto Foundaltc; - if(force) - continue; - if(f->min >= sp && closest-sp >= f->min-sp){ - closest = f->min; + if(f->min >= fmt->rate) + c->rate = f->min; + else if(f->max <= fmt->rate) + c->rate = f->max; + else + c->rate = fmt->rate; + if((n = fmtcmp(c, fmt)) == 0 || bestc == nil){ +Better: bestc = c; beste = e; - }else if(bestc == nil || (f->max < sp && closest < sp && sp-closest > sp-f->max)){ - closest = f->max; - bestc = c; - beste = e; + if((r = n) == 0) + goto Done; + continue; } + if(n > 0 && (r < 0 || fmtcmp(c, bestc) < 0)) + goto Better; + if(n < 0 && (r < 0 && fmtcmp(c, bestc) > 0)) + goto Better; } } - if(bestc == nil){ + +Done: + if(bestc == nil || (exact && r != 0)){ werrstr("no altc found"); return nil; } e = beste; c = bestc; - sp = closest; -Foundaltc: if(setalt(d, e->iface) < 0){ - fprint(2, "setalt: %r\n"); + werrstr("setalt: %r\n"); return nil; } - if(setclock(d, ac, c, sp) < 0){ + if(setclock(d, ac, c, c->rate) < 0){ werrstr("setclock: %r"); return nil; } @@ -416,59 +617,220 @@ werrstr("openep: %r"); return nil; } - devctl(d, "samplesz %d", audiochan*audiores/8); + devctl(d, "samplesz %d", c->channels*c->bits/8); devctl(d, "sampledelay %d", audiodelay); - devctl(d, "hz %d", sp); + devctl(d, "hz %d", c->rate); devctl(d, "name audio%sU%s", e->dir==Ein ? "in" : "", audiodev->hname); - *speed = sp; + ep->aux = c; return d; } +char * +ctrlvalue(Ctrl *c) +{ + static char v[64]; + int x, n; + + if((Conlycur & (1<cs)) == 0){ + if((ushort)c->cur == 0x8000) + x = 0; + else{ + n = (c->max - c->min); + x = (c->cur - c->min) * 100; + x /= n; + } + }else{ + x = !!c->cur; + } + snprint(v, sizeof(v), "%d", x); + return v; +} + void fsread(Req *r) { - char *msg; + static char msg[1024]; + char *s, *e; + Aconf *c; + Ctrl *cur, *prev; + int i; - msg = smprint("master 100 100\nspeed %d\ndelay %d\n", audiofreq, audiodelay); + s = msg; + e = msg+sizeof(msg); + *s = 0; + + if(r->fid->file == ctl){ + if(epout != nil) + s = seprint(s, e, "out %s\n", outoff ? "off" : "on"); + if(epin != nil) + seprint(s, e, "in %s\n", inoff ? "off" : "on"); + } else { + if(epout != nil){ + c = epout->aux; + s = seprint(s, e, "delay %d\nfmtout %!\nspeed %d\n", + audiodelay, c->Pcmdesc, c->rate); + } + if(epin != nil){ + c = epin->aux; + s = seprint(s, e, "fmtin %!\n", c->Pcmdesc); + } + + prev = nil; + for(i = 0; i < nctrl; i++, prev = cur){ + cur = ctrl+i; + if(prev == nil || prev->id != cur->id || prev->cs != cur->cs || prev->group != cur->group) + s = seprint(s, e, "%s%s%d", i==0?"":"\n", ctrlname[cur->cs], cur->id); + s = seprint(s, e, " %s", ctrlvalue(cur)); + } + if(i > 0) + seprint(s, e, "\n"); + } + readstr(r, msg); respond(r, nil); - free(msg); } +int +setctrl(char *f[8], int nf) +{ + int i, j, id, n, x; + char *s, *e; + uchar cs; + Ctrl *c; + + id = -1; + s = nil; + for(e = f[0]; *e; e++){ + if(*e >= '0' && *e <= '9'){ + s = e; + id = strtol(e, &e, 10); + break; + } + } + if(id < 0 || *e != 0){ +Invalid: + werrstr("invalid name"); + goto Error; + } + *s = 0; + for(i = 0, c = ctrl; i < nctrl; i++, c++){ + if(ctrl[i].id == id && strcmp(ctrlname[c->cs], f[0]) == 0) + break; + } + if(i == nctrl) + goto Invalid; + + cs = c->cs; + for(j = 1; j < nf; j++){ + x = atoi(f[j]); + if((Conlycur & (1<cs)) == 0){ + if(x <= 0) + x = 0x8000; + else{ + n = (c->max - c->min) / c->res; + x = (x * n * c->res)/100 + c->min; + if(x < c->min) + x = c->min; + else if(x > c->max) + x = c->max; + } + n = 2; + }else{ + x = !!x; + n = 1; + } +Groupset: + if(setvalue(audiodev, audiocontrol, Rsetcur, c->id, c->cs, c->ch, x, n) < 0) + goto Error; + if(getvalue(audiodev, audiocontrol, Rgetcur, c->id, c->cs, c->ch, &c->cur) < 0) + goto Error; + + c++; + if(c >= ctrl+nctrl || c->id != id || c->cs != cs) + break; /* just ignore anything extra */ + if(nf == 2) + goto Groupset; + } + + return 0; +Error: + werrstr("%s: %r", f[0]); + return -1; +} + void fswrite(Req *r) { - char msg[256], *f[4]; - int nf, speed; + char msg[256], *f[8]; + int nf, off; + Pcmdesc pd; + Aconf *c; + Dev *d; + Ep *e; 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){ +Invalid: respond(r, "invalid ctl message"); return; } - if(strcmp(f[0], "speed") == 0){ - Dev *d; + c = epout->aux; - speed = atoi(f[1]); -Setup: - if((d = setupep(audiodev, audiocontrol, audioepout, &speed, 1)) == nil){ - responderror(r); + if(r->fid->file == ctl){ + if(strcmp(f[0], "out") == 0) + e = epout; + else if(strcmp(f[0], "in") == 0) + e = epin; + else + goto Invalid; + if(strcmp(f[1], "on") == 0) + off = 0; + else if(strcmp(f[1], "off") == 0) + off = 1; + else + goto Invalid; + c = e->aux; + if(c->zb == nil){ + respond(r, "no zero-bandwidth config"); return; } - audiofreq = speed; + if(setalt(audiodev, c->zb) < 0) + goto Error; + if(e == epout) + outoff = off; + else + inoff = off; + } else if((strcmp(f[0], "speed") == 0 || strcmp(f[0], "fmtout") == 0) && epout != nil){ + if(f[0][0] == 's'){ + pd = c->Pcmdesc; + pd.rate = atoi(f[1]); + }else if(mkpcmdesc(f[1], &pd) != 0) + goto Error; +Setup: + if((d = setupep(audiodev, audiocontrol, epout, &pd, 1)) == nil) + goto Error; closedev(d); - if(audioepin != nil) - if(d = setupep(audiodev, audiocontrol, audioepin, &speed, 1)) - closedev(d); - } else if(strcmp(f[0], "delay") == 0){ + } else if(strcmp(f[0], "fmtin") == 0 && epin != nil){ + if(mkpcmdesc(f[1], &pd) != 0) + goto Error; + if((d = setupep(audiodev, audiocontrol, epin, &pd, 1)) == nil) + goto Error; + closedev(d); + } else if(strcmp(f[0], "delay") == 0 && epout != nil){ + pd = c->Pcmdesc; audiodelay = atoi(f[1]); - speed = audiofreq; goto Setup; + } else if(setctrl(f, nf) < 0){ + goto Error; } + r->ofcall.count = r->ifcall.count; respond(r, nil); + return; +Error: + responderror(r); } Srv fs = { @@ -508,6 +870,7 @@ if(argc == 0) usage(); + fmtinstall('!', pcmdescfmt); if((d = getdev(*argv)) == nil) sysfatal("getdev: %r"); audiodev = d; @@ -547,33 +910,40 @@ continue; switch(e->dir){ case Ein: - if(audioepin != nil) + if(epin != nil) continue; - audioepin = e; + epin = e; break; case Eout: - if(audioepout != nil) + if(epout != nil) continue; - audioepout = e; + epout = e; break; } - if((ed = setupep(d, ac, e, &audiofreq, 0)) == nil){ - fprint(2, "setupep: %r\n"); - - if(e == audioepin) - audioepin = nil; - if(e == audioepout) - audioepout = nil; + if((ed = setupep(d, ac, e, &pcmdescdef, 0)) == nil){ + fprint(2, "setupep: %s: %r\n", epout == e ? "out" : "in"); + if(e == epin) + epin = nil; + if(e == epout) + epout = nil; continue; } closedev(ed); } - if(audioepout == nil) - sysfatal("no output stream found"); + if(epout == nil && epin == nil) + sysfatal("no streams found"); + switch(Proto(ac->csp)){ + case Paudio1: + findcontrols(d, ac); + break; + } + fs.tree = alloctree(user, "usb", DMDIR|0555, nil); snprint(buf, sizeof buf, "volumeU%s", audiodev->hname); - createfile(fs.tree->root, buf, user, 0666, nil); + volume = createfile(fs.tree->root, buf, user, 0666, nil); + snprint(buf, sizeof buf, "audioctlU%s", audiodev->hname); + ctl = createfile(fs.tree->root, buf, user, 0666, nil); snprint(buf, sizeof buf, "%d.audio", audiodev->id); postsharesrv(&fs, nil, "usb", buf);