forsyth20121205-174063a80cc13 years agoHistory
441 lines · 7.4 KB · C
RawDownload
#include "dat.h"#include "fns.h"#include "error.h"#include "audio.h"#include <sys/ioctl.h>#include <sys/soundcard.h> #define 	Audio_Mic_Val		SOUND_MIXER_MIC#define 	Audio_Linein_Val	SOUND_MIXER_LINE #define		Audio_Speaker_Val	SOUND_MIXER_PCM // SOUND_MIXER_VOLUME#define		Audio_Headphone_Val	SOUND_MIXER_ALTPCM#define		Audio_Lineout_Val	SOUND_MIXER_CD #define 	Audio_Pcm_Val		AFMT_S16_LE#define 	Audio_Ulaw_Val		AFMT_MU_LAW#define 	Audio_Alaw_Val		AFMT_A_LAW #include "audio-tbls.c"#define	min(a,b)	((a) < (b) ? (a) : (b)) #define DEVAUDIO	"/dev/dsp"#define DEVMIXER	"/dev/mixer" #define DPRINT if(1)print enum {	A_Pause,	A_UnPause,	A_In,	A_Out,}; static struct {	int data;	/* dsp data fd */	int ctl;	/* mixer fd */	int pause;	QLock lk;} afd = {.data = -1, .ctl = -1, .pause =A_UnPause }; static Audio_t av;static QLock inlock;static QLock outlock; static int audio_open(int);static int audio_pause(int, int);static int audio_set_info(int, Audio_d*, int); Audio_t*getaudiodev(void){	return &av;} void audio_file_init(void){	audio_info_init(&av);} voidaudio_file_open(Chan *c, int omode){	USED(c);	DPRINT("audio_file_open %d %d %x\n", afd.data, afd.ctl, omode);	qlock(&afd.lk);	if(waserror()){		qunlock(&afd.lk);		nexterror();	}	if(afd.data >= 0)		error(Einuse);	if(afd.ctl < 0){		afd.ctl = open(DEVMIXER, ORDWR);		if(afd.ctl < 0)			oserror();	}	afd.data = audio_open(omode);	if(afd.data < 0)		oserror();	poperror();	qunlock(&afd.lk);} void    audio_file_close(Chan *c){	USED(c);	DPRINT("audio_file_close %d %d\n", afd.data, afd.ctl);	qlock(&afd.lk);	if(waserror()){		qunlock(&afd.lk);		nexterror();	}	close(afd.data);	afd.data = -1;	qunlock(&afd.lk);	poperror();} longaudio_file_read(Chan *c, void *va, long count, vlong offset){	long ba, status, chunk, total; 	USED(c);	USED(offset);	DPRINT("audio_file_read %d %d\n", afd.data, afd.ctl);	qlock(&inlock);	if(waserror()){		qunlock(&inlock);		nexterror();	} 	if(afd.data < 0)		error(Eperm); 	/* check block alignment */	ba = av.in.bits * av.in.chan / Bits_Per_Byte; 	if(count % ba)		error(Ebadarg);			if(!audio_pause(afd.data, A_UnPause))		error(Eio);		total = 0;	while(total < count){		chunk = count - total;		status = read (afd.data, va + total, chunk);		if(status < 0)			error(Eio);		total += status;	}		if(total != count)		error(Eio); 	poperror();	qunlock(&inlock);		return count;} long                                            audio_file_write(Chan *c, void *va, long count, vlong offset){	long status = -1;	long ba, total, chunk, bufsz;		USED(c);	USED(offset);	DPRINT("audio_file_write %d %d\n", afd.data, afd.ctl);	qlock(&outlock);	if(waserror()){		qunlock(&outlock);		nexterror();	}		if(afd.data < 0)		error(Eperm);		/* check block alignment */	ba = av.out.bits * av.out.chan / Bits_Per_Byte; 	if(count % ba)		error(Ebadarg); 	total = 0;	bufsz = av.out.buf * Audio_Max_Buf / Audio_Max_Val; 	if(bufsz == 0)		error(Ebadarg); 	while(total < count){		chunk = min(bufsz, count - total);		status = write(afd.data, va, chunk);		if(status <= 0)			error(Eio);		total += status;	} 	poperror();	qunlock(&outlock); 	return count;	} longaudio_ctl_write(Chan *c, void *va, long count, vlong offset){	Audio_t tmpav = av;	int tfd; 	USED(c);	USED(offset);	tmpav.in.flags = 0;	tmpav.out.flags = 0;		DPRINT ("audio_ctl_write %X %X\n", afd.data, afd.ctl);	if(!audioparse(va, count, &tmpav))		error(Ebadarg); 	if(!canqlock(&inlock))		error("device busy");	if(waserror()){		qunlock(&inlock);		nexterror();	}	if(!canqlock(&outlock))		error("device busy");	if(waserror()){		qunlock(&outlock);		nexterror();	} 	/* DEVAUDIO needs to be open to issue an ioctl */	tfd = afd.data;	if(tfd < 0){		tfd = open(DEVAUDIO, O_RDONLY|O_NONBLOCK);		if(tfd < 0)			oserror();	}	if(waserror()){		if(tfd != afd.data)			close(tfd);		nexterror();	} 	if(tmpav.in.flags & AUDIO_MOD_FLAG){		if(!audio_pause(tfd, A_Pause))			error(Ebadarg);		if(!audio_set_info(tfd, &tmpav.in, A_In))			error(Ebadarg);	} 	poperror();	if(tfd != afd.data)		close(tfd); 	tmpav.in.flags = 0;	av = tmpav; 	poperror();	qunlock(&outlock);	poperror();	qunlock(&inlock);	return count;} /* Linux/OSS specific stuff */ static intchoosefmt(Audio_d *i){	switch(i->bits){	case 8:		switch(i->enc){		case Audio_Alaw_Val:			return AFMT_A_LAW;		case Audio_Ulaw_Val:			return AFMT_MU_LAW;		case Audio_Pcm_Val:			return AFMT_U8;		}		break;	case 16:		if(i->enc == Audio_Pcm_Val)			return AFMT_S16_LE;		break;	}	return -1;} static intsetvolume(int fd, int what, int left, int right){	int can, v;		if(ioctl(fd, SOUND_MIXER_READ_DEVMASK, &can) < 0)		can = ~0; 	DPRINT("setvolume fd%d %X can mix 0x%X (mask %X)\n", fd, what, (can & (1<<what)), can);	if(!(can & (1<<what)))		return 0;	v = left | (right<<8);	if(ioctl(afd.ctl, MIXER_WRITE(what), &v) < 0)		return 0;	return 1;} static intaudio_set_info(int fd, Audio_d *i, int d){	int status, arg;	int oldfmt, newfmt; 	USED(d);	DPRINT("audio_set_info (%d) %d %d\n", fd, afd.data, afd.ctl);	if(fd < 0)		return 0; 	/* sample rate */	if(i->flags & AUDIO_RATE_FLAG){		arg = i->rate;		if(ioctl(fd, SNDCTL_DSP_SPEED, &arg) < 0)			return 0;	}		/* channels */	if(i->flags & AUDIO_CHAN_FLAG){		arg = i->chan;		if(ioctl(fd, SNDCTL_DSP_CHANNELS, &arg) < 0)			return 0;	} 	/* precision */	if(i->flags & AUDIO_BITS_FLAG){		arg = i->bits;		if(ioctl(fd, SNDCTL_DSP_SAMPLESIZE, &arg) < 0)			return 0;	}		/* encoding */	if(i->flags & AUDIO_ENC_FLAG){		ioctl(fd, SNDCTL_DSP_GETFMTS, &oldfmt); 		newfmt = choosefmt(i);		if(newfmt < 0)			return 0;		if(newfmt != oldfmt){			status = ioctl(fd, SNDCTL_DSP_SETFMT, &arg);			DPRINT ("enc oldfmt newfmt %x status %d\n", oldfmt, newfmt, status);		}	} 	/* dev volume */ 	if(i->flags & (AUDIO_LEFT_FLAG|AUDIO_VOL_FLAG))		return setvolume(afd.ctl, i->dev, i->left, i->right); 	return 1;} static intaudio_set_blocking(int fd){	int val; 	if((val = fcntl(fd, F_GETFL, 0)) == -1)		return 0;		val &= ~O_NONBLOCK; 	if(fcntl(fd, F_SETFL, val) < 0)		return 0; 	return 1;} static intaudio_open(int omode){	int fd;		/* open non-blocking in case someone already has it open */	/* otherwise we would block until they close! */	switch (omode){	case OREAD:		fd = open(DEVAUDIO, O_RDONLY|O_NONBLOCK);		break;	case OWRITE:		fd = open(DEVAUDIO, O_WRONLY|O_NONBLOCK);		break;	case ORDWR:		fd = open(DEVAUDIO, O_RDWR|O_NONBLOCK);		break;	} 	DPRINT("audio_open %d\n", fd);	if(fd < 0)		oserror(); 	/* change device to be blocking */	if(!audio_set_blocking(fd)){		close(fd);		error("cannot set blocking mode");	} 	if(!audio_pause(fd, A_Pause)){		close(fd);		error(Eio);	} 	/* set audio info */	av.in.flags = ~0;	av.out.flags = ~0; 	if(!audio_set_info(fd, &av.in, A_In)){		close(fd);		error(Ebadarg);	} 	av.in.flags = 0; 	/* tada, we're open, blocking, paused and flushed */	return fd;} static intdspsync(int fd){	return ioctl(fd, SNDCTL_DSP_RESET, NULL) >= 0 &&		ioctl(fd, SNDCTL_DSP_SYNC, NULL) >= 0;} static intaudio_pause(int fd, int f){	int status; //	DPRINT ("audio_pause (%d) %d %d\n", fd, afd.data, afd.ctl);	if(fd < 0)		return 0;	if(fd != afd.data)		return dspsync(fd);	qlock(&afd.lk);	if(afd.pause == f){		qunlock(&afd.lk);		return 1;	}	if(waserror()){		qunlock(&afd.lk);		nexterror();	}	status = dspsync(afd.data);	if(status)		afd.pause = f;	poperror();	qunlock(&afd.lk);	return status;}