shithub: npe

Download patch

ref: cdc06f177f6e2b9f4a4e354ac10dabfae0fd459a
parent: 26ce37c49642e2875a36650734b9619b21c49be5
author: qwx <qwx@sciops.net>
date: Fri Mar 6 06:44:33 EST 2026

sdl2: audio streaming fixes and format conversion

- disable regular api while using streaming (temporary)
- implement format conversion for writers via libpcm
- misc fixes

--- a/libnpe_sdl2/audio.c
+++ b/libnpe_sdl2/audio.c
@@ -112,6 +112,10 @@
 {
 	Audiodev *a;
 
+	/* FIXME */
+	extern int audiostreaming;
+	if(audiostreaming)
+		return;
 	a = &au[id];
 	if(a->paused && !pause){
 		if(a->pid < 0)
--- a/libnpe_sdl2/audiostm.c
+++ b/libnpe_sdl2/audiostm.c
@@ -1,12 +1,21 @@
 #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;
@@ -14,130 +23,165 @@
 	void *aux;
 	SDL_AudioSpec;
 	int delay;
-	int tail;
 	int framesz;
 	int nframes;
 	uchar *buf;
-	int avail;
+	vlong bufsz;
+	uchar *outbuf;
+	vlong outsz;
+	Along avail;
+	int tail;
 	int paused;	/* ugh */
-	Channel *c;
-	Channel *pausec;
+	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 as->avail;
+	return agetl(&as->avail);
 }
 
 void
 SDL_AudioStreamClear(SDL_AudioStream *as)
 {
-	qlock(as);
-	as->avail = 0;
-	qunlock(as);
+	long n;
+
+	n = 0;
+	aswapl(&as->avail, n);
 }
 
 int
 SDL_AudioStreamGet(SDL_AudioStream *as, void *buf, int n)
 {
-	uchar *p;
+	int t;
 
-	if(n <= 0 || as->avail < as->framesz)
+	if(as->fd < 0 || as->closing)
 		return 0;
-	if(as->avail < n)
-		n = as->avail;
-	else if(n > as->framesz)
+	if(n <= 0)
+		return 0;
+	if(n > as->framesz)
 		n = as->framesz;
+	if(agetl(&as->avail) < n)
+		return 0;
 	qlock(as);
-	p = as->buf + as->tail * as->framesz;
-	memcpy(buf, p, n);
-	as->avail -= n;
-	as->tail = (as->tail + 1) % as->nframes;
+	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, const void *buf, int n)
+SDL_AudioStreamPut(SDL_AudioStream *as, void *buf, int n)
 {
-	int i, m;
-	uchar *p, *t, *e;
+	int m, k, t;
+	uchar *p, *s, *e;
 
-	if(as->buf == nil)
+	if(as->fd < 0 || as->closing)
 		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;
+	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);
-			as->avail -= as->framesz;
+			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 i, n;
-	uchar *p;
-	SDL_AudioStream *as, *ad, *ae;
+	int n, m, t;
+	uchar *p, *e;
+	SDL_AudioStream *as;
 
-	for(ad=nil, as=devs, ae=as+ndevs; as<ae; as++)
-		if(strcmp(as->name, "/dev/audio") == 0){
-			ad = as;
-			break;
-		}
+	threadsetname("arproc");
 	as = arg;
-	i = 0;
+	p = as->buf;
+	e = as->buf + as->bufsz;
 	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(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);
-		if(as->fd < 0){
-			qunlock(as);
-			break;
+		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;
 		}
-		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;
+		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);
-	chanfree(as->pausec);
-	chanfree(as->c);
-	as->pausec = as->c = nil;
-	free(as->buf);
-	as->buf = nil;
+	close(as->fd);
+	as->fd = -1;
+	rwakeupall(as);
 	qunlock(as);
 }
 
@@ -144,33 +188,66 @@
 static void
 awproc(void *arg)
 {
-	int i, n;
-	uchar *p;
+	int n, m, k;
+	uchar *s, *p, *e;
 	SDL_AudioStream *as;
 
+	threadsetname("awproc");
 	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;
+	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);
-	chanfree(as->pausec);
-	chanfree(as->c);
-	as->pausec = as->c = nil;
-	free(as->buf);
-	as->buf = nil;
+	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)
@@ -179,6 +256,7 @@
 	SDL_AudioStream *as, *ae;
 	Channel *c;
 
+	threadsetname("acbproc");
 	c = arg;
 	for(;;){
 		if(recv(c, &n) < 0)
@@ -185,7 +263,7 @@
 			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);
+				as->fn(as->aux, as, n, agetl(&as->avail));
 	}
 	chanfree(c);
 }
@@ -192,11 +270,15 @@
 
 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;
@@ -210,58 +292,61 @@
 	}
 	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;
-	if((as->buf = mallocz(as->nframes * as->framesz, 1)) == nil)
+	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((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;
-	}
+	fprint(2, "SDL_NewAudioStream: %r");
 	close(as->fd);
 	as->fd = -1;
 	free(as->buf);
+	as->buf = nil;
 	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)
 {
@@ -268,7 +353,7 @@
 	int i, n, dfd, type;
 	ulong len;
 	char *name, dir[] = "/dev/";
-	SDL_AudioStream as;
+	SDL_AudioStream *as;
 	Dir *d;
 
 	if((dfd = open(dir, OREAD)) < 0)
@@ -307,23 +392,23 @@
 				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;
+			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);
 	}
@@ -340,7 +425,8 @@
 {
 	SDL_AudioStream *as, *ae;
 
-	chanclose(ach);
+	if(ach != nil)
+		chanclose(ach);
 	for(as=devs, ae=as+ndevs; as<ae; as++)
 		SDL_FreeAudioStream(as);
 }
--