ref: d696956ababd5fdec5bcb51373896468c310161d
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 uvlong dispdelay; static int framedrop; static uvlong late; static int bw; static int benchmark; static void audioproc(void *x) { Streamframe f; Stream *s; int audiofd, synced; synced = 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){ recvp(audiosync); chanclose(audiosync); synced = 1; } write(audiofd, f.buf, f.sz); } close(audiofd); } if(!synced){ recvp(audiosync); 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){ firstframe = x; lastframe = x; } end = lastframe + 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) sendp(audiosync, nil); 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(void*), 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' ? !showinfo : showinfo; 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); }