mirror of
https://github.com/Harshit-Dhanwalkar/AsciiCam.git
synced 2026-06-15 10:45:13 +02:00
Implementing my own C libs
This commit is contained in:
parent
c0ddc27b5d
commit
5f98c5b633
24 changed files with 1245 additions and 381 deletions
20
C/Makefile
20
C/Makefile
|
|
@ -1,16 +1,28 @@
|
|||
CC = gcc
|
||||
CFLAGS = -Wall -Wextra -O2 -Iinclude
|
||||
LDFLAGS = -lm
|
||||
LDFLAGS += -lpthread # Multi-threaded capture-render, pthreads producer/consumer
|
||||
CFLAGS = -Wall -Wextra -O2 -Iinclude -Ilib -fno-stack-protector
|
||||
# LDFLAGS = -lm
|
||||
LDFLAGS += -nostdlib # drops crt startup files and default libs
|
||||
# LDFLAGS += -nodefaultlibs # drops default libs but keeps crt startup files
|
||||
LDFLAGS += -Wl,--no-as-needed
|
||||
# LDFLAGS += -Wl,-rpath,/lib/x86_64-linux-gnu # for libdl.so # TEST:
|
||||
# LDFLAGS += /lib/x86_64-linux-gnu/libdl.so.2
|
||||
LDFLAGS += -Wl,-dynamic-linker,/lib64/ld-linux-x86-64.so.2
|
||||
LDFLAGS += /usr/lib/x86_64-linux-gnu/crti.o
|
||||
LDFLAGS += /usr/lib/x86_64-linux-gnu/crtn.o
|
||||
LDFLAGS += -ldl # Dynamic loading symbols
|
||||
LDFLAGS += -lpthread # Multi-threaded capture-render, pthreads producer/consumer
|
||||
LDFLAGS += -lc
|
||||
LDFLAGS += -msse4.1 # SIMD - SSE2 for the YUYV to gray conversion
|
||||
# LDFLAGS += -static
|
||||
|
||||
SRCDIR = src
|
||||
INCDIR = include
|
||||
LIBDIR = lib
|
||||
BUILDDIR = build
|
||||
OBJDIR = $(BUILDDIR)/obj
|
||||
|
||||
SOURCES = $(wildcard $(SRCDIR)/*.c)
|
||||
LIBSRCS = $(wildcard $(LIBDIR)/*.c)
|
||||
SOURCES = $(wildcard $(SRCDIR)/*.c) $(LIBSRCS)
|
||||
OBJECTS = $(patsubst $(SRCDIR)/%.c,$(OBJDIR)/%.o,$(SOURCES))
|
||||
TARGET = $(BUILDDIR)/webcam_ascii
|
||||
PLUGIN_SRCS = $(wildcard filters/*.c)
|
||||
|
|
|
|||
96
C/lib/nl_alloc.c
Normal file
96
C/lib/nl_alloc.c
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
#include "nl_alloc.h"
|
||||
#include <stdint.h>
|
||||
|
||||
#define ARENA_SIZE (2 * 1024 * 1024)
|
||||
#define ALIGN 16
|
||||
#define HDR_MAGIC 0xDEAD
|
||||
|
||||
typedef struct block_hdr {
|
||||
size_t size; // 8
|
||||
int free; // 4
|
||||
unsigned short magic; // 2
|
||||
unsigned char _pad[2]; // 2
|
||||
} block_hdr_t;
|
||||
|
||||
static unsigned char _arena[ARENA_SIZE] __attribute__((aligned(ALIGN)));
|
||||
static int _arena_init = 0;
|
||||
|
||||
static void arena_boot(void) {
|
||||
block_hdr_t *h = (block_hdr_t *)_arena;
|
||||
h->size = ARENA_SIZE - sizeof(block_hdr_t);
|
||||
h->free = 1;
|
||||
h->magic = HDR_MAGIC;
|
||||
_arena_init = 1;
|
||||
}
|
||||
|
||||
static inline size_t align_up(size_t n) {
|
||||
return (n + ALIGN - 1) & ~(size_t)(ALIGN - 1);
|
||||
}
|
||||
|
||||
void *nl_malloc(size_t n) {
|
||||
if (!_arena_init)
|
||||
arena_boot();
|
||||
if (n == 0)
|
||||
n = 1;
|
||||
n = align_up(n);
|
||||
|
||||
unsigned char *p = _arena;
|
||||
unsigned char *end = _arena + ARENA_SIZE;
|
||||
|
||||
while (p + sizeof(block_hdr_t) <= end) {
|
||||
block_hdr_t *h = (block_hdr_t *)p;
|
||||
if (h->magic != HDR_MAGIC)
|
||||
break; // heap corruption
|
||||
|
||||
if (h->free && h->size >= n) {
|
||||
size_t leftover = h->size - n;
|
||||
if (leftover > sizeof(block_hdr_t) + ALIGN) {
|
||||
block_hdr_t *next = (block_hdr_t *)(p + sizeof(block_hdr_t) + n);
|
||||
next->size = leftover - sizeof(block_hdr_t);
|
||||
next->free = 1;
|
||||
next->magic = HDR_MAGIC;
|
||||
h->size = n;
|
||||
}
|
||||
h->free = 0;
|
||||
return p + sizeof(block_hdr_t);
|
||||
}
|
||||
p += sizeof(block_hdr_t) + h->size;
|
||||
}
|
||||
return (void *)0; /* OOM */
|
||||
}
|
||||
|
||||
void *nl_calloc(size_t nmemb, size_t size) {
|
||||
size_t total = nmemb * size;
|
||||
void *p = nl_malloc(total);
|
||||
if (p) {
|
||||
// uint8_t *b = (uint8_t *)p;
|
||||
// for (size_t i = 0; i < total; i++)
|
||||
// b[i] = 0;
|
||||
if (nmemb && size > SIZE_MAX / nmemb)
|
||||
return NULL;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
void nl_free(void *ptr) {
|
||||
if (!ptr)
|
||||
return;
|
||||
block_hdr_t *h = (block_hdr_t *)((unsigned char *)ptr - sizeof(block_hdr_t));
|
||||
if (h->magic != HDR_MAGIC)
|
||||
return; // bad pointer guard
|
||||
h->free = 1;
|
||||
|
||||
// Coalesce forward
|
||||
unsigned char *next_p = (unsigned char *)ptr + h->size;
|
||||
unsigned char *end = _arena + ARENA_SIZE;
|
||||
if (next_p + sizeof(block_hdr_t) <= end) {
|
||||
block_hdr_t *next = (block_hdr_t *)next_p;
|
||||
if (next->magic == HDR_MAGIC && next->free) {
|
||||
h->size += sizeof(block_hdr_t) + next->size;
|
||||
next->magic = 0; // invalidate merged header
|
||||
}
|
||||
}
|
||||
|
||||
// Coalesce Backward
|
||||
// TODO:
|
||||
}
|
||||
14
C/lib/nl_alloc.h
Normal file
14
C/lib/nl_alloc.h
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
#ifndef NL_ALLOC_H
|
||||
#define NL_ALLOC_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
void *nl_malloc(size_t n);
|
||||
void *nl_calloc(size_t nmemb, size_t size);
|
||||
void nl_free(void *ptr);
|
||||
|
||||
#define malloc(n) nl_malloc(n)
|
||||
#define calloc(nm, sz) nl_calloc(nm, sz)
|
||||
#define free(p) nl_free(p)
|
||||
|
||||
#endif
|
||||
41
C/lib/nl_errno.c
Normal file
41
C/lib/nl_errno.c
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
#include "nl_errno.h"
|
||||
#include "nl_syscall.h"
|
||||
#include <stddef.h>
|
||||
|
||||
int errno = 0;
|
||||
|
||||
static void _ewrite(const char *s, size_t n) { __sc3(1, 2, (long)s, (long)n); }
|
||||
|
||||
static size_t _slen(const char *s) {
|
||||
const char *p = s;
|
||||
while (*p)
|
||||
p++;
|
||||
return (size_t)(p - s);
|
||||
}
|
||||
|
||||
static const struct {
|
||||
int code;
|
||||
const char *msg;
|
||||
} _errtab[] = {
|
||||
{1, "Operation not permitted"}, {2, "No such file or directory"},
|
||||
{9, "Bad file descriptor"}, {12, "Out of memory"},
|
||||
{13, "Permission denied"}, {16, "Device or resource busy"},
|
||||
{19, "No such device"}, {22, "Invalid argument"},
|
||||
{28, "No space left on device"}, {32, "Broken pipe"},
|
||||
{110, "Connection timed out"}, {0, (const char *)0}};
|
||||
|
||||
void nl_perror(const char *msg) {
|
||||
const char *estr = "Unknown error";
|
||||
for (int i = 0; _errtab[i].msg; i++) {
|
||||
if (_errtab[i].code == errno) {
|
||||
estr = _errtab[i].msg;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (msg && msg[0]) {
|
||||
_ewrite(msg, _slen(msg));
|
||||
_ewrite(": ", 2);
|
||||
}
|
||||
_ewrite(estr, _slen(estr));
|
||||
_ewrite("\n", 1);
|
||||
}
|
||||
12
C/lib/nl_errno.h
Normal file
12
C/lib/nl_errno.h
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
#ifndef NL_ERRNO_H
|
||||
#define NL_ERRNO_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
extern int errno;
|
||||
|
||||
void nl_perror(const char *msg);
|
||||
|
||||
#define perror(msg) nl_perror(msg)
|
||||
|
||||
#endif
|
||||
75
C/lib/nl_getopt.c
Normal file
75
C/lib/nl_getopt.c
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
#include "nl_getopt.h"
|
||||
#include "nl_syscall.h"
|
||||
#include <stddef.h>
|
||||
|
||||
int nl_optind = 1;
|
||||
int nl_opterr = 1;
|
||||
char *nl_optarg = (char *)0;
|
||||
|
||||
static int _optpos = 0;
|
||||
|
||||
static void _ewrite(const char *s, size_t n) { __sc3(1, 2, (long)s, (long)n); }
|
||||
|
||||
int nl_getopt(int argc, char *const argv[], const char *opts) {
|
||||
nl_optarg = (char *)0;
|
||||
|
||||
if (nl_optind < argc &&
|
||||
(argv[nl_optind][0] != '-' || argv[nl_optind][1] == '\0'))
|
||||
return -1;
|
||||
|
||||
if (nl_optind >= argc)
|
||||
return -1;
|
||||
|
||||
const char *arg = argv[nl_optind];
|
||||
if (arg[0] == '-' && arg[1] == '-') {
|
||||
nl_optind++;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (_optpos == 0)
|
||||
_optpos = 1;
|
||||
char c = arg[_optpos++];
|
||||
|
||||
const char *o = opts;
|
||||
while (*o) {
|
||||
if (*o == c)
|
||||
break;
|
||||
o += (*o != ':') ? 1 : 1;
|
||||
}
|
||||
if (!*o) {
|
||||
if (nl_opterr) {
|
||||
char msg[] = "Unknown option: -?\n";
|
||||
msg[17] = c;
|
||||
_ewrite(msg, 19);
|
||||
}
|
||||
if (arg[_optpos] == '\0') {
|
||||
nl_optind++;
|
||||
_optpos = 0;
|
||||
}
|
||||
return '?';
|
||||
}
|
||||
|
||||
if (o[1] == ':') {
|
||||
if (arg[_optpos] != '\0') {
|
||||
nl_optarg = (char *)&arg[_optpos];
|
||||
} else {
|
||||
nl_optind++;
|
||||
if (nl_optind >= argc) {
|
||||
if (nl_opterr)
|
||||
_ewrite("Missing argument\n", 17);
|
||||
_optpos = 0;
|
||||
return '?';
|
||||
}
|
||||
nl_optarg = argv[nl_optind];
|
||||
}
|
||||
nl_optind++;
|
||||
_optpos = 0;
|
||||
} else {
|
||||
if (arg[_optpos] == '\0') {
|
||||
nl_optind++;
|
||||
_optpos = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return (unsigned char)c;
|
||||
}
|
||||
14
C/lib/nl_getopt.h
Normal file
14
C/lib/nl_getopt.h
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
#ifndef NL_GETOPT_H
|
||||
#define NL_GETOPT_H
|
||||
|
||||
extern int nl_optind;
|
||||
extern int nl_opterr;
|
||||
extern char *nl_optarg;
|
||||
|
||||
int nl_getopt(int argc, char *const argv[], const char *opts);
|
||||
|
||||
#define getopt(ac, av, opts) nl_getopt(ac, av, opts)
|
||||
#define optarg nl_optarg
|
||||
#define optind nl_optind
|
||||
|
||||
#endif
|
||||
124
C/lib/nl_io.h
Normal file
124
C/lib/nl_io.h
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
#ifndef NL_IO_H
|
||||
#define NL_IO_H
|
||||
|
||||
/* nl_io.h
|
||||
I/O syscall wrappers: write, read, open, close, mmap, munmap, ioctl, select,
|
||||
inotify
|
||||
*/
|
||||
|
||||
#include "nl_syscall.h"
|
||||
#include <time.h>
|
||||
|
||||
/* Basic I/O */
|
||||
static inline ssize_t nl_write(int fd, const void *buf, size_t n) {
|
||||
return (ssize_t)__sc3(SYS_write, fd, (long)buf, (long)n);
|
||||
}
|
||||
static inline ssize_t nl_read(int fd, void *buf, size_t n) {
|
||||
return (ssize_t)__sc3(SYS_read, fd, (long)buf, (long)n);
|
||||
}
|
||||
static inline int nl_open(const char *path, int flags, int mode) {
|
||||
return (int)__sc3(SYS_open, (long)path, flags, mode);
|
||||
}
|
||||
static inline int nl_close(int fd) { return (int)__sc1(SYS_close, fd); }
|
||||
static inline int nl_unlink(const char *path) {
|
||||
return (int)__sc1(SYS_unlink, (long)path);
|
||||
}
|
||||
static inline int nl_ioctl(int fd, unsigned long req, void *arg) {
|
||||
return (int)__sc3(SYS_ioctl, fd, (long)req, (long)arg);
|
||||
}
|
||||
|
||||
/* mmap / munmap */
|
||||
static inline void *nl_mmap(void *addr, size_t len, int prot, int flags, int fd,
|
||||
long off) {
|
||||
return (void *)__sc6(SYS_mmap, (long)addr, (long)len, prot, flags, fd, off);
|
||||
}
|
||||
static inline int nl_munmap(void *addr, size_t len) {
|
||||
return (int)__sc2(SYS_munmap, (long)addr, (long)len);
|
||||
}
|
||||
|
||||
/* select */
|
||||
struct nl_timeval {
|
||||
long tv_sec;
|
||||
long tv_usec;
|
||||
};
|
||||
typedef struct {
|
||||
unsigned long fds_bits[16];
|
||||
} nl_fd_set;
|
||||
|
||||
#define NL_FD_ZERO(s) __builtin_memset((s), 0, sizeof(*(s)))
|
||||
#define NL_FD_SET(fd, s) ((s)->fds_bits[(fd) / 64] |= (1UL << ((fd) % 64)))
|
||||
#define NL_FD_ISSET(fd, s) ((s)->fds_bits[(fd) / 64] & (1UL << ((fd) % 64)))
|
||||
|
||||
static inline int nl_select(int nfds, nl_fd_set *r, nl_fd_set *w, nl_fd_set *e,
|
||||
struct nl_timeval *tv) {
|
||||
return (int)__sc6(SYS_select, nfds, (long)r, (long)w, (long)e, (long)tv, 0);
|
||||
}
|
||||
|
||||
/* clock / sleep */
|
||||
static inline int nl_clock_gettime(clockid_t id, struct timespec *ts) {
|
||||
return (int)__sc2(SYS_clock_gettime, id, (long)ts);
|
||||
}
|
||||
static inline int nl_nanosleep(const struct timespec *req,
|
||||
struct timespec *rem) {
|
||||
long r = __sc2(SYS_nanosleep, (long)req, (long)rem);
|
||||
if (r < 0) {
|
||||
errno = (int)-r;
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
static inline void nl_usleep(unsigned long us) {
|
||||
struct timespec ts = {(long)(us / 1000000), (long)((us % 1000000) * 1000)};
|
||||
nl_nanosleep(&ts, (struct timespec *)0);
|
||||
}
|
||||
static inline long nl_time(void) { return (long)__sc1(SYS_time, 0); }
|
||||
static inline void nl_exit(int code) {
|
||||
__sc1(SYS_exit_group, code);
|
||||
__builtin_unreachable();
|
||||
}
|
||||
|
||||
/* inotify */
|
||||
static inline int nl_inotify_init1(int flags) {
|
||||
return (int)__sc1(SYS_inotify_init1, flags);
|
||||
}
|
||||
static inline int nl_inotify_add_watch(int fd, const char *path,
|
||||
uint32_t mask) {
|
||||
return (int)__sc3(SYS_inotify_add_watch, fd, (long)path, mask);
|
||||
}
|
||||
|
||||
/* termios via ioctl */
|
||||
#include <termios.h>
|
||||
#define NL_TCGETS 0x5401
|
||||
#define NL_TCSETS 0x5402
|
||||
#define NL_TCSETSF 0x5404
|
||||
|
||||
static inline int nl_tcgetattr(int fd, struct termios *t) {
|
||||
return nl_ioctl(fd, NL_TCGETS, t);
|
||||
}
|
||||
static inline int nl_tcsetattr(int fd, int action, const struct termios *t) {
|
||||
unsigned long req = (action == 2) ? NL_TCSETSF : NL_TCSETS;
|
||||
return nl_ioctl(fd, req, (void *)t);
|
||||
}
|
||||
|
||||
/* Macro redirects */
|
||||
#define write(fd, buf, n) nl_write(fd, buf, n)
|
||||
#define read(fd, buf, n) nl_read(fd, buf, n)
|
||||
#define _open2(p, f) nl_open(p, f, 0)
|
||||
#define _open3(p, f, m) nl_open(p, f, m)
|
||||
#define _open_pick(_1, _2, _3, N, ...) N
|
||||
#define open(...) _open_pick(__VA_ARGS__, _open3, _open2)(__VA_ARGS__)
|
||||
#define close(fd) nl_close(fd)
|
||||
#define unlink(p) nl_unlink(p)
|
||||
#define mmap(a, l, p, f, fd, o) nl_mmap(a, l, p, f, fd, o)
|
||||
#define munmap(a, l) nl_munmap(a, l)
|
||||
#define ioctl(fd, req, arg) nl_ioctl(fd, req, (void *)(long)(arg))
|
||||
#define clock_gettime(id, ts) nl_clock_gettime(id, ts)
|
||||
#define nanosleep(req, rem) nl_nanosleep(req, rem)
|
||||
#define usleep(us) nl_usleep(us)
|
||||
#define time(p) nl_time()
|
||||
#define tcgetattr(fd, t) nl_tcgetattr(fd, t)
|
||||
#define tcsetattr(fd, act, t) nl_tcsetattr(fd, act, t)
|
||||
#define inotify_init1(f) nl_inotify_init1(f)
|
||||
#define inotify_add_watch(f, p, m) nl_inotify_add_watch(f, p, m)
|
||||
|
||||
#endif
|
||||
164
C/lib/nl_printf.c
Normal file
164
C/lib/nl_printf.c
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
#include "nl_printf.h"
|
||||
#include <stdarg.h>
|
||||
#include <stddef.h>
|
||||
|
||||
static int _uint_to_dec(char *out, size_t avail, unsigned long long v) {
|
||||
char tmp[24];
|
||||
int len = 0;
|
||||
if (v == 0) {
|
||||
tmp[len++] = '0';
|
||||
} else {
|
||||
while (v) {
|
||||
tmp[len++] = '0' + (int)(v % 10);
|
||||
v /= 10;
|
||||
}
|
||||
}
|
||||
int w = 0;
|
||||
for (int i = len - 1; i >= 0 && (size_t)w < avail; i--)
|
||||
out[w++] = tmp[i];
|
||||
return w;
|
||||
}
|
||||
|
||||
static int _uint_to_hex(char *out, size_t avail, unsigned long long v) {
|
||||
static const char hx[] = "0123456789abcdef";
|
||||
char tmp[20];
|
||||
int len = 0;
|
||||
if (v == 0) {
|
||||
tmp[len++] = '0';
|
||||
} else {
|
||||
while (v) {
|
||||
tmp[len++] = hx[v & 0xF];
|
||||
v >>= 4;
|
||||
}
|
||||
}
|
||||
int w = 0;
|
||||
for (int i = len - 1; i >= 0 && (size_t)w < avail; i--)
|
||||
out[w++] = tmp[i];
|
||||
return w;
|
||||
}
|
||||
|
||||
int nl_vsnprintf(char *buf, size_t size, const char *fmt, va_list ap) {
|
||||
if (!buf || size == 0)
|
||||
return 0;
|
||||
size_t pos = 0;
|
||||
|
||||
#define PUT(c) \
|
||||
do { \
|
||||
if (pos + 1 < size) \
|
||||
buf[pos++] = (c); \
|
||||
} while (0)
|
||||
|
||||
while (*fmt) {
|
||||
if (*fmt != '%') {
|
||||
PUT(*fmt++);
|
||||
continue;
|
||||
}
|
||||
fmt++; // skip '%'
|
||||
|
||||
int zero_pad = 0, left_align = 0;
|
||||
while (*fmt == '0' || *fmt == '-') {
|
||||
if (*fmt == '0')
|
||||
zero_pad = 1;
|
||||
if (*fmt == '-')
|
||||
left_align = 1;
|
||||
fmt++;
|
||||
}
|
||||
(void)zero_pad;
|
||||
(void)left_align;
|
||||
|
||||
int width = 0;
|
||||
while (*fmt >= '0' && *fmt <= '9')
|
||||
width = width * 10 + (*fmt++ - '0');
|
||||
|
||||
switch (*fmt++) {
|
||||
case 'd': {
|
||||
long long v = (long long)va_arg(ap, int);
|
||||
char tmp[24];
|
||||
int n = 0;
|
||||
if (v < 0) {
|
||||
PUT('-');
|
||||
v = -v;
|
||||
}
|
||||
n = _uint_to_dec(tmp, sizeof(tmp), (unsigned long long)v);
|
||||
for (int i = n; i < width && pos + 1 < size; i++)
|
||||
PUT(' ');
|
||||
for (int i = 0; i < n && pos + 1 < size; i++)
|
||||
PUT(tmp[i]);
|
||||
break;
|
||||
}
|
||||
case 'u': {
|
||||
unsigned long long v = (unsigned long long)va_arg(ap, unsigned int);
|
||||
char tmp[24];
|
||||
int n = _uint_to_dec(tmp, sizeof(tmp), v);
|
||||
for (int i = n; i < width && pos + 1 < size; i++)
|
||||
PUT(' ');
|
||||
for (int i = 0; i < n && pos + 1 < size; i++)
|
||||
PUT(tmp[i]);
|
||||
break;
|
||||
}
|
||||
case 'x': {
|
||||
unsigned long long v = (unsigned long long)va_arg(ap, unsigned int);
|
||||
char tmp[20];
|
||||
int n = _uint_to_hex(tmp, sizeof(tmp), v);
|
||||
for (int i = 0; i < n && pos + 1 < size; i++)
|
||||
PUT(tmp[i]);
|
||||
break;
|
||||
}
|
||||
case 's': {
|
||||
const char *s = va_arg(ap, const char *);
|
||||
if (!s)
|
||||
s = "(null)";
|
||||
int slen = 0;
|
||||
while (s[slen])
|
||||
slen++;
|
||||
for (int i = slen; i < width && pos + 1 < size; i++)
|
||||
PUT(' ');
|
||||
for (int i = 0; i < slen && pos + 1 < size; i++)
|
||||
PUT(s[i]);
|
||||
break;
|
||||
}
|
||||
case 'c':
|
||||
PUT((char)va_arg(ap, int));
|
||||
break;
|
||||
case '%':
|
||||
PUT('%');
|
||||
break;
|
||||
case 'l':
|
||||
if (*fmt == 'd') {
|
||||
fmt++;
|
||||
long long v = va_arg(ap, long long);
|
||||
char tmp[24];
|
||||
int n = 0;
|
||||
if (v < 0) {
|
||||
PUT('-');
|
||||
v = -v;
|
||||
}
|
||||
n = _uint_to_dec(tmp, sizeof(tmp), (unsigned long long)v);
|
||||
for (int i = 0; i < n && pos + 1 < size; i++)
|
||||
PUT(tmp[i]);
|
||||
} else if (*fmt == 'u') {
|
||||
fmt++;
|
||||
unsigned long long v = va_arg(ap, unsigned long long);
|
||||
char tmp[24];
|
||||
int n = _uint_to_dec(tmp, sizeof(tmp), v);
|
||||
for (int i = 0; i < n && pos + 1 < size; i++)
|
||||
PUT(tmp[i]);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
PUT('?');
|
||||
break;
|
||||
}
|
||||
}
|
||||
#undef PUT
|
||||
buf[pos] = '\0';
|
||||
return (int)pos;
|
||||
}
|
||||
|
||||
int nl_snprintf(char *buf, size_t size, const char *fmt, ...) {
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
int r = nl_vsnprintf(buf, size, fmt, ap);
|
||||
va_end(ap);
|
||||
return r;
|
||||
}
|
||||
38
C/lib/nl_printf.h
Normal file
38
C/lib/nl_printf.h
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
#ifndef NL_PRINTF_H
|
||||
#define NL_PRINTF_H
|
||||
|
||||
#include "nl_io.h"
|
||||
#include "nl_string.h"
|
||||
#include <stdarg.h>
|
||||
#include <stddef.h>
|
||||
|
||||
int nl_vsnprintf(char *buf, size_t size, const char *fmt, va_list ap);
|
||||
int nl_snprintf(char *buf, size_t size, const char *fmt, ...);
|
||||
|
||||
static inline void nl_eprint(const char *msg) {
|
||||
nl_write(2, msg, nl_strlen(msg));
|
||||
}
|
||||
|
||||
static inline int nl_fmt_fps(char *buf, size_t sz, double fps) {
|
||||
int whole = (int)fps;
|
||||
int frac = (int)((fps - whole) * 10.0 + 0.5);
|
||||
if (frac >= 10) {
|
||||
whole++;
|
||||
frac = 0;
|
||||
}
|
||||
return nl_snprintf(buf, sz, "%d.%d", whole, frac);
|
||||
}
|
||||
|
||||
#define snprintf nl_snprintf
|
||||
|
||||
#define stderr 2
|
||||
#define fprintf(fd, fmt, ...) \
|
||||
do { \
|
||||
char _fb[1024]; \
|
||||
int _fn = nl_snprintf(_fb, sizeof(_fb), fmt, ##__VA_ARGS__); \
|
||||
if (_fn > 0) { \
|
||||
size_t _nwrite = (_fn < (int)sizeof(_fb) - 1) ? _fn : sizeof(_fb) - 1; \
|
||||
nl_write((int)(long)(fd), _fb, _nwrite); \
|
||||
} \
|
||||
} while (0)
|
||||
#endif
|
||||
31
C/lib/nl_signal.h
Normal file
31
C/lib/nl_signal.h
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
#ifndef NL_SIGNAL_H
|
||||
#define NL_SIGNAL_H
|
||||
|
||||
#include "nl_syscall.h"
|
||||
|
||||
#define SA_RESTORER 0x04000000
|
||||
#define SIGINT 2
|
||||
#define SIGTERM 15
|
||||
|
||||
struct nl_sigaction {
|
||||
void (*sa_handler)(int);
|
||||
unsigned long sa_flags;
|
||||
void (*sa_restorer)(void);
|
||||
unsigned long sa_mask[16];
|
||||
};
|
||||
|
||||
static void __nl_restore(void) {
|
||||
__asm__ volatile("mov $15, %%rax\nsyscall" ::: "rax", "memory");
|
||||
}
|
||||
|
||||
static inline int nl_signal(int sig, void (*handler)(int)) {
|
||||
struct nl_sigaction sa = {0};
|
||||
sa.sa_handler = handler;
|
||||
sa.sa_flags = SA_RESTORER;
|
||||
sa.sa_restorer = __nl_restore;
|
||||
return (int)__sc3(SYS_rt_sigaction, sig, (long)&sa, 0);
|
||||
}
|
||||
|
||||
#define signal(sig, handler) nl_signal(sig, handler)
|
||||
|
||||
#endif
|
||||
14
C/lib/nl_start.c
Normal file
14
C/lib/nl_start.c
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
extern int main(int argc, char *argv[]);
|
||||
|
||||
__attribute__((naked)) void _start(void) {
|
||||
__asm__ volatile(
|
||||
"xor %%rbp, %%rbp \n"
|
||||
"pop %%rdi \n" /* rdi = argc */
|
||||
"mov %%rsp, %%rsi \n" /* rsi = argv */
|
||||
"and $-16, %%rsp \n" /* 16-byte align stack */
|
||||
"call main \n"
|
||||
"mov %%rax, %%rdi \n" /* exit code = return value of main() */
|
||||
"mov $231, %%rax \n" /* SYS_exit_group */
|
||||
"syscall \n" ::
|
||||
: "memory");
|
||||
}
|
||||
83
C/lib/nl_string.h
Normal file
83
C/lib/nl_string.h
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
#ifndef NL_STRING_H
|
||||
#define NL_STRING_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
static inline size_t nl_strlen(const char *s) {
|
||||
const char *p = s;
|
||||
while (*p)
|
||||
p++;
|
||||
return (size_t)(p - s);
|
||||
}
|
||||
|
||||
static inline void *nl_memcpy(void *dst, const void *src, size_t n) {
|
||||
uint8_t *d = (uint8_t *)dst;
|
||||
const uint8_t *s = (const uint8_t *)src;
|
||||
while (n--)
|
||||
*d++ = *s++;
|
||||
return dst;
|
||||
}
|
||||
|
||||
static inline void *nl_memset(void *dst, int c, size_t n) {
|
||||
uint8_t *d = (uint8_t *)dst;
|
||||
while (n--)
|
||||
*d++ = (uint8_t)c;
|
||||
return dst;
|
||||
}
|
||||
|
||||
static inline int nl_strcmp(const char *a, const char *b) {
|
||||
while (*a && *a == *b) {
|
||||
a++;
|
||||
b++;
|
||||
}
|
||||
return (unsigned char)*a - (unsigned char)*b;
|
||||
}
|
||||
|
||||
static inline char *nl_strncpy_safe(char *dst, const char *src, size_t n) {
|
||||
if (n == 0)
|
||||
return dst;
|
||||
size_t i;
|
||||
for (i = 0; i < n - 1 && src[i]; i++)
|
||||
dst[i] = src[i];
|
||||
dst[i] = '\0';
|
||||
return dst;
|
||||
}
|
||||
|
||||
static inline char *nl_basename(char *path) {
|
||||
char *p = path, *last = path;
|
||||
while (*p) {
|
||||
if (*p == '/')
|
||||
last = p + 1;
|
||||
p++;
|
||||
}
|
||||
return last;
|
||||
}
|
||||
|
||||
static inline char *nl_dirname(char *path) {
|
||||
char *last = (char *)0, *p = path;
|
||||
while (*p) {
|
||||
if (*p == '/')
|
||||
last = p;
|
||||
p++;
|
||||
}
|
||||
if (!last) {
|
||||
path[0] = '.';
|
||||
path[1] = '\0';
|
||||
} else if (last == path) {
|
||||
path[1] = '\0';
|
||||
} else {
|
||||
*last = '\0';
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
#define strlen(s) nl_strlen(s)
|
||||
#define memcpy(d, s, n) nl_memcpy(d, s, n)
|
||||
#define memset(d, c, n) nl_memset(d, c, n)
|
||||
#define strcmp(a, b) nl_strcmp(a, b)
|
||||
#define strncpy(d, s, n) nl_strncpy_safe(d, s, n)
|
||||
#define basename(p) nl_basename(p)
|
||||
#define dirname(p) nl_dirname(p)
|
||||
|
||||
#endif
|
||||
93
C/lib/nl_syscall.h
Normal file
93
C/lib/nl_syscall.h
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
#ifndef NL_SYSCALL_H
|
||||
#define NL_SYSCALL_H
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef long ssize_t;
|
||||
typedef int sig_atomic_t;
|
||||
|
||||
// errno
|
||||
extern int errno;
|
||||
#define EINTR 4
|
||||
#define EAGAIN 11
|
||||
|
||||
// File descriptors
|
||||
#define STDIN_FILENO 0
|
||||
#define STDOUT_FILENO 1
|
||||
#define STDERR_FILENO 2
|
||||
|
||||
// File open flags
|
||||
#define O_RDONLY 0
|
||||
#define O_WRONLY 1
|
||||
#define O_RDWR 2
|
||||
#define O_CREAT 0100
|
||||
#define O_TRUNC 01000
|
||||
#define O_NONBLOCK 04000
|
||||
#define O_CLOEXEC 02000000
|
||||
|
||||
// mmap constants
|
||||
#define MAP_FAILED ((void *)-1)
|
||||
#define PROT_READ 1
|
||||
#define PROT_WRITE 2
|
||||
#define MAP_SHARED 1
|
||||
|
||||
// Syscall numbers (x86-64 Linux)
|
||||
#define SYS_read 0
|
||||
#define SYS_write 1
|
||||
#define SYS_open 2
|
||||
#define SYS_close 3
|
||||
#define SYS_mmap 9
|
||||
#define SYS_munmap 11
|
||||
#define SYS_rt_sigaction 13
|
||||
#define SYS_ioctl 16
|
||||
#define SYS_select 23
|
||||
#define SYS_nanosleep 35
|
||||
#define SYS_unlink 87
|
||||
#define SYS_clock_gettime 228
|
||||
#define SYS_exit_group 231
|
||||
#define SYS_time 201
|
||||
#define SYS_inotify_init1 294
|
||||
#define SYS_inotify_add_watch 254
|
||||
|
||||
// Raw syscall asm wrappers
|
||||
static inline long __sc1(long n, long a1) {
|
||||
long r;
|
||||
__asm__ volatile("syscall"
|
||||
: "=a"(r)
|
||||
: "0"(n), "D"(a1)
|
||||
: "rcx", "r11", "memory");
|
||||
return r;
|
||||
}
|
||||
static inline long __sc2(long n, long a1, long a2) {
|
||||
long r;
|
||||
__asm__ volatile("syscall"
|
||||
: "=a"(r)
|
||||
: "0"(n), "D"(a1), "S"(a2)
|
||||
: "rcx", "r11", "memory");
|
||||
return r;
|
||||
}
|
||||
static inline long __sc3(long n, long a1, long a2, long a3) {
|
||||
long r;
|
||||
__asm__ volatile("syscall"
|
||||
: "=a"(r)
|
||||
: "0"(n), "D"(a1), "S"(a2), "d"(a3)
|
||||
: "rcx", "r11", "memory");
|
||||
return r;
|
||||
}
|
||||
static inline long __sc6(long n, long a1, long a2, long a3, long a4, long a5,
|
||||
long a6) {
|
||||
long r;
|
||||
register long r10 __asm__("r10") = a4;
|
||||
register long r8 __asm__("r8") = a5;
|
||||
register long r9 __asm__("r9") = a6;
|
||||
__asm__ volatile("syscall"
|
||||
: "=a"(r)
|
||||
: "0"(n), "D"(a1), "S"(a2), "d"(a3), "r"(r10), "r"(r8),
|
||||
"r"(r9)
|
||||
: "rcx", "r11", "memory");
|
||||
return r;
|
||||
}
|
||||
|
||||
#endif
|
||||
16
C/lib/nolibc.h
Normal file
16
C/lib/nolibc.h
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#ifndef NOLIBC_H
|
||||
#define NOLIBC_H
|
||||
|
||||
#define _SYS_SELECT_H 1
|
||||
#define __FD_SETSIZE 1024
|
||||
|
||||
#include "nl_alloc.h"
|
||||
#include "nl_errno.h"
|
||||
#include "nl_getopt.h"
|
||||
#include "nl_io.h"
|
||||
#include "nl_printf.h"
|
||||
#include "nl_signal.h"
|
||||
#include "nl_string.h"
|
||||
#include "nl_syscall.h"
|
||||
|
||||
#endif
|
||||
154
C/src/ascii.c
154
C/src/ascii.c
|
|
@ -2,42 +2,39 @@
|
|||
|
||||
#include <immintrin.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "nolibc.h"
|
||||
|
||||
// Helpers
|
||||
static inline uint8_t clamp_u8(int v) {
|
||||
return (v < 0) ? 0 : (v > 255) ? 255 : (uint8_t)v;
|
||||
}
|
||||
static inline int my_abs(int x) {
|
||||
return x < 0 ? -x : x;
|
||||
}
|
||||
|
||||
// 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_simd(const uint8_t *yuyv, uint8_t *gray, int width, int height) {
|
||||
int total_pixels = width * height;
|
||||
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);
|
||||
// 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];
|
||||
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) {
|
||||
|
|
@ -59,49 +56,6 @@ 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) {
|
||||
|
|
@ -118,16 +72,8 @@ size_t ascii_out_size(int dst_w, int dst_h, int color) {
|
|||
|
||||
// Sobel edge detection (kernel convolution)
|
||||
static void sobel(const uint8_t *in, uint8_t *out, int w, int h) {
|
||||
static const int Gx[3][3] = {
|
||||
{-1, 0, 1},
|
||||
{-2, 0, 2},
|
||||
{-1, 0, 1}
|
||||
};
|
||||
static const int Gy[3][3] = {
|
||||
{-1, -2, -1},
|
||||
{0, 0, 0},
|
||||
{1, 2, 1}
|
||||
};
|
||||
static const int Gx[3][3] = {{-1, 0, 1}, {-2, 0, 2}, {-1, 0, 1}};
|
||||
static const int Gy[3][3] = {{-1, -2, -1}, {0, 0, 0}, {1, 2, 1}};
|
||||
|
||||
for (int y = 1; y < h - 1; y++) {
|
||||
for (int x = 1; x < w - 1; x++) {
|
||||
|
|
@ -141,7 +87,7 @@ static void sobel(const uint8_t *in, uint8_t *out, int w, int h) {
|
|||
}
|
||||
|
||||
// TEST: test both L1 and L2 normalisations
|
||||
int mag = abs(gx) + abs(gy); // L1 normalisation
|
||||
int mag = my_abs(gx) + my_abs(gy); // L1 normalisation
|
||||
// int mag = sqrt(gx * gx + gy * gy); // L2 normalisation
|
||||
|
||||
out[y * w + x] = (uint8_t)(mag > 255 ? 255 : mag);
|
||||
|
|
@ -153,22 +99,23 @@ 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);
|
||||
|
|
@ -328,22 +275,25 @@ int grayscale_to_ascii(const uint8_t *gray, const uint8_t *rgb, int src_w,
|
|||
return out_idx;
|
||||
}
|
||||
|
||||
|
||||
// FPS overlay
|
||||
void overlay_fps_box(int dst_w, double fps, int color_enabled) {
|
||||
char buf[80];
|
||||
int col = (dst_w - 13) / 2 + 1;
|
||||
if (col < 1) col = 1;
|
||||
int col = (dst_w - 13) / 2 + 1;
|
||||
if (col < 1)
|
||||
col = 1;
|
||||
|
||||
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",
|
||||
col, fps);
|
||||
char fpsbuf[10];
|
||||
nl_fmt_fps(fpsbuf, sizeof(fpsbuf), fps);
|
||||
n = snprintf(
|
||||
buf, sizeof(buf),
|
||||
"\033[1;%dH\033[38;2;0;255;0m\033[48;2;30;30;30m[ FPS: %s ]\033[0m",
|
||||
col, fpsbuf);
|
||||
} else {
|
||||
n = snprintf(buf, sizeof(buf),
|
||||
"\033[1;%dH[ FPS: %4.1f ]",
|
||||
col, fps);
|
||||
char fpsbuf[10];
|
||||
nl_fmt_fps(fpsbuf, sizeof(fpsbuf), fps);
|
||||
n = snprintf(buf, sizeof(buf), "\033[1;%dH[ FPS: %s ]", col, fpsbuf);
|
||||
}
|
||||
if (n > 0 && n < (int)sizeof(buf))
|
||||
(void)write(STDOUT_FILENO, buf, (size_t)n);
|
||||
|
|
|
|||
203
C/src/capture.c
203
C/src/capture.c
|
|
@ -1,138 +1,135 @@
|
|||
#include "capture.h"
|
||||
#include "ascii.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <linux/videodev2.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/select.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "nolibc.h"
|
||||
|
||||
int webcam_init(webcam_t *cam, const char *device, int width, int height) {
|
||||
// Open device (non‑blocking for select usage)
|
||||
cam->fd = open(device, O_RDWR | O_NONBLOCK);
|
||||
if (cam->fd < 0) return -1;
|
||||
// Open device (non‑blocking for select usage)
|
||||
cam->fd = open(device, O_RDWR | O_NONBLOCK, 0);
|
||||
if (cam->fd < 0)
|
||||
return -1;
|
||||
|
||||
// Set format
|
||||
struct v4l2_format fmt = {0};
|
||||
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
fmt.fmt.pix.width = width;
|
||||
fmt.fmt.pix.height = height;
|
||||
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
|
||||
fmt.fmt.pix.field = V4L2_FIELD_NONE;
|
||||
// Set format
|
||||
struct v4l2_format fmt = {0};
|
||||
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
fmt.fmt.pix.width = width;
|
||||
fmt.fmt.pix.height = height;
|
||||
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
|
||||
fmt.fmt.pix.field = V4L2_FIELD_NONE;
|
||||
|
||||
if (ioctl(cam->fd, VIDIOC_S_FMT, &fmt) < 0) {
|
||||
close(cam->fd);
|
||||
return -1;
|
||||
}
|
||||
if (ioctl(cam->fd, VIDIOC_S_FMT, &fmt) < 0) {
|
||||
close(cam->fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Read back actual resolution
|
||||
cam->width = fmt.fmt.pix.width;
|
||||
cam->height = fmt.fmt.pix.height;
|
||||
// Read back actual resolution
|
||||
cam->width = fmt.fmt.pix.width;
|
||||
cam->height = fmt.fmt.pix.height;
|
||||
|
||||
// Request one mmap buffer
|
||||
struct v4l2_requestbuffers req = {0};
|
||||
req.count = 1;
|
||||
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
req.memory = V4L2_MEMORY_MMAP;
|
||||
// Request one mmap buffer
|
||||
struct v4l2_requestbuffers req = {0};
|
||||
req.count = 1;
|
||||
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
req.memory = V4L2_MEMORY_MMAP;
|
||||
|
||||
if (ioctl(cam->fd, VIDIOC_REQBUFS, &req) < 0) {
|
||||
close(cam->fd);
|
||||
return -1;
|
||||
}
|
||||
if (ioctl(cam->fd, VIDIOC_REQBUFS, &req) < 0) {
|
||||
close(cam->fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Query buffer info
|
||||
struct v4l2_buffer buf = {0};
|
||||
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
buf.memory = V4L2_MEMORY_MMAP;
|
||||
buf.index = 0;
|
||||
// Query buffer info
|
||||
struct v4l2_buffer buf = {0};
|
||||
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
buf.memory = V4L2_MEMORY_MMAP;
|
||||
buf.index = 0;
|
||||
|
||||
if (ioctl(cam->fd, VIDIOC_QUERYBUF, &buf) < 0) {
|
||||
close(cam->fd);
|
||||
return -1;
|
||||
}
|
||||
if (ioctl(cam->fd, VIDIOC_QUERYBUF, &buf) < 0) {
|
||||
close(cam->fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// mmap
|
||||
cam->buffer = mmap(NULL, buf.length, PROT_READ | PROT_WRITE,
|
||||
MAP_SHARED, cam->fd, buf.m.offset);
|
||||
if (cam->buffer == MAP_FAILED) {
|
||||
close(cam->fd);
|
||||
return -1;
|
||||
}
|
||||
// mmap
|
||||
cam->buffer = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED,
|
||||
cam->fd, buf.m.offset);
|
||||
if (cam->buffer == MAP_FAILED) {
|
||||
close(cam->fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Store buffer info for later munmap and requeue
|
||||
cam->buf_info = buf;
|
||||
// Store buffer info for later munmap and requeue
|
||||
cam->buf_info = buf;
|
||||
|
||||
// Queue the buffer
|
||||
if (ioctl(cam->fd, VIDIOC_QBUF, &buf) < 0) {
|
||||
munmap(cam->buffer, buf.length);
|
||||
close(cam->fd);
|
||||
return -1;
|
||||
}
|
||||
// Queue the buffer
|
||||
if (ioctl(cam->fd, VIDIOC_QBUF, &buf) < 0) {
|
||||
munmap(cam->buffer, buf.length);
|
||||
close(cam->fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Start streaming
|
||||
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
if (ioctl(cam->fd, VIDIOC_STREAMON, &type) < 0) {
|
||||
munmap(cam->buffer, buf.length);
|
||||
close(cam->fd);
|
||||
return -1;
|
||||
}
|
||||
// Start streaming
|
||||
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
if (ioctl(cam->fd, VIDIOC_STREAMON, &type) < 0) {
|
||||
munmap(cam->buffer, buf.length);
|
||||
close(cam->fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int webcam_wait_frame(webcam_t *cam, int timeout_ms) {
|
||||
fd_set fds;
|
||||
struct timeval tv;
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(cam->fd, &fds);
|
||||
tv.tv_sec = timeout_ms / 1000;
|
||||
tv.tv_usec = (timeout_ms % 1000) * 1000;
|
||||
nl_fd_set fds;
|
||||
struct nl_timeval tv;
|
||||
NL_FD_ZERO(&fds);
|
||||
NL_FD_SET(cam->fd, &fds);
|
||||
tv.tv_sec = timeout_ms / 1000;
|
||||
tv.tv_usec = (timeout_ms % 1000) * 1000;
|
||||
|
||||
int ret = select(cam->fd + 1, &fds, NULL, NULL, &tv);
|
||||
if (ret <= 0) return -1; // timeout or error
|
||||
return 0;
|
||||
int ret = nl_select(cam->fd + 1, &fds, (nl_fd_set *)0, (nl_fd_set *)0, &tv);
|
||||
if (ret <= 0)
|
||||
return -1; // timeout or error
|
||||
return 0;
|
||||
}
|
||||
|
||||
int webcam_capture_frame(webcam_t *cam, uint8_t *gray_buffer) {
|
||||
// Dequeue buffer
|
||||
struct v4l2_buffer buf = cam->buf_info;
|
||||
if (ioctl(cam->fd, VIDIOC_DQBUF, &buf) < 0) return -1;
|
||||
// Dequeue buffer
|
||||
struct v4l2_buffer buf = cam->buf_info;
|
||||
if (ioctl(cam->fd, VIDIOC_DQBUF, &buf) < 0)
|
||||
return -1;
|
||||
|
||||
// Convert YUYV -> grayscale (Y component)
|
||||
// uint8_t *yuyv = (uint8_t *)cam->buffer;
|
||||
// for (int i = 0, j = 0; i < cam->width * cam->height * 2; i += 2, j++) {
|
||||
// gray_buffer[j] = yuyv[i];
|
||||
// }
|
||||
int w = cam->width;
|
||||
int h = cam->height;
|
||||
yuyv_to_gray_simd((uint8_t *)cam->buffer, gray_buffer, w, h);
|
||||
// Convert YUYV -> grayscale (Y component)
|
||||
// uint8_t *yuyv = (uint8_t *)cam->buffer;
|
||||
// for (int i = 0, j = 0; i < cam->width * cam->height * 2; i += 2, j++) {
|
||||
// gray_buffer[j] = yuyv[i];
|
||||
// }
|
||||
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;
|
||||
return 0;
|
||||
// Store updated buffer info for requeue
|
||||
cam->buf_info = buf;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int webcam_requeue_buffer(webcam_t *cam) {
|
||||
if (ioctl(cam->fd, VIDIOC_QBUF, &cam->buf_info) < 0) return -1;
|
||||
return 0;
|
||||
if (ioctl(cam->fd, VIDIOC_QBUF, &cam->buf_info) < 0)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void webcam_cleanup(webcam_t *cam) {
|
||||
if (cam->fd >= 0) {
|
||||
// Stop streaming
|
||||
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
ioctl(cam->fd, VIDIOC_STREAMOFF, &type);
|
||||
// Unmap and close
|
||||
if (cam->buffer != MAP_FAILED)
|
||||
munmap(cam->buffer, cam->buf_info.length);
|
||||
close(cam->fd);
|
||||
}
|
||||
cam->fd = -1;
|
||||
cam->buffer = MAP_FAILED;
|
||||
if (cam->fd >= 0) {
|
||||
// Stop streaming
|
||||
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
ioctl(cam->fd, VIDIOC_STREAMOFF, &type);
|
||||
// Unmap and close
|
||||
if (cam->buffer != MAP_FAILED)
|
||||
munmap(cam->buffer, cam->buf_info.length);
|
||||
close(cam->fd);
|
||||
}
|
||||
cam->fd = -1;
|
||||
cam->buffer = MAP_FAILED;
|
||||
}
|
||||
|
|
|
|||
251
C/src/main.c
251
C/src/main.c
|
|
@ -4,27 +4,26 @@
|
|||
#include "thread_sharing.h"
|
||||
#include "timing.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/inotify.h>
|
||||
#include <sys/mman.h>
|
||||
#include <termios.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "nolibc.h"
|
||||
|
||||
typedef int sig_atomic_t;
|
||||
|
||||
#define MAP_FAILED ((void *)-1)
|
||||
#define PROT_READ 1
|
||||
#define PROT_WRITE 2
|
||||
#define MAP_SHARED 1
|
||||
|
||||
// Defaults
|
||||
#define DEFAULT_ASCII_WIDTH 80
|
||||
#define DEFAULT_ASCII_HEIGHT 40
|
||||
#define DEFAULT_CAPTURE_WIDTH 160
|
||||
#define DEFAULT_ASCII_WIDTH 80
|
||||
#define DEFAULT_ASCII_HEIGHT 40
|
||||
#define DEFAULT_CAPTURE_WIDTH 160
|
||||
#define DEFAULT_CAPTURE_HEIGHT 120
|
||||
#define DEFAULT_FPS 20
|
||||
#define MAX_PLUGINS 8
|
||||
#define DEFAULT_FPS 20
|
||||
#define MAX_PLUGINS 8
|
||||
|
||||
// Signal handling
|
||||
volatile sig_atomic_t keep_running = 1;
|
||||
|
|
@ -35,6 +34,17 @@ void handle_signal(int sig) {
|
|||
|
||||
static struct termios orig_terminal;
|
||||
|
||||
static int my_atoi(const char *s) {
|
||||
int n = 0, neg = 0;
|
||||
if (*s == '-') {
|
||||
neg = 1;
|
||||
s++;
|
||||
}
|
||||
while (*s >= '0' && *s <= '9')
|
||||
n = n * 10 + (*s++ - '0');
|
||||
return neg ? -n : n;
|
||||
}
|
||||
|
||||
// Usage
|
||||
static void print_usage(const char *prog) {
|
||||
fprintf(
|
||||
|
|
@ -60,8 +70,7 @@ static void print_usage(const char *prog) {
|
|||
" -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,
|
||||
prog, DEFAULT_CAPTURE_WIDTH, DEFAULT_CAPTURE_HEIGHT, DEFAULT_FPS,
|
||||
DEFAULT_ASCII_WIDTH, DEFAULT_ASCII_HEIGHT, ASCII_CHARS_DEFAULT);
|
||||
}
|
||||
|
||||
|
|
@ -69,82 +78,71 @@ static void print_usage(const char *prog) {
|
|||
void term_raw_mode(void) {
|
||||
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); }
|
||||
|
||||
static void overlay_panel(int ascii_h, double fps,
|
||||
plugin_loader_t *plugins, int *plugin_params,
|
||||
int count, int selected, int color) {
|
||||
static void overlay_panel(int ascii_h, double fps, plugin_loader_t *plugins,
|
||||
int *plugin_params, int count, int selected,
|
||||
int color) {
|
||||
char buf[1024];
|
||||
int n, base_row = ascii_h + 1; // 1-indexed panel row
|
||||
|
||||
// const char *name = pl->plugin ? pl->plugin->name : "none";
|
||||
// const char *status = pl->status_msg[0] ? pl->status_msg : "ok";
|
||||
//
|
||||
// if (color) {
|
||||
// n = snprintf(buf, sizeof(buf),
|
||||
// "\033[%d;1H\033[38;2;180;180;0m\033[48;2;18;18;18m"
|
||||
// " plugin: %-14s | %s | param: %3d ([ ] ±1 { } ±10 r=reset) "
|
||||
// "\033[0m\033[K",
|
||||
// row, name, status, plugin_param);
|
||||
// } else {
|
||||
// n = snprintf(buf, sizeof(buf),
|
||||
// "\033[%d;1H"
|
||||
// " plugin: %-14s | %s | param: %3d ([ ] ±1 { } ±10 r=reset)"
|
||||
// "\033[K",
|
||||
// row, name, status, plugin_param);
|
||||
// }
|
||||
// if (n > 0 && n < (int)sizeof(buf))
|
||||
// (void)write(STDOUT_FILENO, buf, (size_t)n);
|
||||
// FPS + hint bar
|
||||
char fpsbuf[10];
|
||||
nl_fmt_fps(fpsbuf, sizeof(fpsbuf), fps);
|
||||
if (color) {
|
||||
n = snprintf(buf, sizeof(buf),
|
||||
"\033[%d;1H\033[38;2;0;220;0m\033[48;2;18;18;18m"
|
||||
" FPS: %4.1f │ ↑↓ select [ ] ±1 { } ±10 r reset q quit "
|
||||
"\033[0m\033[K", base_row, fps);
|
||||
n = nl_snprintf(buf, sizeof(buf),
|
||||
"\033[%d;1H\033[38;2;0;220;0m\033[48;2;18;18;18m"
|
||||
" FPS: %s │ ↑↓ select [ ] ±1 { } ±10 r reset q quit "
|
||||
"\033[0m\033[K",
|
||||
base_row, fpsbuf);
|
||||
} else {
|
||||
n = snprintf(buf, sizeof(buf),
|
||||
"\033[%d;1H FPS: %4.1f | up/dn select [ ] +-1 { } +-10 r reset q quit\033[K",
|
||||
base_row, fps);
|
||||
n = nl_snprintf(buf, sizeof(buf),
|
||||
"\033[%d;1H FPS: %s | up/dn select [ ] +-1 { } +-10 r "
|
||||
"reset q quit\033[K",
|
||||
base_row, fpsbuf);
|
||||
}
|
||||
if (n > 0 && n < (int)sizeof(buf))
|
||||
(void)write(STDOUT_FILENO, buf, (size_t)n);
|
||||
|
||||
n = snprintf(buf, sizeof(buf), "\033[%d;1H\033[K", base_row + 1);
|
||||
if (n > 0) (void)write(STDOUT_FILENO, buf, (size_t)n);
|
||||
n = nl_snprintf(buf, sizeof(buf), "\033[%d;1H\033[K", base_row + 1);
|
||||
if (n > 0)
|
||||
(void)write(STDOUT_FILENO, buf, (size_t)n);
|
||||
|
||||
// Plugin cells
|
||||
if (count == 0) {
|
||||
const char *msg = color
|
||||
? "\033[38;2;120;120;120m no plugins loaded \033[0m"
|
||||
: " no plugins loaded";
|
||||
const char *msg = color ? "\033[38;2;120;120;120m no plugins loaded \033[0m"
|
||||
: " no plugins loaded";
|
||||
(void)write(STDOUT_FILENO, msg, strlen(msg));
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
const char *name = plugins[i].plugin ? plugins[i].plugin->name : "???";
|
||||
int param = plugin_params[i];
|
||||
int is_sel = (i == selected);
|
||||
int param = plugin_params[i];
|
||||
int is_sel = (i == selected);
|
||||
|
||||
if (color) {
|
||||
// Selected: bright yellow text on dark blue bg; others: dim
|
||||
if (is_sel)
|
||||
n = snprintf(buf, sizeof(buf),
|
||||
"\033[38;2;255;220;0m\033[48;2;0;40;80m"
|
||||
" ▶ %s [%3d] \033[0m ", name, param);
|
||||
else
|
||||
n = snprintf(buf, sizeof(buf),
|
||||
"\033[38;2;140;140;140m\033[48;2;18;18;18m"
|
||||
" %s [%3d] \033[0m ", name, param);
|
||||
if (is_sel) {
|
||||
n = nl_snprintf(buf, sizeof(buf),
|
||||
"\033[38;2;255;220;0m\033[48;2;0;40;80m"
|
||||
" ▶ %s [%3d] \033[0m ",
|
||||
name, param);
|
||||
} else {
|
||||
n = nl_snprintf(buf, sizeof(buf),
|
||||
"\033[38;2;140;140;140m\033[48;2;18;18;18m"
|
||||
" %s [%3d] \033[0m ",
|
||||
name, param);
|
||||
}
|
||||
} else {
|
||||
n = snprintf(buf, sizeof(buf),
|
||||
is_sel ? " *%s[%3d] " : " %s[%3d] ", name, param);
|
||||
n = nl_snprintf(buf, sizeof(buf), is_sel ? " *%s[%3d] " : " %s[%3d] ",
|
||||
name, param);
|
||||
}
|
||||
if (n > 0 && n < (int)sizeof(buf))
|
||||
(void)write(STDOUT_FILENO, buf, (size_t)n);
|
||||
|
|
@ -155,68 +153,75 @@ fps_counter_t fps_calc = {0};
|
|||
|
||||
// Main
|
||||
int main(int argc, char *argv[]) {
|
||||
signal(SIGINT, handle_signal);
|
||||
signal(SIGTERM, handle_signal);
|
||||
for (int i = 1; i < argc; i++) {
|
||||
if (nl_strcmp(argv[i], "--help") == 0) {
|
||||
print_usage(argv[0]);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
nl_signal(SIGINT, handle_signal);
|
||||
nl_signal(SIGTERM, handle_signal);
|
||||
|
||||
// Config
|
||||
char *device = "/dev/video0";
|
||||
int ascii_w = DEFAULT_ASCII_WIDTH;
|
||||
int ascii_h = DEFAULT_ASCII_HEIGHT;
|
||||
int cap_w = DEFAULT_CAPTURE_WIDTH;
|
||||
int cap_h = DEFAULT_CAPTURE_HEIGHT;
|
||||
int fps = DEFAULT_FPS;
|
||||
int ascii_w = DEFAULT_ASCII_WIDTH;
|
||||
int ascii_h = DEFAULT_ASCII_HEIGHT;
|
||||
int cap_w = DEFAULT_CAPTURE_WIDTH;
|
||||
int cap_h = DEFAULT_CAPTURE_HEIGHT;
|
||||
int fps = DEFAULT_FPS;
|
||||
|
||||
ascii_opts_t opts = {
|
||||
.brightness = 0,
|
||||
.contrast = 100,
|
||||
.invert = 0,
|
||||
.color = 0,
|
||||
.edges = 0,
|
||||
.dither = 0,
|
||||
.charset = NULL,
|
||||
.contrast = 100,
|
||||
.invert = 0,
|
||||
.color = 0,
|
||||
.edges = 0,
|
||||
.dither = 0,
|
||||
.charset = NULL,
|
||||
};
|
||||
|
||||
// Plugins
|
||||
const char *plugin_paths[MAX_PLUGINS];
|
||||
int plugin_path_count = 0;
|
||||
int plugin_path_count = 0;
|
||||
|
||||
// CLI parsing
|
||||
int opt;
|
||||
while ((opt = getopt(argc, argv, "d:W:H:w:h:f:b:c:iCDs:p:")) != -1) {
|
||||
while ((opt = nl_getopt(argc, argv, "d:W:H:w:h:f:b:c:iCDes:p:")) != -1)
|
||||
switch (opt) {
|
||||
case 'd':
|
||||
device = optarg;
|
||||
break;
|
||||
case 'W':
|
||||
ascii_w = atoi(optarg);
|
||||
ascii_w = my_atoi(optarg);
|
||||
if (ascii_w <= 0)
|
||||
ascii_w = DEFAULT_ASCII_WIDTH;
|
||||
break;
|
||||
case 'H':
|
||||
ascii_h = atoi(optarg);
|
||||
ascii_h = my_atoi(optarg);
|
||||
if (ascii_h <= 0)
|
||||
ascii_h = DEFAULT_ASCII_HEIGHT;
|
||||
break;
|
||||
case 'w':
|
||||
cap_w = atoi(optarg);
|
||||
cap_w = my_atoi(optarg);
|
||||
if (cap_w <= 0)
|
||||
cap_w = DEFAULT_CAPTURE_WIDTH;
|
||||
break;
|
||||
case 'h':
|
||||
cap_h = atoi(optarg);
|
||||
cap_h = my_atoi(optarg);
|
||||
if (cap_h <= 0)
|
||||
cap_h = DEFAULT_CAPTURE_HEIGHT;
|
||||
break;
|
||||
case 'f':
|
||||
fps = atoi(optarg);
|
||||
fps = my_atoi(optarg);
|
||||
if (fps <= 0)
|
||||
fps = DEFAULT_FPS;
|
||||
break;
|
||||
case 'b':
|
||||
opts.brightness = atoi(optarg);
|
||||
opts.brightness = my_atoi(optarg);
|
||||
break;
|
||||
case 'c':
|
||||
opts.contrast = atoi(optarg);
|
||||
opts.contrast = my_atoi(optarg);
|
||||
if (opts.contrast <= 0)
|
||||
opts.contrast = 100;
|
||||
break;
|
||||
|
|
@ -239,26 +244,26 @@ int main(int argc, char *argv[]) {
|
|||
if (plugin_path_count < MAX_PLUGINS)
|
||||
plugin_paths[plugin_path_count++] = optarg;
|
||||
else
|
||||
fprintf(stderr, "Warning: max %d plugins, ignoring %s\n", MAX_PLUGINS, optarg);
|
||||
fprintf(stderr, "Warning: max %d plugins, ignoring %s\n", MAX_PLUGINS,
|
||||
optarg);
|
||||
break;
|
||||
default:
|
||||
print_usage(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
timing_init(fps);
|
||||
|
||||
// Initialize plugins
|
||||
plugin_loader_t plugins[MAX_PLUGINS];
|
||||
int plugin_params[MAX_PLUGINS];
|
||||
int plugin_count = 0;
|
||||
int plugin_params[MAX_PLUGINS];
|
||||
int plugin_count = 0;
|
||||
|
||||
for (int i = 0; i < plugin_path_count; i++) {
|
||||
memset(&plugins[i], 0, sizeof(plugin_loader_t));
|
||||
plugins[i].inotify_fd = -1;
|
||||
plugin_params[i] = 128; // default
|
||||
|
||||
plugin_params[i] = 128; // default
|
||||
|
||||
if (plugin_load(&plugins[i], plugin_paths[i]) == 0) {
|
||||
plugin_watch_init(&plugins[i], plugin_paths[i]);
|
||||
plugin_count++;
|
||||
|
|
@ -266,7 +271,7 @@ int main(int argc, char *argv[]) {
|
|||
fprintf(stderr, "Failed to load plugin: %s\n", plugin_paths[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int selected = 0;
|
||||
|
||||
// Open webcam
|
||||
|
|
@ -275,15 +280,15 @@ int main(int argc, char *argv[]) {
|
|||
perror("webcam_init");
|
||||
return 1;
|
||||
}
|
||||
fprintf(stderr, "Device: %s | capture %dx%d | ASCII %dx%d | %d fps | %d plugin(s)%s%s%s%s\n",
|
||||
fprintf(stderr,
|
||||
"Device: %s | capture %dx%d | ASCII %dx%d | %d fps | %d "
|
||||
"plugin(s)%s%s%s%s\n",
|
||||
device, cam.width, cam.height, ascii_w, ascii_h, fps, plugin_count,
|
||||
opts.color ? " | color" : "",
|
||||
opts.edges ? " | edges" : "",
|
||||
opts.dither ? " | dither" : "",
|
||||
opts.invert ? " | inverted" : "");
|
||||
opts.color ? " | color" : "", opts.edges ? " | edges" : "",
|
||||
opts.dither ? " | dither" : "", opts.invert ? " | inverted" : "");
|
||||
|
||||
// Pixel buffers allocation
|
||||
int cam_pixels = cam.width * cam.height;
|
||||
int cam_pixels = cam.width * cam.height;
|
||||
uint8_t *gray = malloc(cam_pixels);
|
||||
uint8_t *rgb = opts.color ? malloc(cam_pixels * 3) : NULL;
|
||||
|
||||
|
|
@ -296,7 +301,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");
|
||||
|
|
@ -338,12 +343,12 @@ int main(int argc, char *argv[]) {
|
|||
clock_gettime(CLOCK_MONOTONIC, &frame_start);
|
||||
|
||||
long frame_diff_ns =
|
||||
(frame_start.tv_sec - last_frame_time.tv_sec) * 1000000000L + // Seconds
|
||||
(frame_start.tv_nsec - last_frame_time.tv_nsec); // Nano seconds
|
||||
(frame_start.tv_sec - last_frame_time.tv_sec) * 1000000000L + // Seconds
|
||||
(frame_start.tv_nsec - last_frame_time.tv_nsec); // Nano seconds
|
||||
|
||||
if (frame_diff_ns > 0)
|
||||
fps_push(&fps_calc, frame_diff_ns);
|
||||
last_frame_time = frame_start;
|
||||
last_frame_time = frame_start;
|
||||
double current_fps = fps_get(&fps_calc);
|
||||
|
||||
// Keypress handling
|
||||
|
|
@ -367,16 +372,35 @@ int main(int argc, char *argv[]) {
|
|||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// adjust plugin param if selected
|
||||
int *p = (plugin_count > 0) ? &plugin_params[selected] : NULL;
|
||||
switch (ch) {
|
||||
case 'q': case 'Q': keep_running = 0; break;
|
||||
case ']' : if (p && *p < 255) (*p)++; break;
|
||||
case '[' : if (p && *p > 0) (*p)--; break;
|
||||
case '}' : if (p) *p = (*p + 10 > 255) ? 255 : *p + 10; break;
|
||||
case '{' : if (p) *p = (*p - 10 < 0) ? 0 : *p - 10; break;
|
||||
case 'r': case 'R': if (p) *p = 128; break;
|
||||
case 'q':
|
||||
case 'Q':
|
||||
keep_running = 0;
|
||||
break;
|
||||
case ']':
|
||||
if (p && *p < 255)
|
||||
(*p)++;
|
||||
break;
|
||||
case '[':
|
||||
if (p && *p > 0)
|
||||
(*p)--;
|
||||
break;
|
||||
case '}':
|
||||
if (p)
|
||||
*p = (*p + 10 > 255) ? 255 : *p + 10;
|
||||
break;
|
||||
case '{':
|
||||
if (p)
|
||||
*p = (*p - 10 < 0) ? 0 : *p - 10;
|
||||
break;
|
||||
case 'r':
|
||||
case 'R':
|
||||
if (p)
|
||||
*p = 128;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!keep_running)
|
||||
|
|
@ -386,7 +410,7 @@ int main(int argc, char *argv[]) {
|
|||
for (int i = 0; i < plugin_count; i++)
|
||||
plugin_check_reload(&plugins[i]);
|
||||
|
||||
// Frame capture
|
||||
// Frame capture
|
||||
if (webcam_wait_frame(&cam, 1000) < 0)
|
||||
continue; // timeout, retry
|
||||
|
||||
|
|
@ -398,7 +422,8 @@ int main(int argc, char *argv[]) {
|
|||
// Run all plugins in order
|
||||
for (int i = 0; i < plugin_count; i++) {
|
||||
if (plugins[i].plugin)
|
||||
plugins[i].plugin->process(gray, cam.width, cam.height, &plugin_params[i]);
|
||||
plugins[i].plugin->process(gray, cam.width, cam.height,
|
||||
&plugin_params[i]);
|
||||
}
|
||||
|
||||
if (opts.color && rgb)
|
||||
|
|
|
|||
|
|
@ -1,13 +1,9 @@
|
|||
#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>
|
||||
|
||||
#include "nolibc.h"
|
||||
|
||||
static int copy_file(const char *src, const char *dst) {
|
||||
int fd_src = open(src, O_RDONLY);
|
||||
|
|
@ -90,16 +86,19 @@ int plugin_load(plugin_loader_t *pl, const char *path) {
|
|||
}
|
||||
|
||||
pl->plugin = get_plugin();
|
||||
snprintf(pl->status_msg, sizeof(pl->status_msg),
|
||||
"loaded: %s", pl->plugin->name);
|
||||
snprintf(pl->status_msg, sizeof(pl->status_msg), "loaded: %s",
|
||||
pl->plugin->name);
|
||||
|
||||
// FIX: Temporary file leak in plugin_load() when dlsym fails
|
||||
// unlink(pl->tmp_path)
|
||||
return 0;
|
||||
}
|
||||
|
||||
void plugin_watch_init(plugin_loader_t *pl, const char *path) {
|
||||
strncpy(pl->path, path, sizeof(pl->path) - 1);
|
||||
nl_strncpy_safe(pl->path, path, sizeof(pl->path) - 1);
|
||||
|
||||
char dir_copy[256];
|
||||
strncpy(dir_copy, path, sizeof(dir_copy) - 1);
|
||||
nl_strncpy_safe(dir_copy, path, sizeof(dir_copy) - 1);
|
||||
const char *dir = dirname(dir_copy);
|
||||
|
||||
pl->inotify_fd = inotify_init1(IN_NONBLOCK);
|
||||
|
|
@ -127,7 +126,7 @@ void plugin_check_reload(plugin_loader_t *pl) {
|
|||
return; // No new filesystem modifications detected
|
||||
|
||||
char path_copy[256];
|
||||
strncpy(path_copy, pl->path, sizeof(path_copy) - 1);
|
||||
nl_strncpy_safe(path_copy, pl->path, sizeof(path_copy) - 1);
|
||||
const char *soname = basename(path_copy);
|
||||
|
||||
int relevant = 0;
|
||||
|
|
@ -144,11 +143,12 @@ void plugin_check_reload(plugin_loader_t *pl) {
|
|||
if (!relevant)
|
||||
return;
|
||||
|
||||
// TODO: Replace with inotify event coalescing for more deterministic reload.
|
||||
usleep(100000); // 0.1s delay for linker
|
||||
|
||||
if (plugin_load(pl, pl->path) == 0) {
|
||||
snprintf(pl->status_msg, sizeof(pl->status_msg),
|
||||
"hot-swapped -> %s", pl->plugin->name);
|
||||
snprintf(pl->status_msg, sizeof(pl->status_msg), "hot-swapped -> %s",
|
||||
pl->plugin->name);
|
||||
} else {
|
||||
snprintf(pl->status_msg, sizeof(pl->status_msg),
|
||||
"hot-swap FAILED - old filter retained");
|
||||
|
|
|
|||
|
|
@ -1,11 +1,15 @@
|
|||
/*
|
||||
Still uses pthread functions, TODO: replace them with raw futex syscalls and clone()
|
||||
*/
|
||||
|
||||
|
||||
#include "ascii.h"
|
||||
#include "capture.h"
|
||||
#include "thread_sharing.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/mman.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "nolibc.h"
|
||||
|
||||
// Producer thread for capturing frames
|
||||
void *capture_thread(void *arg) {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
#include "timing.h"
|
||||
#include <errno.h>
|
||||
|
||||
#include <time.h>
|
||||
|
||||
#include "nolibc.h"
|
||||
|
||||
static long frame_duration_ns = 0; // nanoseconds per frame
|
||||
|
||||
void timing_init(int fps) { frame_duration_ns = 1000000000L / fps; }
|
||||
|
|
@ -15,7 +17,7 @@ void timing_sleep(struct timespec *start_time) {
|
|||
long sleep_ns = frame_duration_ns - elapsed_ns;
|
||||
if (sleep_ns > 0) {
|
||||
struct timespec ts = {sleep_ns / 1000000000L, sleep_ns % 1000000000L};
|
||||
while (nanosleep(&ts, &ts) == -1 && errno == EINTR)
|
||||
while (nl_nanosleep(&ts, &ts) == -1 && errno == EINTR)
|
||||
;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
139
README.md
139
README.md
|
|
@ -1,52 +1,111 @@
|
|||
# AsciiCam Version v1.0.0
|
||||
# AsciiCam
|
||||
|
||||
Ascii video output from webcam in terminal.
|
||||
Real-time ASCII video from your webcam in the terminal - pure C99, no heavy runtime dependencies.
|
||||
|
||||
## Demo
|
||||
<img src="assets/demo2.gif" width="325">
|
||||
<img src="assets/demo.gif" width="325">
|
||||
|
||||
### Edge Detection and invert (brightness) threshold change
|
||||
### Edge detection + threshold plugin
|
||||
|
||||
<img src="assets/demo-edgedetection.gif" width="325">
|
||||
|
||||
## Build and Run
|
||||
```
|
||||
git clone https://github.com/Harshit-Dhanwalkar/AsciiCam.git
|
||||
cd AsciiCam/C/
|
||||
make
|
||||
---
|
||||
|
||||
cd build/
|
||||
./webcam_ascii --help
|
||||
```
|
||||
## Features
|
||||
|
||||
Run with all or selected plugins (currently 3)
|
||||
```
|
||||
./build/webcam_ascii -p build/invert.so -p build/threshold.so -p build/edge_detect.so
|
||||
```
|
||||
|
||||
|
||||
## TODO
|
||||
|
||||
- [x] Adjust width and height of capturing frame.
|
||||
- [x] A producer/consumer thread splitting.
|
||||
- [ ] Custom ASCII charset via config file
|
||||
- [x] Brightness/contrast adjustment.
|
||||
- [x] Reverse video - Invert brightness $\rightarrow$ charset mapping
|
||||
- [x] Color output - Extract U/V channels, map to ANSI/RGB codes
|
||||
- [ ] 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
|
||||
- [ ] Analyzing frames for what changed (inter-frame compression)
|
||||
- [ ] Temporal Compression
|
||||
- [ ] Lookup Table (LUT) Cache Optimization
|
||||
|
||||
|
||||
- [ ] Migrate from C to Cpp after, I consider, I have done enough optimisation in C.
|
||||
| Feature | Details |
|
||||
| ------------------------- | ------------------------------------------------------------------------------------- |
|
||||
| YUYV to grayscale | SSE2 SIMD (`yuyv_to_gray_simd`) - 16 pixels per iteration |
|
||||
| YUYV to RGB | Fixed-point BT.601 conversion for truecolor output |
|
||||
| ASCII rendering | Configurable charset, brightness, contrast, invert |
|
||||
| Sobel edge detection | L1-norm kernel convolution |
|
||||
| Floyd-Steinberg dithering | Error-diffusion to reduce banding |
|
||||
| ANSI truecolor | `\033[38;2;R;G;Bm` per-cell coloring |
|
||||
| Hot-reload plugin system | `inotify` + `dlopen` - rebuild a filter `.so`, it reloads live |
|
||||
| FPS-capped render loop | `CLOCK_MONOTONIC` + `nanosleep` frame pacing |
|
||||
| Producer/consumer threads | Double-buffered capture + render (stubbed in main loop, active in `thread_sharing.c`) |
|
||||
|
||||
---
|
||||
|
||||
Project is under [PolyForm Noncommercial License BY-NC LICENCE](LICENCE)
|
||||
## Build
|
||||
|
||||
For commercial use, please contact me at *harshitpd1729@gmail.com* to purchase a commercial license."
|
||||
```bash
|
||||
git clone https://github.com/Harshit-Dhanwalkar/AsciiCam.git
|
||||
cd AsciiCam/C/
|
||||
make
|
||||
```
|
||||
|
||||
Requires: `gcc`, `linux/videodev2.h` (kernel headers), `libdl`, `libpthread`.
|
||||
No other external dependencies.
|
||||
|
||||
```
|
||||
build/webcam_ascii --help
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Run
|
||||
|
||||
```bash
|
||||
# Basic (grayscale, 80×40, /dev/video0)
|
||||
./build/webcam_ascii
|
||||
|
||||
# Truecolor output
|
||||
./build/webcam_ascii -C
|
||||
|
||||
# With all three plugins
|
||||
./build/webcam_ascii -p build/invert.so -p build/threshold.so -p build/edge_detect.so
|
||||
|
||||
# Edge detection mode, custom resolution
|
||||
./build/webcam_ascii -e -w 320 -h 240 -W 120 -H 50
|
||||
|
||||
# Dithering + inverted charset
|
||||
./build/webcam_ascii -D -i
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Plugin system
|
||||
|
||||
Plugins are shared objects (`.so`).
|
||||
|
||||
```bash
|
||||
gcc -O2 -fPIC -shared -Iinclude filters/my_filter.c -o build/my_filter.so
|
||||
./build/webcam_ascii -p build/my_filter.so
|
||||
```
|
||||
|
||||
**Hot-reload:** the binary watches the `.so` with `inotify`. Recompile it while the viewer is running and it reloads automatically within $\approx$100 ms.
|
||||
|
||||
**Runtime controls:**
|
||||
| Key | Action |
|
||||
|---|---|
|
||||
| `↑` / `↓` | select plugin |
|
||||
| `[` / `]` | param $\pm$1 |
|
||||
| `{` / `}` | param $\pm$10 |
|
||||
| `r` | reset param to 128 |
|
||||
| `q` | quit |
|
||||
|
||||
---
|
||||
|
||||
## TODO
|
||||
|
||||
- [x] Adjustable capture resolution
|
||||
- [x] Producer/consumer thread split (double-buffered)
|
||||
- [x] Brightness / contrast adjustment
|
||||
- [x] Invert brightness to charset mapping
|
||||
- [x] ANSI truecolor output
|
||||
- [x] Floyd-Steinberg dithering
|
||||
- [x] Sobel edge detection
|
||||
- [x] SIMD YUYV to grayscale (SSE2)
|
||||
- [x] Hot-reload plugin system
|
||||
- [x] nolibc - zero libc calls
|
||||
- [ ] Custom charset via config file
|
||||
- [ ] Record to `.mp4` / `.gif`
|
||||
- [ ] Inter-frame delta compression
|
||||
- [ ] LUT cache optimization
|
||||
- [ ] Replace `pthread` with raw `futex` syscalls
|
||||
- [ ] Replace `dlopen` with a minimal ELF loader
|
||||
|
||||
---
|
||||
|
||||
Project is under [PolyForm Noncommercial License BY-NC](LICENCE).
|
||||
For commercial use contact *harshitpd1729@gmail.com*.
|
||||
|
|
|
|||
BIN
assets/demo.gif
BIN
assets/demo.gif
Binary file not shown.
|
Before Width: | Height: | Size: 386 KiB After Width: | Height: | Size: 3.4 MiB |
BIN
assets/demo2.gif
BIN
assets/demo2.gif
Binary file not shown.
|
Before Width: | Height: | Size: 3.4 MiB |
Loading…
Add table
Add a link
Reference in a new issue