[gegl] workshop: add video4linux2 frame source



commit bbe87cc6e1f52e02f36e5c676d696359af52806f
Author: Øyvind Kolås <pippin gimp org>
Date:   Wed Nov 26 22:25:01 2014 +0000

    workshop: add video4linux2 frame source
    
    This should probably fully replace the v4l op, which relies on outdated linux
    kernel headers.

 operations/workshop/v4l2.c |  780 ++++++++++++++++++++++++++++++++++++++++++++
 po/POTFILES.in             |    1 +
 2 files changed, 781 insertions(+), 0 deletions(-)
---
diff --git a/operations/workshop/v4l2.c b/operations/workshop/v4l2.c
new file mode 100644
index 0000000..f577857
--- /dev/null
+++ b/operations/workshop/v4l2.c
@@ -0,0 +1,780 @@
+/* Video4Linux2 frame source op for GEGL
+ *
+ * GEGL is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * GEGL 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GEGL; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Copyright 2004-2008, 2014 Øyvind Kolås <pippin gimp org>
+ */
+
+#include "config.h"
+#include <glib/gi18n-lib.h>
+
+#ifdef GEGL_PROPERTIES
+
+property_file_path (path, _("Path"), "/dev/video0")
+     description (_("video device path"))
+property_int  (width,  _("Width"),  320)
+     description (_("Width for rendered image"))
+property_int  (height, _("Height"), 240)
+     description (_("Height for rendered image"))
+property_int  (frame,  _("Frame"),  0)
+     description (_("current frame number, can be changed to trigger a reload of the image."))
+property_int  (fps, _("FPS"),  0)
+     description (_("autotrigger reload this many times a second."))
+
+#else
+
+#define GEGL_OP_SOURCE
+#define GEGL_OP_C_FILE       "v4l2.c"
+
+#include "gegl-op.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include <getopt.h>             /* getopt_long() */
+
+#include <fcntl.h>              /* low-level i/o */
+#include <unistd.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/mman.h>
+#include <sys/ioctl.h>
+#include <linux/videodev2.h>
+
+struct buffer {
+  void  *start;
+  size_t length;
+};
+
+typedef struct
+{
+  gint           active;
+  gint           w;
+  gint           h;
+  gint           w_stored;
+  gint           h_stored;
+  gint           frame;
+
+  char          *dev_name;
+  int            use_mmap;
+  int            fd;
+  int            io;
+  struct buffer *buffers;
+  unsigned int   n_buffers;
+  int            out_buf;
+  int            force_format;
+  int            frame_count;
+} Priv;
+
+static void
+init (GeglProperties *o)
+{
+  Priv *p = (Priv*)o->user_data;
+
+  if (p==NULL)
+    {
+      p = g_new0 (Priv, 1);
+      o->user_data = (void*) p;
+    }
+
+  p->w = 320;
+  p->h = 240;
+}
+
+static GeglRectangle
+get_bounding_box (GeglOperation *operation)
+{
+  GeglRectangle result ={0,0,640,480};
+  GeglProperties *o = GEGL_PROPERTIES (operation);
+
+  result.width = o->width;
+  result.height = o->height;
+  return result;
+}
+
+#define CLEAR(x) memset(&(x), 0, sizeof(x))
+
+enum io_method {
+       IO_METHOD_MMAP,
+       IO_METHOD_READ,
+       IO_METHOD_USERPTR,
+};
+
+static void errno_exit(const char *s)
+{
+       fprintf(stderr, "%s error %d, %s\n", s, errno, strerror(errno));
+       exit(EXIT_FAILURE);
+}
+
+static int xioctl(int fh, unsigned long int request, void *arg)
+{
+       int r;
+
+       do {
+               r = ioctl(fh, request, arg);
+       } while (-1 == r && EINTR == errno);
+
+       return r;
+}
+
+static int read_frame(GeglOperation *operation, GeglBuffer *output)
+{
+  GeglProperties *o = GEGL_PROPERTIES (operation);
+  Priv *p= (Priv*)o->user_data;
+
+       struct v4l2_buffer buf;
+       unsigned int i;
+
+       switch (p->io) {
+       case IO_METHOD_READ:
+               if (-1 == read(p->fd, p->buffers[0].start, p->buffers[0].length)) {
+                       switch (errno) {
+                       case EAGAIN:
+                               return 0;
+
+                       case EIO:
+                               /* Could ignore EIO, see spec. */
+
+                               /* fall through */
+
+                       default:
+                               errno_exit("read");
+                       }
+               }
+
+               //process_image(priv, p->buffers[0].start, p->buffers[0].length);
+    gegl_buffer_set (output, NULL, 0, NULL, p->buffers[0].start, GEGL_AUTO_ROWSTRIDE);
+               break;
+
+       case IO_METHOD_MMAP:
+               CLEAR(buf);
+
+               buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+               buf.memory = V4L2_MEMORY_MMAP;
+
+               if (-1 == xioctl(p->fd, VIDIOC_DQBUF, &buf)) {
+                       switch (errno) {
+                       case EAGAIN:
+                               return 0;
+
+                       case EIO:
+                               /* Could ignore EIO, see spec. */
+
+                               /* fall through */
+
+                       default:
+                               errno_exit("VIDIOC_DQBUF");
+                       }
+               }
+
+               assert(buf.index < p->n_buffers);
+
+               //process_image(priv, p->buffers[buf.index].start, buf.bytesused);
+    //
+    //
+    {
+      guchar *capbuf = p->buffers[buf.index].start;
+      guchar foobuf[o->width*o->height*4];
+          /* XXX: foobuf is unneeded the conversions resets for every
+           * scanline and could thus have been done in a line by line
+           * manner an fed into the output buffer
+           */
+      gint y;
+      for (y = 0; y < p->h; y++)
+        {
+          gint       x;
+
+          guchar *dst = &foobuf[y*p->w*4];
+          guchar *ysrc = (guchar *) (capbuf + (y) * (p->w) * 2);
+
+          guchar *usrc = ysrc + 3;
+          guchar *vsrc = ysrc + 1;
+
+
+
+          for (x = 0; x < p->w; x++)
+            {
+
+              gint       R, G, B;
+
+#ifndef byteclamp
+#define byteclamp(j) do{if(j<0)j=0; else if(j>255)j=255;}while(0)
+#endif
+
+#define YUV82RGB8(Y,U,V,R,G,B)do{\
+                  R= ((Y<<15)                 + 37355*(V-128))>>15;\
+                  G= ((Y<<15) -12911* (U-128) - 19038*(V-128))>>15;\
+                  B= ((Y<<15) +66454* (U-128)                )>>15;\
+                  byteclamp(R);\
+                  byteclamp(G);\
+                  byteclamp(B);\
+              }while(0)
+
+      /* the format support for this code is not very good, but it
+       * works for the devices I have tested with, conversion even
+       * for chroma subsampled images is something we should let
+       * babl handle.
+       */
+              YUV82RGB8 (*ysrc, *usrc, *vsrc, R, G, B);
+#if 0
+              dst[0] = *ysrc;
+              dst[1] = *ysrc;
+              dst[2] = *ysrc;
+#else
+              dst[0] = B;
+              dst[1] = G;
+              dst[2] = R;
+              dst[3] = 255;
+#endif
+
+              dst  += 4;
+              ysrc += 2;
+
+              if (x % 2)
+                {
+                  usrc += 4;
+                  vsrc += 4;
+                }
+            }
+        }
+    
+    gegl_buffer_set (output, NULL, 0, NULL, foobuf,
+        GEGL_AUTO_ROWSTRIDE);
+    }
+
+               if (-1 == xioctl(p->fd, VIDIOC_QBUF, &buf))
+                       errno_exit("VIDIOC_QBUF");
+               break;
+
+       case IO_METHOD_USERPTR:
+               CLEAR(buf);
+
+               buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+               buf.memory = V4L2_MEMORY_USERPTR;
+
+               if (-1 == xioctl(p->fd, VIDIOC_DQBUF, &buf)) {
+                       switch (errno) {
+                       case EAGAIN:
+                               return 0;
+
+                       case EIO:
+                               /* Could ignore EIO, see spec. */
+
+                               /* fall through */
+
+                       default:
+                               errno_exit("VIDIOC_DQBUF");
+                       }
+               }
+
+               for (i = 0; i < p->n_buffers; ++i)
+                       if (buf.m.userptr == (unsigned long)p->buffers[i].start
+                           && buf.length == p->buffers[i].length)
+                               break;
+
+               assert(i < p->n_buffers);
+
+               //process_image(priv, (void *)buf.m.userptr, buf.bytesused);
+    gegl_buffer_set (output, NULL, 0, NULL, (void*)buf.m.userptr, GEGL_AUTO_ROWSTRIDE);
+
+               if (-1 == xioctl(p->fd, VIDIOC_QBUF, &buf))
+                       errno_exit("VIDIOC_QBUF");
+               break;
+       }
+
+       return 1;
+}
+
+static void stop_capturing(Priv *priv)
+{
+       enum v4l2_buf_type type;
+
+       switch (priv->io) {
+       case IO_METHOD_READ:
+               /* Nothing to do. */
+               break;
+
+       case IO_METHOD_MMAP:
+       case IO_METHOD_USERPTR:
+               type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+               if (-1 == xioctl(priv->fd, VIDIOC_STREAMOFF, &type))
+                       errno_exit("VIDIOC_STREAMOFF");
+               break;
+       }
+}
+
+static void start_capturing(Priv *priv)
+{
+       unsigned int i;
+       enum v4l2_buf_type type;
+
+       switch (priv->io) {
+       case IO_METHOD_READ:
+               /* Nothing to do. */
+               break;
+
+       case IO_METHOD_MMAP:
+               for (i = 0; i < priv->n_buffers; ++i) {
+                       struct v4l2_buffer buf;
+
+                       CLEAR(buf);
+                       buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+                       buf.memory = V4L2_MEMORY_MMAP;
+                       buf.index = i;
+
+                       if (-1 == xioctl(priv->fd, VIDIOC_QBUF, &buf))
+                               errno_exit("VIDIOC_QBUF");
+               }
+               type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+               if (-1 == xioctl(priv->fd, VIDIOC_STREAMON, &type))
+                       errno_exit("VIDIOC_STREAMON");
+               break;
+
+       case IO_METHOD_USERPTR:
+               for (i = 0; i < priv->n_buffers; ++i) {
+                       struct v4l2_buffer buf;
+
+                       CLEAR(buf);
+                       buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+                       buf.memory = V4L2_MEMORY_USERPTR;
+                       buf.index = i;
+                       buf.m.userptr = (unsigned long)priv->buffers[i].start;
+                       buf.length = priv->buffers[i].length;
+
+                       if (-1 == xioctl(priv->fd, VIDIOC_QBUF, &buf))
+                               errno_exit("VIDIOC_QBUF");
+               }
+               type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+               if (-1 == xioctl(priv->fd, VIDIOC_STREAMON, &type))
+                       errno_exit("VIDIOC_STREAMON");
+               break;
+       }
+}
+
+static void uninit_device(Priv *priv)
+{
+       unsigned int i;
+
+       switch (priv->io) {
+       case IO_METHOD_READ:
+               free(priv->buffers[0].start);
+               break;
+
+       case IO_METHOD_MMAP:
+               for (i = 0; i < priv->n_buffers; ++i)
+                       if (-1 == munmap(priv->buffers[i].start, priv->buffers[i].length))
+                               errno_exit("munmap");
+               break;
+
+       case IO_METHOD_USERPTR:
+               for (i = 0; i < priv->n_buffers; ++i)
+                       free(priv->buffers[i].start);
+               break;
+       }
+
+       free(priv->buffers);
+}
+
+static void init_read(Priv *priv, unsigned int buffer_size)
+{
+       priv->buffers = calloc(1, sizeof(*priv->buffers));
+
+       if (!priv->buffers) {
+               fprintf(stderr, "Out of memory\n");
+               exit(EXIT_FAILURE);
+       }
+
+       priv->buffers[0].length = buffer_size;
+       priv->buffers[0].start = malloc(buffer_size);
+
+       if (!priv->buffers[0].start) {
+               fprintf(stderr, "Out of memory\n");
+               exit(EXIT_FAILURE);
+       }
+}
+
+static void init_mmap(Priv *priv)
+{
+       struct v4l2_requestbuffers req;
+
+       CLEAR(req);
+
+       req.count = 4;
+       req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+       req.memory = V4L2_MEMORY_MMAP;
+
+       if (-1 == xioctl(priv->fd, VIDIOC_REQBUFS, &req)) {
+               if (EINVAL == errno) {
+                       fprintf(stderr, "%s does not support "
+                                "memory mapping\n", priv->dev_name);
+                       exit(EXIT_FAILURE);
+               } else {
+                       errno_exit("VIDIOC_REQBUFS");
+               }
+       }
+
+       if (req.count < 2) {
+               fprintf(stderr, "Insufficient buffer memory on %s\n",
+                        priv->dev_name);
+               exit(EXIT_FAILURE);
+       }
+
+       priv->buffers = calloc(req.count, sizeof(*priv->buffers));
+
+       if (!priv->buffers) {
+               fprintf(stderr, "Out of memory\n");
+               exit(EXIT_FAILURE);
+       }
+
+       for (priv->n_buffers = 0; priv->n_buffers < req.count; ++priv->n_buffers) {
+               struct v4l2_buffer buf;
+
+               CLEAR(buf);
+
+               buf.type        = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+               buf.memory      = V4L2_MEMORY_MMAP;
+               buf.index       = priv->n_buffers;
+
+               if (-1 == xioctl(priv->fd, VIDIOC_QUERYBUF, &buf))
+                       errno_exit("VIDIOC_QUERYBUF");
+
+               priv->buffers[priv->n_buffers].length = buf.length;
+               priv->buffers[priv->n_buffers].start =
+                       mmap(NULL /* start anywhere */,
+                             buf.length,
+                             PROT_READ | PROT_WRITE /* required */,
+                             MAP_SHARED /* recommended */,
+                             priv->fd, buf.m.offset);
+
+               if (MAP_FAILED == priv->buffers[priv->n_buffers].start)
+                       errno_exit("mmap");
+       }
+}
+
+static void init_userp(Priv *priv, unsigned int buffer_size)
+{
+       struct v4l2_requestbuffers req;
+
+       CLEAR(req);
+
+       req.count  = 4;
+       req.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+       req.memory = V4L2_MEMORY_USERPTR;
+
+       if (-1 == xioctl(priv->fd, VIDIOC_REQBUFS, &req)) {
+               if (EINVAL == errno) {
+                       fprintf(stderr, "%s does not support "
+                                "user pointer i/o\n", priv->dev_name);
+                       exit(EXIT_FAILURE);
+               } else {
+                       errno_exit("VIDIOC_REQBUFS");
+               }
+       }
+
+       priv->buffers = calloc(4, sizeof(*priv->buffers));
+
+       if (!priv->buffers) {
+               fprintf(stderr, "Out of memory\n");
+               exit(EXIT_FAILURE);
+       }
+
+       for (priv->n_buffers = 0; priv->n_buffers < 4; ++priv->n_buffers) {
+               priv->buffers[priv->n_buffers].length = buffer_size;
+               priv->buffers[priv->n_buffers].start = malloc(buffer_size);
+
+               if (!priv->buffers[priv->n_buffers].start) {
+                       fprintf(stderr, "Out of memory\n");
+                       exit(EXIT_FAILURE);
+               }
+       }
+}
+
+static void init_device(Priv *priv)
+{
+       struct v4l2_capability cap;
+       struct v4l2_cropcap cropcap;
+       struct v4l2_crop crop;
+       struct v4l2_format fmt;
+
+       if (-1 == xioctl(priv->fd, VIDIOC_QUERYCAP, &cap)) {
+               if (EINVAL == errno) {
+                       fprintf(stderr, "%s is no V4L2 device\n",
+                                priv->dev_name);
+                       exit(EXIT_FAILURE);
+               } else {
+                       errno_exit("VIDIOC_QUERYCAP");
+               }
+       }
+
+       if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
+               fprintf(stderr, "%s is no video capture device\n",
+                        priv->dev_name);
+               exit(EXIT_FAILURE);
+       }
+
+       switch (priv->io) {
+       case IO_METHOD_READ:
+               if (!(cap.capabilities & V4L2_CAP_READWRITE)) {
+                       fprintf(stderr, "%s does not support read i/o\n",
+                                priv->dev_name);
+                       exit(EXIT_FAILURE);
+               }
+               break;
+
+       case IO_METHOD_MMAP:
+       case IO_METHOD_USERPTR:
+               if (!(cap.capabilities & V4L2_CAP_STREAMING)) {
+                       fprintf(stderr, "%s does not support streaming i/o\n",
+                                priv->dev_name);
+                       exit(EXIT_FAILURE);
+               }
+               break;
+       }
+
+
+       /* Select video input, video standard and tune here. */
+
+
+       CLEAR(cropcap);
+
+       cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+
+       if (0 == xioctl(priv->fd, VIDIOC_CROPCAP, &cropcap)) {
+               crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+               crop.c = cropcap.defrect; /* reset to default */
+
+               if (-1 == xioctl(priv->fd, VIDIOC_S_CROP, &crop)) {
+                       switch (errno) {
+                       case EINVAL:
+                               /* Cropping not supported. */
+                               break;
+                       default:
+                               /* Errors ignored. */
+                               break;
+                       }
+               }
+       } else {
+               /* Errors ignored. */
+       }
+
+
+       CLEAR(fmt);
+
+       fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+       if (priv->force_format) {
+               fmt.fmt.pix.width       = priv->w;
+               fmt.fmt.pix.height      = priv->h;
+               //fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
+               fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
+               fmt.fmt.pix.field       = V4L2_FIELD_INTERLACED;
+
+               if (-1 == xioctl(priv->fd, VIDIOC_S_FMT, &fmt))
+                       errno_exit("VIDIOC_S_FMT");
+
+               /* Note VIDIOC_S_FMT may change width and height. */
+       } else {
+               /* Preserve original settings as set by v4l2-ctl for example */
+               if (-1 == xioctl(priv->fd, VIDIOC_G_FMT, &fmt))
+                       errno_exit("VIDIOC_G_FMT");
+       }
+
+       switch (priv->io) {
+       case IO_METHOD_READ:
+               init_read(priv, fmt.fmt.pix.sizeimage);
+               break;
+
+       case IO_METHOD_MMAP:
+               init_mmap(priv);
+               break;
+
+       case IO_METHOD_USERPTR:
+               init_userp(priv, fmt.fmt.pix.sizeimage);
+               break;
+       }
+}
+
+static void close_device(Priv *priv)
+{
+       if (-1 == close(priv->fd))
+               errno_exit("close");
+
+       priv->fd = -1;
+}
+
+static void open_device(Priv *priv)
+{
+       struct stat st;
+
+       if (-1 == stat(priv->dev_name, &st)) {
+               fprintf(stderr, "Cannot identify '%s': %d, %s\n",
+                        priv->dev_name, errno, strerror(errno));
+               exit(EXIT_FAILURE);
+       }
+
+       if (!S_ISCHR(st.st_mode)) {
+               fprintf(stderr, "%s is no device\n", priv->dev_name);
+               exit(EXIT_FAILURE);
+       }
+
+       priv->fd = open(priv->dev_name, O_RDWR /* required */ | O_NONBLOCK, 0);
+
+       if (-1 == priv->fd) {
+               fprintf(stderr, "Cannot open '%s': %d, %s\n",
+                        priv->dev_name, errno, strerror(errno));
+               exit(EXIT_FAILURE);
+       }
+}
+
+static void
+prepare (GeglOperation *operation)
+{
+  GeglProperties *o = GEGL_PROPERTIES (operation);
+  Priv *p= (Priv*)o->user_data;
+
+  if (p == NULL)
+    init (o);
+  p = (Priv*)o->user_data;
+
+  gegl_operation_set_format (operation, "output",
+                            babl_format ("R'G'B'A u8"));
+
+
+  if (!p->fd)
+    {
+      p->force_format = 1;
+      p->dev_name = o->path;
+      p->io = IO_METHOD_MMAP;
+      p->w = o->width;
+      p->h = o->height;
+
+      open_device (p);
+      init_device (p);
+
+      start_capturing (p);
+    }
+}
+
+static void
+finalize (GObject *object)
+{
+ GeglProperties *o = GEGL_PROPERTIES (object);
+
+  if (o->user_data)
+    {
+      Priv *p = (Priv*)o->user_data;
+      if (p->fd)
+      {
+             stop_capturing(p);
+        uninit_device (p);
+        close_device (p);
+        p->fd = 0;
+      }
+      g_free (o->user_data);
+      o->user_data = NULL;
+    }
+
+  G_OBJECT_CLASS (gegl_op_parent_class)->finalize (object);
+}
+
+static gboolean update (gpointer operation)
+{
+  GeglRectangle bounds = gegl_operation_get_bounding_box (operation);
+  gegl_operation_invalidate (operation, &bounds, FALSE);
+  return TRUE;
+}
+
+static gboolean
+process (GeglOperation       *operation,
+         GeglBuffer          *output,
+         const GeglRectangle *result,
+         gint                 level)
+{
+  GeglProperties *o = GEGL_PROPERTIES (operation);
+  Priv       *p = (Priv*)o->user_data;
+
+  static gboolean inited = FALSE;
+
+    if (!inited && o->fps != 0)
+      {
+        inited= TRUE;
+        g_timeout_add (1000/o->fps, update, operation);
+      }
+
+
+  {
+    fd_set fds;
+    struct timeval tv;
+    FD_ZERO (&fds);
+    FD_SET (p->fd, &fds);
+
+    tv.tv_sec = 2;
+    tv.tv_usec = 0;
+
+    switch (select (p->fd + 1, &fds, NULL, NULL, &tv))
+    {
+      case -1:
+        if (errno == EINTR)
+        {
+          g_warning ("select");
+          return FALSE;
+        }
+        break;
+      case 0:
+        g_warning ("select timeout");
+        return FALSE;
+        break;
+      default:
+        read_frame (operation, output);
+    }
+  }
+
+  return  TRUE;
+}
+
+static GeglRectangle
+get_cached_region (GeglOperation       *operation,
+                   const GeglRectangle *roi)
+{
+  return get_bounding_box (operation);
+}
+
+static void
+gegl_op_class_init (GeglOpClass *klass)
+{
+  GeglOperationClass       *operation_class;
+  GeglOperationSourceClass *source_class;
+
+  G_OBJECT_CLASS (klass)->finalize = finalize;
+
+  operation_class = GEGL_OPERATION_CLASS (klass);
+  source_class    = GEGL_OPERATION_SOURCE_CLASS (klass);
+
+  source_class->process             = process;
+  operation_class->get_bounding_box = get_bounding_box;
+  operation_class->get_cached_region = get_cached_region;
+  operation_class->prepare          = prepare;
+
+  gegl_operation_class_set_keys (operation_class,
+    "name",         "gegl:v4l2",
+    "title",        _("Video4Linux2 Frame Source"),
+    "categories",   "input:video",
+    "description",  _("Video4Linux2 input, webcams framegrabbers and similar devices."),
+    NULL);
+}
+
+#endif
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 5f55a2d..dd92bf7 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -221,3 +221,4 @@ operations/workshop/kuwahara.c
 operations/workshop/mandelbrot.c
 operations/workshop/rawbayer-load.c
 operations/workshop/snn-percentile.c
+operations/workshop/v4l2.c


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]