shithub: qoistream

Download patch

ref: f4a3042aada0c8012de84907baae3ab193032a43
author: Sigrid Solveig Haflínudóttir <sigrid@ftrv.se>
date: Tue Jul 18 22:47:13 EDT 2023

that's a first

--- /dev/null
+++ b/README.md
@@ -1,0 +1,9 @@
+# qoistream
+
+The idea is to capture screen (or a window) and encode it as a stream
+of [QOI](https://qoiformat.org) images.  That stream is supposed to be
+sent over ethernet to another machine and displayed there.  Kind of
+like a cheap screen sharing.  Each frame is lossless but smaller than
+raw images (~620Kb vs ~8Mb for full hd, for example).
+
+Work in progress.
--- /dev/null
+++ b/main.c
@@ -1,0 +1,278 @@
+#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
+
+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*Nsec)
+			sleep(80);
+		else if (end - ns > 25*Nsec)
+			sleep(20);
+		else if (end - ns > 1*Nsec)
+			sleep(1);
+		else
+			break;
+		ns = nanosec();
+	}while(ns < end);
+}
+
+static Img *
+imgread(int f, int w, int h)
+{
+	int r, n, e;
+	Img *i;
+
+	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(f, i->bgrx+n, e-n, n+5*12)) <= 0){
+			free(i);
+			return nil;
+		}
+	}
+
+	return i;
+}
+
+static void
+encthread(void *)
+{
+	Pix p[64], pix, prevpix;
+	int i, j, run, indpos;
+	Img *img, *prev;
+	u8int *b, *x;
+
+	threadsetname("qoi/encthread");
+	memset(p, 0, sizeof(p));
+	memset(&prevpix, 0, sizeof(0));
+	prevpix.a = 255;
+	prev = nil;
+	b = nil;
+	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(prev == nil){
+			b = malloc(14 + img->w*img->h*4); /* a lot of extra for the worst case */
+			b[0] = 'q';
+			b[1] = 'o';
+			b[2] = 'i';
+			b[3] = 'f';
+			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;
+			b[12] = 3;
+			b[13] = 0;
+		}
+
+		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, w, h, one, debug;
+	char tmp[61], *f[5];
+	Img *img;
+
+	one = 0;
+	debug = 0;
+	fps = 30;
+	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");
+
+	tmp[60] = 0;
+	if(readn(in, tmp, 60) != 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]);
+	frame = chancreate(sizeof(void*), 1);
+	done = chancreate(sizeof(ulong), 1);
+	proccreate(encthread, nil, mainstacksize);
+
+	f₀ = nanosec();
+	for(nframes = 0;; nframes++){
+		fstart = nanosec();
+		if((img = imgread(in, w, h)) == nil)
+			break;
+		if(sendp(frame, img) != 1)
+			break;
+		if(one){
+			chanclose(frame);
+			recvul(done);
+		}
+		fend = nanosec();
+
+		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);
+}
--- /dev/null
+++ b/mkfile
@@ -1,0 +1,13 @@
+</$objtype/mkfile
+
+BIN=/$objtype/bin/video
+TARG=qoi
+
+HFILES=\
+
+OFILES=\
+	main.$O\
+
+default:V: all
+
+</sys/src/cmd/mkone