Monorepo for Aesthetic.Computer aesthetic.computer
at main 259 lines 8.1 kB view raw
1// camera.c — V4L2 camera capture + quirc QR code scanning for AC native 2// Designed for single-frame grab-and-scan (not continuous streaming). 3 4#include "camera.h" 5#include <stdio.h> 6#include <stdlib.h> 7#include <string.h> 8#include <fcntl.h> 9#include <unistd.h> 10#include <errno.h> 11#include <sys/ioctl.h> 12#include <sys/mman.h> 13#include <linux/videodev2.h> 14#include "../lib/quirc/lib/quirc.h" 15 16extern void ac_log(const char *fmt, ...); 17 18// Preferred capture resolution (small = fast QR scan) 19#define CAM_W 640 20#define CAM_H 480 21#define CAM_BUF_COUNT 2 22 23static int xioctl(int fd, int request, void *arg) { 24 int r; 25 do { r = ioctl(fd, request, arg); } 26 while (r == -1 && errno == EINTR); 27 return r; 28} 29 30int camera_open(ACCamera *cam) { 31 memset(cam, 0, sizeof(*cam)); 32 cam->fd = -1; 33 34 // Try /dev/video0 through /dev/video3 35 char devpath[32]; 36 for (int i = 0; i < 4; i++) { 37 snprintf(devpath, sizeof(devpath), "/dev/video%d", i); 38 int fd = open(devpath, O_RDWR | O_NONBLOCK); 39 if (fd < 0) continue; 40 41 // Check it's a capture device 42 struct v4l2_capability cap; 43 if (xioctl(fd, VIDIOC_QUERYCAP, &cap) < 0 || 44 !(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) || 45 !(cap.capabilities & V4L2_CAP_STREAMING)) { 46 close(fd); 47 continue; 48 } 49 50 cam->fd = fd; 51 ac_log("[camera] opened %s (%s)\n", devpath, cap.card); 52 break; 53 } 54 55 if (cam->fd < 0) { 56 snprintf(cam->scan_error, sizeof(cam->scan_error), "no camera found"); 57 return -1; 58 } 59 60 // Set format: YUYV (most common for USB webcams), fallback to MJPEG 61 struct v4l2_format fmt = {0}; 62 fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 63 fmt.fmt.pix.width = CAM_W; 64 fmt.fmt.pix.height = CAM_H; 65 fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; 66 fmt.fmt.pix.field = V4L2_FIELD_NONE; 67 68 if (xioctl(cam->fd, VIDIOC_S_FMT, &fmt) < 0) { 69 // Try MJPEG as fallback 70 fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG; 71 if (xioctl(cam->fd, VIDIOC_S_FMT, &fmt) < 0) { 72 snprintf(cam->scan_error, sizeof(cam->scan_error), 73 "can't set camera format"); 74 close(cam->fd); 75 cam->fd = -1; 76 return -1; 77 } 78 } 79 80 cam->width = fmt.fmt.pix.width; 81 cam->height = fmt.fmt.pix.height; 82 ac_log("[camera] format: %dx%d pixfmt=0x%08x\n", 83 cam->width, cam->height, fmt.fmt.pix.pixelformat); 84 85 // Request buffers 86 struct v4l2_requestbuffers req = {0}; 87 req.count = CAM_BUF_COUNT; 88 req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 89 req.memory = V4L2_MEMORY_MMAP; 90 if (xioctl(cam->fd, VIDIOC_REQBUFS, &req) < 0 || req.count < 1) { 91 snprintf(cam->scan_error, sizeof(cam->scan_error), "buffer request failed"); 92 close(cam->fd); 93 cam->fd = -1; 94 return -1; 95 } 96 cam->buffer_count = (int)req.count; 97 if (cam->buffer_count > 4) cam->buffer_count = 4; 98 99 // Map buffers and queue them 100 for (int i = 0; i < cam->buffer_count; i++) { 101 struct v4l2_buffer buf = {0}; 102 buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 103 buf.memory = V4L2_MEMORY_MMAP; 104 buf.index = i; 105 if (xioctl(cam->fd, VIDIOC_QUERYBUF, &buf) < 0) { 106 snprintf(cam->scan_error, sizeof(cam->scan_error), "querybuf failed"); 107 close(cam->fd); 108 cam->fd = -1; 109 return -1; 110 } 111 cam->buffers[i] = mmap(NULL, buf.length, 112 PROT_READ | PROT_WRITE, MAP_SHARED, 113 cam->fd, buf.m.offset); 114 if (cam->buffers[i] == MAP_FAILED) { 115 cam->buffers[i] = NULL; 116 snprintf(cam->scan_error, sizeof(cam->scan_error), "mmap failed"); 117 close(cam->fd); 118 cam->fd = -1; 119 return -1; 120 } 121 xioctl(cam->fd, VIDIOC_QBUF, &buf); 122 } 123 124 // Start streaming 125 enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 126 if (xioctl(cam->fd, VIDIOC_STREAMON, &type) < 0) { 127 snprintf(cam->scan_error, sizeof(cam->scan_error), "streamon failed"); 128 close(cam->fd); 129 cam->fd = -1; 130 return -1; 131 } 132 cam->streaming = 1; 133 134 // Allocate grayscale buffer 135 cam->gray = malloc(cam->width * cam->height); 136 if (!cam->gray) { 137 snprintf(cam->scan_error, sizeof(cam->scan_error), "gray alloc failed"); 138 camera_close(cam); 139 return -1; 140 } 141 142 // Allocate display buffer (mutex-protected copy for main thread rendering) 143 cam->display = malloc(cam->width * cam->height); 144 if (!cam->display) { 145 snprintf(cam->scan_error, sizeof(cam->scan_error), "display alloc failed"); 146 camera_close(cam); 147 return -1; 148 } 149 pthread_mutex_init(&cam->display_mu, NULL); 150 cam->display_ready = 0; 151 152 ac_log("[camera] ready: %dx%d, %d buffers\n", 153 cam->width, cam->height, cam->buffer_count); 154 return 0; 155} 156 157void camera_close(ACCamera *cam) { 158 if (cam->streaming && cam->fd >= 0) { 159 enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 160 xioctl(cam->fd, VIDIOC_STREAMOFF, &type); 161 cam->streaming = 0; 162 } 163 // Unmap buffers (we don't track individual sizes, but munmap is safe 164 // since we queried them — in practice the kernel handles cleanup on close) 165 if (cam->fd >= 0) { 166 close(cam->fd); 167 cam->fd = -1; 168 } 169 if (cam->gray) { 170 free(cam->gray); 171 cam->gray = NULL; 172 } 173 if (cam->display) { 174 pthread_mutex_destroy(&cam->display_mu); 175 free(cam->display); 176 cam->display = NULL; 177 } 178 cam->gray_ready = 0; 179 cam->display_ready = 0; 180} 181 182int camera_grab(ACCamera *cam) { 183 if (cam->fd < 0 || !cam->streaming || !cam->gray) return -1; 184 185 struct v4l2_buffer buf = {0}; 186 buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 187 buf.memory = V4L2_MEMORY_MMAP; 188 189 if (xioctl(cam->fd, VIDIOC_DQBUF, &buf) < 0) { 190 if (errno == EAGAIN) return -1; // no frame ready yet 191 snprintf(cam->scan_error, sizeof(cam->scan_error), 192 "dqbuf failed: %s", strerror(errno)); 193 return -1; 194 } 195 196 // Convert YUYV to grayscale (Y channel = every other byte) 197 uint8_t *src = cam->buffers[buf.index]; 198 int pixels = cam->width * cam->height; 199 for (int i = 0; i < pixels; i++) { 200 cam->gray[i] = src[i * 2]; // Y from YUYV 201 } 202 cam->gray_ready = 1; 203 204 // Copy to display buffer for main thread rendering 205 if (cam->display) { 206 pthread_mutex_lock(&cam->display_mu); 207 memcpy(cam->display, cam->gray, cam->width * cam->height); 208 cam->display_ready = 1; 209 pthread_mutex_unlock(&cam->display_mu); 210 } 211 212 // Re-queue the buffer 213 xioctl(cam->fd, VIDIOC_QBUF, &buf); 214 return 0; 215} 216 217int camera_scan_qr(ACCamera *cam) { 218 if (!cam->gray || !cam->gray_ready) return 0; 219 220 struct quirc *qr = quirc_new(); 221 if (!qr) return 0; 222 223 if (quirc_resize(qr, cam->width, cam->height) < 0) { 224 quirc_destroy(qr); 225 return 0; 226 } 227 228 // Copy grayscale into quirc's image buffer 229 int w, h; 230 uint8_t *image = quirc_begin(qr, &w, &h); 231 int copy_w = cam->width < w ? cam->width : w; 232 int copy_h = cam->height < h ? cam->height : h; 233 for (int y = 0; y < copy_h; y++) { 234 memcpy(image + y * w, cam->gray + y * cam->width, copy_w); 235 } 236 quirc_end(qr); 237 238 int count = quirc_count(qr); 239 cam->scan_result[0] = 0; 240 241 for (int i = 0; i < count; i++) { 242 struct quirc_code code; 243 struct quirc_data data; 244 quirc_extract(qr, i, &code); 245 if (quirc_decode(&code, &data) == QUIRC_SUCCESS) { 246 int len = data.payload_len; 247 if (len > (int)sizeof(cam->scan_result) - 1) 248 len = sizeof(cam->scan_result) - 1; 249 memcpy(cam->scan_result, data.payload, len); 250 cam->scan_result[len] = 0; 251 ac_log("[camera] QR decoded: %d bytes\n", len); 252 break; // take first successful decode 253 } 254 } 255 256 int found = (cam->scan_result[0] != 0) ? 1 : 0; 257 quirc_destroy(qr); 258 return found; 259}