AsciiCam/C/src/capture_linux.c

283 lines
7.5 KiB
C
Raw Normal View History

#ifdef PLATFORM_LINUX
#include "nolibc.h"
2026-05-25 13:50:49 +05:30
#include "ascii.h"
2026-06-10 20:30:10 +05:30
#include "capture.h"
#include "platform.h"
2026-05-25 13:50:49 +05:30
2026-05-13 22:18:22 +05:30
#include <linux/videodev2.h>
#include <stdint.h>
2026-06-10 20:30:10 +05:30
typedef struct webcam_impl webcam_impl_t;
struct webcam_impl {
struct v4l2_buffer buf_info;
int auto_exposure_disabled;
int auto_wb_disabled;
2026-06-10 20:30:10 +05:30
};
static webcam_impl_t _impl_storage;
2026-05-13 22:18:22 +05:30
2026-06-08 13:36:51 +05:30
int webcam_init(webcam_t *cam, const char *device, int width, int height) {
nl_memset(&_impl_storage, 0, sizeof(_impl_storage));
2026-06-10 20:30:10 +05:30
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);
2026-06-08 13:36:51 +05:30
if (cam->fd < 0)
return -1;
struct v4l2_format fmt = {0};
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
2026-06-10 20:30:10 +05:30
fmt.fmt.pix.width = (unsigned)width;
fmt.fmt.pix.height = (unsigned)height;
2026-06-08 13:36:51 +05:30
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;
}
2026-06-10 20:30:10 +05:30
cam->width = (int)fmt.fmt.pix.width;
cam->height = (int)fmt.fmt.pix.height;
2026-06-08 13:36:51 +05:30
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;
}
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;
}
cam->buffer = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED,
2026-06-10 20:30:10 +05:30
cam->fd, (long)buf.m.offset);
2026-06-08 13:36:51 +05:30
if (cam->buffer == MAP_FAILED) {
close(cam->fd);
return -1;
}
2026-06-10 20:30:10 +05:30
cam->impl->buf_info = buf;
2026-06-08 13:36:51 +05:30
if (ioctl(cam->fd, VIDIOC_QBUF, &buf) < 0) {
munmap(cam->buffer, buf.length);
close(cam->fd);
return -1;
}
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;
2026-05-13 22:18:22 +05:30
}
int webcam_wait_frame(webcam_t *cam, int timeout_ms) {
2026-06-08 13:36:51 +05:30
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 = nl_select(cam->fd + 1, &fds, (nl_fd_set *)0, (nl_fd_set *)0, &tv);
2026-06-10 20:30:10 +05:30
return (ret <= 0) ? -1 : 0;
2026-05-13 22:18:22 +05:30
}
int webcam_capture_frame(webcam_t *cam, uint8_t *gray_buffer) {
2026-06-10 20:30:10 +05:30
struct v4l2_buffer buf = cam->impl->buf_info;
2026-06-08 13:36:51 +05:30
if (ioctl(cam->fd, VIDIOC_DQBUF, &buf) < 0)
return -1;
2026-06-10 20:30:10 +05:30
yuyv_to_gray_simd((uint8_t *)cam->buffer, gray_buffer, cam->width,
cam->height);
cam->impl->buf_info = buf;
2026-06-08 13:36:51 +05:30
return 0;
2026-05-13 22:18:22 +05:30
}
int webcam_requeue_buffer(webcam_t *cam) {
2026-06-10 20:30:10 +05:30
return (ioctl(cam->fd, VIDIOC_QBUF, &cam->impl->buf_info) < 0) ? -1 : 0;
2026-05-13 22:18:22 +05:30
}
void webcam_cleanup(webcam_t *cam) {
2026-06-08 13:36:51 +05:30
if (cam->fd >= 0) {
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ioctl(cam->fd, VIDIOC_STREAMOFF, &type);
if (cam->buffer != MAP_FAILED)
2026-06-10 20:30:10 +05:30
munmap(cam->buffer, cam->impl->buf_info.length);
2026-06-08 13:36:51 +05:30
close(cam->fd);
}
cam->fd = -1;
cam->buffer = MAP_FAILED;
2026-06-10 20:30:10 +05:30
cam->impl = (webcam_impl_t *)0;
2026-05-13 22:18:22 +05:30
}
2026-06-10 20:30:10 +05:30
// Hardware controls (V4L2_CID_*)
static int v4l2_query_range(int fd, unsigned int id, int *min, int *max) {
struct v4l2_queryctrl q;
nl_memset(&q, 0, sizeof(q));
q.id = id;
if (ioctl(fd, VIDIOC_QUERYCTRL, &q) < 0)
return -1;
if (q.flags & V4L2_CTRL_FLAG_DISABLED)
return -1;
if (min)
*min = q.minimum;
if (max)
*max = q.maximum;
return 0;
}
static int v4l2_get_value(int fd, unsigned int id, int *value) {
struct v4l2_control c;
nl_memset(&c, 0, sizeof(c));
c.id = id;
if (ioctl(fd, VIDIOC_G_CTRL, &c) < 0)
return -1;
*value = c.value;
return 0;
}
static int v4l2_set_value(int fd, unsigned int id, int value) {
struct v4l2_control c;
nl_memset(&c, 0, sizeof(c));
c.id = id;
c.value = value;
return ioctl(fd, VIDIOC_S_CTRL, &c);
}
static int v4l2_clamp(int v, int lo, int hi) {
return (v < lo) ? lo : (v > hi) ? hi : v;
}
int webcam_set_auto_exposure(webcam_t *cam, int enable) {
if (!cam || cam->fd < 0)
return -1;
// NOTE: UVC drivers expose V4L2_CID_EXPOSURE_AUTO as a menu (0=manual,
// 1=aperture priority, 3=auto, driver-dependent which subset exists).
if (v4l2_set_value(cam->fd, V4L2_CID_EXPOSURE_AUTO,
enable ? V4L2_EXPOSURE_AUTO : V4L2_EXPOSURE_MANUAL) == 0)
return 0;
return v4l2_set_value(cam->fd, V4L2_CID_AUTOGAIN, enable ? 1 : 0);
}
int webcam_set_auto_white_balance(webcam_t *cam, int enable) {
if (!cam || cam->fd < 0)
return -1;
return v4l2_set_value(cam->fd, V4L2_CID_AUTO_WHITE_BALANCE, enable ? 1 : 0);
}
int webcam_get_exposure(webcam_t *cam, int *value) {
if (!cam || cam->fd < 0 || !value)
return -1;
return v4l2_get_value(cam->fd, V4L2_CID_EXPOSURE_ABSOLUTE, value);
}
int webcam_get_contrast(webcam_t *cam, int *value) {
if (!cam || cam->fd < 0 || !value)
return -1;
return v4l2_get_value(cam->fd, V4L2_CID_CONTRAST, value);
}
int webcam_get_white_balance(webcam_t *cam, int *value) {
if (!cam || cam->fd < 0 || !value)
return -1;
return v4l2_get_value(cam->fd, V4L2_CID_WHITE_BALANCE_TEMPERATURE, value);
}
int webcam_get_exposure_range(webcam_t *cam, int *min, int *max) {
if (!cam || cam->fd < 0)
return -1;
return v4l2_query_range(cam->fd, V4L2_CID_EXPOSURE_ABSOLUTE, min, max);
}
int webcam_get_contrast_range(webcam_t *cam, int *min, int *max) {
if (!cam || cam->fd < 0)
return -1;
return v4l2_query_range(cam->fd, V4L2_CID_CONTRAST, min, max);
}
int webcam_get_white_balance_range(webcam_t *cam, int *min, int *max) {
if (!cam || cam->fd < 0)
return -1;
return v4l2_query_range(cam->fd, V4L2_CID_WHITE_BALANCE_TEMPERATURE, min,
max);
}
int webcam_adjust_exposure(webcam_t *cam, int delta, int *out_value) {
if (!cam || cam->fd < 0)
return -1;
if (!cam->impl->auto_exposure_disabled) {
webcam_set_auto_exposure(cam, 0);
cam->impl->auto_exposure_disabled = 1;
}
int min, max, cur;
if (v4l2_query_range(cam->fd, V4L2_CID_EXPOSURE_ABSOLUTE, &min, &max) < 0)
return -1;
if (v4l2_get_value(cam->fd, V4L2_CID_EXPOSURE_ABSOLUTE, &cur) < 0)
cur = min;
int next = v4l2_clamp(cur + delta, min, max);
if (v4l2_set_value(cam->fd, V4L2_CID_EXPOSURE_ABSOLUTE, next) < 0)
return -1;
if (out_value)
*out_value = next;
return 0;
}
int webcam_adjust_contrast(webcam_t *cam, int delta, int *out_value) {
if (!cam || cam->fd < 0)
return -1;
int min, max, cur;
if (v4l2_query_range(cam->fd, V4L2_CID_CONTRAST, &min, &max) < 0)
return -1;
if (v4l2_get_value(cam->fd, V4L2_CID_CONTRAST, &cur) < 0)
cur = min;
int next = v4l2_clamp(cur + delta, min, max);
if (v4l2_set_value(cam->fd, V4L2_CID_CONTRAST, next) < 0)
return -1;
if (out_value)
*out_value = next;
return 0;
}
int webcam_adjust_white_balance(webcam_t *cam, int delta, int *out_value) {
if (!cam || cam->fd < 0)
return -1;
if (!cam->impl->auto_wb_disabled) {
webcam_set_auto_white_balance(cam, 0);
cam->impl->auto_wb_disabled = 1;
}
int min, max, cur;
if (v4l2_query_range(cam->fd, V4L2_CID_WHITE_BALANCE_TEMPERATURE, &min,
&max) < 0)
return -1;
if (v4l2_get_value(cam->fd, V4L2_CID_WHITE_BALANCE_TEMPERATURE, &cur) < 0)
cur = min;
int next = v4l2_clamp(cur + delta, min, max);
if (v4l2_set_value(cam->fd, V4L2_CID_WHITE_BALANCE_TEMPERATURE, next) < 0)
return -1;
if (out_value)
*out_value = next;
return 0;
}
#endif