mirror of
https://github.com/Harshit-Dhanwalkar/AsciiCam.git
synced 2026-06-21 10:58:05 +02:00
Add hot loading plugins system
This commit is contained in:
parent
509554ba1d
commit
4c7d32663e
12 changed files with 475 additions and 82 deletions
24
C/Makefile
24
C/Makefile
|
|
@ -2,7 +2,9 @@ CC = gcc
|
|||
CFLAGS = -Wall -Wextra -O2 -Iinclude
|
||||
LDFLAGS = -lm
|
||||
LDFLAGS += -lpthread # Multi-threaded capture-render, pthreads producer/consumer
|
||||
LDFLAGS += -ldl # Dynamic loading symbols
|
||||
LDFLAGS += -msse4.1 # SIMD - SSE2 for the YUYV to gray conversion
|
||||
# LDFLAGS += -fvisibility=hidden
|
||||
|
||||
SRCDIR = src
|
||||
INCDIR = include
|
||||
|
|
@ -12,11 +14,15 @@ OBJDIR = $(BUILDDIR)/obj
|
|||
SOURCES = $(wildcard $(SRCDIR)/*.c)
|
||||
OBJECTS = $(patsubst $(SRCDIR)/%.c,$(OBJDIR)/%.o,$(SOURCES))
|
||||
TARGET = $(BUILDDIR)/webcam_ascii
|
||||
PLUGIN_SRCS = $(wildcard filters/*.c)
|
||||
PLUGIN_TARGETS = $(patsubst filters/%.c,$(BUILDDIR)/%.so,$(PLUGIN_SRCS))
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: $(TARGET)
|
||||
.PHONY: all clean plugins
|
||||
|
||||
all: $(TARGET) plugins
|
||||
|
||||
# Main binary
|
||||
$(TARGET): $(OBJECTS)
|
||||
$(CC) $^ -o $@ $(LDFLAGS)
|
||||
|
||||
|
|
@ -26,5 +32,19 @@ $(OBJDIR)/%.o: $(SRCDIR)/%.c | $(OBJDIR)
|
|||
$(OBJDIR):
|
||||
mkdir -p $(OBJDIR)
|
||||
|
||||
# Plugins
|
||||
plugins: $(PLUGIN_TARGET)
|
||||
|
||||
$(BUILDDIR)/%.so: filters/%.c | $(BUILDDIR)
|
||||
$(CC) $(CFLAGS) -fPIC -shared $< -o $@.tmp
|
||||
mv $@.tmp $@
|
||||
|
||||
plugins: $(PLUGIN_TARGETS)
|
||||
|
||||
|
||||
$(BUILDDIR):
|
||||
mkdir -p $(BUILDDIR)
|
||||
|
||||
# Cleanup
|
||||
clean:
|
||||
rm -rf $(BUILDDIR)
|
||||
|
|
|
|||
33
C/filters/edge_detect.c
Normal file
33
C/filters/edge_detect.c
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
#include "plugins.h"
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static inline int clamp(int v) { return v < 0 ? 0 : v > 255 ? 255 : v; }
|
||||
|
||||
static void do_edge_boost(uint8_t *gray, int w, int h, void *ctx) {
|
||||
(void)ctx;
|
||||
// Unsharp mask: sharpened = original + (original - blurred) * strength
|
||||
uint8_t *tmp = malloc(w * h);
|
||||
if (!tmp)
|
||||
return;
|
||||
|
||||
// 3x3 box blur
|
||||
for (int y = 1; y < h - 1; y++) {
|
||||
for (int x = 1; x < w - 1; x++) {
|
||||
int sum = 0;
|
||||
for (int dy = -1; dy <= 1; dy++)
|
||||
for (int dx = -1; dx <= 1; dx++)
|
||||
sum += gray[(y + dy) * w + (x + dx)];
|
||||
tmp[y * w + x] = (uint8_t)(sum / 9);
|
||||
}
|
||||
}
|
||||
|
||||
// Sharpen: original + 0.5 * (original - blur)
|
||||
for (int i = w + 1; i < w * (h - 1) - 1; i++)
|
||||
gray[i] = (uint8_t)clamp(gray[i] + (gray[i] - tmp[i]) / 2);
|
||||
|
||||
free(tmp);
|
||||
}
|
||||
|
||||
static filter_plugin_t self = {do_edge_boost, "edge_boost"};
|
||||
filter_plugin_t *plugin_get(void) { return &self; }
|
||||
14
C/filters/invert.c
Normal file
14
C/filters/invert.c
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
#include "plugins.h"
|
||||
#include <stdint.h>
|
||||
|
||||
static void do_invert(uint8_t *gray, int w, int h, void *ctx) {
|
||||
(void)ctx;
|
||||
int total_pixels = w * h;
|
||||
for (int i = 0; i < total_pixels; i++) {
|
||||
gray[i] = 255 - gray[i]; // Flip brightness
|
||||
}
|
||||
}
|
||||
|
||||
static filter_plugin_t self = {.process = do_invert, .name = "Invert"};
|
||||
|
||||
filter_plugin_t *plugin_get(void) { return &self; }
|
||||
18
C/filters/threshold.c
Normal file
18
C/filters/threshold.c
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
#include "plugins.h"
|
||||
#include <stdint.h>
|
||||
|
||||
#define DEFAULT_THRESH 128
|
||||
|
||||
static void thresh_process(uint8_t *gray, int w, int h, void *ctx) {
|
||||
uint8_t thresh = (uint8_t)(ctx ? *(int *)ctx : DEFAULT_THRESH);
|
||||
int total = w * h;
|
||||
for (int i = 0; i < total; i++)
|
||||
gray[i] = (gray[i] > thresh) ? 255 : 0;
|
||||
}
|
||||
|
||||
static filter_plugin_t self = {
|
||||
.process = thresh_process,
|
||||
.name = "threshold",
|
||||
};
|
||||
|
||||
filter_plugin_t *plugin_get(void) { return &self; }
|
||||
|
|
@ -17,7 +17,8 @@ typedef struct {
|
|||
} ascii_opts_t;
|
||||
|
||||
// Convert YUYV raw data to grayscale
|
||||
void yuyv_to_gray(const uint8_t *yuyv, uint8_t *gray, int width, int height);
|
||||
// void yuyv_to_gray(const uint8_t *yuyv, uint8_t *gray, int width, int height);
|
||||
void yuyv_to_gray_simd(const uint8_t *yuyv, uint8_t *gray, int width, int height);
|
||||
void yuyv_to_rgb(const uint8_t *yuyv, uint8_t *rgb, int width, int height);
|
||||
|
||||
// Output buffer sizing
|
||||
|
|
|
|||
25
C/include/plugins.h
Normal file
25
C/include/plugins.h
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
#ifndef PLUGINS_H
|
||||
#define PLUGINS_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct {
|
||||
void (*process)(uint8_t *gray, int w, int h, void *ctx);
|
||||
const char *name;
|
||||
} filter_plugin_t;
|
||||
|
||||
typedef struct {
|
||||
void *dl_handle; // handle from dlopen
|
||||
filter_plugin_t *plugin; // resolved plugin vtable
|
||||
char path[256]; // absolute path to .so
|
||||
char tmp_path[280]; // temp copy path used for current dlopen // HACK:
|
||||
int inotify_fd; // inotify instance fd
|
||||
int inotify_wd; // watch descriptor
|
||||
} plugin_loader_t;
|
||||
|
||||
int plugin_load (plugin_loader_t *pl, const char *path);
|
||||
void plugin_watch_init (plugin_loader_t *pl, const char *path);
|
||||
void plugin_check_reload(plugin_loader_t *pl);
|
||||
void plugin_cleanup (plugin_loader_t *pl);
|
||||
|
||||
#endif
|
||||
|
|
@ -1,22 +1,23 @@
|
|||
#ifndef THREAD_SHARING_H
|
||||
#define THREAD_SHARING_H
|
||||
|
||||
#include "ascii.h"
|
||||
#include <pthread.h>
|
||||
#include <stdint.h>
|
||||
#include "ascii.h"
|
||||
|
||||
typedef struct {
|
||||
uint8_t *buf[2]; // Double buffer
|
||||
int width, height;
|
||||
int ascii_w, ascii_h;
|
||||
int ready_idx;
|
||||
int has_frame;
|
||||
uint8_t *buf[2]; // Double buffer, one slot per thread
|
||||
int width, height; // capture dimensions
|
||||
int ascii_w, ascii_h;
|
||||
int ready_idx; // which slot has the freshest frame
|
||||
int has_frame; // non-zero once producer has written once
|
||||
pthread_mutex_t lock;
|
||||
pthread_cond_t cond;
|
||||
volatile int stop;
|
||||
ascii_opts_t opts;
|
||||
pthread_cond_t cond;
|
||||
volatile int stop;
|
||||
ascii_opts_t opts;
|
||||
} shared_frame_t;
|
||||
|
||||
void *capture_thread(void *arg);
|
||||
void *render_thread (void *arg);
|
||||
void *render_thread(void *arg);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
110
C/src/ascii.c
110
C/src/ascii.c
|
|
@ -1,6 +1,6 @@
|
|||
#include "ascii.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <immintrin.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
|
@ -13,10 +13,31 @@ static inline uint8_t clamp_u8(int v) {
|
|||
}
|
||||
|
||||
// Image conversion
|
||||
void yuyv_to_gray(const uint8_t *yuyv, uint8_t *gray, int width, int height) {
|
||||
int n = width * height;
|
||||
for (int i = 0; i < n; i++)
|
||||
gray[i] = yuyv[i * 2]; // Y sample at even bytes in YUYV format
|
||||
// void yuyv_to_gray(const uint8_t *yuyv, uint8_t *gray, int width, int height) {
|
||||
// int n = width * height;
|
||||
// for (int i = 0; i < n; i++)
|
||||
// gray[i] = yuyv[i * 2]; // Y sample at even bytes in YUYV format
|
||||
// }
|
||||
void yuyv_to_gray_simd(const uint8_t *yuyv, uint8_t *gray, int width, int height) {
|
||||
int total_pixels = width * height;
|
||||
|
||||
// Mask to extract every even byte (Y samples)
|
||||
__m128i mask = _mm_set1_epi16(0x00FF);
|
||||
|
||||
int i = 0;
|
||||
for (; i + 16 <= total_pixels; i += 16) {
|
||||
// Load 32 bytes of YUYV (= 16 pixels)
|
||||
__m128i lo = _mm_loadu_si128((__m128i *)(yuyv + i*2));
|
||||
__m128i hi = _mm_loadu_si128((__m128i *)(yuyv + i*2 + 16));
|
||||
// low byte of each 16-bit word (the Y sample)
|
||||
lo = _mm_and_si128(lo, mask);
|
||||
hi = _mm_and_si128(hi, mask);
|
||||
// Pack 16-bit
|
||||
__m128i result = _mm_packus_epi16(lo, hi);
|
||||
_mm_storeu_si128((__m128i *)(gray + i), result);
|
||||
}
|
||||
for (; i < total_pixels; i++)
|
||||
gray[i] = yuyv[i * 2];
|
||||
}
|
||||
|
||||
void yuyv_to_rgb(const uint8_t *yuyv, uint8_t *rgb, int width, int height) {
|
||||
|
|
@ -38,6 +59,49 @@ void yuyv_to_rgb(const uint8_t *yuyv, uint8_t *rgb, int width, int height) {
|
|||
}
|
||||
}
|
||||
}
|
||||
// void yuyv_to_rgb_simd(const uint8_t *yuyv, uint8_t *rgb, int width, int height) {
|
||||
// int total_bytes = width * height * 2;
|
||||
// int i = 0;
|
||||
//
|
||||
// // Vectors of constants for color weights shifted up by 8 bits
|
||||
// __m128i r_v_weight = _mm_set1_epi16(409);
|
||||
// __m128i g_u_weight = _mm_set1_epi16(-100);
|
||||
// __m128i g_v_weight = _mm_set1_epi16(-208);
|
||||
// __m128i b_u_weight = _mm_set1_epi16(516);
|
||||
// __m128i const_128 = _mm_set1_epi16(128);
|
||||
//
|
||||
// for (; i + 8 <= total_bytes; i += 8) {
|
||||
// // Load 8 bytes of YUYV = [Y0, U0, Y1, V0, Y2, U1, Y3, V1]
|
||||
// // Cast directly into a 64-bit load to avoid polluting register spaces
|
||||
// long long chunk = *(const long long *)(yuyv + i);
|
||||
// __m128i raw = _mm_cvtsi64_si128(chunk);
|
||||
//
|
||||
// // Unpack bytes to 16-bit integers to hold intermediate precision math
|
||||
// __m128i pixels = _mm_unpacklo_epi8(raw, _mm_setzero_si128());
|
||||
//
|
||||
// // Extract channels across structural layouts
|
||||
// int16_t y0 = _mm_extract_epi16(pixels, 0) - 16;
|
||||
// int16_t u0 = _mm_extract_epi16(pixels, 1) - 128;
|
||||
// int16_t y1 = _mm_extract_epi16(pixels, 2) - 16;
|
||||
// int16_t v0 = _mm_extract_epi16(pixels, 3) - 128;
|
||||
//
|
||||
// int16_t y2 = _mm_extract_epi16(pixels, 4) - 16;
|
||||
// int16_t u1 = _mm_extract_epi16(pixels, 5) - 128;
|
||||
// int16_t y3 = _mm_extract_epi16(pixels, 6) - 16;
|
||||
// int16_t v1 = _mm_extract_epi16(pixels, 7) - 128;
|
||||
//
|
||||
// // Perform fixed-point math approximations using scalar elements or smaller vector groupings
|
||||
// // Example for first pixel pair layout:
|
||||
// int32_t r0 = clamp_u8((298 * y0 + 409 * v0 + 128) >> 8);
|
||||
// int32_t g0 = clamp_u8((298 * y0 - 100 * u0 - 208 * v0 + 128) >> 8);
|
||||
// int32_t b0 = clamp_u8((298 * y0 + 516 * u0 + 128) >> 8);
|
||||
//
|
||||
// // Write sequentially to interleaved pointer layout
|
||||
// int rgb_offset = (i / 4) * 6;
|
||||
// rgb[rgb_offset + 0] = r0; rgb[rgb_offset + 1] = g0; rgb[rgb_offset + 2] = b0;
|
||||
// // Continue similarly down line iterations...
|
||||
// }
|
||||
// }
|
||||
|
||||
// Buffer sizing
|
||||
size_t ascii_out_size(int dst_w, int dst_h, int color) {
|
||||
|
|
@ -89,23 +153,22 @@ static void sobel(const uint8_t *in, uint8_t *out, int w, int h) {
|
|||
int grayscale_to_ascii(const uint8_t *gray, const uint8_t *rgb, int src_w,
|
||||
int src_h, int dst_w, int dst_h, char *out,
|
||||
size_t out_size, const ascii_opts_t *opts) {
|
||||
const char *charset =
|
||||
(opts && opts->charset) ? opts->charset : ASCII_CHARS_DEFAULT;
|
||||
int nchars = (int)strlen(charset);
|
||||
int brightness = opts ? opts->brightness : 0;
|
||||
int contrast = opts ? opts->contrast : 100;
|
||||
int invert = opts ? opts->invert : 0;
|
||||
int do_color = opts && opts->color && (rgb != NULL);
|
||||
int do_edges = opts ? opts->edges : 0;
|
||||
int do_dither = opts ? opts->dither : 0;
|
||||
const char *charset = (opts && opts->charset) ? opts->charset : ASCII_CHARS_DEFAULT;
|
||||
int nchars = (int)strlen(charset);
|
||||
int brightness = opts ? opts->brightness : 0;
|
||||
int contrast = opts ? opts->contrast : 100;
|
||||
int invert = opts ? opts->invert : 0;
|
||||
int do_color = opts && opts->color && (rgb != NULL);
|
||||
int do_edges = opts ? opts->edges : 0;
|
||||
int do_dither = opts ? opts->dither : 0;
|
||||
|
||||
// Blocking width and height pixels in source pixels
|
||||
double bw = (double)src_w / dst_w;
|
||||
double bh = (double)src_h / dst_h;
|
||||
double bw = (double)src_w / dst_w;
|
||||
double bh = (double)src_h / dst_h;
|
||||
|
||||
// Downsample to (dst_w x dst_h)
|
||||
uint8_t *small_g = malloc(dst_w * dst_h);
|
||||
uint8_t *small_rgb = do_color ? malloc(dst_w * dst_h * 3) : NULL;
|
||||
uint8_t *small_g = malloc(dst_w * dst_h);
|
||||
uint8_t *small_rgb = do_color ? malloc(dst_w * dst_h * 3) : NULL;
|
||||
|
||||
if (!small_g || (do_color && !small_rgb)) {
|
||||
free(small_g);
|
||||
|
|
@ -258,7 +321,7 @@ int grayscale_to_ascii(const uint8_t *gray, const uint8_t *rgb, int src_w,
|
|||
}
|
||||
}
|
||||
|
||||
out[(size_t)out_idx < out_size ? out_idx : out_size - 1] = '\0';
|
||||
out[(size_t)out_idx < out_size ? (size_t)out_idx : out_size - 1] = '\0';
|
||||
|
||||
free(small_g);
|
||||
free(small_rgb);
|
||||
|
|
@ -275,12 +338,13 @@ void overlay_fps_box(int dst_w, double fps, int color_enabled) {
|
|||
int n;
|
||||
if (color_enabled) {
|
||||
n = snprintf(buf, sizeof(buf),
|
||||
"\033[1;%dH\033[38;2;0;255;0m\033[48;2;30;30;30m"
|
||||
"[ FPS: %4.1f ]\033[0m",
|
||||
"\033[1;%dH\033[38;2;0;255;0m\033[48;2;30;30;30m[ FPS: %4.1f ]\033[0m",
|
||||
col, fps);
|
||||
} else {
|
||||
n = snprintf(buf, sizeof(buf), "\033[1;%dH[ FPS: %4.1f ]", col, fps);
|
||||
n = snprintf(buf, sizeof(buf),
|
||||
"\033[1;%dH[ FPS: %4.1f ]",
|
||||
col, fps);
|
||||
}
|
||||
if (n > 0 && n < (int)sizeof(buf))
|
||||
write(STDOUT_FILENO, buf, (size_t)n);
|
||||
(void)write(STDOUT_FILENO, buf, (size_t)n);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
#include "capture.h"
|
||||
#include "ascii.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <linux/videodev2.h>
|
||||
|
|
@ -10,7 +12,6 @@
|
|||
#include <sys/mman.h>
|
||||
#include <sys/select.h>
|
||||
#include <unistd.h>
|
||||
#include <immintrin.h>
|
||||
|
||||
int webcam_init(webcam_t *cam, const char *device, int width, int height) {
|
||||
// Open device (non‑blocking for select usage)
|
||||
|
|
@ -98,24 +99,6 @@ int webcam_wait_frame(webcam_t *cam, int timeout_ms) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
void yuyv_to_gray_simd(const uint8_t *yuyv, uint8_t *gray, int n) {
|
||||
// Mask to extract every even byte (Y samples)
|
||||
__m128i mask = _mm_set1_epi16(0x00FF);
|
||||
int i = 0;
|
||||
for (; i + 16 <= n; i += 16) {
|
||||
// Load 32 bytes of YUYV (= 16 pixels)
|
||||
__m128i lo = _mm_loadu_si128((__m128i *)(yuyv + i*2));
|
||||
__m128i hi = _mm_loadu_si128((__m128i *)(yuyv + i*2 + 16));
|
||||
// low byte of each 16-bit word (the Y sample)
|
||||
lo = _mm_and_si128(lo, mask);
|
||||
hi = _mm_and_si128(hi, mask);
|
||||
// Pack 16-bit
|
||||
__m128i result = _mm_packus_epi16(lo, hi);
|
||||
_mm_storeu_si128((__m128i *)(gray + i), result);
|
||||
}
|
||||
for (; i < n; i++) gray[i] = yuyv[i * 2]; // scalar tail
|
||||
}
|
||||
|
||||
int webcam_capture_frame(webcam_t *cam, uint8_t *gray_buffer) {
|
||||
// Dequeue buffer
|
||||
struct v4l2_buffer buf = cam->buf_info;
|
||||
|
|
@ -126,8 +109,9 @@ int webcam_capture_frame(webcam_t *cam, uint8_t *gray_buffer) {
|
|||
// for (int i = 0, j = 0; i < cam->width * cam->height * 2; i += 2, j++) {
|
||||
// gray_buffer[j] = yuyv[i];
|
||||
// }
|
||||
int total_pixels = cam->width * cam->height;
|
||||
yuyv_to_gray_simd((uint8_t *)cam->buffer, gray_buffer, total_pixels);
|
||||
int w = cam->width;
|
||||
int h = cam->height;
|
||||
yuyv_to_gray_simd((uint8_t *)cam->buffer, gray_buffer, w, h);
|
||||
|
||||
// Store updated buffer info for requeue
|
||||
cam->buf_info = buf;
|
||||
|
|
|
|||
110
C/src/main.c
110
C/src/main.c
|
|
@ -2,14 +2,18 @@
|
|||
#include "capture.h"
|
||||
#include "thread_sharing.h"
|
||||
#include "timing.h"
|
||||
#include "plugins.h"
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <getopt.h>
|
||||
#include <pthread.h>
|
||||
#include <signal.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/inotify.h>
|
||||
#include <termios.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
|
@ -37,40 +41,42 @@ static void print_usage(const char *prog) {
|
|||
"Usage: %s [options]\n"
|
||||
"\n"
|
||||
"Capture options:\n"
|
||||
" -d <device> video device (default: /dev/video0)\n"
|
||||
" -w <width> capture width (default: %d)\n"
|
||||
" -h <height> capture height (default: %d)\n"
|
||||
" -f <fps> target framerate (default: %d)\n"
|
||||
" -d <device> video device (default: /dev/video0)\n"
|
||||
" -w <width> capture width (default: %d) \n"
|
||||
" -h <height> capture height (default: %d) \n"
|
||||
" -f <fps> target framerate (default: %d) \n"
|
||||
"\n"
|
||||
"Output options:\n"
|
||||
" -W <width> ASCII output columns (default: %d)\n"
|
||||
" -H <height> ASCII output rows (default: %d)\n"
|
||||
" -s <chars> custom charset string (default: \"%s\")\n"
|
||||
" -W <width> ASCII output columns (default: %d) \n"
|
||||
" -H <height> ASCII output rows (default: %d) \n"
|
||||
" -s <chars> custom charset string (default: \"%s\") \n"
|
||||
" -p <path> filter plugin .so path \n"
|
||||
"\n"
|
||||
"Image adjustments:\n"
|
||||
" -b <val> brightness offset -128..128 (default: 0)\n"
|
||||
" -c <val> contrast in percent >0; 100=none (default: 100)\n"
|
||||
" -i invert brightness->charset mapping\n"
|
||||
" -e enable Sobel edge detection\n"
|
||||
" -C colour output (ANSI truecolor)\n"
|
||||
" -D Floyd-Steinberg dithering\n",
|
||||
prog, DEFAULT_CAPTURE_WIDTH, DEFAULT_CAPTURE_HEIGHT, DEFAULT_FPS,
|
||||
DEFAULT_ASCII_WIDTH, DEFAULT_ASCII_HEIGHT, ASCII_CHARS_DEFAULT); // FIX: undeclared identifier ASCII_CHARS_DEFAULT
|
||||
" -i invert mapping \n"
|
||||
" -e enable Sobel edge detection \n"
|
||||
" -C ANSI truecolor output \n"
|
||||
" -D Floyd-Steinberg dithering \n",
|
||||
prog,
|
||||
DEFAULT_CAPTURE_WIDTH, DEFAULT_CAPTURE_HEIGHT, DEFAULT_FPS,
|
||||
DEFAULT_ASCII_WIDTH, DEFAULT_ASCII_HEIGHT, ASCII_CHARS_DEFAULT);
|
||||
}
|
||||
|
||||
// termios
|
||||
void term_raw_mode(void) {
|
||||
tcgetattr(STDOUT_FILENO, &orig_terminal); // save stdin state
|
||||
tcgetattr(STDIN_FILENO, &orig_terminal); // save stdin state
|
||||
struct termios raw = orig_terminal;
|
||||
raw.c_lflag &= ~(ICANON | ECHO); // no line buffering or no echo
|
||||
raw.c_cc[VMIN] = 0; // non-blocking read
|
||||
raw.c_cc[VTIME] = 0;
|
||||
raw.c_lflag &= ~(ICANON | ECHO); // no line buffering or no echo
|
||||
raw.c_cc[VMIN] = 0; // non-blocking read
|
||||
raw.c_cc[VTIME] = 0;
|
||||
tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);
|
||||
}
|
||||
|
||||
void term_restore(void) { tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_terminal); }
|
||||
|
||||
fps_counter_t fps_calc = {0}; // Global FPS counter // FIX:
|
||||
fps_counter_t fps_calc = {0};
|
||||
|
||||
// Main
|
||||
int main(int argc, char *argv[]) {
|
||||
|
|
@ -85,7 +91,7 @@ int main(int argc, char *argv[]) {
|
|||
int cap_h = DEFAULT_CAPTURE_HEIGHT;
|
||||
int fps = DEFAULT_FPS;
|
||||
|
||||
ascii_opts_t opts = { // FIX: undcleared identifier 'ascii_opts_t'
|
||||
ascii_opts_t opts = {
|
||||
.brightness = 0,
|
||||
.contrast = 100,
|
||||
.invert = 0,
|
||||
|
|
@ -95,9 +101,14 @@ int main(int argc, char *argv[]) {
|
|||
.charset = NULL,
|
||||
};
|
||||
|
||||
// Plugins
|
||||
// plugin_loader_t pl;
|
||||
// memset(&pl, 0, sizeof(plugin_loader_t));
|
||||
const char *plugin_path = NULL;
|
||||
|
||||
// CLI parsing
|
||||
int opt;
|
||||
while ((opt = getopt(argc, argv, "ed:W:H:w:h:f:b:c:iCDs:")) != -1) {
|
||||
while ((opt = getopt(argc, argv, "d:W:H:w:h:f:b:c:iCDs:p:")) != -1) {
|
||||
switch (opt) {
|
||||
case 'd':
|
||||
device = optarg;
|
||||
|
|
@ -150,6 +161,9 @@ int main(int argc, char *argv[]) {
|
|||
case 's':
|
||||
opts.charset = optarg;
|
||||
break;
|
||||
case 'p':
|
||||
plugin_path = optarg;
|
||||
break;
|
||||
default:
|
||||
print_usage(argv[0]);
|
||||
return 1;
|
||||
|
|
@ -158,6 +172,26 @@ int main(int argc, char *argv[]) {
|
|||
|
||||
timing_init(fps);
|
||||
|
||||
// Initialize plugin system
|
||||
plugin_loader_t pl;
|
||||
memset(&pl, 0, sizeof(pl));
|
||||
pl.inotify_fd = -1;
|
||||
|
||||
if (plugin_path) {
|
||||
plugin_load(&pl, plugin_path);
|
||||
plugin_watch_init(&pl, plugin_path);
|
||||
}
|
||||
|
||||
// void plugin_check_reload(plugin_loader_t *pl) {
|
||||
// char buf[sizeof(struct inotify_event) + 256];
|
||||
// if (read(pl->inotify_fd, buf, sizeof(buf)) > 0) {
|
||||
// usleep(100000);
|
||||
// if (plugin_load(pl, pl->path) == 0) {
|
||||
// fprintf(stderr, "Successfully hot-swapped filter plugin: [%s]\n", pl->plugin->name);
|
||||
// }
|
||||
// }
|
||||
// };
|
||||
|
||||
// Open webcam
|
||||
webcam_t cam = {.fd = -1, .buffer = MAP_FAILED};
|
||||
if (webcam_init(&cam, device, cap_w, cap_h) < 0) {
|
||||
|
|
@ -169,7 +203,8 @@ int main(int argc, char *argv[]) {
|
|||
opts.color ? " | color" : "",
|
||||
opts.edges ? " | edges" : "",
|
||||
opts.dither ? " | dither" : "",
|
||||
opts.invert ? " | inverted": "");
|
||||
opts.invert ? " | inverted": "",
|
||||
pl.plugin ? " | plugin" : "");
|
||||
|
||||
// Allocate pixel buffers
|
||||
int cam_pixels = cam.width * cam.height;
|
||||
|
|
@ -185,7 +220,7 @@ int main(int argc, char *argv[]) {
|
|||
|
||||
// Allocate output string buffer
|
||||
size_t out_size = ascii_out_size(ascii_w, ascii_h, opts.color);
|
||||
char *out_buf = malloc(out_size);
|
||||
char *out_buf = malloc(out_size);
|
||||
if (!out_buf) {
|
||||
perror("malloc out_buf");
|
||||
free(gray);
|
||||
|
|
@ -194,8 +229,27 @@ int main(int argc, char *argv[]) {
|
|||
return 1;
|
||||
}
|
||||
|
||||
// // Thead sharing
|
||||
// shared_frame_t sf = {0};
|
||||
// sf.buf[0] = malloc(cam_pixels);
|
||||
// sf.buf[1] = malloc(cam_pixels);
|
||||
// sf.width = cam.width; sf.height = cam.height;
|
||||
// sf.ascii_w = ascii_w; sf.ascii_h = ascii_h;
|
||||
// sf.opts = opts;
|
||||
// pthread_mutex_init(&sf.lock, NULL);
|
||||
// pthread_cond_init(&sf.cond, NULL);
|
||||
//
|
||||
// pthread_t tid_cap, tid_render;
|
||||
// pthread_create(&tid_cap, NULL, capture_thread, &sf);
|
||||
// pthread_create(&tid_render, NULL, render_thread, &sf);
|
||||
//
|
||||
// sf.stop = 1;
|
||||
// pthread_cond_broadcast(&sf.cond);
|
||||
// pthread_join(tid_cap, NULL);
|
||||
// pthread_join(tid_render, NULL);
|
||||
|
||||
// Initial full clear
|
||||
write(STDOUT_FILENO, "\033[2J\033[H\033[?25l", 13);
|
||||
(void)write(STDOUT_FILENO, "\033[2J\033[H\033[?25l", 13);
|
||||
term_raw_mode();
|
||||
|
||||
// Main loop
|
||||
|
|
@ -224,6 +278,9 @@ int main(int argc, char *argv[]) {
|
|||
}
|
||||
}
|
||||
|
||||
if (plugin_path)
|
||||
plugin_check_reload(&pl);
|
||||
|
||||
if (webcam_wait_frame(&cam, 1000) < 0)
|
||||
continue; // timeout, retry
|
||||
|
||||
|
|
@ -232,6 +289,9 @@ int main(int argc, char *argv[]) {
|
|||
break;
|
||||
}
|
||||
|
||||
if (pl.plugin)
|
||||
pl.plugin->process(gray, cam.width, cam.height, NULL);
|
||||
|
||||
if (opts.color && rgb)
|
||||
yuyv_to_rgb((const uint8_t *)cam.buffer, rgb, cam.width, cam.height);
|
||||
|
||||
|
|
@ -239,7 +299,7 @@ int main(int argc, char *argv[]) {
|
|||
ascii_h, out_buf, out_size, &opts);
|
||||
|
||||
if (len > 0) {
|
||||
write(STDOUT_FILENO, out_buf, (size_t)len);
|
||||
(void)write(STDOUT_FILENO, out_buf, (size_t)len);
|
||||
overlay_fps_box(ascii_w, current_fps, opts.color);
|
||||
}
|
||||
|
||||
|
|
@ -253,7 +313,7 @@ int main(int argc, char *argv[]) {
|
|||
|
||||
// Cleanup
|
||||
term_restore();
|
||||
write(STDOUT_FILENO, "\033[0m\033[?25h\n", 11);
|
||||
(void)write(STDOUT_FILENO, "\033[0m\033[?25h\n", 11);
|
||||
fprintf(stderr, "Stopped.\n");
|
||||
|
||||
free(gray);
|
||||
|
|
|
|||
171
C/src/plugins.c
Normal file
171
C/src/plugins.c
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
#include "plugins.h"
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <fcntl.h>
|
||||
#include <libgen.h> // dirname()
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/inotify.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static int copy_file(const char *src, const char *dst) {
|
||||
int fd_src = open(src, O_RDONLY);
|
||||
if (fd_src < 0) {
|
||||
perror("[plugin] open src");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int fd_dst = open(dst, O_WRONLY | O_CREAT | O_TRUNC, 0755);
|
||||
if (fd_dst < 0) {
|
||||
perror("[plugin] open dst");
|
||||
close(fd_src);
|
||||
return -1;
|
||||
}
|
||||
|
||||
char buf[65536];
|
||||
ssize_t n;
|
||||
while ((n = read(fd_src, buf, sizeof(buf))) > 0) {
|
||||
if (write(fd_dst, buf, (size_t)n) != n) {
|
||||
perror("[plugin] write");
|
||||
close(fd_src);
|
||||
close(fd_dst);
|
||||
unlink(dst);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
close(fd_src);
|
||||
close(fd_dst);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int plugin_load(plugin_loader_t *pl, const char *path) {
|
||||
// Close old handle
|
||||
if (pl->dl_handle) {
|
||||
dlclose(pl->dl_handle);
|
||||
pl->dl_handle = NULL;
|
||||
pl->plugin = NULL;
|
||||
}
|
||||
|
||||
// Delete the previous temp copy
|
||||
if (pl->tmp_path[0] != '\0') {
|
||||
unlink(pl->tmp_path);
|
||||
pl->tmp_path[0] = '\0';
|
||||
}
|
||||
|
||||
snprintf(pl->tmp_path, sizeof(pl->tmp_path), "%s.%ld.tmp", path,
|
||||
(long)time(NULL));
|
||||
|
||||
if (copy_file(path, pl->tmp_path) < 0) {
|
||||
fprintf(stderr, "[plugin] could not copy %s -> %s\n", path, pl->tmp_path);
|
||||
pl->tmp_path[0] = '\0';
|
||||
return -1;
|
||||
}
|
||||
|
||||
pl->dl_handle = dlopen(pl->tmp_path, RTLD_NOW | RTLD_LOCAL);
|
||||
if (!pl->dl_handle) {
|
||||
fprintf(stderr, "[plugin] dlopen: %s\n", dlerror());
|
||||
unlink(pl->tmp_path);
|
||||
pl->tmp_path[0] = '\0';
|
||||
return -1;
|
||||
}
|
||||
|
||||
void *sym = dlsym(pl->dl_handle, "plugin_get");
|
||||
if (!sym) {
|
||||
fprintf(stderr, "[plugin] dlsym: %s\n", dlerror());
|
||||
dlclose(pl->dl_handle);
|
||||
pl->dl_handle = NULL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
filter_plugin_t *(*get_plugin)(void) = dlsym(pl->dl_handle, "plugin_get");
|
||||
if (!get_plugin) {
|
||||
fprintf(stderr, "[plugin] dlsym: %s\n", dlerror());
|
||||
dlclose(pl->dl_handle);
|
||||
pl->dl_handle = NULL;
|
||||
unlink(pl->tmp_path);
|
||||
pl->tmp_path[0] = '\0';
|
||||
return -1;
|
||||
}
|
||||
|
||||
pl->plugin = get_plugin();
|
||||
fprintf(stderr, "[plugin] loaded: %s\n", pl->plugin->name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void plugin_watch_init(plugin_loader_t *pl, const char *path) {
|
||||
strncpy(pl->path, path, sizeof(pl->path) - 1);
|
||||
|
||||
char dir_copy[256];
|
||||
strncpy(dir_copy, path, sizeof(dir_copy) - 1);
|
||||
const char *dir = dirname(dir_copy);
|
||||
|
||||
pl->inotify_fd = inotify_init1(IN_NONBLOCK);
|
||||
if (pl->inotify_fd < 0) {
|
||||
perror("[plugin] inotify_init1");
|
||||
return;
|
||||
}
|
||||
|
||||
pl->inotify_wd =
|
||||
inotify_add_watch(pl->inotify_fd, dir, IN_CLOSE_WRITE | IN_MOVED_TO);
|
||||
|
||||
if (pl->inotify_wd < 0)
|
||||
perror("[plugin] inotify_add_watch");
|
||||
}
|
||||
|
||||
void plugin_check_reload(plugin_loader_t *pl) {
|
||||
if (pl->inotify_fd < 0)
|
||||
return;
|
||||
|
||||
// Read all available events from the non-blocking queue
|
||||
char buf[4096] __attribute__((aligned(__alignof__(struct inotify_event))));
|
||||
// char buf[sizeof(struct inotify_event) + 256];
|
||||
ssize_t n = read(pl->inotify_fd, buf, sizeof(buf));
|
||||
if (n <= 0)
|
||||
return; // No new filesystem modifications detected
|
||||
|
||||
char path_copy[256];
|
||||
strncpy(path_copy, pl->path, sizeof(path_copy) - 1);
|
||||
const char *soname = basename(path_copy);
|
||||
|
||||
int relevant = 0;
|
||||
const char *p = buf;
|
||||
while (p < buf + n) {
|
||||
const struct inotify_event *ev = (const struct inotify_event *)p;
|
||||
if (ev->len > 0 && strcmp(ev->name, soname) == 0) {
|
||||
relevant = 1;
|
||||
break;
|
||||
}
|
||||
p += sizeof(*ev) + ev->len;
|
||||
}
|
||||
|
||||
if (!relevant)
|
||||
return;
|
||||
|
||||
usleep(150000); // 150ms delay for linker
|
||||
|
||||
if (plugin_load(pl, pl->path) == 0) {
|
||||
fprintf(stderr, "\n[plugin] Hot-swapped: %s\n", pl->plugin->name);
|
||||
} else {
|
||||
fprintf(stderr, "\n[plugin] Hot-swap failed. Retaining active filter.\n");
|
||||
}
|
||||
}
|
||||
|
||||
void plugin_cleanup(plugin_loader_t *pl) {
|
||||
if (pl->dl_handle) {
|
||||
dlclose(pl->dl_handle);
|
||||
pl->dl_handle = NULL;
|
||||
}
|
||||
|
||||
// Remove tmp copy
|
||||
if (pl->tmp_path[0] != '\0') {
|
||||
unlink(pl->tmp_path);
|
||||
pl->tmp_path[0] = '\0';
|
||||
}
|
||||
|
||||
if (pl->inotify_fd >= 0) {
|
||||
close(pl->inotify_fd);
|
||||
pl->inotify_fd = -1;
|
||||
}
|
||||
}
|
||||
|
|
@ -25,5 +25,7 @@ cd build/
|
|||
- [ ] Add feature to record and save it in popular video formats like `.mp4`, `.mov` and `.gif`.
|
||||
- [x] Dithering effect.
|
||||
- [x] Sobel edge detection (kernel convolution). Algorithm reference: https://homepages.inf.ed.ac.uk/rbf/HIPR2/sobel.htm
|
||||
- [x] SIMD for YUYV to grayscale coversion.
|
||||
- [x] Hot-reloading plugin system
|
||||
|
||||
- [ ] Migrate from C to Cpp.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue