Add macos support

This commit is contained in:
Harshit-Dhanwalkar 2026-06-10 20:30:10 +05:30
parent 49b58febba
commit 14cc7dcc2e
10 changed files with 616 additions and 250 deletions

View file

@ -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);
}

View file

@ -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 (nonblocking 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
View 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 */

View file

@ -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