479 lines · 8.1 KB · C
RawDownload
#include	"u.h"#include	"../port/lib.h"#include	"mem.h"#include	"dat.h"#include	"fns.h"#include	"../port/error.h"#include	"../port/audioif.h" typedef struct Audioprobe Audioprobe;typedef struct Audiochan Audiochan; struct Audioprobe{	char *name;	int (*probe)(Audio*);}; struct Audiochan{	QLock; 	Chan	*owner;	Audio	*adev; 	char	*data;	char	buf[1024+1];}; enum {	Qdir	= 0,	Qaudio,	Qaudioctl,	Qaudiostat,	Qvolume,}; static Dirtab audiodir[] = {	".",	{Qdir, 0, QTDIR},	0,	DMDIR|0555,	"audio",	{Qaudio},	0,	0666,	"audioctl",	{Qaudioctl},	0,	0222,	"audiostat",	{Qaudiostat},	0,	0444,	"volume",	{Qvolume},	0,	0666,};  static	int		naudioprobes;static	Audioprobe	audioprobes[16];static	Audio		*audiodevs; static	char		Evolume[] = "illegal volume specifier";static	char		Ebusy[] = "device is busy"; voidaddaudiocard(char *name, int (*probefn)(Audio *)){	Audioprobe *probe; 	if(naudioprobes >= nelem(audioprobes))		return; 	probe = &audioprobes[naudioprobes++];	probe->name = name;	probe->probe = probefn;} static voidaudioreset(void){	int i, ctlrno;	Audio **pp;	Audioprobe *probe; 	ctlrno = 0;	pp = &audiodevs;	*pp = malloc(sizeof(Audio)); 	for(i=0; i<naudioprobes; i++){		probe = &audioprobes[i]; 		for(;;){			if(*pp == nil){				print("audio: no memory\n");				break;			}			memset(*pp, 0, sizeof(Audio));			(*pp)->ctlrno = ctlrno;			(*pp)->name = probe->name;			if(probe->probe(*pp))				break; 			ctlrno++;			pp = &(*pp)->next;			*pp = malloc(sizeof(Audio));		}	} 	free(*pp);	*pp = nil;} static Audiochan*audioclone(Chan *c, Audio *adev){	Audiochan *ac; 	ac = malloc(sizeof(Audiochan));	if(ac == nil){		cclose(c);		return nil;	} 	c->aux = ac;	ac->owner = c;	ac->adev = adev;	ac->data = nil; 	return ac;} static Chan*audioattach(char *spec){	Audiochan *ac;	Audio *adev;	Chan *c;	int i;	static int attached; 	if(spec != nil && *spec != '\0')		i = strtol(spec, 0, 10);	else		i = 0;	for(adev = audiodevs; adev; adev = adev->next)		if(adev->ctlrno == i)			break;	if(adev == nil)		error(Enodev); 	c = devattach('A', spec);	c->qid.path = Qdir; 	if((ac = audioclone(c, adev)) == nil)		error(Enomem); 	i = 1<<adev->ctlrno;	if((attached & i) == 0){		static char *settings[] = {			"speed 44100",			"delay 882",	/* 20 ms */			"master 100",			"audio 100",			"head 100",		}; 		attached |= i;		for(i=0; i<nelem(settings) && adev->volwrite; i++){			strcpy(ac->buf, settings[i]);			if(!waserror()){				adev->volwrite(adev, ac->buf, strlen(ac->buf), 0);				poperror();			}		}	} 	return c;} static Chan*audioopen(Chan *c, int omode){	Audiochan *ac;	Audio *adev; 	ac = c->aux;	adev = ac->adev;	if((c->qid.path == Qaudio) && (incref(&adev->audioopen) != 1)){		decref(&adev->audioopen);		error(Ebusy);	}	return devopen(c, omode, audiodir, nelem(audiodir), devgen);} static longaudioread(Chan *c, void *a, long n, vlong off){	Audiochan *ac;	Audio *adev;	long (*fn)(Audio *, void *, long, vlong); 	ac = c->aux;	adev = ac->adev; 	fn = nil;	switch((ulong)c->qid.path){	case Qdir:		audiodir[Qaudio].length = adev->buffered ? adev->buffered(adev) : 0;		return devdirread(c, a, n, audiodir, nelem(audiodir), devgen);	case Qaudio:		fn = adev->read;		break;	case Qaudiostat:		fn = adev->status;		break;	case Qvolume:		fn = adev->volread;		break;	}	if(fn == nil)		error(Egreg); 	qlock(ac);	if(waserror()){		qunlock(ac);		nexterror();	}	switch((ulong)c->qid.path){	case Qaudiostat:	case Qvolume:		/* generate the text on first read */		if(ac->data == nil || off == 0){			long l; 			ac->data = nil;			l = fn(adev, ac->buf, sizeof(ac->buf)-1, 0);			if(l < 0)				l = 0;			ac->buf[l] = 0;			ac->data = ac->buf;		}		/* then serve all requests from buffer */		n = readstr(off, a, n, ac->data);		break; 	default:		n = fn(adev, a, n, off);	}	qunlock(ac);	poperror();	return n;} static longaudiowrite(Chan *c, void *a, long n, vlong off){	Audiochan *ac;	Audio *adev;	long (*fn)(Audio *, void *, long, vlong); 	ac = c->aux;	adev = ac->adev; 	fn = nil;	switch((ulong)c->qid.path){	case Qaudio:		fn = adev->write;		break;	case Qaudioctl:		fn = adev->ctl;		break;	case Qvolume:		fn = adev->volwrite;		break;	}	if(fn == nil)		error(Egreg); 	qlock(ac);	if(waserror()){		qunlock(ac);		nexterror();	}	switch((ulong)c->qid.path){	case Qaudioctl:	case Qvolume:		if(n >= sizeof(ac->buf))			error(Etoobig); 		/* copy data to audiochan buffer so it can be modified */		ac->data = nil;		memmove(ac->buf, a, n);		ac->buf[n] = 0;		a = ac->buf;		off = 0;	}	n = fn(adev, a, n, off);	qunlock(ac);	poperror();	return n;} static voidaudioclose(Chan *c){	Audiochan *ac;	Audio *adev; 	ac = c->aux;	adev = ac->adev;	if((c->qid.path == Qaudio) && (c->flag & COPEN)){		if(adev->close){			if(!waserror()){				adev->close(adev);				poperror();			}		}		decref(&adev->audioopen);	}	if(ac->owner == c){		ac->owner = nil;		c->aux = nil;		free(ac);	}} static Walkqid*audiowalk(Chan *c, Chan *nc, char **name, int nname){	Audiochan *ac;	Audio *adev;	Walkqid *wq; 	ac = c->aux;	adev = ac->adev;	wq = devwalk(c, nc, name, nname, audiodir, nelem(audiodir), devgen);	if(wq && wq->clone){		if(audioclone(wq->clone, adev) == nil){			free(wq);			wq = nil;		}	}	return wq;} static intaudiostat(Chan *c, uchar *dp, int n){	Audiochan *ac;	Audio *adev; 	ac = c->aux;	adev = ac->adev;	if((ulong)c->qid.path == Qaudio)		audiodir[Qaudio].length = adev->buffered ? adev->buffered(adev) : 0;	return devstat(c, dp, n, audiodir, nelem(audiodir), devgen);} /* * audioread() made sure the buffer is big enougth so a full volume * table can be serialized in one pass. */longgenaudiovolread(Audio *adev, void *a, long n, vlong,	Volume *vol, int (*volget)(Audio *, int, int *), ulong caps){	int i, j, v[2];	char *p, *e; 	p = a;	e = p + n;	for(i = 0; vol[i].name != 0; ++i){		if(vol[i].cap && (vol[i].cap & caps) == 0)			continue;		v[0] = 0;		v[1] = 0;		if((*volget)(adev, i, v) != 0)			continue;		if(vol[i].type == Absolute)			p += snprint(p, e - p, "%s %d\n", vol[i].name, v[0]);		else {			if(vol[i].range == 0)				continue;			for(j=0; j<2; j++){				if(v[j] < 0)					v[j] = 0;				if(v[j] > vol[i].range)					v[j] = vol[i].range;				v[j] = (v[j]*100)/vol[i].range;			}			switch(vol[i].type){			case Left:				p += snprint(p, e - p, "%s %d\n", vol[i].name, v[0]);				break;			case Right:				p += snprint(p, e - p, "%s %d\n", vol[i].name, v[1]);				break;			case Stereo:				p += snprint(p, e - p, "%s %d %d\n", vol[i].name, v[0], v[1]);				break;			}		}	} 	return p - (char*)a;} /* * genaudiovolwrite modifies the buffer that gets passed to it. this * is ok as long as it is called from inside Audio.volwrite() because * audiowrite() copies the data to Audiochan.buf[] and inserts a * terminating \0 byte before calling Audio.volwrite(). */longgenaudiovolwrite(Audio *adev, void *a, long n, vlong,	Volume *vol, int (*volset)(Audio *, int, int *), ulong caps){	int ntok, i, j, v[2];	char *p, *e, *x, *tok[4]; 	p = a;	e = p + n; 	for(;p < e; p = x){		if(x = strchr(p, '\n'))			*x++ = 0;		else			x = e;		ntok = tokenize(p, tok, 4);		if(ntok <= 0)			continue;		if(ntok == 1){			tok[1] = tok[0];			tok[0] = "master";			ntok = 2;		}		for(i = 0; vol[i].name != 0; i++){			if(vol[i].cap && (vol[i].cap & caps) == 0)				continue;			if(cistrcmp(vol[i].name, tok[0]))				continue;				if((ntok>2) && (!cistrcmp(tok[1], "out") || !cistrcmp(tok[1], "in")))				memmove(tok+1, tok+2, --ntok); 			v[0] = 0;			v[1] = 0;			if(ntok > 1)				v[0] = v[1] = atoi(tok[1]);			if(ntok > 2)				v[1] = atoi(tok[2]);			if(vol[i].type == Absolute)				(*volset)(adev, i, v);			else {				for(j=0; j<2; j++){					v[j] = (50+(v[j]*vol[i].range))/100;					if(v[j] < 0)						v[j] = 0;					if(v[j] > vol[i].range)						v[j] = vol[i].range;				}				(*volset)(adev, i, v);			}			break;		}		if(vol[i].name == nil)			error(Evolume);	} 	return n;} Dev audiodevtab = {	'A',	"audio",	audioreset,	devinit,	devshutdown,	audioattach,	audiowalk,	audiostat,	audioopen,	devcreate,	audioclose,	audioread,	devbread,	audiowrite,	devbwrite,	devremove,	devwstat,};