mirror of
https://github.com/Harshit-Dhanwalkar/AsciiCam.git
synced 2026-06-21 10:58:05 +02:00
Add macos support
This commit is contained in:
parent
49b58febba
commit
14cc7dcc2e
10 changed files with 616 additions and 250 deletions
202
C/src/ascii.c
202
C/src/ascii.c
|
|
@ -1,8 +1,13 @@
|
|||
#include "ascii.h"
|
||||
// NOTE:
|
||||
// SIMD paths:
|
||||
// - x86_64 Linux/macOS: SSE2 via <immintrin.h>
|
||||
// - ARM64 macOS: NEON via <arm_neon.h>
|
||||
|
||||
#include "ascii.h"
|
||||
#include <stdint.h>
|
||||
|
||||
#include "nolibc.h"
|
||||
#include "platform.h"
|
||||
|
||||
// Helpers
|
||||
static inline uint8_t clamp_u8(int v) {
|
||||
|
|
@ -10,101 +15,95 @@ static inline uint8_t clamp_u8(int v) {
|
|||
}
|
||||
static inline int my_abs(int x) { return x < 0 ? -x : x; }
|
||||
|
||||
// Image conversion
|
||||
#ifdef __x86_64__
|
||||
// YUYV to grayscale
|
||||
#if defined(ARCH_X86_64)
|
||||
#include <immintrin.h>
|
||||
|
||||
void yuyv_to_gray_simd(const uint8_t *yuyv, uint8_t *gray, int width,
|
||||
int height) {
|
||||
int total_pixels = width * height;
|
||||
|
||||
// Mask to extract every even byte (Y samples)
|
||||
__m128i mask = _mm_set1_epi16(0x00FF);
|
||||
|
||||
int i = 0;
|
||||
for (; i + 16 <= total_pixels; i += 16) {
|
||||
// Load 32 bytes of YUYV (= 16 pixels)
|
||||
__m128i lo = _mm_loadu_si128((__m128i *)(yuyv + i * 2));
|
||||
__m128i hi = _mm_loadu_si128((__m128i *)(yuyv + i * 2 + 16));
|
||||
// low byte of each 16-bit word (the Y sample)
|
||||
lo = _mm_and_si128(lo, mask);
|
||||
hi = _mm_and_si128(hi, mask);
|
||||
// Pack 16-bit
|
||||
__m128i result = _mm_packus_epi16(lo, hi);
|
||||
_mm_storeu_si128((__m128i *)(gray + i), result);
|
||||
}
|
||||
for (; i < total_pixels; i++)
|
||||
gray[i] = yuyv[i * 2];
|
||||
}
|
||||
#else
|
||||
void yuyv_to_gray_simd(const uint8_t *yuyv, uint8_t *gray, int width,
|
||||
int height) {
|
||||
int total = width * height;
|
||||
for (int i = 0; i < total; i++) {
|
||||
gray[i] = yuyv[i * 2];
|
||||
__m128i mask = _mm_set1_epi16(0x00FF);
|
||||
int i = 0;
|
||||
for (; i + 16 <= total; i += 16) {
|
||||
__m128i lo = _mm_loadu_si128((__m128i *)(yuyv + i * 2));
|
||||
__m128i hi = _mm_loadu_si128((__m128i *)(yuyv + i * 2 + 16));
|
||||
lo = _mm_and_si128(lo, mask);
|
||||
hi = _mm_and_si128(hi, mask);
|
||||
_mm_storeu_si128((__m128i *)(gray + i), _mm_packus_epi16(lo, hi));
|
||||
}
|
||||
for (; i < total; i++)
|
||||
gray[i] = yuyv[i * 2];
|
||||
}
|
||||
|
||||
#elif defined(ARCH_ARM64)
|
||||
#include <arm_neon.h>
|
||||
void yuyv_to_gray_simd(const uint8_t *yuyv, uint8_t *gray, int width,
|
||||
int height) {
|
||||
int total = width * height;
|
||||
// NEON: process 8 YUYV pairs (= 16 px) per iteration
|
||||
int i = 0;
|
||||
for (; i + 16 <= total; i += 16) {
|
||||
// Load 32 bytes: [Y0 U0 Y1 V0 Y2 U1 Y3 V1 ...]
|
||||
uint8x16x2_t yuv = vld2q_u8(yuyv + i * 2);
|
||||
// yuv.val[0] = all Y bytes (even bytes = luma)
|
||||
vst1q_u8(gray + i, yuv.val[0]);
|
||||
}
|
||||
for (; i < total; i++)
|
||||
gray[i] = yuyv[i * 2];
|
||||
}
|
||||
|
||||
#else
|
||||
// fallback
|
||||
void yuyv_to_gray_simd(const uint8_t *yuyv, uint8_t *gray, int width,
|
||||
int height) {
|
||||
int total = width * height;
|
||||
for (int i = 0; i < total; i++)
|
||||
gray[i] = yuyv[i * 2];
|
||||
}
|
||||
#endif
|
||||
|
||||
void yuyv_to_rgb(const uint8_t *yuyv, uint8_t *rgb, int width, int height) {
|
||||
int pairs = (width * height) / 2;
|
||||
for (int i = 0; i < pairs; i++) {
|
||||
int y0 = yuyv[i * 4 + 0];
|
||||
int u = yuyv[i * 4 + 1];
|
||||
int y1 = yuyv[i * 4 + 2];
|
||||
int v = yuyv[i * 4 + 3];
|
||||
int d = u - 128;
|
||||
int e = v - 128;
|
||||
|
||||
int y0 = yuyv[i * 4 + 0], u = yuyv[i * 4 + 1];
|
||||
int y1 = yuyv[i * 4 + 2], v = yuyv[i * 4 + 3];
|
||||
int d = u - 128, e = v - 128;
|
||||
for (int p = 0; p < 2; p++) {
|
||||
int c = ((p == 0) ? y0 : y1) - 16;
|
||||
uint8_t *px = rgb + (i * 2 + p) * 3;
|
||||
px[0] = clamp_u8((298 * c + 409 * e + 128) >> 8); // R
|
||||
px[1] = clamp_u8((298 * c - 100 * d - 208 * e + 128) >> 8); // G
|
||||
px[2] = clamp_u8((298 * c + 516 * d + 128) >> 8); // B
|
||||
px[0] = clamp_u8((298 * c + 409 * e + 128) >> 8);
|
||||
px[1] = clamp_u8((298 * c - 100 * d - 208 * e + 128) >> 8);
|
||||
px[2] = clamp_u8((298 * c + 516 * d + 128) >> 8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Buffer sizing
|
||||
size_t ascii_out_size(int dst_w, int dst_h, int color) {
|
||||
/* prefix: 3 bytes + 1 Char */
|
||||
if (color) {
|
||||
/* Per cell: "\033[38;2;255;255;255m" (20) + char (1) = 21
|
||||
* Per row end: "\033[0m\n" (6) */
|
||||
if (color)
|
||||
return 3 + (size_t)dst_h * ((size_t)dst_w * 21 + 6) + 1;
|
||||
} else {
|
||||
/* Per cell: 1 byte; per row: + newline */
|
||||
return 3 + (size_t)dst_h * ((size_t)dst_w + 1) + 1;
|
||||
}
|
||||
return 3 + (size_t)dst_h * ((size_t)dst_w + 1) + 1;
|
||||
}
|
||||
|
||||
// Sobel edge detection (kernel convolution)
|
||||
// Sobel edge detection
|
||||
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}};
|
||||
|
||||
for (int y = 1; y < h - 1; y++) {
|
||||
for (int x = 1; x < w - 1; x++) {
|
||||
int gx = 0, gy = 0;
|
||||
for (int ky = -1; ky <= 1; ky++) {
|
||||
for (int ky = -1; ky <= 1; ky++)
|
||||
for (int kx = -1; kx <= 1; kx++) {
|
||||
int p = in[(y + ky) * w + (x + kx)];
|
||||
gx += Gx[ky + 1][kx + 1] * p;
|
||||
gy += Gy[ky + 1][kx + 1] * p;
|
||||
}
|
||||
}
|
||||
|
||||
// TEST: test both L1 and L2 normalisations
|
||||
int mag = my_abs(gx) + my_abs(gy); // L1 normalisation
|
||||
// int mag = sqrt(gx * gx + gy * gy); // L2 normalisation
|
||||
|
||||
int mag = my_abs(gx) + my_abs(gy);
|
||||
out[y * w + x] = (uint8_t)(mag > 255 ? 255 : mag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Grayscale to ascii
|
||||
// Grayscale to ASCII
|
||||
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) {
|
||||
|
|
@ -114,17 +113,15 @@ int grayscale_to_ascii(const uint8_t *gray, const uint8_t *rgb, int src_w,
|
|||
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_color = opts && opts->color && rgb;
|
||||
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;
|
||||
|
||||
// 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((size_t)(dst_w * dst_h));
|
||||
uint8_t *small_rgb = do_color ? malloc((size_t)(dst_w * dst_h * 3)) : NULL;
|
||||
|
||||
if (!small_g || (do_color && !small_rgb)) {
|
||||
free(small_g);
|
||||
|
|
@ -133,21 +130,17 @@ int grayscale_to_ascii(const uint8_t *gray, const uint8_t *rgb, int src_w,
|
|||
}
|
||||
|
||||
for (int y = 0; y < dst_h; y++) {
|
||||
int ys = (int)(y * bh);
|
||||
int ye = (int)((y + 1) * bh);
|
||||
int ys = (int)(y * bh), ye = (int)((y + 1) * bh);
|
||||
if (ye <= ys)
|
||||
ye = ys + 1;
|
||||
|
||||
for (int x = 0; x < dst_w; x++) {
|
||||
int xs = (int)(x * bw);
|
||||
int xe = (int)((x + 1) * bw);
|
||||
int xs = (int)(x * bw), xe = (int)((x + 1) * bw);
|
||||
if (xe <= xs)
|
||||
xe = xs + 1;
|
||||
|
||||
long tg = 0, tr = 0, tgv = 0, tb = 0;
|
||||
int count = 0;
|
||||
|
||||
for (int sy = ys; sy < ye && sy < src_h; sy++) {
|
||||
for (int sy = ys; sy < ye && sy < src_h; sy++)
|
||||
for (int sx = xs; sx < xe && sx < src_w; sx++) {
|
||||
tg += gray[sy * src_w + sx];
|
||||
if (do_color) {
|
||||
|
|
@ -158,11 +151,9 @@ int grayscale_to_ascii(const uint8_t *gray, const uint8_t *rgb, int src_w,
|
|||
}
|
||||
count++;
|
||||
}
|
||||
}
|
||||
if (count == 0)
|
||||
if (!count)
|
||||
count = 1;
|
||||
|
||||
// Brightness and contrast
|
||||
int gv = (int)(tg / count);
|
||||
if (contrast != 100)
|
||||
gv = 128 + (gv - 128) * contrast / 100;
|
||||
|
|
@ -170,75 +161,62 @@ int grayscale_to_ascii(const uint8_t *gray, const uint8_t *rgb, int src_w,
|
|||
small_g[y * dst_w + x] = clamp_u8(gv);
|
||||
|
||||
if (do_color) {
|
||||
uint8_t *out_px = small_rgb + (y * dst_w + x) * 3;
|
||||
out_px[0] = clamp_u8((int)(tr / count));
|
||||
out_px[1] = clamp_u8((int)(tgv / count));
|
||||
out_px[2] = clamp_u8((int)(tb / count));
|
||||
uint8_t *op = small_rgb + (y * dst_w + x) * 3;
|
||||
op[0] = clamp_u8((int)(tr / count));
|
||||
op[1] = clamp_u8((int)(tgv / count));
|
||||
op[2] = clamp_u8((int)(tb / count));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sobel edge detection
|
||||
if (do_edges) {
|
||||
// Temporary buffer for detected edges results
|
||||
uint8_t *edge_buf = calloc(dst_w * dst_h, sizeof(uint8_t));
|
||||
if (edge_buf) {
|
||||
sobel(small_g, edge_buf, dst_w, dst_h);
|
||||
// overwite grayscale image with edge map
|
||||
memcpy(small_g, edge_buf, dst_w * dst_h);
|
||||
free(edge_buf);
|
||||
uint8_t *eb = calloc((size_t)(dst_w * dst_h), 1);
|
||||
if (eb) {
|
||||
sobel(small_g, eb, dst_w, dst_h);
|
||||
nl_memcpy(small_g, eb, (size_t)(dst_w * dst_h));
|
||||
free(eb);
|
||||
}
|
||||
}
|
||||
|
||||
// Floyd-Steinberg dithering
|
||||
if (do_dither) {
|
||||
int16_t *eb = malloc(dst_w * dst_h * sizeof(int16_t));
|
||||
int16_t *eb = malloc((size_t)(dst_w * dst_h) * sizeof(int16_t));
|
||||
if (eb) {
|
||||
for (int i = 0; i < dst_w * dst_h; i++)
|
||||
eb[i] = (int16_t)small_g[i];
|
||||
|
||||
for (int y = 0; y < dst_h; y++) {
|
||||
for (int x = 0; x < dst_w; x++) {
|
||||
int old_v = eb[y * dst_w + x];
|
||||
|
||||
// Quantise to nearest charset level
|
||||
int qi = old_v * (nchars - 1) / 255;
|
||||
if (qi < 0)
|
||||
qi = 0;
|
||||
if (qi >= nchars)
|
||||
qi = nchars - 1;
|
||||
int new_v = qi * 255 / (nchars - 1);
|
||||
|
||||
eb[y * dst_w + x] = (int16_t)new_v;
|
||||
int err = old_v - new_v;
|
||||
|
||||
#define FS_ADD(DY, DX, NUM) \
|
||||
#define FS(DY, DX, N) \
|
||||
do { \
|
||||
int ny = y + (DY), nx = x + (DX); \
|
||||
if (nx >= 0 && nx < dst_w && ny >= 0 && ny < dst_h) \
|
||||
eb[ny * dst_w + nx] += (int16_t)(err * (NUM) / 16); \
|
||||
eb[ny * dst_w + nx] += (int16_t)(err * (N) / 16); \
|
||||
} while (0)
|
||||
FS_ADD(0, 1, 7);
|
||||
FS_ADD(1, -1, 3);
|
||||
FS_ADD(1, 0, 5);
|
||||
FS_ADD(1, 1, 1);
|
||||
#undef FS_ADD
|
||||
FS(0, 1, 7);
|
||||
FS(1, -1, 3);
|
||||
FS(1, 0, 5);
|
||||
FS(1, 1, 1);
|
||||
#undef FS
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < dst_w * dst_h; i++)
|
||||
small_g[i] = clamp_u8(eb[i]);
|
||||
|
||||
free(eb);
|
||||
}
|
||||
/* HACK: If malloc fails silently fall back to no dithering */
|
||||
}
|
||||
|
||||
// Render into caller's buffer
|
||||
int out_idx = 0;
|
||||
static const char HOME[] = "\033[H"; // Cursor repositions without erasing
|
||||
static const char HOME[] = "\033[H";
|
||||
if (out_size > sizeof(HOME)) {
|
||||
memcpy(out, HOME, sizeof(HOME) - 1);
|
||||
nl_memcpy(out, HOME, sizeof(HOME) - 1);
|
||||
out_idx = sizeof(HOME) - 1;
|
||||
}
|
||||
|
||||
|
|
@ -252,7 +230,6 @@ int grayscale_to_ascii(const uint8_t *gray, const uint8_t *rgb, int src_w,
|
|||
idx = nchars - 1;
|
||||
if (invert)
|
||||
idx = nchars - 1 - idx;
|
||||
|
||||
char ch = charset[idx];
|
||||
|
||||
if (do_color) {
|
||||
|
|
@ -266,7 +243,6 @@ int grayscale_to_ascii(const uint8_t *gray, const uint8_t *rgb, int src_w,
|
|||
out[out_idx++] = ch;
|
||||
}
|
||||
}
|
||||
|
||||
if (do_color) {
|
||||
int w = snprintf(out + out_idx, out_size - (size_t)out_idx, "\033[0m\n");
|
||||
if (w > 0 && (size_t)(out_idx + w) < out_size)
|
||||
|
|
@ -278,7 +254,6 @@ int grayscale_to_ascii(const uint8_t *gray, const uint8_t *rgb, int src_w,
|
|||
}
|
||||
|
||||
out[(size_t)out_idx < out_size ? (size_t)out_idx : out_size - 1] = '\0';
|
||||
|
||||
free(small_g);
|
||||
free(small_rgb);
|
||||
return out_idx;
|
||||
|
|
@ -291,19 +266,18 @@ void overlay_fps_box(int dst_w, double fps, int color_enabled) {
|
|||
if (col < 1)
|
||||
col = 1;
|
||||
|
||||
char fpsbuf[10];
|
||||
nl_fmt_fps(fpsbuf, sizeof(fpsbuf), fps);
|
||||
|
||||
int n;
|
||||
if (color_enabled) {
|
||||
char fpsbuf[10];
|
||||
nl_fmt_fps(fpsbuf, sizeof(fpsbuf), fps);
|
||||
if (color_enabled)
|
||||
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 {
|
||||
char fpsbuf[10];
|
||||
nl_fmt_fps(fpsbuf, sizeof(fpsbuf), fps);
|
||||
else
|
||||
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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,22 +1,34 @@
|
|||
#include "capture.h"
|
||||
#include "ascii.h"
|
||||
#include "capture.h"
|
||||
#include "platform.h"
|
||||
|
||||
#ifdef PLATFORM_LINUX
|
||||
|
||||
#include "nolibc.h"
|
||||
#include <linux/videodev2.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "nolibc.h"
|
||||
typedef struct webcam_impl webcam_impl_t;
|
||||
|
||||
struct webcam_impl {
|
||||
struct v4l2_buffer buf_info;
|
||||
};
|
||||
|
||||
static webcam_impl_t _impl_storage;
|
||||
|
||||
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, 0);
|
||||
cam->impl = &_impl_storage;
|
||||
cam->buffer = MAP_FAILED;
|
||||
|
||||
// Open device non-blocking (for select)
|
||||
cam->fd = open(device ? device : "/dev/video0", 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.width = (unsigned)width;
|
||||
fmt.fmt.pix.height = (unsigned)height;
|
||||
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
|
||||
fmt.fmt.pix.field = V4L2_FIELD_NONE;
|
||||
|
||||
|
|
@ -25,11 +37,9 @@ int webcam_init(webcam_t *cam, const char *device, int width, int height) {
|
|||
return -1;
|
||||
}
|
||||
|
||||
// Read back actual resolution
|
||||
cam->width = fmt.fmt.pix.width;
|
||||
cam->height = fmt.fmt.pix.height;
|
||||
cam->width = (int)fmt.fmt.pix.width;
|
||||
cam->height = (int)fmt.fmt.pix.height;
|
||||
|
||||
// Request one mmap buffer
|
||||
struct v4l2_requestbuffers req = {0};
|
||||
req.count = 1;
|
||||
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
|
|
@ -40,7 +50,6 @@ int webcam_init(webcam_t *cam, const char *device, int width, int height) {
|
|||
return -1;
|
||||
}
|
||||
|
||||
// Query buffer info
|
||||
struct v4l2_buffer buf = {0};
|
||||
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
buf.memory = V4L2_MEMORY_MMAP;
|
||||
|
|
@ -51,25 +60,21 @@ int webcam_init(webcam_t *cam, const char *device, int width, int height) {
|
|||
return -1;
|
||||
}
|
||||
|
||||
// mmap
|
||||
cam->buffer = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED,
|
||||
cam->fd, buf.m.offset);
|
||||
cam->fd, (long)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;
|
||||
cam->impl->buf_info = buf;
|
||||
|
||||
// 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);
|
||||
|
|
@ -89,47 +94,36 @@ int webcam_wait_frame(webcam_t *cam, int timeout_ms) {
|
|||
tv.tv_usec = (timeout_ms % 1000) * 1000;
|
||||
|
||||
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;
|
||||
return (ret <= 0) ? -1 : 0;
|
||||
}
|
||||
|
||||
int webcam_capture_frame(webcam_t *cam, uint8_t *gray_buffer) {
|
||||
// Dequeue buffer
|
||||
struct v4l2_buffer buf = cam->buf_info;
|
||||
struct v4l2_buffer buf = cam->impl->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);
|
||||
yuyv_to_gray_simd((uint8_t *)cam->buffer, gray_buffer, cam->width,
|
||||
cam->height);
|
||||
|
||||
// Store updated buffer info for requeue
|
||||
cam->buf_info = buf;
|
||||
cam->impl->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;
|
||||
return (ioctl(cam->fd, VIDIOC_QBUF, &cam->impl->buf_info) < 0) ? -1 : 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);
|
||||
munmap(cam->buffer, cam->impl->buf_info.length);
|
||||
close(cam->fd);
|
||||
}
|
||||
cam->fd = -1;
|
||||
cam->buffer = MAP_FAILED;
|
||||
cam->impl = (webcam_impl_t *)0;
|
||||
}
|
||||
|
||||
#endif /* PLATFORM_LINUX */
|
||||
297
C/src/capture_macos.c
Normal file
297
C/src/capture_macos.c
Normal file
|
|
@ -0,0 +1,297 @@
|
|||
#include "capture.h"
|
||||
#include "platform.h"
|
||||
|
||||
#ifdef PLATFORM_MACOS
|
||||
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#import <CoreMedia/CoreMedia.h>
|
||||
#import <CoreVideo/CoreVideo.h>
|
||||
#include <pthread.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define FRAME_BUFS 2
|
||||
|
||||
struct webcam_impl {
|
||||
// AVFoundation objects
|
||||
AVCaptureSession *session;
|
||||
AVCaptureDeviceInput *input;
|
||||
AVCaptureVideoDataOutput *output;
|
||||
id<AVCaptureVideoDataOutputSampleBufferDelegate> delegate;
|
||||
dispatch_queue_t queue;
|
||||
|
||||
// Frame ring buffer
|
||||
uint8_t *gray_buf[FRAME_BUFS];
|
||||
int buf_w, buf_h;
|
||||
int write_idx;
|
||||
int ready_idx;
|
||||
int has_frame;
|
||||
|
||||
pthread_mutex_t lock;
|
||||
pthread_cond_t cond;
|
||||
int stopped;
|
||||
};
|
||||
|
||||
@interface FrameDelegate
|
||||
: NSObject <AVCaptureVideoDataOutputSampleBufferDelegate>
|
||||
@property(assign) struct webcam_impl *impl;
|
||||
@end
|
||||
|
||||
@implementation FrameDelegate
|
||||
|
||||
- (void)captureOutput:(AVCaptureOutput *)output
|
||||
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
|
||||
fromConnection:(AVCaptureConnection *)connection {
|
||||
|
||||
struct webcam_impl *im = self.impl;
|
||||
if (!im || im->stopped)
|
||||
return;
|
||||
|
||||
CVPixelBufferRef pixbuf = CMSampleBufferGetImageBuffer(sampleBuffer);
|
||||
if (!pixbuf)
|
||||
return;
|
||||
|
||||
CVPixelBufferLockBaseAddress(pixbuf, kCVPixelBufferLock_ReadOnly);
|
||||
|
||||
// Request NV12 (YUV 4:2:0 biplanar).
|
||||
// Plane 0 is pure luma (Y)
|
||||
size_t width = CVPixelBufferGetWidth(pixbuf);
|
||||
size_t height = CVPixelBufferGetHeight(pixbuf);
|
||||
|
||||
uint8_t *y_plane = (uint8_t *)CVPixelBufferGetBaseAddressOfPlane(pixbuf, 0);
|
||||
size_t y_stride = CVPixelBufferGetBytesPerRowOfPlane(pixbuf, 0);
|
||||
|
||||
pthread_mutex_lock(&im->lock);
|
||||
|
||||
int wi = im->write_idx;
|
||||
uint8_t *dst = im->gray_buf[wi];
|
||||
|
||||
// Copy luma plane row-by-row
|
||||
for (size_t row = 0; row < height; row++) {
|
||||
memcpy(dst + row * width, y_plane + row * y_stride, width);
|
||||
}
|
||||
|
||||
im->ready_idx = wi;
|
||||
im->has_frame = 1;
|
||||
im->write_idx = wi ^ 1; // swap to other slot
|
||||
pthread_cond_signal(&im->cond);
|
||||
pthread_mutex_unlock(&im->lock);
|
||||
|
||||
CVPixelBufferUnlockBaseAddress(pixbuf, kCVPixelBufferLock_ReadOnly);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
int webcam_init(webcam_t *cam, const char *device, int width, int height) {
|
||||
struct webcam_impl *im = calloc(1, sizeof(struct webcam_impl));
|
||||
if (!im)
|
||||
return -1;
|
||||
|
||||
pthread_mutex_init(&im->lock, NULL);
|
||||
pthread_cond_init(&im->cond, NULL);
|
||||
|
||||
// Allocate two grayscale frame buffers
|
||||
im->buf_w = width;
|
||||
im->buf_h = height;
|
||||
for (int i = 0; i < FRAME_BUFS; i++) {
|
||||
im->gray_buf[i] = calloc((size_t)(width * height), 1);
|
||||
if (!im->gray_buf[i])
|
||||
goto fail;
|
||||
}
|
||||
|
||||
// Find the camera device
|
||||
AVCaptureDevice *dev = nil;
|
||||
if (device) {
|
||||
// Match by localizedName
|
||||
NSString *devName = [NSString stringWithUTF8String:device];
|
||||
NSArray<AVCaptureDevice *> *devices =
|
||||
[AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
|
||||
for (AVCaptureDevice *d in devices) {
|
||||
if ([d.localizedName isEqualToString:devName]) {
|
||||
dev = d;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!dev) {
|
||||
// Fall back to system default
|
||||
if (@available(macOS 10.15, *)) {
|
||||
AVCaptureDeviceDiscoverySession *ds = [AVCaptureDeviceDiscoverySession
|
||||
discoverySessionWithDeviceTypes:@[
|
||||
AVCaptureDeviceTypeBuiltInWideAngleCamera
|
||||
]
|
||||
mediaType:AVMediaTypeVideo
|
||||
position:AVCaptureDevicePositionUnspecified];
|
||||
dev = ds.devices.firstObject;
|
||||
} else {
|
||||
dev = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
|
||||
}
|
||||
}
|
||||
if (!dev) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
// Configure format
|
||||
AVCaptureDeviceFormat *best_fmt = nil;
|
||||
float best_diff = 1e9f;
|
||||
for (AVCaptureDeviceFormat *fmt in dev.formats) {
|
||||
CMFormatDescriptionRef desc = fmt.formatDescription;
|
||||
CMVideoDimensions dim = CMVideoFormatDescriptionGetDimensions(desc);
|
||||
float diff = (float)((dim.width - width) * (dim.width - width) +
|
||||
(dim.height - height) * (dim.height - height));
|
||||
if (diff < best_diff) {
|
||||
best_diff = diff;
|
||||
best_fmt = fmt;
|
||||
}
|
||||
}
|
||||
if (best_fmt) {
|
||||
CMVideoDimensions dim =
|
||||
CMVideoFormatDescriptionGetDimensions(best_fmt.formatDescription);
|
||||
cam->width = dim.width;
|
||||
cam->height = dim.height;
|
||||
im->buf_w = cam->width;
|
||||
im->buf_h = cam->height;
|
||||
if (cam->width != width || cam->height != height) {
|
||||
for (int i = 0; i < FRAME_BUFS; i++) {
|
||||
free(im->gray_buf[i]);
|
||||
im->gray_buf[i] = calloc((size_t)(cam->width * cam->height), 1);
|
||||
if (!im->gray_buf[i])
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
if ([dev lockForConfiguration:nil]) {
|
||||
dev.activeFormat = best_fmt;
|
||||
[dev unlockForConfiguration];
|
||||
}
|
||||
} else {
|
||||
cam->width = width;
|
||||
cam->height = height;
|
||||
}
|
||||
|
||||
im->session = [[AVCaptureSession alloc] init];
|
||||
[im->session beginConfiguration];
|
||||
im->session.sessionPreset = AVCaptureSessionPresetInputPriority;
|
||||
|
||||
NSError *err = nil;
|
||||
im->input = [AVCaptureDeviceInput deviceInputWithDevice:dev error:&err];
|
||||
if (!im->input || err)
|
||||
goto fail_session;
|
||||
|
||||
if (![im->session canAddInput:im->input])
|
||||
goto fail_session;
|
||||
[im->session addInput:im->input];
|
||||
|
||||
im->output = [[AVCaptureVideoDataOutput alloc] init];
|
||||
im->output.videoSettings = @{
|
||||
(NSString *)kCVPixelBufferPixelFormatTypeKey :
|
||||
@(kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange)
|
||||
};
|
||||
im->output.alwaysDiscardsLateVideoFrames = YES;
|
||||
|
||||
im->queue = dispatch_queue_create("asciicam.capture", DISPATCH_QUEUE_SERIAL);
|
||||
|
||||
FrameDelegate *delegate = [[FrameDelegate alloc] init];
|
||||
delegate.impl = im;
|
||||
im->delegate = delegate;
|
||||
|
||||
[im->output setSampleBufferDelegate:delegate queue:im->queue];
|
||||
|
||||
if (![im->session canAddOutput:im->output])
|
||||
goto fail_session;
|
||||
[im->session addOutput:im->output];
|
||||
|
||||
[im->session commitConfiguration];
|
||||
[im->session startRunning];
|
||||
|
||||
cam->impl = im;
|
||||
cam->fd = -1;
|
||||
cam->buffer = NULL;
|
||||
return 0;
|
||||
|
||||
fail_session:
|
||||
[im->session commitConfiguration];
|
||||
fail:
|
||||
for (int i = 0; i < FRAME_BUFS; i++)
|
||||
free(im->gray_buf[i]);
|
||||
pthread_mutex_destroy(&im->lock);
|
||||
pthread_cond_destroy(&im->cond);
|
||||
free(im);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int webcam_wait_frame(webcam_t *cam, int timeout_ms) {
|
||||
struct webcam_impl *im = cam->impl;
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_REALTIME, &ts);
|
||||
ts.tv_sec += timeout_ms / 1000;
|
||||
ts.tv_nsec += (timeout_ms % 1000) * 1000000L;
|
||||
if (ts.tv_nsec >= 1000000000L) {
|
||||
ts.tv_sec++;
|
||||
ts.tv_nsec -= 1000000000L;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&im->lock);
|
||||
while (!im->has_frame && !im->stopped) {
|
||||
if (pthread_cond_timedwait(&im->cond, &im->lock, &ts) != 0) {
|
||||
pthread_mutex_unlock(&im->lock);
|
||||
return -1; // timeout
|
||||
}
|
||||
}
|
||||
int ok = im->has_frame;
|
||||
pthread_mutex_unlock(&im->lock);
|
||||
return ok ? 0 : -1;
|
||||
}
|
||||
|
||||
int webcam_capture_frame(webcam_t *cam, uint8_t *gray_buffer) {
|
||||
struct webcam_impl *im = cam->impl;
|
||||
|
||||
pthread_mutex_lock(&im->lock);
|
||||
if (!im->has_frame) {
|
||||
pthread_mutex_unlock(&im->lock);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int ri = im->ready_idx;
|
||||
im->has_frame = 0;
|
||||
pthread_mutex_unlock(&im->lock);
|
||||
|
||||
// Copy the luma buffer out
|
||||
memcpy(gray_buffer, im->gray_buf[ri], (size_t)(im->buf_w * im->buf_h));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int webcam_requeue_buffer(webcam_t *cam) {
|
||||
(void)cam;
|
||||
return 0; // AVFoundation manages buffers
|
||||
}
|
||||
|
||||
void webcam_cleanup(webcam_t *cam) {
|
||||
struct webcam_impl *im = cam->impl;
|
||||
if (!im)
|
||||
return;
|
||||
|
||||
pthread_mutex_lock(&im->lock);
|
||||
im->stopped = 1;
|
||||
pthread_cond_broadcast(&im->cond);
|
||||
pthread_mutex_unlock(&im->lock);
|
||||
|
||||
if (im->session) {
|
||||
[im->session stopRunning];
|
||||
im->session = nil;
|
||||
im->input = nil;
|
||||
im->output = nil;
|
||||
im->delegate = nil;
|
||||
}
|
||||
|
||||
for (int i = 0; i < FRAME_BUFS; i++)
|
||||
free(im->gray_buf[i]);
|
||||
pthread_mutex_destroy(&im->lock);
|
||||
pthread_cond_destroy(&im->cond);
|
||||
free(im);
|
||||
|
||||
cam->impl = NULL;
|
||||
cam->fd = -1;
|
||||
cam->buffer = NULL;
|
||||
}
|
||||
|
||||
#endif /* PLATFORM_MACOS */
|
||||
11
C/src/main.c
11
C/src/main.c
|
|
@ -4,19 +4,12 @@
|
|||
#include "thread_sharing.h"
|
||||
#include "timing.h"
|
||||
|
||||
#include "nolibc.h"
|
||||
|
||||
#include <pthread.h>
|
||||
#include <stdint.h>
|
||||
#include <time.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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue