/* * Pixelflut UDP renderer. * * Reads raw RGB24 frames that main.py continuously writes to INPUT_FILE * (via mmap) and floods them at a Pixelflut server using the binary UDP * protocol. Pixels are picked in a random order so the picture "dissolves" * onto the wall instead of being drawn scanline by scanline. * * Binary Pixelflut packet layout (little-endian on the wire): * * +------+---------+ pixelflut_packet * | cmd | subcmd | 2-byte header * +------+---------+---------+----+----+----+ pixelflut_pixel[0] * | x (u16) | y (u16) | r | g | b | 7 bytes * +----------------+---------+----+----+----+ * | ... | PIXELS_PER_PACKET pixels * +----------------------------------------+ */ #define _GNU_SOURCE /* sendmmsg(), struct mmsghdr, SO_BINDTODEVICE */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Portable host -> little-endian 16-bit conversion (matches the wire format). */ #if defined(__linux__) # include #elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) # include #elif defined(__APPLE__) # include # define htole16(v) OSSwapHostToLittleInt16(v) #endif /* ---- Frame geometry (must match the producer, see main.py). ------------- */ #define FRAME_WIDTH 640 #define FRAME_HEIGHT 360 #define FRAME_CHANNELS 3 /* RGB, one byte per channel */ #define FRAME_PIXELS (FRAME_WIDTH * FRAME_HEIGHT) #define FRAME_BYTES (FRAME_PIXELS * FRAME_CHANNELS) /* Top-left corner where the frame is drawn on the shared wall. */ #define CANVAS_OFFSET_X 1200 #define CANVAS_OFFSET_Y 40 /* ---- Pixelflut binary protocol. ---------------------------------------- */ #define PIXELFLUT_CMD_SET 0x00 /* "set pixels" command ... */ #define PIXELFLUT_SUBCMD_RGB 0x01 /* ... with 3-byte RGB colours */ #define PIXELS_PER_PACKET 120 /* pixels batched into one UDP datagram */ /* Number of datagrams needed to cover a whole frame once. */ #define PACKET_COUNT (FRAME_PIXELS / PIXELS_PER_PACKET) /* ---- Target / input. --------------------------------------------------- */ /* #define DISPLAY_HOST "100.65.0.2" */ #define DISPLAY_HOST "fcda:f100::d" #define DISPLAY_PORT 5005 #define INPUT_FILE "/tmp/video" /* A single pixel as it appears on the wire. */ #pragma pack(push, 1) struct pixelflut_pixel { uint16_t x; /* little-endian */ uint16_t y; /* little-endian */ uint8_t r; uint8_t g; uint8_t b; }; /* A full datagram: header followed by a batch of pixels. */ struct pixelflut_packet { uint8_t cmd; uint8_t subcmd; struct pixelflut_pixel pixels[PIXELS_PER_PACKET]; }; #pragma pack(pop) _Static_assert(sizeof(struct pixelflut_pixel) == 7, "pixel must be exactly 7 bytes on the wire"); _Static_assert(FRAME_PIXELS % PIXELS_PER_PACKET == 0, "frame pixels must divide evenly into packets"); /* Pre-built datagrams and the scatter/gather metadata sendmmsg() needs. */ static struct pixelflut_packet packets[PACKET_COUNT]; static struct mmsghdr messages[PACKET_COUNT]; static struct iovec iovecs[PACKET_COUNT]; /* xorshf96 PRNG state (Marsaglia), period 2^96-1. */ static unsigned long rng_x = 123456789; static unsigned long rng_y = 362436069; static unsigned long rng_z = 521288629; static unsigned long xorshf96(void) { rng_x ^= rng_x << 16; rng_x ^= rng_x >> 5; rng_x ^= rng_x << 1; unsigned long t = rng_x; rng_x = rng_y; rng_y = rng_z; rng_z = t ^ rng_x ^ rng_y; return rng_z; } /* Continuously blast the pre-built packets at the display. */ static void *send_thread(void *arg) { const char *ifname = arg; int fd = socket(AF_INET6, SOCK_DGRAM, 0); if (fd == -1) { perror("socket"); exit(EXIT_FAILURE); } #ifdef SO_BINDTODEVICE /* Best effort: pin traffic to the given interface (needs privileges). */ if (ifname != NULL && ifname[0] != '\0') { struct ifreq ifr; memset(&ifr, 0, sizeof ifr); snprintf(ifr.ifr_name, sizeof ifr.ifr_name, "%s", ifname); if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof ifr) == -1) { perror("setsockopt(SO_BINDTODEVICE)"); } } #else (void)ifname; #endif struct sockaddr_in6 addr; memset(&addr, 0, sizeof addr); addr.sin6_family = AF_INET6; addr.sin6_port = htons(DISPLAY_PORT); if (inet_pton(AF_INET6, DISPLAY_HOST, &addr.sin6_addr) != 1) { fprintf(stderr, "invalid display host: %s\n", DISPLAY_HOST); exit(EXIT_FAILURE); } if (connect(fd, (struct sockaddr *)&addr, sizeof addr) == -1) { perror("connect"); exit(EXIT_FAILURE); } sleep(1); /* give the producer time to fill the first frame */ int next = 0; for (;;) { int sent = sendmmsg(fd, &messages[next], PACKET_COUNT - next, 0); if (sent == -1) { perror("sendmmsg"); continue; } next += sent; /* resume after a partial send, wrap at the end */ if (next >= PACKET_COUNT) next = 0; } return NULL; /* unreachable */ } int main(int argc, char *argv[]) { if (argc != 2) { fprintf(stderr, "usage: %s \n", argv[0]); return EXIT_FAILURE; } rng_x = (unsigned long)rand(); rng_y = (unsigned long)rand(); rng_z = (unsigned long)rand(); /* Pre-fill the constant parts of every datagram once. */ for (int i = 0; i < PACKET_COUNT; i++) { packets[i].cmd = PIXELFLUT_CMD_SET; packets[i].subcmd = PIXELFLUT_SUBCMD_RGB; iovecs[i].iov_base = &packets[i]; iovecs[i].iov_len = sizeof packets[i]; messages[i].msg_hdr.msg_iov = &iovecs[i]; messages[i].msg_hdr.msg_iovlen = 1; } int frame_fd = open(INPUT_FILE, O_RDONLY); if (frame_fd == -1) { perror("open " INPUT_FILE); return EXIT_FAILURE; } const uint8_t *frame = mmap(NULL, FRAME_BYTES, PROT_READ, MAP_SHARED, frame_fd, 0); if (frame == MAP_FAILED) { perror("mmap"); return EXIT_FAILURE; } pthread_t sender; if (pthread_create(&sender, NULL, send_thread, argv[1]) != 0) { perror("pthread_create"); return EXIT_FAILURE; } /* Repeatedly repaint the frame in a random pixel order. */ for (;;) { for (int i = 0; i < PACKET_COUNT; i++) { for (int j = 0; j < PIXELS_PER_PACKET; j++) { uint64_t src = xorshf96() % FRAME_PIXELS; uint16_t fx = (uint16_t)(src % FRAME_WIDTH); uint16_t fy = (uint16_t)(src / FRAME_WIDTH); const uint8_t *rgb = &frame[src * FRAME_CHANNELS]; struct pixelflut_pixel *px = &packets[i].pixels[j]; px->x = htole16((uint16_t)(CANVAS_OFFSET_X + fx)); px->y = htole16((uint16_t)(CANVAS_OFFSET_Y + fy)); px->r = rgb[0]; px->g = rgb[1]; px->b = rgb[2]; } } } }