shithub: npe

ref: 9c86d94662c9232add279d1240d825de7557e02a
dir: /libnpe_sdl2/audiostm.c/

View raw version
#include "_sdl.h"
#include <pcm.h>

/* FIXME: merge with other api */
int audiostreaming;

SDL_AudioDeviceID SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK = -1;	/* 0 is null/invalid */
SDL_AudioDeviceID SDL_AUDIO_DEVICE_DEFAULT_RECORDING = -1;

enum{
	Delayms = 40,
	Nframes = 16,
};

struct SDL_AudioStream {
	QLock;
	Rendez;
	int closing;
	int fd;
	int type;
	char *name;
	SDL_AudioStreamCallback fn;
	void *aux;
	SDL_AudioSpec;
	int delay;
	int framesz;
	int nframes;
	uchar *buf;
	vlong bufsz;
	uchar *outbuf;
	vlong outsz;
	Along avail;
	int tail;
	int paused;	/* ugh */
	Pcmconv *conv;
};
static SDL_AudioStream *devs;
static Channel *ach;
static int ndevs;

static struct {
	char *spec;
	int ssz; /* samples size */
}fmts[AUDIO_NUM_FORMATS] = {
	[AUDIO_U8] = {"u8", 1},
	[AUDIO_S8] = {"s8", 1},
	[AUDIO_U16LSB] = {"u16", 2},
	[AUDIO_S16LSB] = {"s16", 2},
	[AUDIO_U16MSB] = {"U16", 2},
	[AUDIO_S16MSB] = {"S16", 2},
	[AUDIO_S32LSB] = {"s32", 4},
	[AUDIO_S32MSB] = {"S32", 4},
	[AUDIO_F32LSB] = {"f32", 4},
	[AUDIO_F32MSB] = {"F32", -1}, /* FIXME big endian f32 not supported by pcmconv */
};

int
SDL_AudioStreamAvailable(SDL_AudioStream *as)
{
	return agetl(&as->avail);
}

void
SDL_AudioStreamClear(SDL_AudioStream *as)
{
	long n;

	n = 0;
	aswapl(&as->avail, n);
}

int
SDL_AudioStreamGet(SDL_AudioStream *as, void *buf, int n)
{
	int t;

	if(as->fd < 0 || as->closing)
		return 0;
	if(n <= 0)
		return 0;
	if(n > as->framesz)
		n = as->framesz;
	if(agetl(&as->avail) < n)
		return 0;
	qlock(as);
	aincl(&as->avail, -n);
	t = as->tail;
	if(t + n > as->bufsz)
		n = as->bufsz - t;
	memcpy(buf, as->buf + t, n);
	t += n;
	if(t >= as->bufsz)
		t -= as->bufsz;
	as->tail = t;
	qunlock(as);
	return n;
}

/* FIXME: any reason to have .avail as along now? recheck once rest done */
/* FIXME: make sure streams are unidirectional; dup fd's if we do rw */
int
SDL_AudioStreamPut(SDL_AudioStream *as, void *buf, int n)
{
	int m, k, t;
	uchar *p, *s, *e;

	if(as->fd < 0 || as->closing)
		return -1;
	e = as->buf + as->bufsz;
	for(s=buf; n>0; n-=m, s+=m){
		m = n < as->framesz ? n : as->framesz;
		t = as->tail;
		p = as->buf + t;
		if(p + m > e)
			m = e - p;
		while((k = as->bufsz - agetl(&as->avail)) == 0){
			qlock(as);
			rwakeupall(as);
			rsleep(as);
			qunlock(as);
		}
		if(k < m)
			m = k;
		memcpy(p, s, m);
		t += m;
		if(t == as->bufsz)
			t = 0;
		as->tail = t;
		aincl(&as->avail, m);
		if(canqlock(as)){
			rwakeup(as);
			qunlock(as);
		}
	}
	return 0;
}

/* FIXME: conv + rendez */
static void
arproc(void *arg)
{
	int n, m, t;
	uchar *p, *e;
	SDL_AudioStream *as;

	threadsetname("arproc");
	as = arg;
	p = as->buf;
	e = as->buf + as->bufsz;
	for(;;){
		if(as->paused){
			qlock(as);
			rsleep(as);
			qunlock(as);
		}
		if(as->closing)
			break;
		n = e - p < as->framesz ? e - p : as->framesz;
		if((n = read(as->fd, p, n)) < 0)
			break;
		if(n == 0)
			continue;
		p += n;
		if(p >= e)
			p = as->buf;
		qlock(as);
		m = n + agetl(&as->avail);
		if(m > as->bufsz){
			t = as->tail + n;
			if(t >= as->bufsz)
				t -= as->bufsz;
			as->tail = t;
			m = as->bufsz;
		}
		aswapl(&as->avail, m);
		qunlock(as);
		/* FIXME: once queue fills up, this is bullshit, so this won't work */
		if(as->fn != nil)
			nbsend(ach, &n);
	}
	qlock(as);
	close(as->fd);
	as->fd = -1;
	rwakeupall(as);
	qunlock(as);
}

static void
awproc(void *arg)
{
	int n, m, k;
	uchar *s, *p, *e;
	SDL_AudioStream *as;

	threadsetname("awproc");
	as = arg;
	m = as->framesz;
	p = as->buf;
	e = as->buf + as->bufsz;
	for(; !as->closing;){
		qlock(as);
		rwakeupall(as);
		rsleep(as);
		qunlock(as);
		n = agetl(&as->avail);
		if(as->closing)
			m = n;
		while(n >= m){
			if(as->conv != nil){
				if((k = pcmconv(as->conv, p, as->outbuf, m)) <= 0)
					continue;
				s = as->outbuf;
			}else{
				k = m;
				s = p;
			}
			if(write(as->fd, s, k) != k){
				if(!as->closing)
					fprint(2, "awproc: %r\n");
				goto end;
			}
			n = aincl(&as->avail, -m);
			p += m;
			if(p >= e)
				p = as->buf;
			qlock(as);
			rwakeup(as);
			qunlock(as);
		}
	}
end:
	close(as->fd);
	as->fd = -1;
	qlock(as);
	rwakeupall(as);
	qunlock(as);
}

void
SDL_FreeAudioStream(SDL_AudioStream *as)
{
	if(as->fd < 0)
		return;
	as->closing = 1;
	as->paused = 0;
	qlock(as);
	rwakeupall(as);
	qunlock(as);
}

/* FIXME: bullshit */
static void
acbproc(void *arg)
{
	int n;
	SDL_AudioStream *as, *ae;
	Channel *c;

	threadsetname("acbproc");
	c = arg;
	for(;;){
		if(recv(c, &n) < 0)
			break;
		for(as=devs, ae=as+ndevs; as<ae; as++)
			if(as->fd >= 0 && as->fn != nil)
				as->fn(as->aux, as, n, agetl(&as->avail));
	}
	chanfree(c);
}

SDL_AudioStream* SDL_NewAudioStream(SDL_AudioFormat s, Uint8 sch, int srate, SDL_AudioFormat d, Uint8 dch, int drate)
{
	int n;
	SDL_AudioDeviceID id;
	SDL_AudioStream *as;
	char *af;
	Pcmdesc i, o;

	/* FIXME */
	id = SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK;
	audiostreaming = 1;
	if(--id < 0 || id >= ndevs){
		werrstr("invalid device id %d", id);
		return nil;
	}
	as = devs + id;
	/* FIXME: duplicate entries for rw devices? */
	//if(as->fd >= 0 || !canqlock(as)){
	if(as->fd >= 0){
		werrstr("in use");
		return nil;
	}
	if((as->fd = open(as->name, as->type)) < 0)
		return nil;
	as->closing = as->paused = 0;
	as->outbuf = nil;
	free(as->buf);
	free(as->outbuf);
	as->fn = nil;
	as->aux = nil;
	as->tail = 0;
	as->avail.v = 0;
	as->conv = nil;
	/* FIXME: writers only, dest ignored; we force a destination output */
	if(sch != as->channels || srate != as->freq || s != as->format){
		if(s >= nelem(fmts))
			goto Err;
		if((af = smprint("%sr%dc%d", fmts[s].spec, srate, sch)) == nil)
			goto Err;
		n = mkpcmdesc(af, &i);
		free(af);
		if(n < 0)
			goto Err;
		USED(d, dch, drate);	/* ignored */
		if((af = smprint("%sr%dc%d", fmts[as->format].spec, as->freq, as->channels)) == nil)
			goto Err;
		n = mkpcmdesc(af, &o);
		free(af);
		if(n < 0)
			goto Err;
		if((as->conv = allocpcmconv(&i, &o)) == nil)
			goto Err;
		n = srate / (1000 / Delayms);
		as->framesz = n * fmts[s].ssz * sch;
		/* FIXME: make sure we don't overflow, or add counterpart
		 * to pcmratio in libpcm */
		n = as->freq / (1000 / Delayms);
		n *= fmts[as->format].ssz * as->channels;
		as->outsz = n;
		if((as->outbuf = mallocz(n, 1)) == nil)
			goto Err;
	}else{
		as->framesz = as->delay * 2 * as->channels;
	}
	as->bufsz = as->nframes * as->framesz;
	if(as->buf == nil && (as->buf = mallocz(as->bufsz, 1)) == nil)
		goto Err;
	if(proccreate(as->type != OREAD ? awproc : arproc, as, 8192) < 0)
		goto Err;
	return as;
Err:
	fprint(2, "SDL_NewAudioStream: %r");
	close(as->fd);
	as->fd = -1;
	free(as->buf);
	as->buf = nil;
	return nil;
}

static int
probe(void)
{
	int i, n, dfd, type;
	ulong len;
	char *name, dir[] = "/dev/";
	SDL_AudioStream *as;
	Dir *d;

	if((dfd = open(dir, OREAD)) < 0)
		return -1;
	while((n = dirread(dfd, &d)) > 0){
		for(i = 0; i < n; i++){
			len = strlen(d[i].name);
			if(d[i].mode & DMDIR
			|| len < 5
			|| strncmp(d[i].name, "audio", 5) != 0
			|| strncmp(d[i].name+5, "ctl", 3) == 0
			|| strncmp(d[i].name+5, "stat", 4) == 0)
				continue;
			if((name = smprint("%s%s", dir, d[i].name)) == nil){
				free(d);
				return -1;
			}
			type = -1;
			if(access(name, AREAD) == 0)
				type = OREAD;
			if(access(name, AWRITE) == 0){
				/*
				if(type != -1)
					type = ORDWR;
				else
				*/
					type = OWRITE;
			}
			if(type == -1){
				free(name);
				continue;
			}
			if(strcmp(d[i].name, "audio") == 0){
				if(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK == -1 && type != OREAD)
					SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK = ndevs + 1;
				if(SDL_AUDIO_DEVICE_DEFAULT_RECORDING == -1 && type != OWRITE)
					SDL_AUDIO_DEVICE_DEFAULT_RECORDING = ndevs + 1;
			}
			if((devs = realloc(devs, (ndevs+1) * sizeof *devs)) == nil){
				free(name);
				free(d);
				return -1;	/* FIXME: devs is fucked now, we'll just segfault */
			}
			as = devs + ndevs++;
			memset(as, 0, sizeof *as);
			as->Rendez.l = &as->QLock;
			as->fd = -1;
			as->type = type;
			as->name = name;
			/* FIXME: read */
			as->freq = 44100;
			as->channels = 2;
			as->format = AUDIO_S16LSB;
			as->delay = as->freq / (1000 / Delayms);
			as->nframes = Nframes;
		}
		free(d);
	}
	close(dfd);
	if(ndevs == 0){
		werrstr("no devices found");
		return -1;
	}
	return 0;
}

void
npe_sdl_kill_audio(void)
{
	SDL_AudioStream *as, *ae;

	if(ach != nil)
		chanclose(ach);
	for(as=devs, ae=as+ndevs; as<ae; as++)
		SDL_FreeAudioStream(as);
}

int
npe_sdl_init_audio(void)
{
	if(probe() < 0)
		return -1;
	if((ach = chancreate(sizeof(int), 16)) == nil)
		return -1;
	if(proccreate(acbproc, ach, 8192) < 0)
		return -1;
	return 0;
}