/* ------------------------------------------------------------- * waveform_tcp.c * * * 600×400 ARGB8888 frame is streamed over a single‑client TCP socket. * * The received frame is transformed so that the resulting bitmap * can be displayed on a video waveform monitor and will look like * the original picture. * * Build (Linux/macOS): * gcc -Wall -Wextra -pedantic -std=c11 waveform_tcp.c \ * -lSDL2 -lpthread -o waveform_tcp * * Run: * ./waveform_tcp # opens a window and listens on 0.0.0.0:12345 * * Test client (Unix‑like, sends one frame of solid gray): * dd if=/dev/zero bs=1 count=$((600*400*4)) | tr \\0 \\377 > gray.bin * cat gray.bin | nc 127.0.0.1 12345 * * ------------------------------------------------------------- */ #include #include #include #include #include // memcpy, memset #include // close, read, write #include // fcntl #include #include #include #include #include /* ------------------------------------------------------------- * Configuration * ------------------------------------------------------------- */ #define WIDTH 800 #define HEIGHT 600 #define LISTEN_PORT 12345 // change if you like #define BACKLOG 1 // only one pending connection /* ------------------------------------------------------------- * Simple pixel helpers (ARGB8888 – 0xAARRGGBB in memory) * ------------------------------------------------------------- */ typedef Uint32 pixel_t; static inline pixel_t make_pixel(Uint8 r, Uint8 g, Uint8 b, Uint8 a) { return (pixel_t)((a << 24) | (r << 16) | (g << 8) | b); } #define MAKE_OPAQUE_PIXEL(r,g,b) make_pixel((r),(g),(b),0xFF) /* ------------------------------------------------------------- * Global buffers * ------------------------------------------------------------- */ /* 1. The raw frame received from the network */ static pixel_t net_frame[HEIGHT][WIDTH]; /* 2. The image that we actually draw (after the waveform conversion) */ static pixel_t draw_buf[HEIGHT][WIDTH]; /* ------------------------------------------------------------- * Demo pattern – what we see until the first network frame arrives * ------------------------------------------------------------- */ static void init_demo_pattern(void) { for (int y = 0; y < HEIGHT; ++y) { for (int x = 0; x < WIDTH; ++x) { Uint8 r = (HEIGHT-y)*255/HEIGHT;//(Uint8)((x * 255) / (WIDTH - 1)); Uint8 g = (HEIGHT-y)*255/HEIGHT;//(Uint8)((y * 255) / (HEIGHT - 1)); Uint8 b = (HEIGHT-y)*255/HEIGHT;//0x80; draw_buf[y][x] = MAKE_OPAQUE_PIXEL(r, g, b); } } } /* ------------------------------------------------------------- * Luminance conversion – ITU‑BT.601 (Y = 0.299R + 0.587G + 0.114B) * ------------------------------------------------------------- */ static inline Uint8 rgb_to_luma(Uint8 r, Uint8 g, Uint8 b) { /* weighted sum, integer arithmetic, rounded */ return (Uint8)((299 * r + 587 * g + 114 * b) / 1000); } /* ------------------------------------------------------------- * Transform the raw ARGB frame into a waveform‑ready image. * * * dest is the global draw_buf * * every source pixel (sx, sy) becomes a white dot at * dx = sx * dy = round( luma * (HEIGHT‑1) / 255 ) * * The whole destination buffer is cleared to black first. * ------------------------------------------------------------- */ static void convert_to_waveform(void) { const pixel_t WHITE = 0xFFFFFFFFU; /* ARGB = opaque white */ const pixel_t BLACK = 0xFF000000U; /* opaque black (background) */ /* 1. clear destination */ for (int y = 0; y < HEIGHT; ++y) memset(draw_buf[y], 0, WIDTH * sizeof(pixel_t)); for (int y = 0; y < HEIGHT; ++y) for (int x = 0; x < WIDTH; ++x) draw_buf[y][x] = BLACK; /* 2. plot points */ for (int sy = 0; sy < HEIGHT; ++sy) { for (int sx = 0; sx < WIDTH; ++sx) { pixel_t p = net_frame[sy][sx]; Uint8 r = (Uint8)((p >> 16) & 0xFF); Uint8 g = (Uint8)((p >> 8) & 0xFF); Uint8 b = (Uint8)( p & 0xFF); Uint8 luma = rgb_to_luma(r, g, b); /* 0‑255 */ if(luma >= 150) draw_buf[sy][sx] = 0xff<<24 | (255-sy/2) << 16 | (255-sy/2) << 8 | (255-sy/2); /* overwrite – white dot */ else draw_buf[sy][sx] = 0xff<<24; //draw_buf[sy][sx] = (255-sy/2)<<24 | 0xFFFFFFU; } } } /* ------------------------------------------------------------- * ---------- TCP SERVER ------------------------------------ * ------------------------------------------------------------- */ /* Returns a non‑blocking listening socket, or -1 on error. */ static int create_listen_socket(void) { int lst = socket(AF_INET, SOCK_STREAM, 0); if (lst < 0) { perror("socket"); return -1; } int opt = 1; setsockopt(lst, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); struct sockaddr_in addr = { .sin_family = AF_INET, .sin_port = htons(LISTEN_PORT), .sin_addr = { .s_addr = INADDR_ANY } }; if (bind(lst, (struct sockaddr *)&addr, sizeof(addr)) < 0) { perror("bind"); close(lst); return -1; } if (listen(lst, BACKLOG) < 0) { perror("listen"); close(lst); return -1; } /* make non‑blocking */ int flags = fcntl(lst, F_GETFL, 0); fcntl(lst, F_SETFL, flags | O_NONBLOCK); printf("[net] Listening on 0.0.0.0:%d (single client allowed)\n", LISTEN_PORT); return lst; } /* Helper – set a socket to non‑blocking mode */ static void set_nonblocking(int fd) { int flags = fcntl(fd, F_GETFL, 0); if (flags != -1) fcntl(fd, F_SETFL, flags | O_NONBLOCK); } /* ------------------------------------------------------------- * MAIN * ------------------------------------------------------------- */ int main(void) { /* ---------- SDL initialisation -------------------------------- */ if (SDL_Init(SDL_INIT_VIDEO) != 0) { fprintf(stderr, "SDL_Init error: %s\n", SDL_GetError()); return EXIT_FAILURE; } SDL_Window *window = SDL_CreateWindow("Waveform‑ready demo (TCP input)", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, WIDTH, HEIGHT, SDL_WINDOW_SHOWN); if (!window) { fprintf(stderr, "SDL_CreateWindow error: %s\n", SDL_GetError()); SDL_Quit(); return EXIT_FAILURE; } SDL_Renderer *renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); if (!renderer) { fprintf(stderr, "SDL_CreateRenderer error: %s\n", SDL_GetError()); SDL_DestroyWindow(window); SDL_Quit(); return EXIT_FAILURE; } SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, WIDTH, HEIGHT); if (!texture) { fprintf(stderr, "SDL_CreateTexture error: %s\n", SDL_GetError()); SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); SDL_Quit(); return EXIT_FAILURE; } /* ---------- Demo pattern (until first network frame) ---------- */ init_demo_pattern(); /* ---------- Networking --------------------------------------- */ int listen_sock = create_listen_socket(); /* may be -1 on failure */ int client_sock = -1; /* -1 → no client yet */ const size_t FRAME_BYTES = WIDTH * HEIGHT * sizeof(pixel_t); size_t recv_offset = 0; /* bytes already received */ bool quit = false; SDL_Event ev; /* ---------- Main loop --------------------------------------- */ while (!quit) { /* 1) SDL events */ while (SDL_PollEvent(&ev)) { if (ev.type == SDL_QUIT) quit = true; else if (ev.type == SDL_KEYDOWN && ev.key.keysym.sym == SDLK_ESCAPE) quit = true; } /* 2) Accept a new client if none is present */ if (listen_sock >= 0 && client_sock < 0) { struct sockaddr_in remote; socklen_t remote_len = sizeof(remote); int tmp = accept(listen_sock, (struct sockaddr *)&remote, &remote_len); if (tmp >= 0) { client_sock = tmp; set_nonblocking(client_sock); recv_offset = 0; char ip[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &remote.sin_addr, ip, sizeof(ip)); printf("[net] Client connected from %s:%d\n", ip, ntohs(remote.sin_port)); } else if (errno != EAGAIN && errno != EWOULDBLOCK) { perror("[net] accept"); } } /* 3) Read raw pixel data from the client */ if (client_sock >= 0) { size_t still_needed = FRAME_BYTES - recv_offset; ssize_t n = recv(client_sock, ((Uint8 *)net_frame) + recv_offset, still_needed, 0); if (n > 0) { recv_offset += (size_t)n; if (recv_offset == FRAME_BYTES) { /* Full frame received – convert to waveform image */ convert_to_waveform(); recv_offset = 0; /* ready for the next frame */ } } else if (n == 0) { printf("[net] Client disconnected.\n"); close(client_sock); client_sock = -1; } else if (n < 0 && errno != EAGAIN && errno != EWOULDBLOCK) { perror("[net] recv"); close(client_sock); client_sock = -1; } } /* 4) Copy the (possibly transformed) draw buffer into the texture */ void *tex_pixels; int tex_pitch; if (SDL_LockTexture(texture, NULL, &tex_pixels, &tex_pitch) != 0) { fprintf(stderr, "SDL_LockTexture error: %s\n", SDL_GetError()); break; } for (int y = 0; y < HEIGHT; ++y) { memcpy((Uint8 *)tex_pixels + y * tex_pitch, draw_buf[y], WIDTH * sizeof(pixel_t)); } SDL_UnlockTexture(texture); /* 5) Render */ SDL_RenderClear(renderer); SDL_RenderCopy(renderer, texture, NULL, NULL); SDL_RenderPresent(renderer); /* VSYNC caps the loop to the monitor refresh rate. */ } /* ------------------------------------------------------------- * Cleanup * ------------------------------------------------------------- */ if (client_sock >= 0) close(client_sock); if (listen_sock >= 0) close(listen_sock); SDL_DestroyTexture(texture); SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); SDL_Quit(); return EXIT_SUCCESS; }