ref: 0c77ac17f41b22db134a50a3d9dcdeaf149c9482
parent: 0a7d581eec1319c643337d302176b3f9ba565ea5
author: Sigrid Solveig Haflínudóttir <sigrid@ftrv.se>
date: Thu Apr 4 20:50:47 EDT 2024
nusb/audio: great new features - automatically choose configuration with the format closest to the s16c2r44100 - allow independent in/out formats to be configured (mono mic with lower rate will work) - provide "fmtout" and "fmtin" via /dev/volumeU*; those can be changed by writing to /dev/volumeU* - expose some of the useful controls (volume, mute etc), if available, via /dev/volumeU - add "in on/off" and "out on/off" via /dev/audioctlU* for input/output toggling, which helps with headsets that force low quality on both - expose supported intput/output formats via /dev/audiostatU* Tested with: - vid 0x1b3f did 0x2008 GeneralPlus 'USB Audio Device' (audio 1.0, in + out) - vid 0x14ed did 0x3004 Shure 'Shure AONIC 50 USB Hi-Res' (audio 2.0, out only) - vid 0x14ed did 0x3003 'Shure Inc' 'Shure AONIC 50 USB' (audio 1.0, headset, in + out) Known issues: - on MNT Reform, changing rate to 48kHz on GeneralPlus results in silence on the output if the device is connected to either of the closest (to the user) usb ports; it works well with the one closer to the back
--- a/sys/src/cmd/nusb/audio/audio.c
+++ b/sys/src/cmd/nusb/audio/audio.c
@@ -3,6 +3,8 @@
#include <fcall.h>
#include <thread.h>
#include <9p.h>
+#include <pcm.h>
+#pragma varargck type "!" Pcmdesc
#include "usb.h"
enum {
@@ -10,15 +12,53 @@
Paudio2 = 0x20,
Csamfreq = 0x01,
+ Cclockvalid = 0x02,
/* audio 1 */
Rsetcur = 0x01,
+ Rgetcur = 0x81,
+ Rgetmin,
+ Rgetmax,
+ Rgetres,
/* audio 2 */
Rcur = 0x01,
Rrange = 0x02,
+
+ /* feature unit control values */
+ Cmute = 1,
+ Cvolume,
+ Cbass,
+ Cmid,
+ Ctreble,
+ Cagc = 7,
+ Cbassboost = 9,
+ Cloudness,
+ Cnum,
+
+ Conlycur = 1<<Cmute | 1<<Cagc | 1<<Cbassboost | 1<<Cloudness,
+ Cszone = 1<<Cbass | 1<<Cmid | 1<<Ctreble,
+ Cmask1 = (1<<Cvolume | Conlycur | Cszone)>>1,
+ Cmask2 = (
+ 3<<Cmute*2 | 3<<Cvolume*2 | 3<<Cbass*2 |
+ 3<<Cmid*2 | 3<<Ctreble*2 | 3<<Cagc*2 |
+ 3<<Cbassboost*2 | 3<<Cloudness*2
+ )>>2,
+
+ Silence = 0x8000,
};
+char *ctrlname[Cnum] = {
+ [Cmute] = "mute",
+ [Cvolume] = "volume",
+ [Cbass] = "bass",
+ [Cmid] = "mid",
+ [Ctreble] = "treble",
+ [Cagc] = "agc",
+ [Cbassboost] = "bassboost",
+ [Cloudness] = "loudness",
+};
+
typedef struct Range Range;
struct Range
{
@@ -26,17 +66,29 @@
uint max;
};
+typedef struct Ctrl Ctrl;
+struct Ctrl
+{
+ uchar id;
+ uchar cs;
+ uchar cn;
+ short cur;
+ short min;
+ short max;
+ short res;
+};
+
typedef struct Aconf Aconf;
struct Aconf
{
+ Pcmdesc;
+
Ep *ep;
- int bits;
int bps; /* subslot size (bytes per sample) */
- int format;
- int channels;
int terminal;
Range *freq;
int nfreq;
+ Iface *zb;
/* audio 1 */
int controls;
@@ -45,17 +97,20 @@
int clock;
};
-int audiodelay = 1764; /* 40 ms */
-int audiofreq = 44100;
-int audiochan = 2;
-int audiores = 16;
+int audiodelay = 1764; /* 40 ms for 44.1kHz */
char user[] = "audio";
-Dev *audiodev = nil;
-Iface *audiocontrol = nil;
-Ep *audioepin = nil;
-Ep *audioepout = nil;
+Dev *adev;
+Iface *ac;
+Ep *epin;
+Ep *epout;
+int inoff, outoff;
+File *ctl;
+File *status;
+File *volume;
+Ctrl ctrl[32];
+int nctrl;
Iface*
findiface(Conf *conf, int class, int subclass, int id)
@@ -74,18 +129,18 @@
}
Desc*
-findiad(Usbdev *u, int id, int csp)
+findiad(int csp)
{
int i;
Desc *dd;
uchar *b;
- for(i = 0; i < nelem(u->ddesc); i++){
- dd = u->ddesc[i];
+ for(i = 0; i < nelem(adev->usb->ddesc); i++){
+ dd = adev->usb->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]))
+ if(b[0] == ac->id && b[0]+b[1] <= Niface && csp == CSP(b[2], b[3], b[4]))
return dd;
}
return nil;
@@ -92,14 +147,14 @@
}
Desc*
-findacheader(Usbdev *u, Iface *ac)
+findacheader(void)
{
Desc *dd;
uchar *b;
int i;
- for(i = 0; i < nelem(u->ddesc); i++){
- dd = u->ddesc[i];
+ for(i = 0; i < nelem(adev->usb->ddesc); i++){
+ dd = adev->usb->ddesc[i];
if(dd == nil || dd->iface != ac || dd->data.bDescriptorType != 0x24)
continue;
if(dd->data.bLength < 8 || dd->data.bbytes[0] != 1)
@@ -120,14 +175,14 @@
}
Desc*
-findterminal(Usbdev *u, Iface *ac, int id)
+findterminal(int id)
{
Desc *dd;
uchar *b;
int i;
- for(i = 0; i < nelem(u->ddesc); i++){
- dd = u->ddesc[i];
+ for(i = 0; i < nelem(adev->usb->ddesc); i++){
+ dd = adev->usb->ddesc[i];
if(dd == nil || dd->iface != ac)
continue;
if(dd->data.bDescriptorType != 0x24 || dd->data.bLength < 4)
@@ -148,14 +203,14 @@
}
Desc*
-findclocksource(Usbdev *u, Iface *ac, int id)
+findclocksource(int id)
{
Desc *dd;
uchar *b;
int i;
- for(i = 0; i < nelem(u->ddesc); i++){
- dd = u->ddesc[i];
+ for(i = 0; i < nelem(adev->usb->ddesc); i++){
+ dd = adev->usb->ddesc[i];
if(dd == nil || dd->iface != ac)
continue;
if(dd->data.bDescriptorType != 0x24 || dd->data.bLength != 8)
@@ -167,7 +222,20 @@
return nil;
}
-void
+Rune
+tofmt(int v)
+{
+ switch(v){
+ case 1: return L's';
+ case 2: return L'u';
+ case 3: return L'f';
+ case 4: return L'a';
+ case 5: return L'µ';
+ }
+ return 0;
+}
+
+int
parseasdesc1(Desc *dd, Aconf *c)
{
uchar *b;
@@ -177,20 +245,22 @@
switch(dd->data.bDescriptorType<<8 | b[0]){
case 0x2501: /* CS_ENDPOINT, EP_GENERAL */
if(dd->data.bLength != 7)
- return;
+ return -1;
c->controls = b[1];
break;
case 0x2401: /* CS_INTERFACE, AS_GENERAL */
if(dd->data.bLength != 7)
- return;
+ return -1;
c->terminal = b[1];
- c->format = GET2(&b[3]);
+ c->fmt = tofmt(GET2(&b[3]));
+ if(!c->fmt)
+ return -1;
break;
case 0x2402: /* CS_INTERFACE, FORMAT_TYPE */
if(dd->data.bLength < 8 || b[1] != 1)
- return;
+ return -1;
c->channels = b[2];
c->bps = b[3];
c->bits = b[4];
@@ -199,7 +269,7 @@
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 */
+ } else { /* discrete sampling frequencies */
c->nfreq = b[5];
c->freq = emallocz(c->nfreq * sizeof(*f), 1);
b += 6;
@@ -210,9 +280,10 @@
}
break;
}
+ return 0;
}
-void
+int
parseasdesc2(Desc *dd, Aconf *c)
{
uchar *b;
@@ -221,23 +292,26 @@
switch(dd->data.bDescriptorType<<8 | b[0]){
case 0x2401: /* CS_INTERFACE, AS_GENERAL */
if(dd->data.bLength != 16 || b[3] != 1)
- return;
+ return -1;
c->terminal = b[1];
- c->format = GET4(&b[4]);
c->channels = b[8];
+ c->fmt = tofmt(GET4(&b[4]));
+ if(!c->fmt)
+ return -1;
break;
case 0x2402: /* CS_INTERFACE, FORMAT_TYPE */
if(dd->data.bLength != 6 || b[1] != 1)
- return;
+ return -1;
c->bps = b[2];
c->bits = b[3];
break;
}
+ return 0;
}
int
-setclock(Dev *d, Iface *ac, Aconf *c, int speed)
+setclock(Aconf *c, int speed)
{
uchar b[4];
int index;
@@ -252,22 +326,30 @@
index = c->ep->id & Epmax;
if(c->ep->dir == Ein)
index |= 0x80;
- return usbcmd(d, Rh2d|Rclass|Rep, Rsetcur, Csamfreq<<8, index, b, 3);
+ if(usbcmd(adev, Rh2d|Rclass|Rep, Rsetcur, Csamfreq<<8, index, b, 3) < 0)
+ break;
+ if(usbcmd(adev, Rd2h|Rclass|Rep, Rgetcur, Csamfreq<<8, index, b, 3) != 3)
+ break;
+ return b[0] | b[1]<<8 | b[2]<<16;
case Paudio2:
PUT4(b, speed);
index = c->clock<<8 | ac->id;
- return usbcmd(d, Rh2d|Rclass|Riface, Rcur, Csamfreq<<8, index, b, 4);
+ if(usbcmd(adev, Rh2d|Rclass|Riface, Rcur, Csamfreq<<8, index, b, 4) < 0)
+ break;
+ if(usbcmd(adev, Rd2h|Rclass|Riface, Rcur, Csamfreq<<8, index, b, 4) != 4)
+ break;
+ return GET4(b);
}
- return 0;
+ return -1;
}
int
-getclockrange(Dev *d, Iface *ac, Aconf *c)
+getclockrange(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));
+ rc = usbcmd(adev, 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){
@@ -281,11 +363,204 @@
return 0;
}
+int
+setvalue(uchar id, uchar hi, uchar lo, short value, int sz)
+{
+ uchar b[2];
+
+ if(sz == 1)
+ b[0] = value;
+ else
+ PUT2(b, value);
+
+ if(Proto(ac->csp) == Paudio1)
+ return usbcmd(adev, Rh2d|Rclass|Riface, Rsetcur, hi<<8 | lo, id<<8 | ac->id, b, sz);
+
+ return usbcmd(adev, Rh2d|Rclass|Riface, Rcur, hi<<8 | lo, id<<8 | ac->id, b, sz);
+}
+
+int
+getvalue(int r, uchar id, uchar hi, uchar lo, short *value)
+{
+ uchar b[2 + 32*3*4];
+ int rc, i, n;
+
+ *value = 0;
+ i = 0;
+ if(Proto(ac->csp) == Paudio1)
+ rc = usbcmd(adev, Rd2h|Rclass|Riface, r, hi<<8 | lo, id<<8 | ac->id, b, sizeof(b));
+ else {
+ i = r - Rgetmin;
+ r = r == Rgetcur ? Rcur : Rrange;
+ rc = usbcmd(adev, Rd2h|Rclass|Riface, r, hi<<8 | lo, id<<8 | ac->id, b, sizeof(b));
+ }
+ if(rc < 0)
+ return -1;
+ if(rc < 1)
+ goto Invalid;
+ if(r == Rrange){
+ rc -= 2;
+ if(rc < 3 || (n = GET2(b)) < 1)
+ goto Invalid;
+ else if(rc == n*3*2)
+ *value = GET2(&b[2 + i*2]);
+ else if(rc == n*3*1)
+ *value = b[2 + i];
+ else
+ goto Invalid;
+ } else
+ *value = rc > 1 ? GET2(b) : b[0];
+ return 0;
+Invalid:
+ werrstr("invalid response");
+ return -1;
+}
+
+int
+getvalues(Ctrl *c)
+{
+ char *s;
+ int onlycur;
+
+ onlycur = Conlycur & (1<<c->cs);
+ if(((s = "cur") && getvalue(Rgetcur, c->id, c->cs, c->cn, &c->cur) < 0) ||
+ (!onlycur && (
+ ((s = "min") && getvalue(Rgetmin, c->id, c->cs, c->cn, &c->min) < 0) ||
+ ((s = "max") && getvalue(Rgetmax, c->id, c->cs, c->cn, &c->max) < 0) ||
+ ((s = "res") && getvalue(Rgetres, c->id, c->cs, c->cn, &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->id != b->id)
+ return a->id - b->id;
+ if(a->cs != b->cs)
+ return a->cs - b->cs;
+ return a->cn - b->cn;
+}
+
void
-parsestream(Dev *d, Iface *ac, int id)
+findcontrols1(void)
{
- Iface *as;
+ int i, k, n, x;
+ uchar cs, cn;
+ uchar *b;
Desc *dd;
+ Ctrl *c;
+
+ for(i = 0; i < nelem(adev->usb->ddesc); i++){
+ dd = adev->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, cn = 0; k <= dd->data.bLength-2-n-1; k += n, cn++){
+ x = (n > 1 ? GET2(b+k) : b[k]) & Cmask1;
+ if(x == 0)
+ continue;
+ for(cs = 1; cs < Cnum && nctrl < nelem(ctrl); cs++, x >>= 1){
+ if((x & 1) != 1)
+ continue;
+ c = &ctrl[nctrl++];
+ c->cs = cs;
+ c->cn = cn;
+ c->id = b[1];
+ getvalues(c);
+ }
+ }
+ break;
+ }
+ }
+}
+
+void
+findcontrols2(void)
+{
+ int i, k, n, x;
+ uchar cs, cn;
+ uchar *b;
+ Desc *dd;
+ Ctrl *c;
+
+ for(i = 0; i < nelem(adev->usb->ddesc); i++){
+ dd = adev->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 < 9 || nctrl >= nelem(ctrl))
+ continue;
+ for(k = 0; k < nctrl; k++)
+ if(ctrl[k].id == b[1])
+ break;
+ if(k < nctrl)
+ break;
+ n = 4;
+ for(k = 3, cn = 0; k <= dd->data.bLength-2-n-1; k += n, cn++){
+ x = GET4(b+k) & Cmask2;
+ if(x == 0)
+ continue;
+ for(cs = 1; cs < Cnum && nctrl < nelem(ctrl); cs++, x >>= 2){
+ if((x & 3) != 3)
+ continue;
+ c = &ctrl[nctrl++];
+ c->cs = cs;
+ c->cn = cn;
+ c->id = b[1];
+ getvalues(c);
+ }
+ }
+ break;
+ }
+ }
+}
+
+void
+parseterminal2(Desc *dd, Aconf *c)
+{
+ uchar *b;
+
+ 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(int id)
+{
+ Iface *as, *zb;
+ Desc *dd;
Ep *e;
Aconf *c;
uchar *b;
@@ -292,8 +567,11 @@
int i;
/* find AS interface */
- as = findiface(d->usb->conf[0], Claudio, 2, id);
+ as = findiface(adev->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,18 +591,21 @@
as->aux = nil;
continue;
}
+ c->zb = zb;
/* parse AS descriptors */
- for(i = 0; i < nelem(d->usb->ddesc); i++){
- dd = d->usb->ddesc[i];
+ for(i = 0; i < nelem(adev->usb->ddesc); i++){
+ dd = adev->usb->ddesc[i];
if(dd == nil || dd->iface != as)
continue;
switch(Proto(ac->csp)){
case Paudio1:
- parseasdesc1(dd, c);
+ if(parseasdesc1(dd, c) != 0)
+ goto Skip;
break;
case Paudio2:
- parseasdesc2(dd, c);
+ if(parseasdesc2(dd, c) != 0)
+ goto Skip;
break;
}
}
@@ -332,20 +613,12 @@
if(Proto(ac->csp) == Paudio1)
continue;
- dd = findterminal(d->usb, ac, c->terminal);
+ dd = findterminal(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(dd, c);
- dd = findclocksource(d->usb, ac, c->clock);
+ dd = findclocksource(c->clock);
if(dd == nil)
goto Skip;
b = dd->data.bbytes;
@@ -352,123 +625,367 @@
/* 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);
+ if(getclockrange(c) != 0){
+ fprint(2, "getclockrange %d: %r\n", c->clock);
goto Skip;
}
}
}
+int
+fmtcmp(Pcmdesc *a, Pcmdesc *b)
+{
+ if(a->rate != b->rate)
+ return a->rate - b->rate;
+ if(a->channels != b->channels)
+ return a->channels - b->channels;
+ if(a->bits != b->bits)
+ return a->bits - b->bits;
+ 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;
+}
+
Dev*
-setupep(Dev *d, Iface *ac, Ep *e, int *speed, int force)
+setupep(Ep *e, Pcmdesc *fmt, int exact)
{
- int dir = e->dir;
Aconf *c, *bestc;
+ Ep *beste, *ep;
+ int n, r, dir;
+ Pcmdesc p;
Range *f;
- Ep *beste;
- int closest, sp;
+ Dev *d;
+ if(e == epout && outoff){
+ werrstr("output disabled");
+ return nil;
+ }
+ if(e == epin && inoff){
+ werrstr("input disabled");
+ return nil;
+ }
+
+ 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->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;
+ p = *c;
+ if(f->min >= fmt->rate)
+ p.rate = f->min;
+ else if(f->max <= fmt->rate)
+ p.rate = f->max;
+ else
+ p.rate = fmt->rate;
+ if((n = fmtcmp(&p, fmt)) == 0 || bestc == nil){
+Better:
+ c->rate = p.rate;
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;
}
+ /* both better, but the new is closer */
+ if(n > 0 && (r > 0 && fmtcmp(&p, bestc) < 0))
+ goto Better;
+ /* both worse, but the new one is better so far */
+ if(n < 0 && (r < 0 && fmtcmp(&p, 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");
+ /* jump to alt 0 before trying to do anything */
+ if(c->zb != nil)
+ setalt(adev, c->zb);
+
+ if(setalt(adev, e->iface) < 0){
+ werrstr("setalt: %r\n");
return nil;
}
- if(setclock(d, ac, c, sp) < 0){
- werrstr("setclock: %r");
- return nil;
- }
- if((d = openep(d, e)) == nil){
+
+ /* ignore errors as updated clock isn't always required */
+ r = setclock(c, c->rate);
+
+ if((d = openep(adev, e)) == nil){
werrstr("openep: %r");
return nil;
}
- devctl(d, "samplesz %d", audiochan*audiores/8);
+
+ /* update the rate only if the value makes sense */
+ if(r >= c->rate*9/10 && r <= c->rate*10/9)
+ c->rate = r;
+
+ ep->aux = c;
+ devctl(d, "samplesz %d", c->channels*c->bits/8);
devctl(d, "sampledelay %d", audiodelay);
- devctl(d, "hz %d", sp);
- devctl(d, "name audio%sU%s", e->dir==Ein ? "in" : "", audiodev->hname);
- *speed = sp;
+ devctl(d, "hz %d", c->rate);
+ devctl(d, "name audio%sU%s", e->dir==Ein ? "in" : "", adev->hname);
return d;
}
+char *
+ctrlvalue(Ctrl *c)
+{
+ static char v[64];
+ int x, n;
+
+ if((Conlycur & (1<<c->cs)) == 0){
+ if((ushort)c->cur == Silence)
+ 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;
+}
+
+char *
+seprintaconf(char *s, char *e, Ep *ep)
+{
+ Pcmdesc d;
+ Range *f;
+ Aconf *c;
+ int dir;
+
+ dir = ep->dir;
+ for(; ep != nil; ep = ep->next){
+ c = ep->iface->aux;
+ if(c == nil || ep != c->ep || ep->dir != dir || c->bits != 8*c->bps)
+ continue;
+ for(f = c->freq; f != c->freq+c->nfreq; f++){
+ d = c->Pcmdesc;
+ d.rate = f->min;
+ s = seprint(s, e, " %!", d);
+ if(f->min < f->max){
+ d.rate = f->max;
+ s = seprint(s, e, "-%!", d);
+ }
+ }
+ }
+ return seprint(s, e, "\n");
+}
+
void
fsread(Req *r)
{
- char *msg;
+ static char msg[2048];
+ Ctrl *cur, *prev;
+ char *s, *e;
+ Aconf *c;
+ 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(r->fid->file == status){
+ s = seprint(s, e, "bufsize %6d buffered %6d\n", 0, 0);
+ if(epout != nil)
+ s = seprintaconf(seprint(s, e, "fmtout"), e, epout);
+ if(epin != nil)
+ seprintaconf(seprint(s, e, "fmtin"), e, epin);
+ } else if(r->fid->file == volume){
+ 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)
+ s = seprint(s, e, "%s%s%d", i?"\n":"", ctrlname[cur->cs], cur->id);
+ s = seprint(s, e, " %s", ctrlvalue(cur));
+ }
+ if(i > 0)
+ seprint(s, e, "\n");
+ } else {
+ respond(r, "protocol botch");
+ return;
+ }
+
readstr(r, msg);
respond(r, nil);
- free(msg);
}
+int
+setctrl(char *f[8], int nf)
+{
+ int i, j, id, n, x, sz;
+ 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<<c->cs)) == 0){
+ sz = (Cszone & (1<<c->cs)) ? 1 : 2;
+ if(x <= 0 && sz == 2)
+ x = Silence;
+ 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;
+ }
+ } else {
+ x = !!x;
+ sz = 1;
+ }
+Groupset:
+ if(setvalue(c->id, c->cs, c->cn, x, sz) < 0)
+ goto Error;
+ if(getvalue(Rgetcur, c->id, c->cs, c->cn, &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(adev, c->zb) < 0)
+ goto Error;
+ if(e == epout)
+ outoff = off;
+ else
+ inoff = off;
+ } else if(r->fid->file != volume){
+ werrstr("protocol botch");
+ goto Error;
+ } 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(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(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 = {
@@ -487,10 +1004,9 @@
main(int argc, char *argv[])
{
char buf[32];
- Dev *d, *ed;
+ Dev *ed;
Desc *dd;
Conf *conf;
- Iface *ac;
Aconf *c;
Ep *e;
uchar *b;
@@ -508,37 +1024,36 @@
if(argc == 0)
usage();
- if((d = getdev(*argv)) == nil)
+ fmtinstall('!', pcmdescfmt);
+ if((adev = getdev(*argv)) == nil)
sysfatal("getdev: %r");
- audiodev = d;
- conf = d->usb->conf[0];
+ conf = adev->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);
+ dd = findacheader();
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]);
+ parsestream(b[i]);
break;
case Paudio2:
- dd = findiad(d->usb, ac->id, CSP(Claudio, 0, Paudio2));
+ dd = findiad(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);
+ parsestream(i);
break;
}
- for(i = 0; i < nelem(d->usb->ep); i++){
- for(e = d->usb->ep[i]; e != nil; e = e->next){
+ for(i = 0; i < nelem(adev->usb->ep); i++){
+ for(e = adev->usb->ep[i]; e != nil; e = e->next){
c = e->iface->aux;
if(c != nil && c->ep == e)
break;
@@ -547,36 +1062,49 @@
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(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:
+ findcontrols1();
+ break;
+ case Paudio2:
+ findcontrols2();
+ break;
+ }
+ qsort(ctrl, nctrl, sizeof(Ctrl), cmpctrl);
+
fs.tree = alloctree(user, "usb", DMDIR|0555, nil);
- snprint(buf, sizeof buf, "volumeU%s", audiodev->hname);
- createfile(fs.tree->root, buf, user, 0666, nil);
+ snprint(buf, sizeof buf, "audioctlU%s", adev->hname);
+ ctl = createfile(fs.tree->root, buf, user, 0666, nil);
+ snprint(buf, sizeof buf, "audiostatU%s", adev->hname);
+ status = createfile(fs.tree->root, buf, user, 0444, nil);
+ snprint(buf, sizeof buf, "volumeU%s", adev->hname);
+ volume = createfile(fs.tree->root, buf, user, 0666, nil);
- snprint(buf, sizeof buf, "%d.audio", audiodev->id);
+ snprint(buf, sizeof buf, "%d.audio", adev->id);
postsharesrv(&fs, nil, "usb", buf);
- exits(0);
+ exits(nil);
}