Files
waveform-draw/waveform.c

322 lines
11 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
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.
/* -------------------------------------------------------------
* waveform_tcp.c
*
* * 600×400 ARGB8888 frame is streamed over a singleclient 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 (Unixlike, 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 <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 <errno.h>
/* -------------------------------------------------------------
* 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 ITUBT.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 waveformready image.
*
* * dest is the global draw_buf
* * every source pixel (sx, sy) becomes a white dot at
* dx = sx
* dy = round( luma * (HEIGHT1) / 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); /* 0255 */
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 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;
}
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 nonblocking */
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
* ------------------------------------------------------------- */
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("Waveformready 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;
}