diff --git a/C/Makefile b/C/Makefile index a9df9a1..7731dde 100644 --- a/C/Makefile +++ b/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) diff --git a/C/lib/nl_alloc.c b/C/lib/nl_alloc.c new file mode 100644 index 0000000..42198ae --- /dev/null +++ b/C/lib/nl_alloc.c @@ -0,0 +1,96 @@ +#include "nl_alloc.h" +#include + +#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: +} diff --git a/C/lib/nl_alloc.h b/C/lib/nl_alloc.h new file mode 100644 index 0000000..c953d31 --- /dev/null +++ b/C/lib/nl_alloc.h @@ -0,0 +1,14 @@ +#ifndef NL_ALLOC_H +#define NL_ALLOC_H + +#include + +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 diff --git a/C/lib/nl_errno.c b/C/lib/nl_errno.c new file mode 100644 index 0000000..099f2b9 --- /dev/null +++ b/C/lib/nl_errno.c @@ -0,0 +1,41 @@ +#include "nl_errno.h" +#include "nl_syscall.h" +#include + +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); +} diff --git a/C/lib/nl_errno.h b/C/lib/nl_errno.h new file mode 100644 index 0000000..aa16e7f --- /dev/null +++ b/C/lib/nl_errno.h @@ -0,0 +1,12 @@ +#ifndef NL_ERRNO_H +#define NL_ERRNO_H + +#include + +extern int errno; + +void nl_perror(const char *msg); + +#define perror(msg) nl_perror(msg) + +#endif diff --git a/C/lib/nl_getopt.c b/C/lib/nl_getopt.c new file mode 100644 index 0000000..f199720 --- /dev/null +++ b/C/lib/nl_getopt.c @@ -0,0 +1,75 @@ +#include "nl_getopt.h" +#include "nl_syscall.h" +#include + +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; +} diff --git a/C/lib/nl_getopt.h b/C/lib/nl_getopt.h new file mode 100644 index 0000000..40b6341 --- /dev/null +++ b/C/lib/nl_getopt.h @@ -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 diff --git a/C/lib/nl_io.h b/C/lib/nl_io.h new file mode 100644 index 0000000..998131c --- /dev/null +++ b/C/lib/nl_io.h @@ -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 + +/* 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 +#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 diff --git a/C/lib/nl_printf.c b/C/lib/nl_printf.c new file mode 100644 index 0000000..53c0d4b --- /dev/null +++ b/C/lib/nl_printf.c @@ -0,0 +1,164 @@ +#include "nl_printf.h" +#include +#include + +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; +} diff --git a/C/lib/nl_printf.h b/C/lib/nl_printf.h new file mode 100644 index 0000000..74aa9bc --- /dev/null +++ b/C/lib/nl_printf.h @@ -0,0 +1,38 @@ +#ifndef NL_PRINTF_H +#define NL_PRINTF_H + +#include "nl_io.h" +#include "nl_string.h" +#include +#include + +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 diff --git a/C/lib/nl_signal.h b/C/lib/nl_signal.h new file mode 100644 index 0000000..0282584 --- /dev/null +++ b/C/lib/nl_signal.h @@ -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 diff --git a/C/lib/nl_start.c b/C/lib/nl_start.c new file mode 100644 index 0000000..21babee --- /dev/null +++ b/C/lib/nl_start.c @@ -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"); +} diff --git a/C/lib/nl_string.h b/C/lib/nl_string.h new file mode 100644 index 0000000..8b50e29 --- /dev/null +++ b/C/lib/nl_string.h @@ -0,0 +1,83 @@ +#ifndef NL_STRING_H +#define NL_STRING_H + +#include +#include + +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 diff --git a/C/lib/nl_syscall.h b/C/lib/nl_syscall.h new file mode 100644 index 0000000..ac26f2a --- /dev/null +++ b/C/lib/nl_syscall.h @@ -0,0 +1,93 @@ +#ifndef NL_SYSCALL_H +#define NL_SYSCALL_H + +#include +#include +#include + +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 diff --git a/C/lib/nolibc.h b/C/lib/nolibc.h new file mode 100644 index 0000000..3a8fe0a --- /dev/null +++ b/C/lib/nolibc.h @@ -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 diff --git a/C/src/ascii.c b/C/src/ascii.c index a335853..8fc3d42 100644 --- a/C/src/ascii.c +++ b/C/src/ascii.c @@ -2,42 +2,39 @@ #include #include -#include -#include -#include -#include + +#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); diff --git a/C/src/capture.c b/C/src/capture.c index 2d1c82e..fc742cf 100644 --- a/C/src/capture.c +++ b/C/src/capture.c @@ -1,138 +1,135 @@ #include "capture.h" #include "ascii.h" -#include -#include #include #include -#include -#include -#include -#include -#include -#include -#include + +#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; } diff --git a/C/src/main.c b/C/src/main.c index 9ab2a2b..e63c3db 100644 --- a/C/src/main.c +++ b/C/src/main.c @@ -4,27 +4,26 @@ #include "thread_sharing.h" #include "timing.h" -#include -#include #include -#include #include -#include -#include -#include -#include -#include -#include #include -#include + +#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) diff --git a/C/src/plugins.c b/C/src/plugins.c index ce0d67c..6a143a0 100644 --- a/C/src/plugins.c +++ b/C/src/plugins.c @@ -1,13 +1,9 @@ #include "plugins.h" #include -#include -#include // dirname() -#include -#include #include -#include -#include + +#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"); diff --git a/C/src/thread_sharing.c b/C/src/thread_sharing.c index fb62afd..38b261a 100644 --- a/C/src/thread_sharing.c +++ b/C/src/thread_sharing.c @@ -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 -#include -#include -#include +#include + +#include "nolibc.h" // Producer thread for capturing frames void *capture_thread(void *arg) { diff --git a/C/src/timing.c b/C/src/timing.c index 6861940..a8062e1 100644 --- a/C/src/timing.c +++ b/C/src/timing.c @@ -1,7 +1,9 @@ #include "timing.h" -#include + #include +#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) ; } } diff --git a/README.md b/README.md index dd7e9e2..5a8906c 100644 --- a/README.md +++ b/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 - + -### Edge Detection and invert (brightness) threshold change +### Edge detection + threshold plugin -## 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*. diff --git a/assets/demo.gif b/assets/demo.gif index 7937ba0..0255e52 100644 Binary files a/assets/demo.gif and b/assets/demo.gif differ diff --git a/assets/demo2.gif b/assets/demo2.gif deleted file mode 100644 index 0255e52..0000000 Binary files a/assets/demo2.gif and /dev/null differ