ref: c95031c0888f4c16fc19056e7669d64d6249254c
dir: /main.c/
#include <u.h>
#include <libc.h>
#include <draw.h>
#include <memdraw.h>
#include <mouse.h>
#include <keyboard.h>
#include <thread.h>
#include "frame.h"
#include "misc.h"
#include "stream.h"
#include "decoder.h"
int mainstacksize = 128*1024;
uvlong trackpref;
static Streamframe subfr[8];
static int nsubfr;
static Image *curim;
static Rectangle videor;
static u8int *curpx;
static uvlong firstframe, lastframe;
static int audiofd = -1;
static Channel *audiosync;
static Channel *audiofinished;
static char info[256];
static int showinfo;
static int paused;
static uvlong dispdelay;
static int framedrop;
static uvlong late;
static uvlong vidts;
static int bw;
static int benchmark;
static void
audioproc(void *x)
{
Streamframe f;
Stream *s;
int audiofd, synced;
uvlong sent, ts, ats, skip;
synced = 0;
sent = 0;
skip = 0;
ts = 0;
if((audiofd = open("/dev/audio", OWRITE|OCEXEC)) < 0){
fprint(2, "runaudio: %r\n");
}else{
for(s = x; Sread(s, &f) == 0 && f.sz > 0;){
if(!synced || paused){
recv(audiosync, &ts);
chanclose(audiosync);
ats = (sent*2500000ULL)/441ULL;
if(ats > ts)
nsleep(ats - ts);
else
skip = ((ts - ats)*441ULL)/2500000ULL;
synced = 1;
}
if(skip){
if(skip > f.sz)
skip -= f.sz;
else
skip = 0;
}else
write(audiofd, f.buf, f.sz);
sent += f.sz;
}
close(audiofd);
}
if(!synced){
recv(audiosync, &ts);
chanclose(audiosync);
}
sendp(audiofinished, nil);
threadexits(nil);
}
static void
drawsub(Stream *s)
{
Streamframe *f;
int i, l, h;
uvlong ts;
Point p;
if(s == nil)
return;
/* read until we have something to show, but don't go too far */
ts = lastframe - firstframe;
for(; nsubfr < nelem(subfr) && (nsubfr == 0 || subfr[nsubfr-1].timestamp <= ts); nsubfr++){
f = &subfr[nsubfr];
if(Sread(s, f) != 0){
fprint(2, "%r\n");
break;
}
if(f->nlines == 0)
return;
}
/* remove stale text, calculate height of the lines currently displayed */
h = 0;
for(i = 0; i < nsubfr; i++){
f = &subfr[i];
if(f->timestamp + f->dt < ts){
for(l = 0; l < nelem(f->lines) && f->lines[l] != nil; l++)
free(f->lines[l]);
ts = lastframe - firstframe;
memmove(subfr+i, subfr+i+1, sizeof(Streamframe)*(nsubfr-i));
nsubfr--;
i--;
continue;
}
if(f->timestamp <= ts && f->timestamp+f->dt >= ts)
h += font->height*(f->nlines + 1); /* one extra for spacing */
}
/* draw */
p.y = videor.max.y - h;
for(i = 0; i < nsubfr; i++){
f = &subfr[i];
if(f->timestamp <= ts && f->timestamp+f->dt >= ts){
for(l = 0; l < nelem(f->lines) && f->lines[l] != nil; l++){
p.x = screen->r.min.x + Dx(screen->r)/2 - stringwidth(font, f->lines[l])/2;
stringbg(screen, p, display->white, ZP, font, f->lines[l], display->black, ZP);
p.y += font->height;
}
p.y += font->height; /* spacing */
}
}
}
static void
drawframe(Frame *f)
{
uvlong x, end, left;
Rectangle r, clip;
int y;
x = nanosec();
if(lastframe == 0){
vidts = 0;
firstframe = x;
lastframe = x;
}
end = lastframe + f->dt;
vidts += f->dt;
if(x+dispdelay >= end){
late = x+dispdelay - end;
if(late > 0 && framedrop)
goto drop;
}else{
late = 0;
}
if(curim != nil && (Dx(curim->r) != f->w || Dy(curim->r) != f->h)){
freeimage(curim);
curim = nil;
free(curpx);
curpx = nil;
}
r = Rect(0,0,f->w,f->h);
if(bw){
if(curim == nil)
curim = allocimage(display, r, GREY8, 0, DNofill);
if(curpx == nil)
curpx = malloc(f->w*f->h);
for(y = 0; y < f->h; y++)
memmove(curpx+f->w*y, f->yuv[0]+f->ystride*y, f->w);
loadimage(curim, r, curpx, f->w*f->h);
}else{
if(curim == nil)
curim = allocimage(display, r, XRGB32, 0, DNofill);
if(curpx == nil)
curpx = malloc(f->w*f->h*4);
yuv420_xrgb32(f->w, f->h, f->yuv[0], f->yuv[1], f->yuv[2], f->ystride, f->uvstride, curpx, f->w*4);
loadimage(curim, r, curpx, f->w*f->h*4);
}
dispdelay = nanosec() - x;
if(!benchmark)
while(1){
x = nanosec();
if(x >= end - 1*1000ULL*1000ULL)
break;
left = end - x;
if(left > 80ULL*1000ULL*1000ULL)
sleep(70);
else if(left > 30ULL*1000ULL*1000ULL)
sleep(20);
}
clip = Rect(f->crop.left, f->crop.top, f->crop.right, f->crop.bottom);
if(badrect(clip))
clip = curim->r;
replclipr(curim, 0, clip);
if(Dx(clip) < Dx(screen->r) && Dy(clip) < Dy(screen->r))
r = rectsubpt(screen->r, divpt(Pt(Dx(clip)-Dx(screen->r), Dy(clip)-Dy(screen->r)), 2));
else
r = screen->r;
draw(screen, r, curim, nil, ZP);
videor.min = r.min;
videor.max.x = r.min.x + Dx(clip);
videor.max.y = r.min.y + Dy(clip);
if(showinfo)
stringbg(screen, screen->r.min, display->white, ZP, font, info, display->black, ZP);
dispdelay += nanosec() - x;
drop:
if(audiosync != nil)
send(audiosync, &vidts);
lastframe += f->dt;
free(f);
}
static void
usage(void)
{
fprint(2, "usage: %s [-a audiofile] [-S subtitlesfile] [-t n -t m ...] file.mp4\n", argv0);
threadexitsall("usage");
}
void
threadmain(int argc, char **argv)
{
enum {
Cframe,
Cplayerdone,
Cmouse,
Ckeyboard,
Cresize,
Cnum,
};
Frame *frame;
Mousectl *mctl;
Keyboardctl *kctl;
Rune key;
Mouse m;
char *s, *audio, *sub, *status;
Stream *stream, *svideo, *saudio, *ssub;
Decoder *d;
int i, j, end, done, forced, res, nstreams;
Alt a[Cnum+1] =
{
[Cframe] = { nil, &frame, CHANRCV },
[Cplayerdone] = { nil, &status, CHANRCV },
[Cmouse] = { nil, &m, CHANRCV },
[Ckeyboard] = { nil, &key, CHANRCV },
[Cresize] = { nil, nil, CHANRCV },
[Cnum] = { nil, nil, CHANEND },
};
quotefmtinstall();
fmtinstall('H', encodefmt);
debug = 0;
audio = nil;
sub = nil;
trackpref = 0;
ARGBEGIN{
case 'a':
if(audio != nil){
fprint(2, "only one audio file can be specified\n");
usage();
}
audio = EARGF(usage());
break;
case 'B':
benchmark++;
break;
case 'b':
bw++;
break;
case 'D':
framedrop++;
break;
case 'd':
debug++;
break;
case 'S':
if(sub != nil){
fprint(2, "only one subtitle file can be specified\n");
usage();
}
sub = EARGF(usage());
break;
case 't':
trackpref |= 1<<atoi(EARGF(usage()));
break;
default:
usage();
}ARGEND
if(argc < 1)
usage();
nproc = atoi((s = getenv("NPROC")) != nil ? s : "1");
if(nproc < 1)
nproc = 1;
else if(nproc > 4)
nproc = 4;
srand(nanosec());
if(initdraw(nil, nil, "treason") < 0)
sysfatal("initdraw: %r");
if((mctl = initmouse(nil, screen)) == nil)
sysfatal("initmouse: %r");
if((kctl = initkeyboard(nil)) == nil)
sysfatal("initkeyboard: %r");
a[Cmouse].c = mctl->c;
a[Cresize].c = mctl->resizec;
a[Ckeyboard].c = kctl->c;
saudio = audio ? Sopen(audio, &nstreams) : nil;
ssub = sub ? subopen(sub, &nstreams, &i) : nil;
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");
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 && !benchmark)
saudio = stream+j;
else if(stream[j].type == Ssubtitles && ssub == nil)
ssub = 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;
nsubfr = 0;
if(saudio != nil){
audiosync = chancreate(sizeof(uvlong), 1);
audiofinished = chancreate(sizeof(void*), 0);
proccreate(audioproc, saudio, 4096);
}else{
audiosync = nil;
audiofinished = nil;
}
for(done = forced = 0; !done && !end;){
res = alt(a);
switch(res){
case Cframe:
if(showinfo){
snprint(
info, sizeof(info),
"decode=%-5lld yuv→rgb=%-3lld display=%-3lld total=%-3lld late=%-3lld %dx%d %s %s %s %s",
d->decodetime/1000000ULL,
yuv→rgb/1000000ULL,
dispdelay/1000000ULL,
(d->decodetime + yuv→rgb + dispdelay)/1000000ULL,
late/1000000ULL,
frame->w, frame->h,
d->info,
d->s->info,
saudio ? saudio->info : "no audio",
ssub ? ssub->info : "no subtitles"
);
}
drawframe(frame);
drawsub(ssub);
flushimage(display, 1);
break;
case Cplayerdone:
done = 1;
if(status != nil)
forced = 1;
break;
case Cmouse:
break;
case Ckeyboard:
end = key == 'q' || key == Kdel;
done = key == '\n';
showinfo ^= key == 'i';
if(key == 'p'){
if(!paused){
audiosync = chancreate(sizeof(uvlong), 1);
a[Cframe].op = CHANNOP;
}else{
a[Cframe].op = CHANRCV;
lastframe = nanosec();
firstframe = lastframe - vidts;
}
paused ^= 1;
}
forced = 1;
break;
case Cresize:
if(getwindow(display, Refnone) < 0)
sysfatal("getwindow: %r");
draw(screen, screen->r, display->black, nil, ZP);
flushimage(display, 1);
break;
}
}
if(forced && saudio != nil){
Sclose(saudio);
chanclose(audiosync);
}
if(audiofinished != nil){
recvp(audiofinished);
chanclose(audiofinished);
audiofinished = nil;
}
if(!forced)
Sclose(saudio);
saudio = nil;
if(ssub != nil){
Sclose(ssub);
if(sub != nil)
free(ssub);
}
Dclose(d);
for(i = 0; i < nstreams; i++)
Sclose(stream+i);
free(stream);
}
threadexitsall(nil);
}