shithub: npe

ref: 979ae53cfee913c2d303ab0f95aaa65dace1c7b5
dir: /libnpe_sdl3/audio.c/

View raw version
#include "_sdl.h"

#ifdef FIXME
enum {
	Aout = 2,
	Arec,

	Audiosamples = 8192,
};

typedef struct Audiodev Audiodev;

struct Audiodev {
	Lock;
	void (*cb)(void *, Uint8 *, int);
	void *userdata;
	char *name;
	Channel *wait;
	Uint8 *buf;
	int bufsz;
	int paused;
	int fd;
	int pid;
	int pidconv;
	int mode;
};

/* FIXME extra USB audio devices? */
static Audiodev au[4] = {
	[0] = {.fd = -1, .mode = -1},
	[1] = {.fd = -1, .mode = -1},
	[Aout] = {.name = "/dev/audio", .fd = -1, .pid = -1, .mode = OWRITE},
	[Arec] = {.name = "/dev/audio", .fd = -1, .pid = -1, .mode = OREAD},
};

static struct {
	char *spec;
	int ssz; /* samples size */
}fmts[] = {
	[SDL_AUDIO_U8] = {"u8", 1},
	[SDL_AUDIO_S8] = {"s8", 1},
	[SDL_AUDIO_S16LE] = {"s16", 2},
	[SDL_AUDIO_S16BE] = {"S16", 2},
	[SDL_AUDIO_S32LE] = {"s32", 4},
	[SDL_AUDIO_S32BE] = {"S32", 4},
	[SDL_AUDIO_F32LE] = {"f32", 4},
	[SDL_AUDIO_F32BE] = {"F32", -1}, /* FIXME big endian f32 not supported by pcmconv */
};

static void
audiothread(void *p)
{
	Audiodev *a;

	a = p;
	threadsetname("%s (%s)", a->name, a->mode == OREAD ? "out" : "in");

	for(;;){
		if(a->mode == OREAD && readn(a->fd, a->buf, a->bufsz) != a->bufsz)
			break;

		lock(a);
		if(a->mode == OWRITE && a->paused)
			memset(a->buf, 0, a->bufsz);
		else
			a->cb(a->userdata, a->buf, a->bufsz);
		unlock(a);

		if(a->mode == OWRITE && write(a->fd, a->buf, a->bufsz) != a->bufsz)
			break;
	}

	lock(a);
	(a->mode == OWRITE ? write : read)(a->fd, a->buf, 0);
	chanclose(a->wait);
	unlock(a);

	threadexits(nil);
}

void
SDL_PauseAudioDevice(SDL_AudioDeviceID id, SDL_bool pause)
{
	Audiodev *a;

	a = &au[id];
	if(a->paused && !pause){
		if(a->pid < 0)
			a->pid = proccreate(, a, 4096);
		a->paused = 0;
	}else if(!a->paused && pause){
		a->paused = 1;
	}
}

void
SDL_PauseAudio(int pause_on)
{
	SDL_PauseAudioDevice(1, pause_on);
}

static int
convspec(SDL_AudioSpec *s, char *spec, int n)
{
	int ssz;

	ssz = -1;
	if(s->format < 0 || s->format >= nelem(fmts))
		werrstr("invalid audio format: #%d", s->format);
	else if(fmts[s->format].ssz < 1)
		werrstr("unsupported audio format: #%d", s->format);
	else if(s->channels < 1)
		werrstr("invalid number of channels: %d", s->channels);
	else if(s->freq < 1)
		werrstr("invalid sampling rate: %d", s->freq);
	else if(snprint(spec, n, "%sc%dr%d", fmts[s->format].spec, s->channels, s->freq) >= n)
		werrstr("audio spec does not fit");
	else
		ssz = fmts[s->format].ssz;

	return ssz;
}

// https://wiki.libsdl.org/SDL3/SDL_ConvertAudio
int
SDL_ConvertAudio(SDL_AudioCVT *cvt)
{
	USED(cvt);
	return 0;
}

// https://wiki.libsdl.org/SDL3/SDL_BuildAudioCVT
int
SDL_BuildAudioCVT(SDL_AudioCVT *cvt, SDL_AudioFormat src_format, Uint8 src_channels, int src_rate, SDL_AudioFormat dst_format, Uint8 dst_channels, int dst_rate)
{
	USED(cvt);
	USED(src_format, src_channels, src_rate);
	USED(dst_format, dst_channels, dst_rate);
	return 0;
}

static void
setpipebuf(int f, int sz)
{
	Dir d;

	nulldir(&d);
	d.length = sz;
	dirfwstat(f, &d);
}

void
SDL_CloseAudioDevice(SDL_AudioDeviceID id)
{
	Audiodev *a;
	Waitmsg *w;
	int pid;

	a = &au[id];

	if(a->fd < 0)
		return;

	lock(a);
		close(a->fd);
	unlock(a);

	if(a->pid >= 0)
		recvul(a->wait);
	chanfree(a->wait);

	free(a->buf);
	a->fd = -1;
	a->pid = -1;
again:
	if(a->pidconv >= 0 && (w = wait()) != nil){
		if(w->msg[0])
			fprint(2, "SDL_CloseAudioDevice: %s: %s\n", a->name, w->msg);
		pid = w->pid;
		free(w);
		if(pid != a->pidconv)
			goto again;
	}
}

SDL_AudioDeviceID
SDL_OpenAudioDevice(char *dev, int rec, SDL_AudioSpec *want, SDL_AudioSpec *have, u32int change)
{
	SDL_AudioDeviceID id;
	int p[2], ssz, fd;
	char spec[16];
	Audiodev *a;

	/* FIXME look for extra USB devices? */
	USED(dev);

	id = rec ? Arec : Aout;
	a = &au[id];

	if(have == nil)
		have = want;
	*have = *want;
	if(have->freq < 44100 && (change & SDL_AUDIO_ALLOW_FREQUENCY_CHANGE) != 0)
		have->freq = 44100;
	if(have->format <= 0 || have->format >= nelem(fmts) || fmts[have->format].ssz < 1 && (change & SDL_AUDIO_ALLOW_FORMAT_CHANGE) != 0)
		have->format = SDL_AUDIO_S16;
	if(have->channels < 1 && (change & SDL_AUDIO_ALLOW_CHANNELS_CHANGE) != 0)
		have->channels = 2;
	if(have->samples < 2 || (have->samples & (have->samples-1)) != 0){
		if(change & SDL_AUDIO_ALLOW_SAMPLES_CHANGE)
			have->samples = Audiosamples;
		else{
			werrstr("invalid number of samples: %d", have->samples);
			goto err;
		}
	}

	if((ssz = convspec(have, spec, sizeof(spec))) < 1)
		goto err;

	a->userdata = have->userdata;
	a->cb = have->callback;
	a->wait = chancreate(sizeof(ulong), 0);
	a->bufsz = have->samples * ssz * have->channels;
	a->buf = malloc(a->bufsz);
	if(a->wait == nil || a->buf == nil){
		werrstr("memory");
		goto err;
	}

	a->paused = 1;
	a->pid = -1;
	a->pidconv = -1;
	if(have->freq != 44100 || have->format != AUDIO_S16 || have->channels != 2){
		if((fd = open(a->name, a->mode)) < 0)
			goto err;
		pipe(p);
		setpipebuf(p[0], a->bufsz);
		if((a->pidconv = rfork(RFPROC|RFNOTEG|RFFDG|RFCENVG)) == 0){
			dup(fd, rec ? 0 : 1); close(fd);
			dup(p[0], rec ? 1 : 0); close(p[0]);
			close(p[1]);
			//close(2);
			if(execl("/bin/audio/pcmconv", "pcmconv", rec ? "-o" : "-i", spec, nil) != 0)
				exits("%r");
		}else if(a->pidconv < 0){
			werrstr("pcmconv: %r");
			close(fd);
			goto err;
		}
		a->fd = p[1];
		close(p[0]);
		close(fd);
	}else if(a->fd < 0 && (a->fd = open(a->name, a->mode|OCEXEC)) < 0)
		goto err;

	return id;
err:
	werrstr("SDL_OpenAudioDevice: %r");
	if(a->fd >= 0){
		close(a->fd);
		a->fd = -1;
	}
	free(a->buf);
	a->buf = nil;
	if(a->wait != nil){
		chanfree(a->wait);
		a->wait = nil;
	}

	return 0;
}
#endif




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;

bool
SDL_GetAudioDeviceFormat(SDL_AudioDeviceID id, SDL_AudioSpec *spec, int *nsamp)
{
	SDL_AudioStream *as;

	if(--id < 0 || id >= ndevs){
		werrstr("invalid device id %d", id);
		return false;
	}
	as = devs + id;
	if(spec != nil)
		*spec = as->SDL_AudioSpec;
	if(nsamp != nil)
		*nsamp = as->delay;
	return true;
}

bool
SDL_GetAudioStreamFormat(SDL_AudioStream *as, SDL_AudioSpec *in, SDL_AudioSpec *out)
{
	if(in != nil)
		*in = as->SDL_AudioSpec;
	if(out != nil)
		*out = as->SDL_AudioSpec;
	return true;
}

bool
SDL_SetAudioStreamFormat(SDL_AudioStream *as, SDL_AudioSpec *in, SDL_AudioSpec *out)
{
	/* FIXME: set new input and output formats from ptr */
	if(in != nil){
		if(memcmp(&as->SDL_AudioSpec, in, sizeof *in) != 0){
			/* FIXME */
		}
		as->SDL_AudioSpec = *in;
	}
	if(out != nil){
		if(memcmp(&as->SDL_AudioSpec, out, sizeof *out) != 0){
			/* FIXME */
		}
		as->SDL_AudioSpec = *out;
	}
	return true;
}

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

int
SDL_GetAudioStreamData(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;
}

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

	if(as->buf == nil)
		return false;
	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 false;	/* FIXME: recovery? */
			i = (i + 1) % as->nframes;
			qlock(as);
			as->avail -= as->framesz;
			qunlock(as);
		}
	}
	return true;
}

SDL_AudioDeviceID *
SDL_GetAudioRecordingDevices(int *count)
{
	int nbuf;
	SDL_AudioDeviceID *buf;
	SDL_AudioStream *as, *ae;

	nbuf = 0;
	buf = nil;
	if(count != nil)
		*count = 0;
	for(as=devs, ae=as+ndevs; as<ae; as++){
		if(as->type == OWRITE)
			continue;
		if((buf = realloc(buf, (nbuf+1) * sizeof *buf)) == nil)
			return nil;
		buf[nbuf++] = as - devs + 1;
		if(count != nil)
			(*count)++;
	}
	if((buf = realloc(buf, (nbuf+1) * sizeof *buf)) == nil)
		return nil;
	buf[nbuf] = 0;
	return buf;
}

SDL_AudioDeviceID *
SDL_GetAudioPlaybackDevices(int *count)
{
	int nbuf;
	SDL_AudioDeviceID *buf;
	SDL_AudioStream *as, *ae;

	nbuf = 0;
	buf = nil;
	if(count != nil)
		*count = 0;
	for(as=devs, ae=as+ndevs; as<ae; as++){
		if(as->type == OREAD)
			continue;
		if((buf = realloc(buf, (nbuf+1) * sizeof *buf)) == nil)
			return nil;
		buf[nbuf++] = as - devs + 1;
		if(count != nil)
			(*count)++;
	}
	if((buf = realloc(buf, (nbuf+1) * sizeof *buf)) == nil)
		return nil;
	buf[nbuf] = 0;
	return buf;
}

const char*
SDL_GetAudioDeviceName(SDL_AudioDeviceID id)
{
	SDL_AudioStream *as;

	if(--id < 0 || id >= ndevs){
		werrstr("invalid device id %d", id);
		return nil;
	}
	as = devs + id;
	return as->name;
}

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

bool
SDL_ResumeAudioStreamDevice(SDL_AudioStream *as)
{
	if(!as->paused)
		return true;
	as->paused = 0;
	nbsendul(as->pausec, 1);
	return true;
}

bool
SDL_PauseAudioStreamDevice(SDL_AudioStream *as)
{
	if(as->paused)
		return true;
	as->paused = 1;
	return true;
}

SDL_AudioStream*
SDL_OpenAudioDeviceStream(SDL_AudioDeviceID id, const SDL_AudioSpec *f, SDL_AudioStreamCallback fn, void *aux)
{
	SDL_AudioStream *as;

	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 = aux;
	as->tail = 0;
	/* FIXME: format */
	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(f);	/* FIXME: format conversion */
	as->fn = fn;
	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_DestroyAudioStream(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 = SDL_AUDIO_S16LE;
			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_DestroyAudioStream(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;
}