shithub: treason

ref: febd271a231c83b67d94e6cb2b9942edfe37e363
dir: /main.c/

View raw version
#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,
						dispdelay/1000000ULL,
						yuv→rgb/1000000ULL,
						(d->decodetime + 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);
}