17#include <linux/videodev2.h>
26#define WEBCAM_BUFFER_COUNT_DEFAULT 4
27#define WEBCAM_BUFFER_COUNT_MAX 8
28#define WEBCAM_DEVICE_INDEX_MAX 99
29#define WEBCAM_READ_RETRY_COUNT 3
41 webcam_buffer_t *buffers;
56static void yuyv_to_rgb24(
const uint8_t *yuyv,
uint8_t *rgb,
int width,
int height) {
57 const int num_pixels = width * height;
58 for (
int i = 0; i < num_pixels; i += 2) {
60 const int yuyv_idx = i * 2;
61 const int y0 = yuyv[yuyv_idx + 0];
62 const int u = yuyv[yuyv_idx + 1];
63 const int y1 = yuyv[yuyv_idx + 2];
64 const int v = yuyv[yuyv_idx + 3];
70 const int c0 = y0 - 16;
71 const int c1 = y1 - 16;
72 const int d = u - 128;
73 const int e = v - 128;
76 int r = (298 * c0 + 409 * e + 128) >> 8;
77 int g = (298 * c0 - 100 * d - 208 * e + 128) >> 8;
78 int b = (298 * c0 + 516 * d + 128) >> 8;
80 const int rgb_idx0 = i * 3;
81 rgb[rgb_idx0 + 0] = (
uint8_t)(r < 0 ? 0 : (r > 255 ? 255 : r));
82 rgb[rgb_idx0 + 1] = (
uint8_t)(g < 0 ? 0 : (g > 255 ? 255 : g));
83 rgb[rgb_idx0 + 2] = (
uint8_t)(b < 0 ? 0 : (b > 255 ? 255 : b));
86 r = (298 * c1 + 409 * e + 128) >> 8;
87 g = (298 * c1 - 100 * d - 208 * e + 128) >> 8;
88 b = (298 * c1 + 516 * d + 128) >> 8;
90 const int rgb_idx1 = (i + 1) * 3;
91 rgb[rgb_idx1 + 0] = (
uint8_t)(r < 0 ? 0 : (r > 255 ? 255 : r));
92 rgb[rgb_idx1 + 1] = (
uint8_t)(g < 0 ? 0 : (g > 255 ? 255 : g));
93 rgb[rgb_idx1 + 2] = (
uint8_t)(b < 0 ? 0 : (b > 255 ? 255 : b));
108static int webcam_v4l2_set_format(
webcam_context_t *ctx,
int width,
int height) {
109 struct v4l2_format fmt = {0};
110 fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
111 fmt.fmt.pix.width = width;
112 fmt.fmt.pix.height = height;
113 fmt.fmt.pix.field = V4L2_FIELD_ANY;
116 fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB24;
117 if (ioctl(ctx->fd, VIDIOC_S_FMT, &fmt) == 0 && fmt.fmt.pix.pixelformat == V4L2_PIX_FMT_RGB24) {
118 ctx->pixelformat = V4L2_PIX_FMT_RGB24;
119 ctx->width = fmt.fmt.pix.width;
120 ctx->height = fmt.fmt.pix.height;
121 log_info(
"V4L2 format set to RGB24 %dx%d", ctx->width, ctx->height);
126 fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
127 if (ioctl(ctx->fd, VIDIOC_S_FMT, &fmt) == 0 && fmt.fmt.pix.pixelformat == V4L2_PIX_FMT_YUYV) {
128 ctx->pixelformat = V4L2_PIX_FMT_YUYV;
129 ctx->width = fmt.fmt.pix.width;
130 ctx->height = fmt.fmt.pix.height;
131 log_info(
"V4L2 format set to YUYV %dx%d (will convert to RGB)", ctx->width, ctx->height);
135 log_error(
"Failed to set V4L2 format: device supports neither RGB24 nor YUYV");
140 struct v4l2_requestbuffers req = {0};
141 req.count = WEBCAM_BUFFER_COUNT_DEFAULT;
142 req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
143 req.memory = V4L2_MEMORY_MMAP;
145 if (ioctl(ctx->fd, VIDIOC_REQBUFS, &req) == -1) {
156 if (req.count > WEBCAM_BUFFER_COUNT_MAX) {
157 log_warn(
"Driver requested %d buffers, limiting to %d", req.count, WEBCAM_BUFFER_COUNT_MAX);
158 req.count = WEBCAM_BUFFER_COUNT_MAX;
161 ctx->buffer_count = req.count;
164 ctx->buffers =
SAFE_MALLOC(
sizeof(webcam_buffer_t) * ctx->buffer_count, webcam_buffer_t *);
166 log_error(
"Failed to allocate buffer array");
171 memset(ctx->buffers, 0,
sizeof(webcam_buffer_t) * ctx->buffer_count);
173 for (
int i = 0; i < ctx->buffer_count; i++) {
174 struct v4l2_buffer buf = {0};
175 buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
176 buf.memory = V4L2_MEMORY_MMAP;
179 if (ioctl(ctx->fd, VIDIOC_QUERYBUF, &buf) == -1) {
184 ctx->buffers[i].length = buf.length;
185 ctx->buffers[i].start =
mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, ctx->fd, buf.m.offset);
187 if (ctx->buffers[i].start == MAP_FAILED) {
198 for (
int i = 0; i < ctx->buffer_count; i++) {
199 struct v4l2_buffer buf = {0};
200 buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
201 buf.memory = V4L2_MEMORY_MMAP;
204 if (ioctl(ctx->fd, VIDIOC_QBUF, &buf) == -1) {
210 enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
211 if (ioctl(ctx->fd, VIDIOC_STREAMON, &type) == -1) {
230 if (device_index > WEBCAM_DEVICE_INDEX_MAX) {
232 return SET_ERRNO(
ERROR_WEBCAM,
"Invalid device index: %d (max: %d)", device_index, WEBCAM_DEVICE_INDEX_MAX);
236 char device_path[32];
237 SAFE_SNPRINTF(device_path,
sizeof(device_path),
"/dev/video%d", device_index);
241 if (context->fd == -1) {
243 if (
errno == ENOENT) {
246 "V4L2 device %s does not exist.\n"
247 "No webcam found. Try:\n"
248 " 1. Check if camera is connected: ls /dev/video*\n"
249 " 2. Use test pattern instead: --test-pattern",
251 }
else if (
errno == EACCES) {
254 "Permission denied accessing %s.\n"
255 "Try: sudo usermod -a -G video $USER\n"
256 "Then log out and log back in.",
258 }
else if (
errno == EBUSY) {
268 struct v4l2_capability cap;
269 if (ioctl(context->fd, VIDIOC_QUERYCAP, &cap) == -1) {
275 if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
282 if (webcam_v4l2_set_format(context, 640, 480) != 0) {
289 if (webcam_v4l2_init_buffers(context) != 0) {
296 if (webcam_v4l2_start_streaming(context) != 0) {
298 for (
int i = 0; i < context->buffer_count; i++) {
299 if (context->buffers[i].start != MAP_FAILED) {
300 munmap(context->buffers[i].start, context->buffers[i].length);
310 log_info(
"V4L2 webcam initialized successfully on %s", device_path);
319 enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
320 if (ioctl(ctx->fd, VIDIOC_STREAMOFF, &type) == 0) {
321 log_debug(
"V4L2 streaming stopped for flush");
323 ioctl(ctx->fd, VIDIOC_STREAMON, &type);
332 enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
333 ioctl(ctx->fd, VIDIOC_STREAMOFF, &type);
337 for (
int i = 0; i < ctx->buffer_count; i++) {
338 if (ctx->buffers[i].start != MAP_FAILED) {
339 munmap(ctx->buffers[i].start, ctx->buffers[i].length);
354 struct v4l2_buffer buf = {0};
355 buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
356 buf.memory = V4L2_MEMORY_MMAP;
360 while (retry_count < WEBCAM_READ_RETRY_COUNT) {
361 if (ioctl(ctx->fd, VIDIOC_DQBUF, &buf) == 0) {
370 if (retry_count >= WEBCAM_READ_RETRY_COUNT) {
379 if (buf.index >= (
unsigned int)ctx->buffer_count) {
380 log_error(
"V4L2 returned invalid buffer index %u (max: %d)", buf.index, ctx->buffer_count - 1);
385 if (!ctx->buffers || !ctx->buffers[buf.index].start) {
386 log_error(
"V4L2 buffer %u not initialized (start=%p, buffers=%p)", buf.index,
387 ctx->buffers ? ctx->buffers[buf.index].start : NULL, ctx->buffers);
394 log_error(
"Failed to allocate image buffer");
396 if (ioctl(ctx->fd, VIDIOC_QBUF, &buf) == -1) {
403 if (ctx->pixelformat == V4L2_PIX_FMT_YUYV) {
405 yuyv_to_rgb24(ctx->buffers[buf.index].start, (
uint8_t *)img->
pixels, ctx->width, ctx->height);
410 log_error(
"Failed to calculate frame size: width=%d, height=%d (would overflow)", ctx->width, ctx->height);
414 memcpy(img->
pixels, ctx->buffers[buf.index].start, frame_size);
418 if (ioctl(ctx->fd, VIDIOC_QBUF, &buf) == -1) {
420 ctx->fd, buf.type, buf.memory);
428 if (!ctx || !width || !height)
432 *height = ctx->height;
437 if (!out_devices || !out_count) {
445 unsigned int device_count = 0;
446 for (
int i = 0; i <= WEBCAM_DEVICE_INDEX_MAX; i++) {
447 char device_path[32];
448 snprintf(device_path,
sizeof(device_path),
"/dev/video%d", i);
450 int fd = open(device_path, O_RDONLY);
455 struct v4l2_capability cap;
456 if (ioctl(fd, VIDIOC_QUERYCAP, &cap) == 0 && (cap.device_caps & V4L2_CAP_VIDEO_CAPTURE)) {
462 if (device_count == 0) {
474 unsigned int idx = 0;
475 for (
int i = 0; i <= WEBCAM_DEVICE_INDEX_MAX && idx < device_count; i++) {
476 char device_path[32];
477 snprintf(device_path,
sizeof(device_path),
"/dev/video%d", i);
479 int fd = open(device_path, O_RDONLY);
484 struct v4l2_capability cap;
485 if (ioctl(fd, VIDIOC_QUERYCAP, &cap) == 0 && (cap.device_caps & V4L2_CAP_VIDEO_CAPTURE)) {
486 devices[idx].
index = (
unsigned int)i;
493 *out_devices = devices;
Cross-platform file I/O interface for ascii-chat.
#define SAFE_STRNCPY(dst, src, size)
#define SAFE_MALLOC(size, cast)
#define SAFE_SNPRINTF(buffer, buffer_size,...)
#define SAFE_STRERROR(errnum)
#define SAFE_CALLOC(count, size, cast)
#define SET_ERRNO_SYS(code, context_msg,...)
Set error code with custom message and system error context.
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it.
asciichat_error_t
Error and exit codes - unified status values (0-255)
@ ERROR_WEBCAM_PERMISSION
#define log_warn(...)
Log a WARN message.
#define log_error(...)
Log an ERROR message.
#define log_info(...)
Log an INFO message.
#define log_debug(...)
Log a DEBUG message.
void image_destroy(image_t *p)
Destroy an image allocated with image_new()
image_t * image_new(size_t width, size_t height)
Create a new image with standard allocation.
asciichat_error_t webcam_init_context(webcam_context_t **ctx, unsigned short int device_index)
Initialize webcam context for advanced operations.
asciichat_error_t webcam_list_devices(webcam_device_info_t **out_devices, unsigned int *out_count)
Enumerate available webcam devices.
void webcam_free_device_list(webcam_device_info_t *devices)
Free device list returned by webcam_list_devices()
void webcam_flush_context(webcam_context_t *ctx)
Flush/interrupt pending read operations on webcam context.
struct webcam_context_t webcam_context_t
Opaque webcam context structure.
asciichat_error_t webcam_get_dimensions(webcam_context_t *ctx, int *width, int *height)
Get webcam frame dimensions.
#define WEBCAM_DEVICE_NAME_MAX
Maximum length of webcam device name.
image_t * webcam_read_context(webcam_context_t *ctx)
Capture a frame from webcam context.
void webcam_cleanup_context(webcam_context_t *ctx)
Clean up webcam context and release resources.
✅ Safe Integer Arithmetic and Overflow Detection
rgb_pixel_t * pixels
Pixel data array (width * height RGB pixels, row-major order)
Webcam device information structure.
unsigned int index
Device index (use with webcam_init)
asciichat_error_t image_calc_rgb_size(size_t width, size_t height, size_t *out_size)
Calculate total RGB buffer size from dimensions.
🖼️ Safe overflow-checked buffer size calculations for images and video frames