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, ×tamp) || (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);