diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..003df8c --- /dev/null +++ b/Makefile @@ -0,0 +1,18 @@ +CC ?= cc +CFLAGS ?= -std=c11 -O2 -pthread \ + -Wall -Wextra -Wpedantic -Wshadow -Wformat=2 \ + -Wcast-align -Wstrict-prototypes -Wmissing-prototypes \ + -Wpointer-arith -Wwrite-strings +LDFLAGS ?= -pthread + +TARGET := pixelflut-render +SRC := new.c + +$(TARGET): $(SRC) + $(CC) $(CFLAGS) -o $@ $(SRC) $(LDFLAGS) + +clean: + rm -f $(TARGET) + +.PHONY: clean + diff --git a/new.c b/new.c index 1ec68f6..8a18c1d 100644 --- a/new.c +++ b/new.c @@ -1,143 +1,239 @@ -#include -#include +/* + * 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 +#include +#include +#include +#include -#define PIXELS 120 -#define MSGSIZE (2 + 7 * PIXELS) -#define WIDTH (640) -#define HEIGHT (360) -#define OFFSET_X 1200 -#define OFFSET_Y 40 -//#define DISPLAY_HOST "100.65.0.2" +#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 SIZE WIDTH*HEIGHT*3 -#define INPUT_FILE "/tmp/video" -#define RANDOMNESS_LEN (WIDTH*HEIGHT) -#include -struct mmsghdr msg[RANDOMNESS_LEN/PIXELS]; -struct iovec iovec[RANDOMNESS_LEN/PIXELS]; -uint8_t buf[RANDOMNESS_LEN/PIXELS][MSGSIZE]; -uint64_t randomness[RANDOMNESS_LEN]; -int randomness_o =0; -static unsigned long x=123456789, y=362436069, z=521288629; +#define INPUT_FILE "/tmp/video" -unsigned long xorshf96(void) { //period 2^96-1 - unsigned long t; - x ^= x << 16; - x ^= x >> 5; - x ^= x << 1; +/* 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; +}; - t = x; - x = y; - y = z; - z = t ^ x ^ y; +/* 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) - return z; -} -void shuffle(uint64_t *array, size_t n) +_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) { - if (n > 1) - { - size_t i; - for (i = 0; i < n - 1; i++) - { - size_t j = i + rand() / (RAND_MAX / (n - i) + 1); - unsigned long t = array[j]; - array[j] = array[i]; - array[i] = t; - } - } -} -void *send_thread(void * ifname){ - int sockfd; - struct sockaddr_in6 addr; - sockfd = socket(AF_INET6, SOCK_DGRAM, 0); - if (sockfd == -1) { - perror("socket()"); - exit(EXIT_FAILURE); - } - struct ifreq ifr; - memset(&ifr, 0, sizeof(ifr)); - snprintf(ifr.ifr_name, sizeof(ifr.ifr_name),(char *) ifname); - addr.sin6_family = AF_INET6; - addr.sin6_port = htons(DISPLAY_PORT); - inet_pton(AF_INET6, DISPLAY_HOST, &addr.sin6_addr); + rng_x ^= rng_x << 16; + rng_x ^= rng_x >> 5; + rng_x ^= rng_x << 1; - if (connect(sockfd, (struct sockaddr *) &addr, sizeof(addr)) == -1) { - perror("connect()"); - exit(EXIT_FAILURE); - } - int from = 0; - sleep(1); - while(1){ - int retval = sendmmsg(sockfd, &msg[from], (RANDOMNESS_LEN/PIXELS)-from, 0); - if (retval == -1) { - perror("sendmmsg()"); - } - from += retval; - if(from <= (RANDOMNESS_LEN/PIXELS)){ - from = 0; - } - } -} -int main(int argc, char *argv[]) { - printf(argv[1]); - x=rand(); - y=rand(); - z=rand(); - for(int i = 0; i < RANDOMNESS_LEN; i++) { - randomness[i] = i; - } - shuffle(randomness,RANDOMNESS_LEN); - for(int i = 0; i < RANDOMNESS_LEN/PIXELS; i++){ - iovec[i].iov_base = buf[i]; - iovec[i].iov_len = MSGSIZE; - msg[i].msg_hdr.msg_iov = &iovec[i]; - msg[i].msg_hdr.msg_iovlen = 1; - buf[i][0] = 0x00; - buf[i][1] = 0x01; - } - int fd = open(INPUT_FILE,O_RDONLY); - printf("fd: %d\n", fd); - uint8_t * data = mmap(NULL, SIZE, PROT_READ , MAP_SHARED, fd, 0); - if (data == MAP_FAILED) { - perror("mmap"); - } - pthread_t th1,th2,th3,th4; - pthread_create(&th1, NULL, send_thread, argv[1]); - while (1) { - for(int i = 0; i < (int)(RANDOMNESS_LEN/PIXELS); i++){ - for(int j=0; j> 8) & 0xff; - buf[i][2+j*7+2] = draw_y & 0xff; - buf[i][2+j*7+3] = (draw_y >> 8) & 0xff; - buf[i][2+j*7+4] = *(data+pixel*3+0) & 0xff; - buf[i][2+j*7+5] = *(data+pixel*3+1) & 0xff; - buf[i][2+j*7+6] = *(data+pixel*3+2) & 0xff; + unsigned long t = rng_x; + rng_x = rng_y; + rng_y = rng_z; + rng_z = t ^ rng_x ^ rng_y; - } - } - } - exit(0); + 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]; + } + } + } }