shithub: treason

Download patch

ref: a94b98ef756ec9e9bc0a712c04455c407416a73a
parent: 19cbe98daef0d058a0387f03cc36e44f9169e04b
author: Sigrid Haflínudóttir <ftrvxmtrx@gmail.com>
date: Mon Sep 14 09:20:56 EDT 2020

add a very basic audio support

--- a/README.md
+++ b/README.md
@@ -10,7 +10,8 @@
 
 It can play [AV1](https://en.wikipedia.org/wiki/AV1)-encoded MP4 files
 on AMD64 machines.  Only 8-bit per component YUV 4:2:0 is supported
-atm.
+atm.  AAC audio is supported (inside and outside of a container).
+OPUS audio is supported outside a container.
 
 More is coming soon.
 
@@ -33,6 +34,8 @@
 Vesa video driver? No.
 
 Drawterm? No.
+
+There is nothing that keeps the audio in sync with the video atm.
 
 ## Contact
 
--- a/decoder.c
+++ b/decoder.c
@@ -55,5 +55,6 @@
 	chanclose(d->frames);
 	sendp(d->stop, nil);
 	recvp(d->finished);
+	chanclose(d->finished);
 	free(d);
 }
--- a/decoder_av1.c
+++ b/decoder_av1.c
@@ -33,7 +33,6 @@
 	s->ops.aux = data;
 	if(Sread(s, &f) != 0)
 		return -1;
-	data->m.offset = f.offset;
 	data->m.timestamp = f.timestamp;
 
 	return 0;
@@ -112,7 +111,6 @@
 	free(a);
 	chanclose(d->stop);
 	sendp(d->finished, nil);
-	chanclose(d->finished);
 
 	threadexits(nil);
 }
--- a/main.c
+++ b/main.c
@@ -14,6 +14,8 @@
 
 static Image *curim;
 static uvlong lastframe;
+static uvlong lastframeaudio;
+static int audiofd = -1;
 
 static void
 drawframe(Frame *f)
@@ -65,6 +67,33 @@
 	free(f);
 }
 
+static void
+runaudio(Stream *s)
+{
+	Streamframe f;
+
+	if(s == nil)
+		return;
+	if(audiofd == -1 && (audiofd = open("/dev/audio", OWRITE)) < 0){
+		fprint(2, "runaudio: %r");
+		audiofd = -2;
+	}
+	if(audiofd < 0 || lastframe < lastframeaudio)
+		return;
+
+	if(Sread(s, &f) != 0)
+		return;
+	write(audiofd, f.buf, f.sz);
+	lastframeaudio = f.timestamp + f.dt;
+}
+
+static void
+usage(void)
+{
+	fprint(2, "usage: %s [-a AUDIO] file.mp4\n", argv0);
+	threadexitsall("usage");
+}
+
 void
 threadmain(int argc, char **argv)
 {
@@ -81,10 +110,10 @@
 	Keyboardctl *kctl;
 	Rune key;
 	Mouse m;
-	char *s;
-	Stream *stream;
+	char *s, *audio;
+	Stream *stream, *svideo, *saudio;
 	Decoder *d;
-	int i, end, done, res, nstreams;
+	int i, j, end, done, res, nstreams;
 	Alt a[Cnum+1] =
 	{
 		[Cframe] = { nil, &frame, CHANRCV },
@@ -96,7 +125,15 @@
 	};
 
 	debug = 0;
+	audio = nil;
 	ARGBEGIN{
+	case 'a':
+		if(audio != nil){
+			fprint(2, "only one audio file can be specified\n");
+			usage();
+		}
+		audio = EARGF(usage());
+		break;
 	case 'd':
 		debug++;
 		break;
@@ -103,7 +140,7 @@
 	}ARGEND
 
 	if(argc < 1)
-		sysfatal("usage");
+		usage();
 
 	nproc = atoi((s = getenv("NPROC")) != nil ? s : "1");
 	if(nproc < 1)
@@ -124,16 +161,35 @@
 	a[Cresize].c = mctl->resizec;
 	a[Ckeyboard].c = kctl->c;
 
+	saudio = nil;
+	if(audio != nil)
+		saudio = Sopen(audio, &nstreams);
+
 	for(end = i = 0; !end && i < argc; i++){
 		draw(screen, screen->r, display->black, nil, ZP);
+
 		if((stream = Sopen(argv[i], &nstreams)) == nil)
 			sysfatal("%r");
-		if((d = Dopen(stream)) == nil)
+
+		svideo = nil;
+		for(j = 0; j < nstreams; j++){
+			if(stream[j].type == Svideo && svideo == nil)
+				svideo = stream+j;
+			else if(stream[j].type == Saudio && saudio == nil)
+				saudio = stream+j;
+			else
+				Sclose(stream+j);
+		}
+		if(svideo == nil)
+			sysfatal("%s: no video", argv[i]);
+
+		if((d = Dopen(svideo)) == nil)
 			sysfatal("%r");
 
 		a[Cframe].c = d->frames;
 		a[Cplayerdone].c = d->finished;
 		lastframe = 0;
+		lastframeaudio = 0;
 
 		for(done = 0; !done && !end;){
 			res = alt(a);
@@ -140,6 +196,7 @@
 			switch(res){
 			case Cframe:
 				drawframe(frame);
+				runaudio(saudio);
 				break;
 
 			case Cplayerdone:
@@ -167,6 +224,8 @@
 			}
 		}
 
+		Sclose(saudio);
+		saudio = nil;
 		Dclose(d);
 		for(i = 0; i < nstreams; i++)
 			Sclose(stream+i);
--- a/mkfile
+++ b/mkfile
@@ -16,6 +16,7 @@
 	main.$O\
 	misc.$O\
 	stream.$O\
+	stream_audio.$O\
 	stream_ivf.$O\
 	stream_mc.$O\
 	yuv.$O\
--- a/stream.c
+++ b/stream.c
@@ -2,8 +2,7 @@
 #include <libc.h>
 #include "stream.h"
 
-extern Streamops ivfops;
-extern Streamops mcops;
+extern Streamops ivfops, mcops, audops;
 
 static struct {
 	char *name;
@@ -11,6 +10,7 @@
 }ops[] = {
 	{"mc", &mcops},
 	{"ivf", &ivfops},
+	{"audio", &audops},
 };
 
 Stream *
@@ -38,7 +38,10 @@
 void
 Sclose(Stream *s)
 {
+	if(s->type < 0)
+		return;
 	s->ops.close(s);
+	s->type = -1;
 }
 
 int
@@ -45,10 +48,4 @@
 Sread(Stream *s, Streamframe *f)
 {
 	return s->ops.read(s, f);
-}
-
-vlong
-Soffset(Stream *s)
-{
-	return s->ops.offset(s);
 }
--- a/stream.h
+++ b/stream.h
@@ -19,10 +19,10 @@
 };
 
 struct Streamframe {
+	uvlong timestamp;
+	uvlong dt;
 	u8int *buf;
 	int sz;
-	uvlong offset;
-	uvlong timestamp;
 };
 
 struct Streamops {
@@ -29,7 +29,6 @@
 	Stream *(*open)(char *path, int *num, int *failed);
 	u8int *(*alloc)(void *aux, int sz);
 	void (*close)(Stream *s);
-	vlong (*offset)(Stream *s);
 	int (*read)(Stream *s, Streamframe *f);
 
 	void *aux;
@@ -60,6 +59,7 @@
 	Streamops ops;
 
 	/* private stuff */
+	uvlong timestamp;
 	void *b;
 	u8int *buf;
 	int bufsz;
@@ -66,8 +66,8 @@
 };
 
 Stream *Sopen(char *filename, int *num);
-vlong Soffset(Stream *s);
 void Sclose(Stream *s);
 int Sread(Stream *s, Streamframe *f);
 
 int ivfopenb(void *bio, Stream *s, int *failed);
+int audopenfd(int fd, Stream *s, int *failed);
--- /dev/null
+++ b/stream_audio.c
@@ -1,0 +1,120 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include "misc.h"
+#include "stream.h"
+
+enum {
+	Onesec = 2*2*44100, /* 16-bit, stereo */
+	Framesz = Onesec/10, /* 1/10th second */
+};
+
+static char *progs[] = {
+	[FmtAAC] = "/bin/audio/aacdec",
+	[FmtOpus] = "/bin/audio/opusdec",
+};
+
+extern Streamops audops;
+
+int
+audopenfd(int fd, Stream *s, int *failed)
+{
+	int p[2], pid;
+	char *prog;
+	Dir *d;
+
+	if(fd < 0)
+		return -1;
+	if(s->fmt >= nelem(progs) || (prog = progs[s->fmt]) == nil){
+		werrstr("unsupported audio format %d", s->fmt);
+		goto err;
+	}
+
+	*failed = 1;
+	if((d = dirstat(prog)) == nil){
+		werrstr("can't decode, %s isn't available", prog);
+		goto err;
+	}
+	free(d);
+
+	pipe(p);
+	if((pid = rfork(RFFDG|RFPROC|RFNOMNT)) == 0){
+		dup(fd, 0); close(fd);
+		close(p[0]);
+		dup(p[1], 1); close(p[1]);
+		if(!debug){
+			dup(fd = open("/dev/null", OWRITE), 2);
+			close(fd);
+		}
+		execl(prog, prog, nil);
+		sysfatal("exec: %r");
+	}
+	close(p[1]);
+	if(pid < 0){
+		close(p[0]);
+		goto err;
+	}
+	close(fd);
+
+	s->type = Saudio;
+	s->timebase.denum = 1;
+	s->timebase.num = 1;
+	s->b = Bfdopen(p[0], OREAD);
+	memmove(&s->ops, &audops, sizeof(audops));
+	*failed = 0;
+
+	return 0;
+err:
+	close(fd);
+	return -1;
+}
+
+static Stream *
+audopen(char *path, int *num, int *failed)
+{
+	Stream *s;
+
+	if((s = calloc(1, sizeof(*s))) == nil){
+		*failed = 1;
+		return nil;
+	}
+
+	*num = 1;
+	if(audopenfd(open(path, OREAD), s, failed) != 0){
+		free(s);
+		s = nil;
+	}
+
+	return s;
+}
+
+static int
+audread(Stream *s, Streamframe *f)
+{
+	if(s->buf == nil)
+		s->buf = malloc(Framesz);
+	f->buf = s->buf;
+	f->timestamp = s->timestamp;
+	if((f->sz = Bread(s->b, f->buf, Framesz)) < 0){ /* eof */
+		f->sz = 0;
+		return -1;
+	}
+
+	f->dt = f->sz * 1000000000ULL / Onesec;
+	s->timestamp += f->dt;
+
+	return 0;
+}
+
+static void
+audclose(Stream *s)
+{
+	Bterm(s->b);
+	free(s->buf);
+}
+
+Streamops audops = {
+	.open = audopen,
+	.read = audread,
+	.close = audclose,
+};
--- a/stream_ivf.c
+++ b/stream_ivf.c
@@ -141,7 +141,6 @@
 	u8int *buf;
 	int n;
 
-	f->offset = Boffset(s->b);
 	if(Bu32le(s->b, &sz) < 0 || Bu64le(s->b, &timestamp) || (int)sz < 0){
 		/* eof */
 		f->sz = 0;
@@ -168,12 +167,6 @@
 	return 0;
 }
 
-static vlong
-ivfoffset(Stream *s)
-{
-	return Boffset(s->b);
-}
-
 static void
 ivfclose(Stream *s)
 {
@@ -184,6 +177,5 @@
 Streamops ivfops = {
 	.open = ivfopen,
 	.read = ivfread,
-	.offset = ivfoffset,
 	.close = ivfclose,
 };
--- a/stream_mc.c
+++ b/stream_mc.c
@@ -33,6 +33,23 @@
 	return pid;
 }
 
+static int
+audfmt(char *fmt)
+{
+	static char *fmts[] = {
+		[FmtAAC] = "mp4a",
+		[FmtOpus] = "opus",
+	};
+	int i;
+
+	for(i = 0; i < nelem(fmts); i++){
+		if(fmts[i] != nil && strcmp(fmt, fmts[i]) == 0)
+			return i;
+	}
+
+	return -1;
+}
+
 static Stream *
 mcopen(char *filename, int *num, int *failed)
 {
@@ -66,12 +83,19 @@
 			s = streams+ns;
 
 			if(nvideo < 1 && strcmp(v[1], "video") == 0){
+				s->video.w = atoi(v[3]);
+				s->video.h = atoi(v[4]);
 				if(mcfs(argv, &sp) > 0 && ivfopenb(Bfdopen(sp, OREAD), s, failed) == 0){
 					nvideo++;
 					ns++;
 				}
-			}else if(naudio < 1 && strcmp(v[1], "audio") == 0){
-				/* FIXME add audio streams */
+			}else if(naudio < 1 && strcmp(v[1], "audio") == 0 && (s->fmt = audfmt(v[2])) >= 0){
+				s->audio.nchan = atoi(v[3]);
+				s->audio.srate = atoi(v[4]);
+				if(mcfs(argv, &sp) > 0 && audopenfd(sp, s, failed) == 0){
+					naudio++;
+					ns++;
+				}
 			}
 		}
 		free(line);