shithub: dav1d

ref: de18a5f5d2f4f03b78663d203f72e6099ea59d0e
dir: /src/av19.c/

View raw version
#include "dav1d.h"
#include "tools/input/input.h"
#include <draw.h>
#include <memdraw.h>
#include <mouse.h>
#include <keyboard.h>
#include <tos.h>
/* FIXME this one is slow as hell */
#define STB_IMAGE_RESIZE_IMPLEMENTATION
#define STBIR_MALLOC(x,u) malloc(x)
#define STBIR_FREE(x,u) free(x)
#define STBIR_ASSERT(x) assert(x)
typedef uintptr size_t;
#include "stb_image_resize.h"
typedef struct Frame Frame;
typedef struct Player Player;

struct Frame {
	Dav1dPicture pic;
	uvlong n;
};

struct Player {
	Dav1dData data;
	Dav1dContext *c;
	DemuxerContext *dc;
	Channel *done;
	uvlong fps;
	uvlong lastframe;
	uvlong n;
};

// FIXME why does it need this much?
int mainstacksize = 512*1024;

static Player *curplayer;
static Image *curim;
static Channel **converters;
static QLock framelock;
static uvlong todisplay;
static int resize = 1;
static int maxframes;

/* yuv→rgb by Adrien Descamps */

#define clamp(v) ((v)<0?0 : ((v)>255?255:v))
#define FIXED_POINT_VALUE(value, precision) ((int)(((value)*(1<<precision))+0.5))

typedef struct
{
	u8int cb_factor;   // [(255*CbNorm)/CbRange]
	u8int cr_factor;   // [(255*CrNorm)/CrRange]
	u8int g_cb_factor; // [Bf/Gf*(255*CbNorm)/CbRange]
	u8int g_cr_factor; // [Rf/Gf*(255*CrNorm)/CrRange]
	u8int y_factor;    // [(YMax-YMin)/255]
	u8int y_offset;    // YMin
} YUV2RGBParam;

static char *layout[] = {
	[DAV1D_PIXEL_LAYOUT_I400] = "i400",
    [DAV1D_PIXEL_LAYOUT_I420] = "i420",
    [DAV1D_PIXEL_LAYOUT_I422] = "i422",
    [DAV1D_PIXEL_LAYOUT_I444] = "i444",
};

#define YUV2RGB_PARAM(Rf, Bf, YMin, YMax, CbCrRange) \
{.cb_factor=FIXED_POINT_VALUE(255.0*(2.0*(1-Bf))/CbCrRange, 6), \
.cr_factor=FIXED_POINT_VALUE(255.0*(2.0*(1-Rf))/CbCrRange, 6), \
.g_cb_factor=FIXED_POINT_VALUE(Bf/(1.0-Bf-Rf)*255.0*(2.0*(1-Bf))/CbCrRange, 7), \
.g_cr_factor=FIXED_POINT_VALUE(Rf/(1.0-Bf-Rf)*255.0*(2.0*(1-Rf))/CbCrRange, 7), \
.y_factor=FIXED_POINT_VALUE(255.0/(YMax-YMin), 7), \
.y_offset=YMin}

static const YUV2RGBParam YUV2RGB[3] = {
	// ITU-T T.871 (JPEG)
	YUV2RGB_PARAM(0.299, 0.114, 0.0, 255.0, 255.0),
	// ITU-R BT.601-7
	YUV2RGB_PARAM(0.299, 0.114, 16.0, 235.0, 224.0),
	// ITU-R BT.709-6
	YUV2RGB_PARAM(0.2126, 0.0722, 16.0, 235.0, 224.0)
};

static void yuv420_rgb24(
	u32int width, u32int height,
	const u8int *Y, const u8int *U, const u8int *V, u32int Y_stride, u32int UV_stride,
	u8int *RGB, u32int RGB_stride)
{
	const YUV2RGBParam *const param = &(YUV2RGB[0]);
	u32int x, y;
	for(y=0; y<(height-1); y+=2)
	{
		const u8int *y_ptr1=Y+y*Y_stride,
			*y_ptr2=Y+(y+1)*Y_stride,
			*u_ptr=U+(y/2)*UV_stride,
			*v_ptr=V+(y/2)*UV_stride;

		u8int *rgb_ptr1=RGB+y*RGB_stride,
			*rgb_ptr2=RGB+(y+1)*RGB_stride;

		for(x=0; x<(width-1); x+=2)
		{
			s8int u_tmp, v_tmp;
			u_tmp = u_ptr[0]-128;
			v_tmp = v_ptr[0]-128;

			//compute Cb Cr color offsets, common to four pixels
			s16int b_cb_offset, r_cr_offset, g_cbcr_offset;
			b_cb_offset = (param->cb_factor*u_tmp)>>6;
			r_cr_offset = (param->cr_factor*v_tmp)>>6;
			g_cbcr_offset = (param->g_cb_factor*u_tmp + param->g_cr_factor*v_tmp)>>7;

			s16int y_tmp;
			y_tmp = (param->y_factor*(y_ptr1[0]-param->y_offset))>>7;
			rgb_ptr1[2] = clamp(y_tmp + r_cr_offset);
			rgb_ptr1[1] = clamp(y_tmp - g_cbcr_offset);
			rgb_ptr1[0] = clamp(y_tmp + b_cb_offset);

			y_tmp = (param->y_factor*(y_ptr1[1]-param->y_offset))>>7;
			rgb_ptr1[5] = clamp(y_tmp + r_cr_offset);
			rgb_ptr1[4] = clamp(y_tmp - g_cbcr_offset);
			rgb_ptr1[3] = clamp(y_tmp + b_cb_offset);

			y_tmp = (param->y_factor*(y_ptr2[0]-param->y_offset))>>7;
			rgb_ptr2[2] = clamp(y_tmp + r_cr_offset);
			rgb_ptr2[1] = clamp(y_tmp - g_cbcr_offset);
			rgb_ptr2[0] = clamp(y_tmp + b_cb_offset);

			y_tmp = (param->y_factor*(y_ptr2[1]-param->y_offset))>>7;
			rgb_ptr2[5] = clamp(y_tmp + r_cr_offset);
			rgb_ptr2[4] = clamp(y_tmp - g_cbcr_offset);
			rgb_ptr2[3] = clamp(y_tmp + b_cb_offset);

			rgb_ptr1 += 6;
			rgb_ptr2 += 6;
			y_ptr1 += 2;
			y_ptr2 += 2;
			u_ptr += 1;
			v_ptr += 1;
		}
	}
}

static void
freeframe(Frame *f)
{
	dav1d_picture_unref(&f->pic);
	free(f);
}

static uvlong
nanosec(void)
{
	static uvlong fasthz, xstart;
	uvlong x, div;

	if(fasthz == ~0ULL)
		return nsec() - xstart;

	if(fasthz == 0){
		if((fasthz = _tos->cyclefreq) == 0){
			fasthz = ~0ULL;
			xstart = nsec();
			fprint(2, "cyclefreq not available, falling back to nsec()\n");
			fprint(2, "you might want to disable aux/timesync\n");
			return 0;
		}else{
			cycles(&xstart);
		}
	}
	cycles(&x);
	x -= xstart;

	/* this is ugly */
	for(div = 1000000000ULL; x < 0x1999999999999999ULL && div > 1 ; div /= 10ULL, x *= 10ULL);

	return x / (fasthz / div);
}

static void
drawframe(void)
{
	uvlong thisframe, start;
	static uvlong delay;

	thisframe = curplayer->lastframe + 1000000000ULL/curplayer->fps - delay;
	while(nanosec() < thisframe)
		sleep(10);

	lockdisplay(display);
	start = nanosec();
	draw(screen, screen->r, curim, nil, ZP);
	flushimage(display, 1);
	unlockdisplay(display);
	delay = nanosec() - start;
	curplayer->lastframe = start;
}

static void
playerproc(void *aux)
{
	Player *p;
	Frame *f;
	int res;

	p = aux;

	do{
		res = dav1d_send_data(p->c, &p->data);
		if(res < 0 && res != DAV1D_ERR(EAGAIN)){
			fprint(2, "dav1d_send_data: %d\n", res);
			break;
		}else{
			f = calloc(1, sizeof(*f));
			if((res = dav1d_get_picture(p->c, &f->pic)) < 0){
				if(res != DAV1D_ERR(EAGAIN)){
					fprint(2, "dav1d_get_picture: %d\n", res);
					break;
				}
			}else{
				f->n = p->n++;
				sendp(converters[f->n % maxframes], f);
			}
		}
	}while(p->data.sz > 0 || input_read(p->dc, &p->data) == 0);

	if(p->data.sz > 0)
		dav1d_data_unref(&p->data);

	// FIXME there might be more here

	sendul(p->done, 1);

	threadexits(nil);
}

static void
freeplayer(Player *p)
{
	// FIXME
	chanfree(p->done);
	free(p);
}

static Player *
newplayer(char *filename)
{
	Player *p;
	unsigned fps[2], timebase[2], total;
	Dav1dSettings av1s;

	p = calloc(1, sizeof(*p));
	if(input_open(&p->dc, "ivf", filename, fps, &total, timebase) < 0){
		werrstr("input_open");
		goto err;
	}
	p->fps = fps[0]/fps[1]; // FIXME that's not precise
	if(input_read(p->dc, &p->data) < 0){
		werrstr("input_read");
		goto err;
	}

	dav1d_default_settings(&av1s);
	av1s.n_frame_threads = maxframes;
	av1s.n_tile_threads = maxframes;

	if(dav1d_open(&p->c, &av1s) != 0){
		werrstr("dav1d_open");
		goto err;
	}

	p->done = chancreate(sizeof(ulong), 0);
	p->lastframe = 0;

	proccreate(playerproc, p, mainstacksize);

	return p;
err:
	werrstr("%s: %r", filename);
	free(p);
	return nil;
}

static void
rgbready(uchar *rgb, int w, int h, uvlong n)
{
	Rectangle r;

	r = Rect(0,0,w,h);
	for(;;){
		qlock(&framelock);
		if(todisplay == n){
			if(curim != nil && Dx(curim->r) != w){
				freeimage(curim);
				curim = nil;
			}
			if(curim == nil)
				curim = allocimage(display, r, RGB24, 0, DNofill);
			loadimage(curim, r, rgb, w*h*3);
			free(rgb);
			drawframe();
			todisplay++;
			qunlock(&framelock);
			break;
		}
		qunlock(&framelock);
		sleep(10);
	}
}

static void
converterproc(void *c)
{
	Frame *f;
	uchar *rgb;
	Dav1dPicture *p;
	uchar *out;
	int w, h;

	for(; (f = recvp(c)) != nil;){
		p = &f->pic;
		if((rgb = malloc(p->p.w * p->p.h * 3)) != nil){
			yuv420_rgb24(p->p.w, p->p.h, p->data[0], p->data[1], p->data[2], p->stride[0], p->stride[1], rgb, p->p.w*3);
			w = p->p.w;
			h = p->p.h;

			if(resize){
				w = Dx(screen->r);
				h = Dy(screen->r);
				if((out = malloc(w*h*3)) != nil){
					stbir_resize_uint8_generic(
						rgb, p->p.w, p->p.h, p->p.w*3,
						out, w, h, w*3,
						3, -1, 0,
						STBIR_EDGE_CLAMP, STBIR_FILTER_MITCHELL, STBIR_COLORSPACE_LINEAR,
						NULL);
				}
				free(rgb);
				rgb = out;
			}
			if(rgb != nil)
				rgbready(rgb, w, h, f->n);
		}

		freeframe(f);
	}

	threadexits(nil);
}

void
threadmain(int argc, char **argv)
{
	enum {
		Cplayerdone,
		Cmouse,
		Ckeyboard,
		Cresize,
		Cnum,
	};
	Mousectl *mctl;
	Keyboardctl *kctl;
	char *s;
	Rune key;
	Mouse m;
	int i, end, done;
	Alt a[Cnum+1] =
	{
		[Cplayerdone] = { nil, nil, CHANRCV },
		[Cmouse] = { nil, &m, CHANRCV },
		[Ckeyboard] = { nil, &key, CHANRCV },
		[Cresize] =  { nil, nil, CHANRCV },
		[Cnum] = { nil, nil, CHANEND },
	};

	ARGBEGIN{
	}ARGEND

	if(argc < 1)
		sysfatal("usage");

	srand(nanosec());
	if(initdraw(nil, nil, "treason") < 0)
		sysfatal("initdraw: %r");
	display->locking = 1;
	unlockdisplay(display);
	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;

	maxframes = atoi((s = getenv("NPROC")) != nil ? s : "1");
	if(maxframes < 1)
		maxframes = 1;
	converters = calloc(maxframes, sizeof(*converters));
	for(i = 0; i < maxframes; i++){
		converters[i] = chancreate(sizeof(Frame*), 0);
		proccreate(converterproc, converters[i], 4096);
	}

	for(end = i = 0; !end && i < argc; i++){
		todisplay = 0;
		if((curplayer = newplayer(argv[0])) == nil)
			sysfatal("%r");

		a[Cplayerdone].c = curplayer->done;

		for(done = 0; !done && !end;){
			switch(alt(a)){
			case Cplayerdone:
				done = 1;
				break;

			case Cmouse:
				break;

			case Ckeyboard:
				if(key == 'q' || key == Kdel){
					end = 1;
					break;
				}
				if(key == 'r')
					resize = !resize;
				break;

			case Cresize:
				qlock(&framelock);
				if(getwindow(display, Refnone) < 0)
					sysfatal("getwindow: %r");
				lockdisplay(display);
				freeimage(curim);
				curim = nil;
				unlockdisplay(display);
				qunlock(&framelock);
				break;
			}
		}

		freeplayer(curplayer);
	}

	for(i = 0; i < nelem(converters); i++)
		chanclose(converters[i]);

	threadexitsall(nil);
}