diff options
Diffstat (limited to 'zbar/video/v4l2.c')
-rw-r--r-- | zbar/video/v4l2.c | 1237 |
1 files changed, 1237 insertions, 0 deletions
diff --git a/zbar/video/v4l2.c b/zbar/video/v4l2.c new file mode 100644 index 0000000..338a4d1 --- /dev/null +++ b/zbar/video/v4l2.c @@ -0,0 +1,1237 @@ +/*------------------------------------------------------------------------ + * Copyright 2007-2009 (c) Jeff Brown <spadix@users.sourceforge.net> + * + * This file is part of the ZBar Bar Code Reader. + * + * The ZBar Bar Code Reader is free software; you can redistribute it + * and/or modify it under the terms of the GNU Lesser Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * The ZBar Bar Code Reader is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser Public License for more details. + * + * You should have received a copy of the GNU Lesser Public License + * along with the ZBar Bar Code Reader; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * http://sourceforge.net/projects/zbar + *------------------------------------------------------------------------*/ + +#include "config.h" +#ifdef HAVE_INTTYPES_H +#include <inttypes.h> +#endif +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#include <assert.h> +#include <errno.h> +#include <string.h> +#include <unistd.h> +#ifdef HAVE_SYS_IOCTL_H +#include <sys/ioctl.h> +#endif +#ifdef HAVE_SYS_MMAN_H +#include <sys/mman.h> +#endif +#ifdef HAVE_LIBV4L2_H +#include <fcntl.h> +#include <libv4l2.h> +#else +#define v4l2_close close +#define v4l2_ioctl ioctl +#define v4l2_mmap mmap +#define v4l2_munmap munmap +#endif +#include <linux/videodev2.h> + +#include "image.h" +#include "video.h" + +#define V4L2_FORMATS_MAX 64 +#define V4L2_FORMATS_SIZE_MAX 256 + +typedef struct video_controls_priv_s { + struct video_controls_s s; + + // Private fields + __u32 id; +} video_controls_priv_t; + +static int v4l2_nq(zbar_video_t *vdo, zbar_image_t *img) +{ + if (vdo->iomode == VIDEO_READWRITE) + return (video_nq_image(vdo, img)); + + if (video_unlock(vdo)) + return (-1); + + struct v4l2_buffer vbuf; + memset(&vbuf, 0, sizeof(vbuf)); + vbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (vdo->iomode == VIDEO_MMAP) { + vbuf.memory = V4L2_MEMORY_MMAP; + vbuf.index = img->srcidx; + } else { + vbuf.memory = V4L2_MEMORY_USERPTR; + vbuf.m.userptr = (unsigned long)img->data; + vbuf.length = img->datalen; + vbuf.index = img->srcidx; /* FIXME workaround broken drivers */ + } + if (v4l2_ioctl(vdo->fd, VIDIOC_QBUF, &vbuf) < 0) + return (err_capture(vdo, SEV_ERROR, ZBAR_ERR_SYSTEM, __func__, + "queuing video buffer (VIDIOC_QBUF)")); + return (0); +} + +static zbar_image_t *v4l2_dq(zbar_video_t *vdo) +{ + zbar_image_t *img; + int fd = vdo->fd; + + if (vdo->iomode != VIDEO_READWRITE) { + video_iomode_t iomode = vdo->iomode; + if (video_unlock(vdo)) + return (NULL); + + struct v4l2_buffer vbuf; + memset(&vbuf, 0, sizeof(vbuf)); + vbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (iomode == VIDEO_MMAP) + vbuf.memory = V4L2_MEMORY_MMAP; + else + vbuf.memory = V4L2_MEMORY_USERPTR; + + if (v4l2_ioctl(fd, VIDIOC_DQBUF, &vbuf) < 0) + return (NULL); + + if (iomode == VIDEO_MMAP) { + assert(vbuf.index >= 0); + assert(vbuf.index < vdo->num_images); + img = vdo->images[vbuf.index]; + } else { + /* reverse map pointer back to image (FIXME) */ + assert(vbuf.m.userptr >= (unsigned long)vdo->buf); + assert(vbuf.m.userptr < (unsigned long)(vdo->buf + vdo->buflen)); + int i = (vbuf.m.userptr - (unsigned long)vdo->buf) / vdo->datalen; + assert(i >= 0); + assert(i < vdo->num_images); + img = vdo->images[i]; + assert(vbuf.m.userptr == (unsigned long)img->data); + } + } else { + img = video_dq_image(vdo); + if (!img) + return (NULL); + + /* FIXME should read entire image */ + ssize_t datalen = read(fd, (void *)img->data, img->datalen); + if (datalen < 0) { + perror("v4l2_dq read"); + return (NULL); + } else if (datalen != img->datalen) + zprintf(0, "WARNING: read() size mismatch: 0x%lx != 0x%lx\n", + datalen, img->datalen); + } + return (img); +} + +static int v4l2_start(zbar_video_t *vdo) +{ + if (vdo->iomode == VIDEO_READWRITE) + return (0); + + enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (v4l2_ioctl(vdo->fd, VIDIOC_STREAMON, &type) < 0) + return (err_capture(vdo, SEV_ERROR, ZBAR_ERR_SYSTEM, __func__, + "starting video stream (VIDIOC_STREAMON)")); + return (0); +} + +static int v4l2_stop(zbar_video_t *vdo) +{ + if (vdo->iomode == VIDEO_READWRITE) + return (0); + + enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (v4l2_ioctl(vdo->fd, VIDIOC_STREAMOFF, &type) < 0) + return (err_capture(vdo, SEV_ERROR, ZBAR_ERR_SYSTEM, __func__, + "stopping video stream (VIDIOC_STREAMOFF)")); + return (0); +} + +static int v4l2_cleanup(zbar_video_t *vdo) +{ + if (vdo->iomode == VIDEO_READWRITE) + return (0); + + struct v4l2_requestbuffers rb; + memset(&rb, 0, sizeof(rb)); + rb.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (vdo->iomode == VIDEO_MMAP) { + rb.memory = V4L2_MEMORY_MMAP; + int i; + for (i = 0; i < vdo->num_images; i++) { + zbar_image_t *img = vdo->images[i]; + if (img->data && v4l2_munmap((void *)img->data, img->datalen)) + err_capture(vdo, SEV_WARNING, ZBAR_ERR_SYSTEM, __func__, + "unmapping video frame buffers"); + img->data = NULL; + img->datalen = 0; + } + } else + rb.memory = V4L2_MEMORY_USERPTR; + + /* requesting 0 buffers + * should implicitly disable streaming + */ + if (v4l2_ioctl(vdo->fd, VIDIOC_REQBUFS, &rb) < 0) + err_capture(vdo, SEV_WARNING, ZBAR_ERR_SYSTEM, __func__, + "releasing video frame buffers (VIDIOC_REQBUFS)"); + + /* v4l2_close v4l2_open device */ + if (vdo->fd >= 0) { + v4l2_close(vdo->fd); + vdo->fd = -1; + } + return (0); +} + +static int v4l2_mmap_buffers(zbar_video_t *vdo) +{ + struct v4l2_buffer vbuf; + memset(&vbuf, 0, sizeof(vbuf)); + vbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + vbuf.memory = V4L2_MEMORY_MMAP; + + int i; + for (i = 0; i < vdo->num_images; i++) { + vbuf.index = i; + if (v4l2_ioctl(vdo->fd, VIDIOC_QUERYBUF, &vbuf) < 0) + /* FIXME cleanup */ + return (err_capture(vdo, SEV_ERROR, ZBAR_ERR_SYSTEM, __func__, + "querying video buffer (VIDIOC_QUERYBUF)")); + + if (vbuf.length < vdo->datalen) + fprintf( + stderr, + "WARNING: insufficient v4l2 video buffer size:\n" + "\tvbuf[%d].length=%x datalen=%lx image=%d x %d %.4s(%08x)\n", + i, vbuf.length, vdo->datalen, vdo->width, vdo->height, + (char *)&vdo->format, vdo->format); + + zbar_image_t *img = vdo->images[i]; + img->datalen = vbuf.length; + img->data = v4l2_mmap(NULL, vbuf.length, PROT_READ | PROT_WRITE, + MAP_SHARED, vdo->fd, vbuf.m.offset); + if (img->data == MAP_FAILED) + /* FIXME cleanup */ + return (err_capture(vdo, SEV_ERROR, ZBAR_ERR_SYSTEM, __func__, + "mapping video frame buffers")); + zprintf(2, " buf[%d] 0x%lx bytes @%p\n", i, img->datalen, img->data); + } + return (0); +} + +static int v4l2_request_buffers(zbar_video_t *vdo, uint32_t num_images) +{ + struct v4l2_requestbuffers rb; + memset(&rb, 0, sizeof(rb)); + rb.count = num_images; + rb.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + if (vdo->iomode == VIDEO_MMAP) + rb.memory = V4L2_MEMORY_MMAP; + else + rb.memory = V4L2_MEMORY_USERPTR; + + if (v4l2_ioctl(vdo->fd, VIDIOC_REQBUFS, &rb) < 0) + return (err_capture(vdo, SEV_ERROR, ZBAR_ERR_SYSTEM, __func__, + "requesting video frame buffers (VIDIOC_REQBUFS)")); + if (num_images && rb.count) + vdo->num_images = rb.count; + return (0); +} + +static int v4l2_set_format(zbar_video_t *vdo, uint32_t fmt) +{ + struct v4l2_format vfmt; + struct v4l2_pix_format *vpix = &vfmt.fmt.pix; + memset(&vfmt, 0, sizeof(vfmt)); + vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + vpix->width = vdo->width; + vpix->height = vdo->height; + vpix->pixelformat = fmt; + vpix->field = V4L2_FIELD_NONE; + int rc = 0; + if ((rc = v4l2_ioctl(vdo->fd, VIDIOC_S_FMT, &vfmt)) < 0) { + /* several broken drivers return an error if we request + * no interlacing (NB v4l2 spec violation) + * ...try again with an interlaced request + */ + zprintf(1, "VIDIOC_S_FMT returned %d(%d), trying interlaced...\n", rc, + errno); + + /* FIXME this might be _ANY once we can de-interlace */ + vpix->field = V4L2_FIELD_INTERLACED; + + if (v4l2_ioctl(vdo->fd, VIDIOC_S_FMT, &vfmt) < 0) + return (err_capture_int(vdo, SEV_ERROR, ZBAR_ERR_SYSTEM, __func__, + "setting format %x (VIDIOC_S_FMT)", fmt)); + + zprintf(0, "WARNING: broken driver returned error when non-interlaced" + " format requested\n"); + } + + struct v4l2_format newfmt; + struct v4l2_pix_format *newpix = &newfmt.fmt.pix; + memset(&newfmt, 0, sizeof(newfmt)); + newfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (v4l2_ioctl(vdo->fd, VIDIOC_G_FMT, &newfmt) < 0) + return (err_capture(vdo, SEV_ERROR, ZBAR_ERR_SYSTEM, __func__, + "querying format (VIDIOC_G_FMT)")); + + if (newpix->field != V4L2_FIELD_NONE) + err_capture(vdo, SEV_WARNING, ZBAR_ERR_INVALID, __func__, + "video driver only supports interlaced format," + " vertical scanning may not work"); + + if (newpix->pixelformat != fmt + /* FIXME bpl/bpp checks? */) + return (err_capture(vdo, SEV_ERROR, ZBAR_ERR_INVALID, __func__, + "video driver can't provide compatible format")); + + vdo->format = fmt; + vdo->width = newpix->width; + vdo->height = newpix->height; + vdo->datalen = newpix->sizeimage; + + zprintf(1, "set new format: %.4s(%08x) %u x %u (0x%lx)\n", + (char *)&vdo->format, vdo->format, vdo->width, vdo->height, + vdo->datalen); + return (0); +} + +static int v4l2_init(zbar_video_t *vdo, uint32_t fmt) +{ + struct v4l2_requestbuffers rb; + if (v4l2_set_format(vdo, fmt)) + return (-1); + + memset(&rb, 0, sizeof(rb)); + rb.count = vdo->num_images; + rb.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (vdo->iomode == VIDEO_MMAP) + rb.memory = V4L2_MEMORY_MMAP; + else + rb.memory = V4L2_MEMORY_USERPTR; + + if (v4l2_ioctl(vdo->fd, VIDIOC_REQBUFS, &rb) < 0) + return (err_capture(vdo, SEV_ERROR, ZBAR_ERR_SYSTEM, __func__, + "requesting video frame buffers (VIDIOC_REQBUFS)")); + + if (!rb.count) + return (err_capture(vdo, SEV_ERROR, ZBAR_ERR_INVALID, __func__, + "driver returned 0 buffers")); + + if (vdo->num_images > rb.count) + vdo->num_images = rb.count; + + zprintf(1, "using %u buffers (of %d requested)\n", rb.count, + vdo->num_images); + + if (vdo->iomode == VIDEO_MMAP) + return (v4l2_mmap_buffers(vdo)); + if (vdo->iomode == VIDEO_USERPTR) + return (v4l2_request_buffers(vdo, vdo->num_images)); + return (0); +} + +static int v4l2_probe_iomode(zbar_video_t *vdo) +{ + struct v4l2_requestbuffers rb; + memset(&rb, 0, sizeof(rb)); + rb.count = vdo->num_images; + rb.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (vdo->iomode == VIDEO_MMAP) + rb.memory = V4L2_MEMORY_MMAP; + else + rb.memory = V4L2_MEMORY_USERPTR; + + if (v4l2_ioctl(vdo->fd, VIDIOC_REQBUFS, &rb) < 0) { + if (vdo->iomode) + return (err_capture_int(vdo, SEV_ERROR, ZBAR_ERR_INVALID, __func__, + "unsupported iomode requested (%d)", + vdo->iomode)); + else if (errno != EINVAL) + return (err_capture(vdo, SEV_ERROR, ZBAR_ERR_SYSTEM, __func__, + "querying streaming mode (VIDIOC_REQBUFS)")); +#ifdef HAVE_SYS_MMAN_H + err_capture(vdo, SEV_WARNING, ZBAR_ERR_SYSTEM, __func__, + "USERPTR failed. Falling back to mmap"); + vdo->iomode = VIDEO_MMAP; +#else + return err_capture(vdo, SEV_ERROR, ZBAR_ERR_SYSTEM, __func__, + "Userptr not supported, and zbar was compiled without mmap support")); +#endif + } else { + if (!vdo->iomode) + rb.memory = V4L2_MEMORY_USERPTR; + /* Update the num_images with the max supported by the driver */ + if (rb.count) + vdo->num_images = rb.count; + else + err_capture( + vdo, SEV_WARNING, ZBAR_ERR_SYSTEM, __func__, + "Something is wrong: number of buffers returned by REQBUF is zero!"); + + /* requesting 0 buffers + * This cleans up the buffers allocated previously on probe + */ + rb.count = 0; + if (v4l2_ioctl(vdo->fd, VIDIOC_REQBUFS, &rb) < 0) + err_capture(vdo, SEV_WARNING, ZBAR_ERR_SYSTEM, __func__, + "releasing video frame buffers (VIDIOC_REQBUFS)"); + } + return (0); +} + +static inline void v4l2_max_size(zbar_video_t *vdo, uint32_t pixfmt, + uint32_t *max_width, uint32_t *max_height) +{ + int mwidth = 0, mheight = 0, i; + struct v4l2_frmsizeenum frm; + + for (i = 0; i < V4L2_FORMATS_SIZE_MAX; i++) { + memset(&frm, 0, sizeof(frm)); + frm.index = i; + frm.pixel_format = pixfmt; + + if (v4l2_ioctl(vdo->fd, VIDIOC_ENUM_FRAMESIZES, &frm)) + break; + + switch (frm.type) { + case V4L2_FRMSIZE_TYPE_DISCRETE: + mwidth = frm.discrete.width; + mheight = frm.discrete.height; + break; + case V4L2_FRMSIZE_TYPE_CONTINUOUS: + case V4L2_FRMSIZE_TYPE_STEPWISE: + mwidth = frm.stepwise.max_width; + mheight = frm.stepwise.max_height; + break; + default: + continue; + } + if (mwidth > *max_width) + *max_width = mwidth; + if (mheight > *max_height) + *max_height = mheight; + } +} + +static inline int v4l2_probe_formats(zbar_video_t *vdo) +{ + int n_formats = 0, n_emu_formats = 0; + uint32_t max_width = 0, max_height = 0; + + if (vdo->width && vdo->height) + zprintf(1, "Caller requested an specific size: %d x %d\n", vdo->width, + vdo->height); + + zprintf(2, "enumerating supported formats:\n"); + struct v4l2_fmtdesc desc; + memset(&desc, 0, sizeof(desc)); + desc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + for (desc.index = 0; desc.index < V4L2_FORMATS_MAX; desc.index++) { + if (v4l2_ioctl(vdo->fd, VIDIOC_ENUM_FMT, &desc) < 0) + break; + zprintf(2, " [%d] %.4s : %s%s%s\n", desc.index, + (char *)&desc.pixelformat, desc.description, + (desc.flags & V4L2_FMT_FLAG_COMPRESSED) ? " COMPRESSED" : "", + (desc.flags & V4L2_FMT_FLAG_EMULATED) ? " EMULATED" : ""); + if (desc.flags & V4L2_FMT_FLAG_EMULATED) { + vdo->emu_formats = realloc(vdo->emu_formats, + (n_emu_formats + 2) * sizeof(uint32_t)); + vdo->emu_formats[n_emu_formats++] = desc.pixelformat; + } else { + vdo->formats = + realloc(vdo->formats, (n_formats + 2) * sizeof(uint32_t)); + vdo->formats[n_formats++] = desc.pixelformat; + } + + if (!vdo->width || !vdo->height) + v4l2_max_size(vdo, desc.pixelformat, &max_width, &max_height); + } + + if (!vdo->width || !vdo->height) { + zprintf(1, "Max supported size: %d x %d\n", max_width, max_height); + if (max_width && max_height) { + vdo->width = max_width; + vdo->height = max_height; + } else { + /* fallback to large size, driver reduces to max available */ + vdo->width = 640 * 64; + vdo->height = 480 * 64; + } + } + + if (!desc.index) + return (err_capture(vdo, SEV_ERROR, ZBAR_ERR_SYSTEM, __func__, + "enumerating video formats (VIDIOC_ENUM_FMT)")); + if (vdo->formats) + vdo->formats[n_formats] = 0; + if (vdo->emu_formats) + vdo->emu_formats[n_emu_formats] = 0; + if (!vdo->formats && vdo->emu_formats) { + /* + * If only emu formats are available, just move them to vdo->formats. + * This happens when libv4l detects that the only available fourcc + * formats are webcam proprietary formats or bayer formats. + */ + vdo->formats = vdo->emu_formats; + vdo->emu_formats = NULL; + } + + zprintf(2, "Found %d formats and %d emulated formats.\n", n_formats, + n_emu_formats); + + struct v4l2_format fmt; + struct v4l2_pix_format *pix = &fmt.fmt.pix; + memset(&fmt, 0, sizeof(fmt)); + fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (v4l2_ioctl(vdo->fd, VIDIOC_G_FMT, &fmt) < 0) + return (err_capture(vdo, SEV_ERROR, ZBAR_ERR_SYSTEM, __func__, + "querying current video format (VIDIO_G_FMT)")); + + zprintf(1, "current format: %.4s(%08x) %u x %u%s (line=0x%x size=0x%x)\n", + (char *)&pix->pixelformat, pix->pixelformat, pix->width, + pix->height, (pix->field != V4L2_FIELD_NONE) ? " INTERLACED" : "", + pix->bytesperline, pix->sizeimage); + + vdo->format = pix->pixelformat; + vdo->datalen = pix->sizeimage; + if (pix->width == vdo->width && pix->height == vdo->height) + return (0); + + struct v4l2_format maxfmt; + struct v4l2_pix_format *maxpix = &maxfmt.fmt.pix; + memcpy(&maxfmt, &fmt, sizeof(maxfmt)); + maxpix->width = vdo->width; + maxpix->height = vdo->height; + + zprintf(1, "setting requested size: %d x %d\n", vdo->width, vdo->height); + if (v4l2_ioctl(vdo->fd, VIDIOC_S_FMT, &maxfmt) < 0) { + zprintf(1, "set FAILED...trying to recover original format\n"); + /* ignore errors (driver broken anyway) */ + v4l2_ioctl(vdo->fd, VIDIOC_S_FMT, &fmt); + } + + memset(&fmt, 0, sizeof(fmt)); + fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (v4l2_ioctl(vdo->fd, VIDIOC_G_FMT, &fmt) < 0) + return (err_capture(vdo, SEV_ERROR, ZBAR_ERR_SYSTEM, __func__, + "querying current video format (VIDIOC_G_FMT)")); + + zprintf(1, "final format: %.4s(%08x) %u x %u%s (line=0x%x size=0x%x)\n", + (char *)&pix->pixelformat, pix->pixelformat, pix->width, + pix->height, (pix->field != V4L2_FIELD_NONE) ? " INTERLACED" : "", + pix->bytesperline, pix->sizeimage); + + vdo->width = pix->width; + vdo->height = pix->height; + vdo->datalen = pix->sizeimage; + return (0); +} + +static inline int v4l2_reset_crop(zbar_video_t *vdo) +{ + /* check cropping */ + struct v4l2_cropcap ccap; + memset(&ccap, 0, sizeof(ccap)); + ccap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (v4l2_ioctl(vdo->fd, VIDIOC_CROPCAP, &ccap) < 0) + return (err_capture(vdo, SEV_ERROR, ZBAR_ERR_SYSTEM, __func__, + "querying crop support (VIDIOC_CROPCAP)")); + + zprintf(1, "crop bounds: %d x %d @ (%d, %d)\n", ccap.bounds.width, + ccap.bounds.height, ccap.bounds.left, ccap.bounds.top); + zprintf(1, "current crop win: %d x %d @ (%d, %d) aspect %d / %d\n", + ccap.defrect.width, ccap.defrect.height, ccap.defrect.left, + ccap.defrect.top, ccap.pixelaspect.numerator, + ccap.pixelaspect.denominator); + +#if 0 + // This logic causes the device to fallback to the current resolution + if(!vdo->width || !vdo->height) { + vdo->width = ccap.defrect.width; + vdo->height = ccap.defrect.height; + } +#endif + + /* reset crop parameters */ + struct v4l2_crop crop; + memset(&crop, 0, sizeof(crop)); + crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + crop.c = ccap.defrect; + if (v4l2_ioctl(vdo->fd, VIDIOC_S_CROP, &crop) < 0 && errno != EINVAL && + errno != ENOTTY) + return (err_capture(vdo, SEV_ERROR, ZBAR_ERR_SYSTEM, __func__, + "setting default crop window (VIDIOC_S_CROP)")); + return (0); +} + +/** locate a control entry + */ +static struct video_controls_priv_s *v4l2_g_control_def(zbar_video_t *vdo, + const char *name) +{ + struct video_controls_priv_s *p = (void *)vdo->controls; + + while (p) { + if (!strcasecmp(p->s.name, name)) + break; + p = p->s.next; + } + + if (!p->s.name) { + zprintf(1, "Control not found: %s", name); + return NULL; + } + + return p; +} + +void v4l2_free_controls(zbar_video_t *vdo) +{ + int i; + + if (vdo->controls) { + struct video_controls_s *p = vdo->controls; + while (p) { + free(p->name); + free(p->group); + if (p->menu) { + for (i = 0; i < p->menu_size; i++) + free(p->menu[i].name); + free(p->menu); + } + p = p->next; + } + free(vdo->controls); + } + vdo->controls = NULL; +} + +#ifdef VIDIOC_QUERY_EXT_CTRL +static const char *v4l2_ctrl_type(uint32_t type) +{ + switch (type) { + // All controls below are available since, at least, Kernel 2.6.31 + case V4L2_CTRL_TYPE_INTEGER: + return "int"; + case V4L2_CTRL_TYPE_BOOLEAN: + return "bool"; + case V4L2_CTRL_TYPE_MENU: + return "menu"; + case V4L2_CTRL_TYPE_BUTTON: + return "button"; + case V4L2_CTRL_TYPE_INTEGER64: + return "int64"; + case V4L2_CTRL_TYPE_CTRL_CLASS: + return "ctrl class"; + case V4L2_CTRL_TYPE_STRING: + return "string"; +#ifdef V4L2_CTRL_TYPE_INTEGER_MENU + case V4L2_CTRL_TYPE_INTEGER_MENU: + return "int menu"; +#endif +#ifdef V4L2_CTRL_TYPE_U32 + // Newer controls. All of them should be there since Kernel 3.16 + case V4L2_CTRL_TYPE_BITMASK: + return "bitmask"; + case V4L2_CTRL_TYPE_U8: + return "compound u8"; + case V4L2_CTRL_TYPE_U16: + return "compound u16"; + case V4L2_CTRL_TYPE_U32: + return "compound 32"; +#endif + default: + return "unknown"; + } +} + +static const char *v4l2_ctrl_class(uint32_t class) +{ + switch (class) { + // All classes below are available since, at least, Kernel 2.6.31 + case V4L2_CTRL_CLASS_USER: + return "User"; + case V4L2_CTRL_CLASS_MPEG: + return "MPEG-compression"; + case V4L2_CTRL_CLASS_CAMERA: + return "Camera"; + case V4L2_CTRL_CLASS_FM_TX: + return "FM Modulator"; +#ifdef V4L2_CTRL_CLASS_DETECT + // Newer classes added up to Kernel 3.16 + case V4L2_CTRL_CLASS_FLASH: + return "Camera flash"; + case V4L2_CTRL_CLASS_JPEG: + return "JPEG-compression"; + case V4L2_CTRL_CLASS_IMAGE_SOURCE: + return "Image source"; + case V4L2_CTRL_CLASS_IMAGE_PROC: + return "Image processing"; + case V4L2_CTRL_CLASS_DV: + return "Digital Video"; + case V4L2_CTRL_CLASS_FM_RX: + return "FM Receiver"; + case V4L2_CTRL_CLASS_RF_TUNER: + return "RF tuner"; + case V4L2_CTRL_CLASS_DETECT: + return "Detection"; +#endif + default: + return "Unknown"; + } +} + +// return values: 1: ignore, 0: added, -1: silently ignore +static int v4l2_add_control(zbar_video_t *vdo, + struct v4l2_query_ext_ctrl *query, + struct video_controls_priv_s **ptr) +{ + // Control is disabled, ignore it. Please notice that disabled controls + // can be re-enabled. The right thing here would be to get those too, + // and add a logic to + if (query->flags & V4L2_CTRL_FLAG_DISABLED) + return 1; + + /* Silently ignore control classes */ + if (query->type == V4L2_CTRL_TYPE_CTRL_CLASS) + return -1; + + // There's not much sense on displaying permanent read-only controls + if (query->flags & V4L2_CTRL_FLAG_READ_ONLY) + return 1; + + // Allocate a new element on the linked list + if (!vdo->controls) { + *ptr = calloc(1, sizeof(**ptr)); + vdo->controls = (void *)*ptr; + } else { + (*ptr)->s.next = calloc(1, sizeof(**ptr)); + *ptr = (*ptr)->s.next; + } + + // Fill control data + (*ptr)->id = query->id; + (*ptr)->s.name = strdup((const char *)query->name); + (*ptr)->s.group = strdup(v4l2_ctrl_class(V4L2_CTRL_ID2CLASS(query->id))); + switch (query->type) { + case V4L2_CTRL_TYPE_INTEGER: + (*ptr)->s.type = VIDEO_CNTL_INTEGER; + (*ptr)->s.min = query->minimum; + (*ptr)->s.max = query->maximum; + (*ptr)->s.def = query->default_value; + (*ptr)->s.step = query->step; + return (0); + case V4L2_CTRL_TYPE_INTEGER64: + (*ptr)->s.type = VIDEO_CNTL_INTEGER64; + (*ptr)->s.min = query->minimum; + (*ptr)->s.max = query->maximum; + (*ptr)->s.def = query->default_value; + (*ptr)->s.step = query->step; + return (0); + case V4L2_CTRL_TYPE_BOOLEAN: + (*ptr)->s.type = VIDEO_CNTL_BOOLEAN; + return (0); + case V4L2_CTRL_TYPE_BUTTON: + (*ptr)->s.type = VIDEO_CNTL_BUTTON; + return (0); + case V4L2_CTRL_TYPE_STRING: + (*ptr)->s.type = VIDEO_CNTL_STRING; + return (0); +#ifdef V4L2_CTRL_TYPE_INTEGER_MENU + case V4L2_CTRL_TYPE_INTEGER_MENU: +#endif + case V4L2_CTRL_TYPE_MENU: { + struct v4l2_querymenu menu; + struct video_control_menu_s *first = NULL, *p; + int n_menu = 0; + + memset(&menu, 0, sizeof(menu)); + menu.id = query->id; + + for (menu.index = query->minimum; menu.index <= query->maximum; + menu.index++) { + if (!ioctl(vdo->fd, VIDIOC_QUERYMENU, &menu)) { + first = realloc(first, (n_menu + 1) * sizeof(*(*ptr)->s.menu)); + + p = &first[n_menu]; + p->value = menu.index; + +#ifdef V4L2_CTRL_TYPE_INTEGER_MENU + if (query->type == V4L2_CTRL_TYPE_INTEGER_MENU) + asprintf(p->name, "%i", menu.value); + else +#endif /* V4L2_CTRL_TYPE_INTEGER_MENU */ + p->name = strdup((const char *)menu.name); + + n_menu++; + } + } + (*ptr)->s.menu = first; + (*ptr)->s.menu_size = n_menu; + (*ptr)->s.min = query->minimum; + (*ptr)->s.max = query->maximum; + (*ptr)->s.def = query->default_value; + (*ptr)->s.type = VIDEO_CNTL_MENU; + return (0); + } + default: + return (1); + } +} + +static int v4l2_query_controls(zbar_video_t *vdo) +{ + struct video_controls_priv_s *ptr = NULL; + struct v4l2_query_ext_ctrl query; + int ignore; + const char *old_class = NULL; + + // Free controls list if not NULL + v4l2_free_controls(vdo); + + memset(&query, 0, sizeof(query)); + query.id = V4L2_CTRL_FLAG_NEXT_CTRL; + while (!v4l2_ioctl(vdo->fd, VIDIOC_QUERY_EXT_CTRL, &query)) { + ignore = v4l2_add_control(vdo, &query, &ptr); + + if (ignore >= 0 && _zbar_verbosity) { + int i; + const char *class = v4l2_ctrl_class(V4L2_CTRL_ID2CLASS(query.id)); + if (class != old_class) + zprintf(1, "Control class %s:\n", class); + + zprintf(1, "%-10s %-32s - 0x%x%s\n", v4l2_ctrl_type(query.type), + query.name, query.id, ignore ? " - Ignored" : ""); + + for (i = 0; i < ptr->s.menu_size; i++) + zprintf(1, " %" PRId64 ": %s\n", ptr->s.menu[i].value, + ptr->s.menu[i].name); + + old_class = class; + } + + query.id |= V4L2_CTRL_FLAG_NEXT_CTRL; + } + + return (0); +} + +static int v4l2_s_control(zbar_video_t *vdo, const char *name, void *value) +{ + struct v4l2_ext_controls ctrls; + struct v4l2_ext_control c; + struct video_controls_priv_s *p; + + p = v4l2_g_control_def(vdo, name); + if (!p) + return ZBAR_ERR_UNSUPPORTED; // we have no such a control on the list + + memset(&ctrls, 0, sizeof(ctrls)); + ctrls.count = 1; +#ifdef V4L2_CTRL_ID2WHICH + ctrls.which = V4L2_CTRL_ID2WHICH(p->id); +#else + ctrls.ctrl_class = V4L2_CTRL_ID2CLASS(p->id); +#endif + ctrls.controls = &c; + + memset(&c, 0, sizeof(c)); + c.id = p->id; + + switch (p->s.type) { + case VIDEO_CNTL_INTEGER: + case VIDEO_CNTL_BOOLEAN: + case VIDEO_CNTL_BUTTON: + case VIDEO_CNTL_MENU: + c.value = *(int *)value; + break; +#if 0 + //FIXME: Need to check callers with respect bufffer size + case VIDEO_CNTL_INTEGER64: + c.value64 = *(int64_t *)value; + break; +#endif + default: + return ZBAR_ERR_UNSUPPORTED; + } + + int rv = v4l2_ioctl(vdo->fd, VIDIOC_S_EXT_CTRLS, &ctrls); + if (rv) { + zprintf(1, "v4l2 set user control \"%s\" returned %d\n", p->s.name, rv); + rv = ZBAR_ERR_INVALID; + } + zprintf(1, "%-32s id: 0x%x set to value %d\n", name, p->id, *(int *)value); + + return 0; +} + +static int v4l2_g_control(zbar_video_t *vdo, const char *name, void *value) +{ + struct v4l2_ext_controls ctrls; + struct v4l2_ext_control c; + struct video_controls_priv_s *p; + + p = v4l2_g_control_def(vdo, name); + if (!p) + return ZBAR_ERR_UNSUPPORTED; // we have no such a control on the list + + memset(&ctrls, 0, sizeof(ctrls)); + ctrls.count = 1; +#ifdef V4L2_CTRL_ID2WHICH + ctrls.which = V4L2_CTRL_ID2WHICH(p->id); +#else + ctrls.ctrl_class = V4L2_CTRL_ID2CLASS(p->id); +#endif + ctrls.controls = &c; + + memset(&c, 0, sizeof(c)); + c.id = p->id; + + int rv = v4l2_ioctl(vdo->fd, VIDIOC_G_EXT_CTRLS, &ctrls); + if (rv) { + zprintf(1, "v4l2 get user control \"%s\" returned %d\n", p->s.name, rv); + return ZBAR_ERR_UNSUPPORTED; + } + + switch (p->s.type) { + case VIDEO_CNTL_INTEGER: + case VIDEO_CNTL_BOOLEAN: + case VIDEO_CNTL_BUTTON: + case VIDEO_CNTL_MENU: + *(int *)value = c.value; + zprintf(1, "v4l2 get user control \"%s\" = %d\n", p->s.name, c.value); + return (0); +#if 0 + //FIXME: Need to check callers with respect bufffer size + case VIDEO_CNTL_INTEGER64: + *(int64_t *)value = c.value64; + return(0); +#endif + default: + return ZBAR_ERR_UNSUPPORTED; + } +} + +#else /* For very old Kernels < 3.16 (2014) */ +static void v4l2_add_control(zbar_video_t *vdo, char *group_name, + struct v4l2_queryctrl *query, + struct video_controls_priv_s **ptr) +{ + // Control is disabled, ignore it. Please notice that disabled controls + // can be re-enabled. The right thing here would be to get those too, + // and add a logic to + if (query->flags & V4L2_CTRL_FLAG_DISABLED) + return; + + // FIXME: add support also for other control types + if (!((query->type == V4L2_CTRL_TYPE_INTEGER) || + (query->type == V4L2_CTRL_TYPE_BOOLEAN))) + return; + + zprintf(1, "%s %s ctrl %-32s id: 0x%x\n", group_name, + (query->type == V4L2_CTRL_TYPE_INTEGER) ? "int " : "bool", + query->name, query->id); + + // Allocate a new element on the linked list + if (!vdo->controls) { + *ptr = calloc(1, sizeof(**ptr)); + vdo->controls = (void *)*ptr; + } else { + (*ptr)->s.next = calloc(1, sizeof(**ptr)); + *ptr = (*ptr)->s.next; + } + + // Fill control data + (*ptr)->id = query->id; + (*ptr)->s.name = strdup((const char *)query->name); + if (query->type == V4L2_CTRL_TYPE_INTEGER) { + (*ptr)->s.type = VIDEO_CNTL_INTEGER; + (*ptr)->s.min = query->minimum; + (*ptr)->s.max = query->maximum; + (*ptr)->s.def = query->default_value; + (*ptr)->s.step = query->step; + } else { + (*ptr)->s.type = VIDEO_CNTL_BOOLEAN; + } +} + +static int v4l2_query_controls(zbar_video_t *vdo) +{ + int id = 0; + struct video_controls_priv_s *ptr; + struct v4l2_queryctrl query; + + // Free controls list if not NULL + ptr = (void *)vdo->controls; + while (ptr) { + free(ptr->s.name); + ptr = ptr->s.next; + } + free(vdo->controls); + vdo->controls = NULL; + ptr = NULL; + + id = 0; + do { + query.id = id | V4L2_CTRL_FLAG_NEXT_CTRL; + if (v4l2_ioctl(vdo->fd, VIDIOC_QUERYCTRL, &query)) + break; + + v4l2_add_control(vdo, "extended", &query, &ptr); + id = query.id; + } while (1); + + id = V4L2_CID_PRIVATE_BASE; + do { + query.id = id; + if (v4l2_ioctl(vdo->fd, VIDIOC_QUERYCTRL, &query)) + break; + v4l2_add_control(vdo, "private", &query, &ptr); + id = query.id; + } while (1); + + return (0); +} + +static int v4l2_s_control(zbar_video_t *vdo, const char *name, void *value) +{ + struct v4l2_control cs; + struct video_controls_priv_s *p; + + p = v4l2_g_control_def(vdo, name); + if (!p) + return ZBAR_ERR_UNSUPPORTED; // we have no such a control on the list + + zprintf(1, "%-32s id: 0x%x set to value %d\n", name, p->id, *(int *)value); + + // FIXME: add support for VIDIOC_S_EXT_CTRL + memset(&cs, 0, sizeof(cs)); + cs.id = p->id; + cs.value = *(int *)value; + int rv = v4l2_ioctl(vdo->fd, VIDIOC_S_CTRL, &cs); + if (rv != 0) { + zprintf(1, "v4l2 set user control \"%s\" returned %d\n", p->s.name, rv); + rv = ZBAR_ERR_INVALID; + } + return rv; +} + +static int v4l2_g_control(zbar_video_t *vdo, const char *name, void *value) +{ + struct v4l2_control cs; + struct video_controls_priv_s *p; + + p = v4l2_g_control_def(vdo, name); + if (!p) + return ZBAR_ERR_UNSUPPORTED; // we have no such a control on the list + + memset(&cs, 0, sizeof(cs)); + + cs.id = p->id; + cs.value = *(int *)value; + int rv = v4l2_ioctl(vdo->fd, VIDIOC_G_CTRL, &cs); + *(int *)value = cs.value; + if (rv != 0) { + zprintf(1, "v4l2 get user control \"%s\" returned %d\n", p->s.name, rv); + rv = ZBAR_ERR_UNSUPPORTED; + } + return rv; +} +#endif /* VIDIOC_QUERY_EXT_CTRL */ + +static int v4l2_sort_resolutions(const void *__a, const void *__b) +{ + const struct video_resolution_s *a = __a; + const struct video_resolution_s *b = __b; + int r; + + r = (int)b->width - a->width; + if (!r) + r = (int)b->height - a->height; + + return r; +} + +static float v4l2_get_max_fps_discrete(zbar_video_t *vdo, + struct v4l2_frmsizeenum *frmsize) +{ + struct v4l2_frmivalenum frmival = { 0 }; + float fps, max_fps = -1; + + frmival.width = frmsize->discrete.width; + frmival.height = frmsize->discrete.height; + frmival.pixel_format = frmsize->pixel_format; + frmival.index = 0; + + for (frmival.index = 0; + !v4l2_ioctl(vdo->fd, VIDIOC_ENUM_FRAMEINTERVALS, &frmival); + frmival.index++) { + fps = + ((float)frmival.discrete.denominator) / frmival.discrete.numerator; + if (fps > max_fps) + max_fps = fps; + } + return max_fps; +} + +static void v4l2_insert_resolution(zbar_video_t *vdo, unsigned int *n_res, + unsigned int width, unsigned int height, + float max_fps) +{ + unsigned int i; + + for (i = 0; i < *n_res; i++) { + if (vdo->res[i].width == width && vdo->res[i].height == height) + return; + } + + vdo->res = + realloc(vdo->res, (*n_res + 1) * sizeof(struct video_resolution_s)); + + vdo->res[*n_res].width = width; + vdo->res[*n_res].height = height; + vdo->res[*n_res].max_fps = max_fps; + + (*n_res)++; +} + +static int v4l2_get_supported_resolutions(zbar_video_t *vdo) +{ + struct v4l2_fmtdesc fmt = { 0 }; + struct v4l2_frmsizeenum frmsize = { 0 }; + int i; + unsigned int width, height, n_res = 0; + + vdo->res = NULL; + + fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + for (fmt.index = 0; !v4l2_ioctl(vdo->fd, VIDIOC_ENUM_FMT, &fmt); + fmt.index++) { + if (vdo->format != fmt.pixelformat) + continue; + + frmsize.pixel_format = fmt.pixelformat; + frmsize.index = 0; + + while (!v4l2_ioctl(vdo->fd, VIDIOC_ENUM_FRAMESIZES, &frmsize)) { + if (frmsize.type == V4L2_FRMSIZE_TYPE_DISCRETE) { + v4l2_insert_resolution(vdo, &n_res, frmsize.discrete.width, + frmsize.discrete.height, + v4l2_get_max_fps_discrete(vdo, + &frmsize)); + } else if (frmsize.type == V4L2_FRMSIZE_TYPE_STEPWISE) { + for (i = 0; i <= 4; i++) { + width = frmsize.stepwise.min_width + + i * + (frmsize.stepwise.max_width - + frmsize.stepwise.min_width) / + 4; + height = frmsize.stepwise.min_height + + i * + (frmsize.stepwise.max_height - + frmsize.stepwise.min_height) / + 4; + v4l2_insert_resolution(vdo, &n_res, width, height, -1); + } + } + frmsize.index++; + } + } + qsort(vdo->res, n_res, sizeof(struct video_resolution_s), + v4l2_sort_resolutions); + + for (i = 0; i < n_res; i++) { + zprintf(1, "%dx%d (%0.2f fps)\n", vdo->res[i].width, vdo->res[i].height, + vdo->res[i].max_fps); + } + + /* Make the list zero-terminated */ + v4l2_insert_resolution(vdo, &n_res, 0, 0, 0); + + return 0; +} + +int _zbar_v4l2_probe(zbar_video_t *vdo) +{ + /* check capabilities */ + struct v4l2_capability vcap; + memset(&vcap, 0, sizeof(vcap)); + if (v4l2_ioctl(vdo->fd, VIDIOC_QUERYCAP, &vcap) < 0) + return (err_capture( + vdo, SEV_WARNING, ZBAR_ERR_UNSUPPORTED, __func__, + "video4linux version 2 not supported (VIDIOC_QUERYCAP)")); + + zprintf(1, "%.32s on %.32s driver %.16s (version %u.%u.%u)\n", vcap.card, + (vcap.bus_info[0]) ? (char *)vcap.bus_info : "<unknown>", + vcap.driver, (vcap.version >> 16) & 0xff, + (vcap.version >> 8) & 0xff, vcap.version & 0xff); + zprintf(1, " capabilities:%s%s%s%s\n", + (vcap.device_caps & V4L2_CAP_VIDEO_CAPTURE) ? " CAPTURE" : "", + (vcap.device_caps & V4L2_CAP_VIDEO_OVERLAY) ? " OVERLAY" : "", + (vcap.device_caps & V4L2_CAP_READWRITE) ? " READWRITE" : "", + (vcap.device_caps & V4L2_CAP_STREAMING) ? " STREAMING" : ""); + + if (!(vcap.device_caps & V4L2_CAP_VIDEO_CAPTURE) || + !(vcap.device_caps & (V4L2_CAP_READWRITE | V4L2_CAP_STREAMING))) + return (err_capture(vdo, SEV_WARNING, ZBAR_ERR_UNSUPPORTED, __func__, + "v4l2 device does not support usable CAPTURE")); + + if (v4l2_reset_crop(vdo)) + /* ignoring errors (driver cropping support questionable) */; + + if (v4l2_probe_formats(vdo)) + return (-1); + + if (v4l2_query_controls(vdo)) + return (-1); + + if (v4l2_get_supported_resolutions(vdo)) + return (-1); + + /* FIXME report error and fallback to readwrite? (if supported...) */ + if (vdo->iomode != VIDEO_READWRITE && + (vcap.device_caps & V4L2_CAP_STREAMING) && v4l2_probe_iomode(vdo)) + return (-1); + if (!vdo->iomode) + vdo->iomode = VIDEO_READWRITE; + + zprintf(1, "using I/O mode: %s\n", + (vdo->iomode == VIDEO_READWRITE) ? "READWRITE" : + (vdo->iomode == VIDEO_MMAP) ? "MMAP" : + (vdo->iomode == VIDEO_USERPTR) ? "USERPTR" : + "<UNKNOWN>"); + + vdo->intf = VIDEO_V4L2; + vdo->init = v4l2_init; + vdo->cleanup = v4l2_cleanup; + vdo->start = v4l2_start; + vdo->stop = v4l2_stop; + vdo->nq = v4l2_nq; + vdo->dq = v4l2_dq; + vdo->set_control = v4l2_s_control; + vdo->get_control = v4l2_g_control; + vdo->free = v4l2_free_controls; + return (0); +} |