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