Atlas - SDL_camera_v4l2.c

Home / ext / SDL / src / camera / v4l2 Lines: 1 | Size: 31268 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)]
[FILE BEGIN]
1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2025 Sam Lantinga <[email protected]> 4 5 This software is provided 'as-is', without any express or implied 6 warranty. In no event will the authors be held liable for any damages 7 arising from the use of this software. 8 9 Permission is granted to anyone to use this software for any purpose, 10 including commercial applications, and to alter it and redistribute it 11 freely, subject to the following restrictions: 12 13 1. The origin of this software must not be misrepresented; you must not 14 claim that you wrote the original software. If you use this software 15 in a product, an acknowledgment in the product documentation would be 16 appreciated but is not required. 17 2. Altered source versions must be plainly marked as such, and must not be 18 misrepresented as being the original software. 19 3. This notice may not be removed or altered from any source distribution. 20*/ 21#include "SDL_internal.h" 22 23#ifdef SDL_CAMERA_DRIVER_V4L2 24 25#include <dirent.h> 26#include <errno.h> 27#include <fcntl.h> // low-level i/o 28#include <stddef.h> 29#include <sys/ioctl.h> 30#include <sys/mman.h> 31#include <sys/stat.h> 32#include <unistd.h> 33#include <linux/videodev2.h> 34 35#ifndef V4L2_PIX_FMT_RGBX32 36#define V4L2_PIX_FMT_RGBX32 v4l2_fourcc('X','B','2','4') 37#endif 38#ifndef V4L2_CAP_DEVICE_CAPS 39// device_caps was added to struct v4l2_capability as of kernel 3.4. 40#define device_caps reserved[0] 41SDL_COMPILE_TIME_ASSERT(v4l2devicecaps, offsetof(struct v4l2_capability,device_caps) == offsetof(struct v4l2_capability,capabilities) + 4); 42#endif 43 44#include "../SDL_syscamera.h" 45#include "../SDL_camera_c.h" 46#include "../../video/SDL_pixels_c.h" 47#include "../../video/SDL_surface_c.h" 48#include "../../thread/SDL_systhread.h" 49#include "../../core/linux/SDL_evdev_capabilities.h" 50#include "../../core/linux/SDL_udev.h" 51 52#ifndef SDL_USE_LIBUDEV 53#include <dirent.h> 54#endif 55 56typedef struct V4L2DeviceHandle 57{ 58 char *bus_info; 59 char *path; 60} V4L2DeviceHandle; 61 62 63typedef enum io_method { 64 IO_METHOD_INVALID, 65 IO_METHOD_READ, 66 IO_METHOD_MMAP, 67 IO_METHOD_USERPTR 68} io_method; 69 70struct buffer { 71 void *start; 72 size_t length; 73 int available; // Is available in userspace 74}; 75 76struct SDL_PrivateCameraData 77{ 78 int fd; 79 io_method io; 80 int nb_buffers; 81 struct buffer *buffers; 82 int driver_pitch; 83}; 84 85static int xioctl(int fh, int request, void *arg) 86{ 87 int r; 88 89 do { 90 r = ioctl(fh, request, arg); 91 } while ((r == -1) && (errno == EINTR)); 92 93 return r; 94} 95 96static bool V4L2_WaitDevice(SDL_Camera *device) 97{ 98 const int fd = device->hidden->fd; 99 100 int rc; 101 102 do { 103 fd_set fds; 104 FD_ZERO(&fds); 105 FD_SET(fd, &fds); 106 107 struct timeval tv; 108 tv.tv_sec = 0; 109 tv.tv_usec = 100 * 1000; 110 111 rc = select(fd + 1, &fds, NULL, NULL, &tv); 112 if ((rc == -1) && (errno == EINTR)) { 113 rc = 0; // pretend it was a timeout, keep looping. 114 } else if (rc > 0) { 115 return true; 116 } 117 118 // Thread is requested to shut down 119 if (SDL_GetAtomicInt(&device->shutdown)) { 120 return true; 121 } 122 123 } while (rc == 0); 124 125 return false; 126} 127 128static SDL_CameraFrameResult V4L2_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS, float *rotation) 129{ 130 const int fd = device->hidden->fd; 131 const io_method io = device->hidden->io; 132 size_t size = device->hidden->buffers[0].length; 133 struct v4l2_buffer buf; 134 ssize_t amount; 135 136 switch (io) { 137 case IO_METHOD_READ: 138 if ((amount = read(fd, device->hidden->buffers[0].start, size)) == -1) { 139 switch (errno) { 140 case EAGAIN: 141 return SDL_CAMERA_FRAME_SKIP; 142 143 case EIO: 144 // Could ignore EIO, see spec. 145 // fall through 146 147 default: 148 SDL_SetError("read"); 149 return SDL_CAMERA_FRAME_ERROR; 150 } 151 } 152 153 *timestampNS = SDL_GetTicksNS(); // oh well, close enough. 154 frame->pixels = device->hidden->buffers[0].start; 155 if (device->hidden->driver_pitch) { 156 frame->pitch = device->hidden->driver_pitch; 157 } else { 158 frame->pitch = (int)amount; 159 } 160 break; 161 162 case IO_METHOD_MMAP: 163 SDL_zero(buf); 164 165 buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 166 buf.memory = V4L2_MEMORY_MMAP; 167 168 if (xioctl(fd, VIDIOC_DQBUF, &buf) == -1) { 169 switch (errno) { 170 case EAGAIN: 171 return SDL_CAMERA_FRAME_SKIP; 172 173 case EIO: 174 // Could ignore EIO, see spec. 175 // fall through 176 177 default: 178 SDL_SetError("VIDIOC_DQBUF: %d", errno); 179 return SDL_CAMERA_FRAME_ERROR; 180 } 181 } 182 183 if ((int)buf.index < 0 || (int)buf.index >= device->hidden->nb_buffers) { 184 SDL_SetError("invalid buffer index"); 185 return SDL_CAMERA_FRAME_ERROR; 186 } 187 188 frame->pixels = device->hidden->buffers[buf.index].start; 189 if (device->hidden->driver_pitch) { 190 frame->pitch = device->hidden->driver_pitch; 191 } else { 192 frame->pitch = buf.bytesused; 193 } 194 device->hidden->buffers[buf.index].available = 1; 195 196 *timestampNS = (((Uint64) buf.timestamp.tv_sec) * SDL_NS_PER_SECOND) + SDL_US_TO_NS(buf.timestamp.tv_usec); 197 198 #if DEBUG_CAMERA 199 SDL_Log("CAMERA: debug mmap: image %d/%d data[0]=%p", buf.index, device->hidden->nb_buffers, (void *)frame->pixels); 200 #endif 201 break; 202 203 case IO_METHOD_USERPTR: 204 SDL_zero(buf); 205 206 buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 207 buf.memory = V4L2_MEMORY_USERPTR; 208 209 if (xioctl(fd, VIDIOC_DQBUF, &buf) == -1) { 210 switch (errno) { 211 case EAGAIN: 212 return SDL_CAMERA_FRAME_SKIP; 213 214 case EIO: 215 // Could ignore EIO, see spec. 216 // fall through 217 218 default: 219 SDL_SetError("VIDIOC_DQBUF"); 220 return SDL_CAMERA_FRAME_ERROR; 221 } 222 } 223 224 int i; 225 for (i = 0; i < device->hidden->nb_buffers; ++i) { 226 if (buf.m.userptr == (unsigned long)device->hidden->buffers[i].start && buf.length == size) { 227 break; 228 } 229 } 230 231 if (i >= device->hidden->nb_buffers) { 232 SDL_SetError("invalid buffer index"); 233 return SDL_CAMERA_FRAME_ERROR; 234 } 235 236 frame->pixels = (void *)buf.m.userptr; 237 if (device->hidden->driver_pitch) { 238 frame->pitch = device->hidden->driver_pitch; 239 } else { 240 frame->pitch = buf.bytesused; 241 } 242 device->hidden->buffers[i].available = 1; 243 244 *timestampNS = (((Uint64) buf.timestamp.tv_sec) * SDL_NS_PER_SECOND) + SDL_US_TO_NS(buf.timestamp.tv_usec); 245 246 #if DEBUG_CAMERA 247 SDL_Log("CAMERA: debug userptr: image %d/%d data[0]=%p", buf.index, device->hidden->nb_buffers, (void *)frame->pixels); 248 #endif 249 break; 250 251 case IO_METHOD_INVALID: 252 SDL_assert(!"Shouldn't have hit this"); 253 break; 254 } 255 256 return SDL_CAMERA_FRAME_READY; 257} 258 259static void V4L2_ReleaseFrame(SDL_Camera *device, SDL_Surface *frame) 260{ 261 struct v4l2_buffer buf; 262 const int fd = device->hidden->fd; 263 const io_method io = device->hidden->io; 264 int i; 265 266 for (i = 0; i < device->hidden->nb_buffers; ++i) { 267 if (frame->pixels == device->hidden->buffers[i].start) { 268 break; 269 } 270 } 271 272 if (i >= device->hidden->nb_buffers) { 273 return; // oh well, we didn't own this. 274 } 275 276 switch (io) { 277 case IO_METHOD_READ: 278 break; 279 280 case IO_METHOD_MMAP: 281 SDL_zero(buf); 282 283 buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 284 buf.memory = V4L2_MEMORY_MMAP; 285 buf.index = i; 286 287 if (xioctl(fd, VIDIOC_QBUF, &buf) == -1) { 288 // !!! FIXME: disconnect the device. 289 return; //SDL_SetError("VIDIOC_QBUF"); 290 } 291 device->hidden->buffers[i].available = 0; 292 break; 293 294 case IO_METHOD_USERPTR: 295 SDL_zero(buf); 296 297 buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 298 buf.memory = V4L2_MEMORY_USERPTR; 299 buf.index = i; 300 buf.m.userptr = (unsigned long)frame->pixels; 301 buf.length = (int) device->hidden->buffers[i].length; 302 303 if (xioctl(fd, VIDIOC_QBUF, &buf) == -1) { 304 // !!! FIXME: disconnect the device. 305 return; //SDL_SetError("VIDIOC_QBUF"); 306 } 307 device->hidden->buffers[i].available = 0; 308 break; 309 310 case IO_METHOD_INVALID: 311 SDL_assert(!"Shouldn't have hit this"); 312 break; 313 } 314} 315 316static bool EnqueueBuffers(SDL_Camera *device) 317{ 318 const int fd = device->hidden->fd; 319 const io_method io = device->hidden->io; 320 switch (io) { 321 case IO_METHOD_READ: 322 break; 323 324 case IO_METHOD_MMAP: 325 for (int i = 0; i < device->hidden->nb_buffers; ++i) { 326 if (device->hidden->buffers[i].available == 0) { 327 struct v4l2_buffer buf; 328 329 SDL_zero(buf); 330 buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 331 buf.memory = V4L2_MEMORY_MMAP; 332 buf.index = i; 333 334 if (xioctl(fd, VIDIOC_QBUF, &buf) == -1) { 335 return SDL_SetError("VIDIOC_QBUF"); 336 } 337 } 338 } 339 break; 340 341 case IO_METHOD_USERPTR: 342 for (int i = 0; i < device->hidden->nb_buffers; ++i) { 343 if (device->hidden->buffers[i].available == 0) { 344 struct v4l2_buffer buf; 345 346 SDL_zero(buf); 347 buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 348 buf.memory = V4L2_MEMORY_USERPTR; 349 buf.index = i; 350 buf.m.userptr = (unsigned long)device->hidden->buffers[i].start; 351 buf.length = (int) device->hidden->buffers[i].length; 352 353 if (xioctl(fd, VIDIOC_QBUF, &buf) == -1) { 354 return SDL_SetError("VIDIOC_QBUF"); 355 } 356 } 357 } 358 break; 359 360 case IO_METHOD_INVALID: SDL_assert(!"Shouldn't have hit this"); break; 361 } 362 return true; 363} 364 365static bool AllocBufferRead(SDL_Camera *device, size_t buffer_size) 366{ 367 device->hidden->buffers[0].length = buffer_size; 368 device->hidden->buffers[0].start = SDL_calloc(1, buffer_size); 369 return (device->hidden->buffers[0].start != NULL); 370} 371 372static bool AllocBufferMmap(SDL_Camera *device) 373{ 374 const int fd = device->hidden->fd; 375 int i; 376 for (i = 0; i < device->hidden->nb_buffers; ++i) { 377 struct v4l2_buffer buf; 378 379 SDL_zero(buf); 380 381 buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 382 buf.memory = V4L2_MEMORY_MMAP; 383 buf.index = i; 384 385 if (xioctl(fd, VIDIOC_QUERYBUF, &buf) == -1) { 386 return SDL_SetError("VIDIOC_QUERYBUF"); 387 } 388 389 device->hidden->buffers[i].length = buf.length; 390 device->hidden->buffers[i].start = 391 mmap(NULL /* start anywhere */, 392 buf.length, 393 PROT_READ | PROT_WRITE /* required */, 394 MAP_SHARED /* recommended */, 395 fd, buf.m.offset); 396 397 if (MAP_FAILED == device->hidden->buffers[i].start) { 398 return SDL_SetError("mmap"); 399 } 400 } 401 return true; 402} 403 404static bool AllocBufferUserPtr(SDL_Camera *device, size_t buffer_size) 405{ 406 int i; 407 for (i = 0; i < device->hidden->nb_buffers; ++i) { 408 device->hidden->buffers[i].length = buffer_size; 409 device->hidden->buffers[i].start = SDL_calloc(1, buffer_size); 410 411 if (!device->hidden->buffers[i].start) { 412 return false; 413 } 414 } 415 return true; 416} 417 418static void format_v4l2_to_sdl(Uint32 fmt, SDL_PixelFormat *format, SDL_Colorspace *colorspace) 419{ 420 switch (fmt) { 421 #define CASE(x, y, z) case x: *format = y; *colorspace = z; return 422 CASE(V4L2_PIX_FMT_YUYV, SDL_PIXELFORMAT_YUY2, SDL_COLORSPACE_BT709_LIMITED); 423 CASE(V4L2_PIX_FMT_MJPEG, SDL_PIXELFORMAT_MJPG, SDL_COLORSPACE_SRGB); 424 CASE(V4L2_PIX_FMT_RGBX32, SDL_PIXELFORMAT_RGBX32, SDL_COLORSPACE_SRGB); 425 #undef CASE 426 default: 427 #if DEBUG_CAMERA 428 SDL_Log("CAMERA: Unknown format V4L2_PIX_FORMAT '%c%c%c%c' (0x%x)", 429 (char)(Uint8)(fmt >> 0), 430 (char)(Uint8)(fmt >> 8), 431 (char)(Uint8)(fmt >> 16), 432 (char)(Uint8)(fmt >> 24), fmt); 433 #endif 434 break; 435 } 436 *format = SDL_PIXELFORMAT_UNKNOWN; 437 *colorspace = SDL_COLORSPACE_UNKNOWN; 438} 439 440static Uint32 format_sdl_to_v4l2(SDL_PixelFormat fmt) 441{ 442 switch (fmt) { 443 #define CASE(y, x) case x: return y 444 CASE(V4L2_PIX_FMT_YUYV, SDL_PIXELFORMAT_YUY2); 445 CASE(V4L2_PIX_FMT_MJPEG, SDL_PIXELFORMAT_MJPG); 446 CASE(V4L2_PIX_FMT_RGBX32, SDL_PIXELFORMAT_RGBX32); 447 #undef CASE 448 default: 449 return 0; 450 } 451} 452 453static void V4L2_CloseDevice(SDL_Camera *device) 454{ 455 if (!device) { 456 return; 457 } 458 459 if (device->hidden) { 460 const io_method io = device->hidden->io; 461 const int fd = device->hidden->fd; 462 463 if ((io == IO_METHOD_MMAP) || (io == IO_METHOD_USERPTR)) { 464 enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 465 xioctl(fd, VIDIOC_STREAMOFF, &type); 466 } 467 468 if (device->hidden->buffers) { 469 switch (io) { 470 case IO_METHOD_INVALID: 471 break; 472 473 case IO_METHOD_READ: 474 SDL_free(device->hidden->buffers[0].start); 475 break; 476 477 case IO_METHOD_MMAP: 478 for (int i = 0; i < device->hidden->nb_buffers; ++i) { 479 if (munmap(device->hidden->buffers[i].start, device->hidden->buffers[i].length) == -1) { 480 SDL_SetError("munmap"); 481 } 482 } 483 break; 484 485 case IO_METHOD_USERPTR: 486 for (int i = 0; i < device->hidden->nb_buffers; ++i) { 487 SDL_free(device->hidden->buffers[i].start); 488 } 489 break; 490 } 491 492 SDL_free(device->hidden->buffers); 493 } 494 495 if (fd != -1) { 496 close(fd); 497 } 498 SDL_free(device->hidden); 499 500 device->hidden = NULL; 501 } 502} 503 504static bool V4L2_OpenDevice(SDL_Camera *device, const SDL_CameraSpec *spec) 505{ 506 const V4L2DeviceHandle *handle = (const V4L2DeviceHandle *) device->handle; 507 struct stat st; 508 struct v4l2_capability cap; 509 const int fd = open(handle->path, O_RDWR /* required */ | O_NONBLOCK, 0); 510 511 // most of this probably shouldn't fail unless the filesystem node changed out from under us since MaybeAddDevice(). 512 if (fd == -1) { 513 return SDL_SetError("Cannot open '%s': %d, %s", handle->path, errno, strerror(errno)); 514 } else if (fstat(fd, &st) == -1) { 515 close(fd); 516 return SDL_SetError("Cannot identify '%s': %d, %s", handle->path, errno, strerror(errno)); 517 } else if (!S_ISCHR(st.st_mode)) { 518 close(fd); 519 return SDL_SetError("%s is not a character device", handle->path); 520 } else if (xioctl(fd, VIDIOC_QUERYCAP, &cap) == -1) { 521 const int err = errno; 522 close(fd); 523 if (err == EINVAL) { 524 return SDL_SetError("%s is unexpectedly not a V4L2 device", handle->path); 525 } 526 return SDL_SetError("Error VIDIOC_QUERYCAP errno=%d device%s is no V4L2 device", err, handle->path); 527 } else if ((cap.device_caps & V4L2_CAP_VIDEO_CAPTURE) == 0) { 528 close(fd); 529 return SDL_SetError("%s is unexpectedly not a video capture device", handle->path); 530 } 531 532 device->hidden = (struct SDL_PrivateCameraData *) SDL_calloc(1, sizeof (struct SDL_PrivateCameraData)); 533 if (device->hidden == NULL) { 534 close(fd); 535 return false; 536 } 537 538 device->hidden->fd = fd; 539 device->hidden->io = IO_METHOD_INVALID; 540 541 // Select video input, video standard and tune here. 542 // errors in the crop code are not fatal. 543 struct v4l2_cropcap cropcap; 544 SDL_zero(cropcap); 545 cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 546 if (xioctl(fd, VIDIOC_CROPCAP, &cropcap) == 0) { 547 struct v4l2_crop crop; 548 SDL_zero(crop); 549 crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 550 crop.c = cropcap.defrect; // reset to default 551 xioctl(fd, VIDIOC_S_CROP, &crop); 552 } 553 554 struct v4l2_format fmt; 555 SDL_zero(fmt); 556 fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 557 fmt.fmt.pix.width = spec->width; 558 fmt.fmt.pix.height = spec->height; 559 fmt.fmt.pix.pixelformat = format_sdl_to_v4l2(spec->format); 560 //fmt.fmt.pix.field = V4L2_FIELD_INTERLACED; 561 fmt.fmt.pix.field = V4L2_FIELD_ANY; 562 563 #if DEBUG_CAMERA 564 SDL_Log("CAMERA: set SDL format %s", SDL_GetPixelFormatName(spec->format)); 565 { const Uint32 f = fmt.fmt.pix.pixelformat; SDL_Log("CAMERA: set format V4L2_format=%d %c%c%c%c", f, (f >> 0) & 0xff, (f >> 8) & 0xff, (f >> 16) & 0xff, (f >> 24) & 0xff); } 566 #endif 567 568 if (xioctl(fd, VIDIOC_S_FMT, &fmt) == -1) { 569 return SDL_SetError("Error VIDIOC_S_FMT"); 570 } 571 572 if (spec->framerate_numerator && spec->framerate_denominator) { 573 struct v4l2_streamparm setfps; 574 SDL_zero(setfps); 575 setfps.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 576 if (xioctl(fd, VIDIOC_G_PARM, &setfps) == 0) { 577 if ( (setfps.parm.capture.timeperframe.denominator != spec->framerate_numerator) || 578 (setfps.parm.capture.timeperframe.numerator != spec->framerate_denominator) ) { 579 setfps.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 580 setfps.parm.capture.timeperframe.numerator = spec->framerate_denominator; 581 setfps.parm.capture.timeperframe.denominator = spec->framerate_numerator; 582 if (xioctl(fd, VIDIOC_S_PARM, &setfps) == -1) { 583 return SDL_SetError("Error VIDIOC_S_PARM"); 584 } 585 } 586 } 587 } 588 589 SDL_zero(fmt); 590 fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 591 if (xioctl(fd, VIDIOC_G_FMT, &fmt) == -1) { 592 return SDL_SetError("Error VIDIOC_G_FMT"); 593 } 594 device->hidden->driver_pitch = fmt.fmt.pix.bytesperline; 595 596 io_method io = IO_METHOD_INVALID; 597 if ((io == IO_METHOD_INVALID) && (cap.device_caps & V4L2_CAP_STREAMING)) { 598 struct v4l2_requestbuffers req; 599 SDL_zero(req); 600 req.count = 8; 601 req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 602 req.memory = V4L2_MEMORY_MMAP; 603 if ((xioctl(fd, VIDIOC_REQBUFS, &req) == 0) && (req.count >= 2)) { 604 io = IO_METHOD_MMAP; 605 device->hidden->nb_buffers = req.count; 606 } else { // mmap didn't work out? Try USERPTR. 607 SDL_zero(req); 608 req.count = 8; 609 req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 610 req.memory = V4L2_MEMORY_USERPTR; 611 if (xioctl(fd, VIDIOC_REQBUFS, &req) == 0) { 612 io = IO_METHOD_USERPTR; 613 device->hidden->nb_buffers = 8; 614 } 615 } 616 } 617 618 if ((io == IO_METHOD_INVALID) && (cap.device_caps & V4L2_CAP_READWRITE)) { 619 io = IO_METHOD_READ; 620 device->hidden->nb_buffers = 1; 621 } 622 623 if (io == IO_METHOD_INVALID) { 624 return SDL_SetError("Don't have a way to talk to this device"); 625 } 626 627 device->hidden->io = io; 628 629 device->hidden->buffers = SDL_calloc(device->hidden->nb_buffers, sizeof(*device->hidden->buffers)); 630 if (!device->hidden->buffers) { 631 return false; 632 } 633 634 size_t size, pitch; 635 if (!SDL_CalculateSurfaceSize(device->spec.format, device->spec.width, device->spec.height, &size, &pitch, false)) { 636 return false; 637 } 638 639 bool rc = true; 640 switch (io) { 641 case IO_METHOD_READ: 642 rc = AllocBufferRead(device, size); 643 break; 644 645 case IO_METHOD_MMAP: 646 rc = AllocBufferMmap(device); 647 break; 648 649 case IO_METHOD_USERPTR: 650 rc = AllocBufferUserPtr(device, size); 651 break; 652 653 case IO_METHOD_INVALID: 654 SDL_assert(!"Shouldn't have hit this"); 655 break; 656 } 657 658 if (!rc) { 659 return false; 660 } else if (!EnqueueBuffers(device)) { 661 return false; 662 } else if (io != IO_METHOD_READ) { 663 enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 664 if (xioctl(fd, VIDIOC_STREAMON, &type) == -1) { 665 return SDL_SetError("VIDIOC_STREAMON"); 666 } 667 } 668 669 // Currently there is no user permission prompt for camera access, but maybe there will be a D-Bus portal interface at some point. 670 SDL_CameraPermissionOutcome(device, true); 671 672 return true; 673} 674 675static bool FindV4L2CameraByBusInfoCallback(SDL_Camera *device, void *userdata) 676{ 677 const V4L2DeviceHandle *handle = (const V4L2DeviceHandle *) device->handle; 678 return (SDL_strcmp(handle->bus_info, (const char *) userdata) == 0); 679} 680 681static bool AddCameraFormat(const int fd, CameraFormatAddData *data, SDL_PixelFormat sdlfmt, SDL_Colorspace colorspace, Uint32 v4l2fmt, int w, int h) 682{ 683 struct v4l2_frmivalenum frmivalenum; 684 SDL_zero(frmivalenum); 685 frmivalenum.pixel_format = v4l2fmt; 686 frmivalenum.width = (Uint32) w; 687 frmivalenum.height = (Uint32) h; 688 689 while (ioctl(fd, VIDIOC_ENUM_FRAMEINTERVALS, &frmivalenum) == 0) { 690 if (frmivalenum.type == V4L2_FRMIVAL_TYPE_DISCRETE) { 691 const int numerator = (int) frmivalenum.discrete.numerator; 692 const int denominator = (int) frmivalenum.discrete.denominator; 693 #if DEBUG_CAMERA 694 const float fps = (float) denominator / (float) numerator; 695 SDL_Log("CAMERA: * Has discrete frame interval (%d / %d), fps=%f", numerator, denominator, fps); 696 #endif 697 if (!SDL_AddCameraFormat(data, sdlfmt, colorspace, w, h, denominator, numerator)) { 698 return false; // Probably out of memory; we'll go with what we have, if anything. 699 } 700 frmivalenum.index++; // set up for the next one. 701 } else if ((frmivalenum.type == V4L2_FRMIVAL_TYPE_STEPWISE) || (frmivalenum.type == V4L2_FRMIVAL_TYPE_CONTINUOUS)) { 702 int d = frmivalenum.stepwise.min.denominator; 703 // !!! FIXME: should we step by the numerator...? 704 for (int n = (int) frmivalenum.stepwise.min.numerator; n <= (int) frmivalenum.stepwise.max.numerator; n += (int) frmivalenum.stepwise.step.numerator) { 705 #if DEBUG_CAMERA 706 const float fps = (float) d / (float) n; 707 SDL_Log("CAMERA: * Has %s frame interval (%d / %d), fps=%f", (frmivalenum.type == V4L2_FRMIVAL_TYPE_STEPWISE) ? "stepwise" : "continuous", n, d, fps); 708 #endif 709 // SDL expects framerate, V4L2 provides interval 710 if (!SDL_AddCameraFormat(data, sdlfmt, colorspace, w, h, d, n)) { 711 return false; // Probably out of memory; we'll go with what we have, if anything. 712 } 713 d += (int) frmivalenum.stepwise.step.denominator; 714 } 715 break; 716 } 717 } 718 719 return true; 720} 721 722 723static void MaybeAddDevice(const char *path) 724{ 725 if (!path) { 726 return; 727 } 728 729 struct stat st; 730 const int fd = open(path, O_RDWR /* required */ | O_NONBLOCK, 0); 731 if (fd == -1) { 732 return; // can't open it? skip it. 733 } else if (fstat(fd, &st) == -1) { 734 close(fd); 735 return; // can't stat it? skip it. 736 } else if (!S_ISCHR(st.st_mode)) { 737 close(fd); 738 return; // not a character device. 739 } 740 741 struct v4l2_capability vcap; 742 const int rc = ioctl(fd, VIDIOC_QUERYCAP, &vcap); 743 if (rc != 0) { 744 close(fd); 745 return; // probably not a v4l2 device at all. 746 } else if ((vcap.device_caps & V4L2_CAP_VIDEO_CAPTURE) == 0) { 747 close(fd); 748 return; // not a video capture device. 749 } else if (SDL_FindPhysicalCameraByCallback(FindV4L2CameraByBusInfoCallback, vcap.bus_info)) { 750 close(fd); 751 return; // already have it. 752 } 753 754 #if DEBUG_CAMERA 755 SDL_Log("CAMERA: V4L2 camera path='%s' bus_info='%s' name='%s'", path, (const char *) vcap.bus_info, vcap.card); 756 #endif 757 758 CameraFormatAddData add_data; 759 SDL_zero(add_data); 760 761 struct v4l2_fmtdesc fmtdesc; 762 SDL_zero(fmtdesc); 763 fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 764 while (ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc) == 0) { 765 SDL_PixelFormat sdlfmt = SDL_PIXELFORMAT_UNKNOWN; 766 SDL_Colorspace colorspace = SDL_COLORSPACE_UNKNOWN; 767 format_v4l2_to_sdl(fmtdesc.pixelformat, &sdlfmt, &colorspace); 768 769 #if DEBUG_CAMERA 770 SDL_Log("CAMERA: - Has format '%s'%s%s", SDL_GetPixelFormatName(sdlfmt), 771 (fmtdesc.flags & V4L2_FMT_FLAG_EMULATED) ? " [EMULATED]" : "", 772 (fmtdesc.flags & V4L2_FMT_FLAG_COMPRESSED) ? " [COMPRESSED]" : ""); 773 #endif 774 775 fmtdesc.index++; // prepare for next iteration. 776 777 if (sdlfmt == SDL_PIXELFORMAT_UNKNOWN) { 778 continue; // unsupported by SDL atm. 779 } 780 781 struct v4l2_frmsizeenum frmsizeenum; 782 SDL_zero(frmsizeenum); 783 frmsizeenum.pixel_format = fmtdesc.pixelformat; 784 785 while (ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &frmsizeenum) == 0) { 786 if (frmsizeenum.type == V4L2_FRMSIZE_TYPE_DISCRETE) { 787 const int w = (int) frmsizeenum.discrete.width; 788 const int h = (int) frmsizeenum.discrete.height; 789 #if DEBUG_CAMERA 790 SDL_Log("CAMERA: * Has discrete size %dx%d", w, h); 791 #endif 792 if (!AddCameraFormat(fd, &add_data, sdlfmt, colorspace, fmtdesc.pixelformat, w, h)) { 793 break; // Probably out of memory; we'll go with what we have, if anything. 794 } 795 frmsizeenum.index++; // set up for the next one. 796 } else if ((frmsizeenum.type == V4L2_FRMSIZE_TYPE_STEPWISE) || (frmsizeenum.type == V4L2_FRMSIZE_TYPE_CONTINUOUS)) { 797 const int minw = (int) frmsizeenum.stepwise.min_width; 798 const int minh = (int) frmsizeenum.stepwise.min_height; 799 const int maxw = (int) frmsizeenum.stepwise.max_width; 800 const int maxh = (int) frmsizeenum.stepwise.max_height; 801 const int stepw = (int) frmsizeenum.stepwise.step_width; 802 const int steph = (int) frmsizeenum.stepwise.step_height; 803 for (int w = minw; w <= maxw; w += stepw) { 804 for (int h = minh; h <= maxh; h += steph) { 805 #if DEBUG_CAMERA 806 SDL_Log("CAMERA: * Has %s size %dx%d", (frmsizeenum.type == V4L2_FRMSIZE_TYPE_STEPWISE) ? "stepwise" : "continuous", w, h); 807 #endif 808 if (!AddCameraFormat(fd, &add_data, sdlfmt, colorspace, fmtdesc.pixelformat, w, h)) { 809 break; // Probably out of memory; we'll go with what we have, if anything. 810 } 811 } 812 } 813 break; 814 } 815 } 816 } 817 818 close(fd); 819 820 #if DEBUG_CAMERA 821 SDL_Log("CAMERA: (total specs: %d)", add_data.num_specs); 822 #endif 823 824 if (add_data.num_specs > 0) { 825 V4L2DeviceHandle *handle = (V4L2DeviceHandle *) SDL_calloc(1, sizeof (V4L2DeviceHandle)); 826 if (handle) { 827 handle->path = SDL_strdup(path); 828 if (handle->path) { 829 handle->bus_info = SDL_strdup((char *)vcap.bus_info); 830 if (handle->bus_info) { 831 if (SDL_AddCamera((const char *) vcap.card, SDL_CAMERA_POSITION_UNKNOWN, add_data.num_specs, add_data.specs, handle)) { 832 SDL_free(add_data.specs); 833 return; // good to go. 834 } 835 SDL_free(handle->bus_info); 836 } 837 SDL_free(handle->path); 838 } 839 SDL_free(handle); 840 } 841 } 842 SDL_free(add_data.specs); 843} 844 845static void V4L2_FreeDeviceHandle(SDL_Camera *device) 846{ 847 if (device) { 848 V4L2DeviceHandle *handle = (V4L2DeviceHandle *) device->handle; 849 SDL_free(handle->path); 850 SDL_free(handle->bus_info); 851 SDL_free(handle); 852 } 853} 854 855#ifdef SDL_USE_LIBUDEV 856static bool FindV4L2CameraByPathCallback(SDL_Camera *device, void *userdata) 857{ 858 const V4L2DeviceHandle *handle = (const V4L2DeviceHandle *) device->handle; 859 return (SDL_strcmp(handle->path, (const char *) userdata) == 0); 860} 861 862static void MaybeRemoveDevice(const char *path) 863{ 864 if (path) { 865 SDL_CameraDisconnected(SDL_FindPhysicalCameraByCallback(FindV4L2CameraByPathCallback, (void *) path)); 866 } 867} 868 869static void CameraUdevCallback(SDL_UDEV_deviceevent udev_type, int udev_class, const char *devpath) 870{ 871 if (devpath && (udev_class & SDL_UDEV_DEVICE_VIDEO_CAPTURE)) { 872 if (udev_type == SDL_UDEV_DEVICEADDED) { 873 MaybeAddDevice(devpath); 874 } else if (udev_type == SDL_UDEV_DEVICEREMOVED) { 875 MaybeRemoveDevice(devpath); 876 } 877 } 878} 879#endif // SDL_USE_LIBUDEV 880 881static void V4L2_Deinitialize(void) 882{ 883#ifdef SDL_USE_LIBUDEV 884 SDL_UDEV_DelCallback(CameraUdevCallback); 885 SDL_UDEV_Quit(); 886#endif // SDL_USE_LIBUDEV 887} 888 889static void V4L2_DetectDevices(void) 890{ 891#ifdef SDL_USE_LIBUDEV 892 if (SDL_UDEV_Init()) { 893 if (SDL_UDEV_AddCallback(CameraUdevCallback)) { 894 SDL_UDEV_Scan(); // Force a scan to build the initial device list 895 } 896 return; 897 } 898#endif // SDL_USE_LIBUDEV 899 900 DIR *dirp = opendir("/dev"); 901 if (dirp) { 902 struct dirent *dent; 903 while ((dent = readdir(dirp)) != NULL) { 904 int num = 0; 905 if (SDL_sscanf(dent->d_name, "video%d", &num) == 1) { 906 char fullpath[64]; 907 SDL_snprintf(fullpath, sizeof (fullpath), "/dev/video%d", num); 908 MaybeAddDevice(fullpath); 909 } 910 } 911 closedir(dirp); 912 } 913} 914 915static bool V4L2_Init(SDL_CameraDriverImpl *impl) 916{ 917 impl->DetectDevices = V4L2_DetectDevices; 918 impl->OpenDevice = V4L2_OpenDevice; 919 impl->CloseDevice = V4L2_CloseDevice; 920 impl->WaitDevice = V4L2_WaitDevice; 921 impl->AcquireFrame = V4L2_AcquireFrame; 922 impl->ReleaseFrame = V4L2_ReleaseFrame; 923 impl->FreeDeviceHandle = V4L2_FreeDeviceHandle; 924 impl->Deinitialize = V4L2_Deinitialize; 925 926 return true; 927} 928 929CameraBootStrap V4L2_bootstrap = { 930 "v4l2", "SDL Video4Linux2 camera driver", V4L2_Init, false 931}; 932 933#endif // SDL_CAMERA_DRIVER_V4L2 934 935
[FILE END]
(C) 2025 0x4248 (C) 2025 4248 Media and 4248 Systems, All part of 0x4248 See LICENCE files for more information. Not all files are by 0x4248 always check Licencing.