ref: 7c9d7652b5a768d74376c95b773fc20db9bf9ce6
dir: /libnpe_sdl2/audiostm.c/
#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 */
};
static int
setpri(int pri)
{
int n, fd, pid;
char path[32];
if((pid = getpid()) == 0)
return -1;
snprint(path, sizeof path, "/proc/%d/ctl", pid);
if((fd = open(path, OWRITE)) < 0)
return -1;
n = fprint(fd, "pri %d\n", pri);
close(fd);
if(n < 0)
return -1;
return 0;
}
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");
if(setpri(13) < 0)
fprint(2, "setpri: %r");
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 *)
{
int n;
SDL_AudioStream *as, *ae;
threadsetname("acbproc");
for(;;){
if(recv(ach, &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(ach);
ach = nil;
}
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;
for(as=devs, ae=as+ndevs; as<ae; as++)
SDL_FreeAudioStream(as);
}
int
npe_sdl_init_audio(void)
{
if(ach != nil)
return 0;
if(probe() < 0)
return -1;
if((ach = chancreate(sizeof(int), 16)) == nil)
return -1;
if(proccreate(acbproc, nil, 8192) < 0)
return -1;
return 0;
}