summaryrefslogtreecommitdiffstats
path: root/zbar/video/v4l1.c
diff options
context:
space:
mode:
Diffstat (limited to 'zbar/video/v4l1.c')
-rw-r--r--zbar/video/v4l1.c404
1 files changed, 404 insertions, 0 deletions
diff --git a/zbar/video/v4l1.c b/zbar/video/v4l1.c
new file mode 100644
index 0000000..cb0b9c1
--- /dev/null
+++ b/zbar/video/v4l1.c
@@ -0,0 +1,404 @@
+/*------------------------------------------------------------------------
+ * Copyright 2007-2011 (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 <string.h>
+#ifdef HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+#ifdef HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#ifdef HAVE_SYS_IOCTL_H
+#include <sys/ioctl.h>
+#endif
+#ifdef HAVE_SYS_MMAN_H
+#include <sys/mman.h>
+#endif
+#include <linux/videodev.h>
+
+#include "image.h"
+#include "video.h"
+
+typedef struct v4l1_format_s {
+ uint32_t format;
+ uint8_t bpp;
+} v4l1_format_t;
+
+/* static v4l1 "palette" mappings
+ * documentation for v4l1 formats is terrible...
+ */
+static const v4l1_format_t v4l1_formats[17] = {
+ /* format bpp */
+ { 0, 0 },
+ { fourcc('G', 'R', 'E', 'Y'), 8 }, /* GREY */
+ { fourcc('H', 'I', '2', '4'), 8 }, /* HI240 (BT848) */
+
+ /* component ordering for RGB palettes is unspecified,
+ * convention appears to place red in the most significant bits
+ * FIXME is this true for other drivers? big endian machines?
+ */
+ { fourcc('R', 'G', 'B', 'P'), 16 }, /* RGB565 */
+ { fourcc('B', 'G', 'R', '3'), 24 }, /* RGB24 */
+ { fourcc('B', 'G', 'R', '4'), 32 }, /* RGB32 */
+ { fourcc('R', 'G', 'B', 'O'), 16 }, /* RGB555 */
+ { fourcc('Y', 'U', 'Y', '2'), 16 }, /* YUV422 (8 bpp?!) */
+ { fourcc('Y', 'U', 'Y', 'V'), 16 }, /* YUYV */
+ { fourcc('U', 'Y', 'V', 'Y'), 16 }, /* UYVY */
+ { 0, 12 }, /* YUV420 (24 bpp?) FIXME?! */
+ { fourcc('Y', '4', '1', 'P'), 12 }, /* YUV411 */
+ { 0, 0 }, /* Bt848 raw */
+ { fourcc('4', '2', '2', 'P'), 16 }, /* YUV422P (24 bpp?) */
+ { fourcc('4', '1', '1', 'P'), 12 }, /* YUV411P */
+ { fourcc('Y', 'U', '1', '2'), 12 }, /* YUV420P */
+ { fourcc('Y', 'U', 'V', '9'), 9 }, /* YUV410P */
+};
+
+static int v4l1_nq(zbar_video_t *vdo, zbar_image_t *img)
+{
+ if (video_nq_image(vdo, img))
+ return (-1);
+
+ if (vdo->iomode != VIDEO_MMAP)
+ return (0);
+
+ struct video_mmap vmap;
+ vmap.frame = img->srcidx;
+ vmap.width = vdo->width;
+ vmap.height = vdo->height;
+ vmap.format = vdo->palette;
+ if (ioctl(vdo->fd, VIDIOCMCAPTURE, &vmap) < 0)
+ return (err_capture(vdo, SEV_ERROR, ZBAR_ERR_SYSTEM, __func__,
+ "initiating video capture (VIDIOCMCAPTURE)"));
+
+ return (0);
+}
+
+static zbar_image_t *v4l1_dq(zbar_video_t *vdo)
+{
+ video_iomode_t iomode = vdo->iomode;
+ int fd = vdo->fd;
+ zbar_image_t *img = video_dq_image(vdo);
+ if (!img)
+ return (NULL);
+
+ if (iomode == VIDEO_MMAP) {
+ int frame = img->srcidx;
+ if (ioctl(fd, VIDIOCSYNC, &frame) < 0)
+ return (NULL);
+ } else if (read(fd, (void *)img->data, img->datalen) != img->datalen)
+ return (NULL);
+
+ return (img);
+}
+
+static int v4l1_mmap_buffers(zbar_video_t *vdo)
+{
+#ifdef HAVE_SYS_MMAN_H
+ /* map camera image to memory */
+ struct video_mbuf vbuf;
+ memset(&vbuf, 0, sizeof(vbuf));
+ if (ioctl(vdo->fd, VIDIOCGMBUF, &vbuf) < 0)
+ return (err_capture(vdo, SEV_ERROR, ZBAR_ERR_SYSTEM, __func__,
+ "querying video frame buffers (VIDIOCGMBUF)"));
+ assert(vbuf.frames && vbuf.size);
+
+ zprintf(1, "mapping %d buffers size=0x%x\n", vbuf.frames, vbuf.size);
+ vdo->buflen = vbuf.size;
+ vdo->buf =
+ mmap(0, vbuf.size, PROT_READ | PROT_WRITE, MAP_SHARED, vdo->fd, 0);
+ if (vdo->buf == MAP_FAILED)
+ return (err_capture(vdo, SEV_ERROR, ZBAR_ERR_SYSTEM, __func__,
+ "mapping video frame buffers"));
+
+ int i;
+ for (i = 0; i < vbuf.frames; i++) {
+ zbar_image_t *img = vdo->images[i];
+ zprintf(2, " [%02d] @%08x\n", img->srcidx, vbuf.offsets[i]);
+ img->data = vdo->buf + vbuf.offsets[i];
+ img->datalen = vdo->datalen;
+ int next_offset =
+ ((i + 1 < vdo->num_images) ? vbuf.offsets[i + 1] : vbuf.size);
+ if (next_offset < vbuf.offsets[i] + vdo->datalen)
+ fprintf(stderr,
+ "WARNING: insufficient v4l1 video buffer size:\n"
+ "\tvbuf[%d]=%x vbuf[%d]=%x datalen=%lx\n"
+ "\timage=%d x %d %.4s(%08x) palette=%d\n",
+ i, vbuf.offsets[i], i + 1, next_offset, vdo->datalen,
+ vdo->width, vdo->height, (char *)&vdo->format, vdo->format,
+ vdo->palette);
+ }
+ return (0);
+#else
+ return (err_capture(vdo, SEV_ERROR, ZBAR_ERR_UNSUPPORTED, __func__,
+ "memory mapping not supported"));
+#endif
+}
+
+static int v4l1_start(zbar_video_t *vdo)
+{
+ return (0);
+}
+
+static int v4l1_stop(zbar_video_t *vdo)
+{
+ return (0);
+}
+
+static inline int v4l1_set_format(zbar_video_t *vdo, uint32_t fmt)
+{
+ struct video_picture vpic;
+ memset(&vpic, 0, sizeof(vpic));
+ if (ioctl(vdo->fd, VIDIOCGPICT, &vpic) < 0)
+ return (err_capture(vdo, SEV_ERROR, ZBAR_ERR_SYSTEM, __func__,
+ "querying video format (VIDIOCGPICT)"));
+
+ vdo->palette = 0;
+ int ifmt;
+ for (ifmt = 1; ifmt <= VIDEO_PALETTE_YUV410P; ifmt++)
+ if (v4l1_formats[ifmt].format == fmt)
+ break;
+ if (!fmt || ifmt >= VIDEO_PALETTE_YUV410P)
+ return (err_capture_int(vdo, SEV_ERROR, ZBAR_ERR_INVALID, __func__,
+ "invalid v4l1 format: %x", fmt));
+
+ vpic.palette = ifmt;
+ vpic.depth = v4l1_formats[ifmt].bpp;
+ if (ioctl(vdo->fd, VIDIOCSPICT, &vpic) < 0)
+ return (err_capture(vdo, SEV_ERROR, ZBAR_ERR_SYSTEM, __func__,
+ "setting format (VIDIOCSPICT)"));
+
+ memset(&vpic, 0, sizeof(vpic));
+ if (ioctl(vdo->fd, VIDIOCGPICT, &vpic) < 0)
+ return (err_capture(vdo, SEV_ERROR, ZBAR_ERR_SYSTEM, __func__,
+ "querying video format (VIDIOCGPICT)"));
+
+ if (vpic.palette != ifmt || vpic.depth != v4l1_formats[ifmt].bpp) {
+ fprintf(stderr,
+ "WARNING: set v4l1 palette %d which should have depth %d bpp\n"
+ " but probed palette %d with depth %d bpp?"
+ " ...continuing anyway\n",
+ ifmt, v4l1_formats[ifmt].bpp, vpic.palette, vpic.depth);
+ err_capture_int(vdo, SEV_WARNING, ZBAR_ERR_INVALID, __func__,
+ "driver format (%x) inconsistency", fmt);
+ }
+ vdo->format = fmt;
+ vdo->palette = ifmt;
+ vdo->datalen = (vdo->width * vdo->height * v4l1_formats[ifmt].bpp + 7) >> 3;
+
+ zprintf(1, "set new format: %.4s(%08x) depth=%d palette=%d size=0x%lx\n",
+ (char *)&vdo->format, vdo->format, vpic.depth, vdo->palette,
+ vdo->datalen);
+ return (0);
+}
+
+static int v4l1_init(zbar_video_t *vdo, uint32_t fmt)
+{
+ if (v4l1_set_format(vdo, fmt))
+ return (-1);
+ if (vdo->iomode == VIDEO_MMAP && v4l1_mmap_buffers(vdo))
+ return (-1);
+ return (0);
+}
+
+static int v4l1_cleanup(zbar_video_t *vdo)
+{
+#ifdef HAVE_SYS_MMAN_H
+ /* FIXME should avoid holding onto mmap'd buffers so long? */
+ if (vdo->iomode == VIDEO_MMAP && vdo->buf) {
+ if (munmap(vdo->buf, vdo->buflen))
+ return (err_capture(vdo, SEV_ERROR, ZBAR_ERR_SYSTEM, __func__,
+ "unmapping video frame buffers"));
+ vdo->buf = NULL;
+ /* FIXME reset image */
+ }
+#endif
+
+ /* close open device */
+ if (vdo->fd >= 0) {
+ close(vdo->fd);
+ vdo->fd = -1;
+ }
+ return (0);
+}
+
+static int v4l1_probe_iomode(zbar_video_t *vdo)
+{
+ vdo->iomode = VIDEO_READWRITE;
+#ifdef HAVE_SYS_MMAN_H
+ struct video_mbuf vbuf;
+ memset(&vbuf, 0, sizeof(vbuf));
+ if (ioctl(vdo->fd, VIDIOCGMBUF, &vbuf) < 0) {
+ if (errno != EINVAL)
+ return (err_capture(vdo, SEV_ERROR, ZBAR_ERR_SYSTEM, __func__,
+ "querying video frame buffers (VIDIOCGMBUF)"));
+ /* not supported */
+ return (0);
+ }
+ if (!vbuf.frames || !vbuf.size)
+ return (0);
+ vdo->iomode = VIDEO_MMAP;
+ if (vdo->num_images > vbuf.frames)
+ vdo->num_images = vbuf.frames;
+#endif
+ zprintf(1, "using %d images in %s mode\n", vdo->num_images,
+ (vdo->iomode == VIDEO_READWRITE) ? "READ" : "MMAP");
+ return (0);
+}
+
+static inline int v4l1_probe_formats(zbar_video_t *vdo)
+{
+ struct video_picture vpic;
+ memset(&vpic, 0, sizeof(vpic));
+ if (ioctl(vdo->fd, VIDIOCGPICT, &vpic) < 0)
+ return (err_capture(vdo, SEV_ERROR, ZBAR_ERR_SYSTEM, __func__,
+ "querying format (VIDIOCGPICT)"));
+
+ vdo->format = 0;
+ if (vpic.palette <= VIDEO_PALETTE_YUV410P)
+ vdo->format = v4l1_formats[vpic.palette].format;
+
+ zprintf(1, "current format: %.4s(%08x) depth=%d palette=%d\n",
+ (char *)&vdo->format, vdo->format, vpic.depth, vpic.palette);
+
+ vdo->formats = calloc(16, sizeof(uint32_t));
+ if (!vdo->formats)
+ return (err_capture(vdo, SEV_FATAL, ZBAR_ERR_NOMEM, __func__,
+ "allocating format list"));
+
+ int num_formats = 0;
+ zprintf(2, "probing supported formats:\n");
+ int i;
+ for (i = 1; i <= VIDEO_PALETTE_YUV410P; i++) {
+ if (!v4l1_formats[i].format)
+ continue;
+ vpic.depth = v4l1_formats[i].bpp;
+ vpic.palette = i;
+ if (ioctl(vdo->fd, VIDIOCSPICT, &vpic) < 0) {
+ zprintf(2, " [%02d] %.4s...no (set fails)\n", i,
+ (char *)&v4l1_formats[i].format);
+ continue;
+ }
+ if (ioctl(vdo->fd, VIDIOCGPICT, &vpic) < 0 || vpic.palette != i) {
+ zprintf(2, " [%02d] %.4s...no (set ignored)\n", i,
+ (char *)&v4l1_formats[i].format);
+ continue;
+ }
+ zprintf(2, " [%02d] %.4s...yes\n", i,
+ (char *)&v4l1_formats[i].format);
+ vdo->formats[num_formats++] = v4l1_formats[i].format;
+ }
+ vdo->formats = realloc(vdo->formats, (num_formats + 1) * sizeof(uint32_t));
+ assert(vdo->formats);
+
+ return (v4l1_set_format(vdo, vdo->format));
+}
+
+static inline int v4l1_init_window(zbar_video_t *vdo)
+{
+ struct video_window vwin;
+ memset(&vwin, 0, sizeof(vwin));
+ if (ioctl(vdo->fd, VIDIOCGWIN, &vwin) < 0)
+ return (err_capture(vdo, SEV_ERROR, ZBAR_ERR_SYSTEM, __func__,
+ "querying video window settings (VIDIOCGWIN)"));
+
+ zprintf(1, "current window: %d x %d @(%d, %d)%s\n", vwin.width, vwin.height,
+ vwin.x, vwin.y, (vwin.flags & 1) ? " INTERLACE" : "");
+
+ if (vwin.width == vdo->width && vwin.height == vdo->height)
+ /* max window already set */
+ return (0);
+
+ struct video_window maxwin;
+ memcpy(&maxwin, &vwin, sizeof(maxwin));
+ maxwin.width = vdo->width;
+ maxwin.height = vdo->height;
+
+ zprintf(1, "setting max win: %d x %d @(%d, %d)%s\n", maxwin.width,
+ maxwin.height, maxwin.x, maxwin.y,
+ (maxwin.flags & 1) ? " INTERLACE" : "");
+ if (ioctl(vdo->fd, VIDIOCSWIN, &maxwin) < 0) {
+ zprintf(1, "set FAILED...trying to recover original window\n");
+ /* ignore errors (driver broken anyway) */
+ ioctl(vdo->fd, VIDIOCSWIN, &vwin);
+ }
+
+ /* re-query resulting parameters */
+ memset(&vwin, 0, sizeof(vwin));
+ if (ioctl(vdo->fd, VIDIOCGWIN, &vwin) < 0)
+ return (err_capture(vdo, SEV_ERROR, ZBAR_ERR_SYSTEM, __func__,
+ "querying video window settings (VIDIOCGWIN)"));
+
+ zprintf(1, " final window: %d x %d @(%d, %d)%s\n", vwin.width,
+ vwin.height, vwin.x, vwin.y, (vwin.flags & 1) ? " INTERLACE" : "");
+ vdo->width = vwin.width;
+ vdo->height = vwin.height;
+ return (0);
+}
+
+int _zbar_v4l1_probe(zbar_video_t *vdo)
+{
+ /* check capabilities */
+ struct video_capability vcap;
+ memset(&vcap, 0, sizeof(vcap));
+ if (ioctl(vdo->fd, VIDIOCGCAP, &vcap) < 0)
+ return (
+ err_capture(vdo, SEV_ERROR, ZBAR_ERR_UNSUPPORTED, __func__,
+ "video4linux version 1 not supported (VIDIOCGCAP)"));
+
+ zprintf(1, "%s (%sCAPTURE) (%d x %d) - (%d x %d)\n", vcap.name,
+ (vcap.type & VID_TYPE_CAPTURE) ? "" : "*NO* ", vcap.minwidth,
+ vcap.minheight, vcap.maxwidth, vcap.maxheight);
+
+ if (!(vcap.type & VID_TYPE_CAPTURE))
+ return (err_capture(vdo, SEV_ERROR, ZBAR_ERR_UNSUPPORTED, __func__,
+ "v4l1 device does not support CAPTURE"));
+
+ if (!vdo->width || !vdo->height) {
+ vdo->width = vcap.maxwidth;
+ vdo->height = vcap.maxheight;
+ }
+
+ if (v4l1_init_window(vdo) || v4l1_probe_formats(vdo) ||
+ v4l1_probe_iomode(vdo))
+ return (-1);
+
+ vdo->intf = VIDEO_V4L1;
+ vdo->init = v4l1_init;
+ vdo->cleanup = v4l1_cleanup;
+ vdo->start = v4l1_start;
+ vdo->stop = v4l1_stop;
+ vdo->nq = v4l1_nq;
+ vdo->dq = v4l1_dq;
+ return (0);
+}