shithub: npe

ref: ab547d8e97c1bbedfff2fe7d5ff18fc3171abc6d
dir: /libnpe_sdl2/audiostm.c/

View raw version
#include "_sdl.h"

/* FIXME: merge with other api */

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

struct SDL_AudioStream {
	QLock;
	int fd;
	int type;
	char *name;
	SDL_AudioStreamCallback fn;
	void *aux;
	SDL_AudioSpec;
	int delay;
	int tail;
	int framesz;
	int nframes;
	uchar *buf;
	int avail;
	int paused;	/* ugh */
	Channel *c;
	Channel *pausec;
};
static SDL_AudioStream *devs;
static Channel *ach;
static int ndevs;

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

void
SDL_AudioStreamClear(SDL_AudioStream *as)
{
	qlock(as);
	as->avail = 0;
	qunlock(as);
}

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

	if(n <= 0 || as->avail < as->framesz)
		return 0;
	if(as->avail < n)
		n = as->avail;
	else if(n > as->framesz)
		n = as->framesz;
	qlock(as);
	p = as->buf + as->tail * as->framesz;
	memcpy(buf, p, n);
	as->avail -= n;
	as->tail = (as->tail + 1) % as->nframes;
	qunlock(as);
	return n;
}

int
SDL_AudioStreamPut(SDL_AudioStream *as, const void *buf, int n)
{
	int i, m;
	uchar *p, *t, *e;

	if(as->buf == nil)
		return -1;
	i = as->tail;	/* FIXME: actually head */
	for(; n>0; n-=m){
		p = as->buf + i * as->framesz;
		t = p + as->avail;
		e = as->buf + as->nframes * as->framesz;
		m = e - t < n ? e - t : n;
		qlock(as);
		memcpy(t, buf, m);
		as->avail += m;
		qunlock(as);
		while(as->avail >= as->framesz){
			if(send(as->c, &i) < 0)
				return -1;	/* FIXME: recovery? */
			i = (i + 1) % as->nframes;
			qlock(as);
			as->avail -= as->framesz;
			qunlock(as);
		}
	}
	return 0;
}

static void
arproc(void *arg)
{
	int i, n;
	uchar *p;
	SDL_AudioStream *as, *ad, *ae;

	for(ad=nil, as=devs, ae=as+ndevs; as<ae; as++)
		if(strcmp(as->name, "/dev/audio") == 0){
			ad = as;
			break;
		}
	as = arg;
	i = 0;
	for(;;){
		if(as->paused)
			recvul(as->pausec);
		p = as->buf + i * as->framesz;
		if((n = read(as->fd, p, as->framesz)) < 0)	/* FIXME: no eof? */
			break;
		if(ad->fd >= 0){
			fprint(2, "wrote directly %d\n", n);
			write(ad->fd, p, n);
			continue;
		}
		if(n == 0)
			continue;
		qlock(as);
		if(as->fd < 0){
			qunlock(as);
			break;
		}
		as->avail += n;
		if(as->avail > as->nframes * as->framesz)
			as->avail = as->nframes * as->framesz;
		nbsend(ach, &n);
		i = (i + 1) % as->nframes;
		if(i == as->tail)
			as->tail = (i + 1) % as->nframes;
		qunlock(as);
	}
	qlock(as);
	chanfree(as->pausec);
	chanfree(as->c);
	as->pausec = as->c = nil;
	free(as->buf);
	as->buf = nil;
	qunlock(as);
}

static void
awproc(void *arg)
{
	int i, n;
	uchar *p;
	SDL_AudioStream *as;

	as = arg;
	for(;;){
		if(as->paused)
			recvul(as->pausec);
		if(recv(as->c, &i) < 0)
			break;
		n = as->framesz;
		p = as->buf + i * n;
		fprint(2, "write %d to %s\n", n, as->name);
		if(write(as->fd, p, n) != n){
			fprint(2, "awproc: %r\n");
			break;
		}
	}
	qlock(as);
	chanfree(as->pausec);
	chanfree(as->c);
	as->pausec = as->c = nil;
	free(as->buf);
	as->buf = nil;
	qunlock(as);
}

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

	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, as->avail);
	}
	chanfree(c);
}

SDL_AudioStream* SDL_NewAudioStream(SDL_AudioFormat s, Uint8 sch, int srate, SDL_AudioFormat d, Uint8 dch, int drate)
{
	SDL_AudioDeviceID id;
	SDL_AudioStream *as;

	/* FIXME */
	id = SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK;
	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->aux = nil;
	as->tail = 0;
	if((as->buf = mallocz(as->nframes * as->framesz, 1)) == nil)
		goto Err;
	if((as->c = chancreate(sizeof(int), 8)) == nil)
		goto Err;
	if((as->pausec = chancreate(sizeof(int), 0)) == nil)
		goto Err;
	if(proccreate(as->type != OREAD ? awproc : arproc, as, 8192) < 0)
		goto Err;
	USED(s, sch, srate);	/* FIXME: format conversion */
	USED(d, dch, drate);
	as->fn = nil;	/* FIXME */
	return as;
Err:
	if(as->c != nil){
		chanfree(as->c);
		as->c = nil;
	}
	if(as->pausec != nil){
		chanfree(as->pausec);
		as->pausec = nil;
	}
	close(as->fd);
	as->fd = -1;
	free(as->buf);
	return nil;
}

void
SDL_ClearAudioStream(SDL_AudioStream *as)
{
	if(as->fd < 0)
		return;
	// FIXME
}

void
SDL_FreeAudioStream(SDL_AudioStream *as)
{
	if(as->fd < 0)
		return;
	qlock(as);
	close(as->fd);
	as->fd = -1;
	chanclose(as->c);
	chanclose(as->pausec);
	as->fn = nil;
	as->aux = nil;
	qunlock(as);
}

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;
			}
			memset(&as, 0, sizeof as);
			as.fd = -1;
			as.type = type;
			as.name = name;
			/* FIXME: read */
			as.freq = 44100;
			as.channels = 2;
			as.format = AUDIO_S16LSB;
			as.delay = 1764;
			as.framesz = as.delay * 2 * as.channels;
			as.nframes = 8;
			if((devs = realloc(devs, (ndevs+1) * sizeof *devs)) == nil){
				free(name);
				free(d);
				return -1;	/* FIXME: devs is fucked now, we'll just segfault */
			}
			devs[ndevs++] = as;
		}
		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;

	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;
}