shithub: qoistream

ref: 68a4d1cef5db92f15102ec14ae327430150e8d56
dir: /qoisend.c/

View raw version
#include <u.h>
#include <libc.h>
#include <thread.h>
#include <tos.h>

enum {
	Opind = 0x00,
	Opdif = 0x40,
	Oplum = 0x80,
	Oprun = 0xc0,
	Oprgb = 0xfe,
};

#define Nsec 1000000000ULL
#define Nmsec 1000000ULL

typedef struct Img Img;
typedef union Pix Pix;

struct Img {
	int w;
	int h;
	u8int bgrx[];
};

union Pix {
	struct {
		u8int r, g, b, a;
	};
	u32int v;
};

static Channel *frame, *done;
int mainstacksize = 8192;

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

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

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

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

	return x / (fasthz / div);
}

static void
nsleep(uvlong ns)
{
	uvlong start, end;

	start = nanosec();
	end = start + ns;
	ns = start;
	do{
		if(end - ns > 85*Nmsec)
			sleep(80);
		else if (end - ns > 25*Nmsec)
			sleep(20);
		else if (end - ns > 1*Nmsec)
			sleep(1);
		else
			break;
		ns = nanosec();
	}while(ns < end);
}

static Img *
imgread(int in)
{
	char tmp[61], *f[5];
	int r, n, e, w, h;
	Img *i;

	tmp[60] = 0;
	if(pread(in, tmp, 60, 0) != 60 || tokenize(tmp, f, 5) != 5)
		sysfatal("invalid image");
	if(strcmp(f[0]+1, "8r8g8b8") != 0)
		sysfatal("only [ax]8r8g8b8 is supported");
	w = atoi(f[3]) - atoi(f[1]);
	h = atoi(f[4]) - atoi(f[2]);

	e = w*h*4;
	i = malloc(sizeof(*i) + e);
	i->w = w;
	i->h = h;
	for(n = 0; n < e; n += r){
		if((r = pread(in, i->bgrx+n, e-n, n+5*12)) <= 0){
			free(i);
			return nil;
		}
	}

	return i;
}

static void
encthread(void *)
{
	int i, j, bsz, run, indpos;
	Pix p[64], pix, prevpix;
	Img *img, *prev;
	u8int *b, *x;

	threadsetname("qoi/encthread");
	prev = nil;
	b = nil;
	bsz = 0;
	for(;;){
		if(recv(frame, &img) < 0)
			break;
		if(prev != nil && memcmp(img->bgrx, prev->bgrx, img->w*img->h*4) == 0){
			free(img);
			continue;
		}else if(bsz < (i = 14+img->w*img->h*4)){
			b = realloc(b, bsz = i); /* a lot of extra for the worst case */
			if(b == nil)
				sysfatal("memory");
			b[0] = 'q';
			b[1] = 'o';
			b[2] = 'i';
			b[3] = 'f';
			b[12] = 3;
			b[13] = 0;
		}
		b[4] = img->w>>24;
		b[5] = img->w>>16;
		b[6] = img->w>>8;
		b[7] = img->w;
		b[8] = img->h>>24;
		b[9] = img->h>>16;
		b[10] = img->h>>8;
		b[11] = img->h;
		memset(p, 0, sizeof(p));
		memset(&prevpix, 0, sizeof(0));
		prevpix.a = 255;

		x = img->bgrx;
		run = 0;
		j = 14;
		for(i = 0; i < img->w*img->h; i++){
			pix.b = *x++;
			pix.g = *x++;
			pix.r = *x++;
			x++;

			if(pix.v == prevpix.v){
				run++;
				if(run == 62 || i+1 == img->w*img->h){
					b[j++] = Oprun | (run-1);
					run = 0;
				}
			}else{
				if(run > 0){
					b[j++] = Oprun | (run-1);
					run = 0;
				}
				indpos = (pix.r*3 + pix.g*5 + pix.b*7 + 255*11) % 64;
				if(pix.v == p[indpos].v){
					b[j++] = Opind | indpos;
				}else{
					signed char Δr, Δg, Δb, Δgr, Δgb;
					p[indpos].v = pix.v;
					Δr = pix.r - prevpix.r;
					Δg = pix.g - prevpix.g;
					Δb = pix.b - prevpix.b;
					Δgr = Δr - Δg;
					Δgb = Δb - Δg;
					if(Δr > -3 && Δr < 2 && Δg > -3 && Δg < 2 && Δb > -3 && Δb < 2){
						b[j++] = Opdif | (Δr + 2)<<4 | (Δg + 2)<<2 | (Δb + 2);
					}else if(Δgr > -9 && Δgr < 8 && Δg > -33 && Δg < 32 && Δgb > -9 && Δgb < 8){
						b[j++] = Oplum | (Δg + 32);
						b[j++] = (Δgr + 8)<<4 | (Δgb + 8);
					}else{
						b[j++] = Oprgb;
						b[j++] = pix.r;
						b[j++] = pix.g;
						b[j++] = pix.b;
					}
				}
			}
			prevpix.v = pix.v;
		}
		/* padding */
		for(i = 0; i < 7; i++)
			b[j++] = 0;
		b[j++] = 1;

		if(write(1, b, j) != j)
			break;

		free(prev);
		prev = img;
	}
	sendul(done, 0);

	threadexits(nil);
}

static void
usage(void)
{
	fprint(2, "usage: %s [-f FPS] FILE\n", argv0);
	threadexitsall("usage");
}

void
threadmain(int argc, char **argv)
{
	uvlong fstart, fend, f₀, nframes;
	int fps, in, one, debug;
	Img *img;

	one = 0;
	debug = 0;
	fps = 60;
	ARGBEGIN{
	case '1':
		one++;
		break;
	case 'd':
		debug++;
		break;
	case 'f':
		fps = atoi(EARGF(usage()));
		break;
	default:
		usage();
	}ARGEND

	if(argc != 1)
		usage();
	if((in = open(*argv, OREAD)) < 0)
		sysfatal("input: %r");

	frame = chancreate(sizeof(void*), 1);
	done = chancreate(sizeof(ulong), 1);
	proccreate(encthread, nil, mainstacksize);

	f₀ = nanosec();
	for(nframes = 0;;){
		fstart = nanosec();
		while((img = imgread(in)) == nil);
		if(sendp(frame, img) != 1)
			break;
		if(one){
			chanclose(frame);
			recvul(done);
		}
		fend = nanosec();
		nframes++;

		if(debug && nframes > 0 && (nframes % fps) == 0){
			fprint(2, "avg fps: %llud\n", nframes/((fend - f₀)/Nsec));
			f₀ = nanosec();
			nframes = -1;
		}

		if(Nsec/fps > (fend - fstart))
			nsleep(Nsec/fps - (fend - fstart));
	}

	threadexitsall(nil);
}