Files
waveform-draw/test.c
2025-12-07 11:55:39 +01:00

281 lines
10 KiB
C
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* -------------------------------------------------------------
* framebuffer_demo_tcp.c
*
* * 600×400 ARGB8888 framebuffer in RAM
* * SDL2 window that displays the framebuffer
* * Simple TCP server (single client) that streams raw pixel data
*
* Build (Linux/macOS):
* gcc -Wall -Wextra -pedantic -std=c11 framebuffer_demo_tcp.c \
* -lSDL2 -lpthread -o framebuffer_demo_tcp
*
* Run:
* ./framebuffer_demo_tcp # opens a window and listens on 0.0.0.0:12345
*
* Test client (Unixlike):
* # Send a static colour (red) for one frame
* dd if=/dev/zero bs=1 count=$((600*400*4)) | tr \\0 \\377 > red.bin
* cat red.bin | nc 127.0.0.1 12345
*
* ------------------------------------------------------------- */
#include <SDL2/SDL.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h> // memcpy, memset
#include <unistd.h> // close, read, write
#include <fcntl.h> // fcntl
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/select.h> // select()
#include <errno.h>
/* -------------------------------------------------------------
* Configuration
* ------------------------------------------------------------- */
#define WIDTH 600
#define HEIGHT 400
#define LISTEN_PORT 12345 // change if you like
#define BACKLOG 1 // only one pending connection
/* -------------------------------------------------------------
* Simple pixel format helpers
* ------------------------------------------------------------- */
typedef Uint32 pixel_t; // 0xAARRGGBB in memory (littleendian)
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 framebuffer (contiguous block)
* ------------------------------------------------------------- */
static pixel_t framebuffer[HEIGHT][WIDTH]; // 600*400*4 = 960000 bytes
/* -------------------------------------------------------------
* Fill with a cute demo pattern (visible before any network data)
* ------------------------------------------------------------- */
static void init_demo_pattern(void)
{
for (int y = 0; y < HEIGHT; ++y) {
for (int x = 0; x < WIDTH; ++x) {
Uint8 r = (Uint8)((x * 255) / (WIDTH - 1));
Uint8 g = (Uint8)((y * 255) / (HEIGHT - 1));
Uint8 b = 0x80;
framebuffer[y][x] = MAKE_OPAQUE_PIXEL(r, g, b);
}
}
}
/* -------------------------------------------------------------
* ---------- TCP SERVER ------------------------------------
* ------------------------------------------------------------- */
/* Returns a nonblocking 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;
}
/* Allow immediate reuse of the address (helps during rapid restarts). */
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 the socket nonblocking so that accept() never blocks. */
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 nonblocking 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 entry point
* ------------------------------------------------------------- */
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("Framebuffer 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;
}
/* ---------- Initialise demo framebuffer ----------------------- */
init_demo_pattern();
/* ---------- Initialise networking ----------------------------- */
int listen_sock = create_listen_socket();
if (listen_sock < 0) {
/* Continue without network the window will just show the demo. */
fprintf(stderr, "[net] Continuing without network input.\n");
}
int client_sock = -1; // -1 means “no client connected”
size_t bytes_needed = WIDTH * HEIGHT * sizeof(pixel_t);
size_t recv_offset = 0; // how many bytes of the current frame we already have
bool quit = false;
SDL_Event ev;
/* ---------- Main loop ---------------------------------------- */
while (!quit) {
/* ---- 1) Process SDL events (nonblocking) ------------- */
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 client if we have none yet ------------- */
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; // start a fresh frame
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) {
/* Try to read the remainder of the current frame. */
size_t still_needed = bytes_needed - recv_offset;
ssize_t n = recv(client_sock,
((Uint8 *)framebuffer) + recv_offset,
still_needed,
0);
if (n > 0) {
recv_offset += (size_t)n;
if (recv_offset == bytes_needed) {
/* Full frame received it will be displayed on the next render pass. */
recv_offset = 0; // start collecting the next frame
}
} else if (n == 0) {
/* Client performed an orderly shutdown. */
printf("[net] Client disconnected.\n");
close(client_sock);
client_sock = -1;
} else if (n < 0) {
if (errno != EAGAIN && errno != EWOULDBLOCK) {
perror("[net] recv");
close(client_sock);
client_sock = -1;
}
/* If EAGAIN/EWOULDBLOCK we simply have no more data right now. */
}
}
/* ---- 4) Copy framebuffer into 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;
}
/* The texture pitch may be larger than WIDTH*4, so copy linebyline. */
for (int y = 0; y < HEIGHT; ++y) {
memcpy((Uint8 *)tex_pixels + y * tex_pitch,
framebuffer[y],
WIDTH * sizeof(pixel_t));
}
SDL_UnlockTexture(texture);
/* ---- 5) Render ------------------------------------------ */
SDL_RenderClear(renderer);
SDL_RenderCopy(renderer, texture, NULL, NULL);
SDL_RenderPresent(renderer);
/* VSYNC in the renderer 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;
}