shithub: npe

Download patch

ref: 1e9b7af7dee79ea1611241649a7124cb2c957e9b
parent: 2b93fd2cd792cfc97b836c991d01a76f00f7ef7e
author: qwx <qwx@sciops.net>
date: Mon Mar 2 19:38:49 EST 2026

sdl2: add wip audio stream api

in future, should be merged with older api, which is
entirely removed from sdl3

--- a/include/npe/SDL2/SDL_audio.h
+++ b/include/npe/SDL2/SDL_audio.h
@@ -32,7 +32,15 @@
 
 typedef struct SDL_AudioSpec SDL_AudioSpec;
 typedef int SDL_AudioDeviceID;
+typedef struct SDL_AudioStream SDL_AudioStream;
 
+#pragma incomplete SDL_AudioStream
+
+typedef void (*SDL_AudioStreamCallback)(void *userdata, SDL_AudioStream *stream, int additional_amount, int total_amount);
+
+extern SDL_AudioDeviceID SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK;
+extern SDL_AudioDeviceID SDL_AUDIO_DEVICE_DEFAULT_RECORDING;
+
 struct SDL_AudioSpec {
 	void (*callback)(void *, Uint8 *, int);
 	void *userdata;
@@ -69,5 +77,12 @@
 
 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);
 int SDL_ConvertAudio(SDL_AudioCVT *cvt);
+
+int SDL_AudioStreamPut(SDL_AudioStream *stream, const void *buf, int len);
+int SDL_AudioStreamAvailable(SDL_AudioStream *stream);
+int SDL_AudioStreamGet(SDL_AudioStream *stream, void *buf, int len);
+void SDL_AudioStreamClear(SDL_AudioStream *stream);
+void SDL_FreeAudioStream(SDL_AudioStream *stream);
+SDL_AudioStream* SDL_NewAudioStream(SDL_AudioFormat src, Uint8 src_ch, int src_rate, SDL_AudioFormat dst, Uint8 dst_ch, int dst_rate);
 
 #endif
--- a/libnpe_sdl2/_sdl.h
+++ b/libnpe_sdl2/_sdl.h
@@ -33,5 +33,7 @@
 extern struct npe_sdl npe_sdl;
 
 int npe_sdl_init_input(void);
+int npe_sdl_init_audio(void);
+void npe_sdl_kill_audio(void);
 void *npe_sdl_scale(u32int *src, int iw, int ih, u32int *dst, int ow, int oh);
 int npe_sdl_windowresized(int*, int*);
--- /dev/null
+++ b/libnpe_sdl2/audiostm.c
@@ -1,0 +1,358 @@
+#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;
+}
--- a/libnpe_sdl2/mkfile
+++ b/libnpe_sdl2/mkfile
@@ -7,6 +7,7 @@
 
 OFILES=\
 	audio.$O\
+	audiostm.$O\
 	events.$O\
 	image.$O\
 	rwops.$O\
--- a/libnpe_sdl2/sdl2.c
+++ b/libnpe_sdl2/sdl2.c
@@ -56,7 +56,8 @@
 SDL_InitSubSystem(int mask)
 {
 	/* FIXME implement */
-	USED(mask);
+	if(mask & SDL_INIT_AUDIO && npe_sdl_init_audio() < 0)
+		return -1;
 	return 0;
 }
 
@@ -64,7 +65,8 @@
 SDL_QuitSubSystem(int mask)
 {
 	/* FIXME implement */
-	USED(mask);
+	if(mask & SDL_INIT_AUDIO)
+		npe_sdl_kill_audio();
 	return 0;
 }
 
@@ -233,6 +235,8 @@
 		goto err;
 	if(npe_sdl_init_input() != 0)
 		goto err;
+	if(mask & SDL_INIT_AUDIO && npe_sdl_init_audio() < 0)
+		goto err;
 	npe_sdl.scale = 1;
 	physw = Dx(screen->r);
 	physh = Dy(screen->r);
@@ -483,6 +487,7 @@
 SDL_Quit(void)
 {
 	/* FIXME deinitialize */
+	npe_sdl_kill_audio();
 }
 
 void
--