]> git.sesse.net Git - vlc/blobdiff - modules/access/v4l2/demux.c
BluRay instead of Blu-Ray. Re-using short messages for long ones
[vlc] / modules / access / v4l2 / demux.c
index bdbeb954eb88bf683172862300cb355432662be8..9d9b2ffcca4e87887ca892f8beb875b8f963071a 100644 (file)
 /*****************************************************************************
  * demux.c : V4L2 raw video demux module for vlc
  *****************************************************************************
- * Copyright (C) 2002-2011 the VideoLAN team
+ * Copyright (C) 2002-2011 VLC authors and VideoLAN
  *
  * Authors: Benjamin Pracht <bigben at videolan dot org>
  *          Richard Hosking <richard at hovis dot net>
  *          Antoine Cellerier <dionoea at videolan d.t org>
  *          Dennis Lou <dlou99 at yahoo dot com>
  *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
+ * This program 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 2.1 of the License, or
  * (at your option) any later version.
  *
  * This program 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 General Public License for more details.
+ * 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 General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
  *****************************************************************************/
 
 #ifdef HAVE_CONFIG_H
 # include "config.h"
 #endif
 
-#include "v4l2.h"
-#include <vlc_demux.h>
+#include <math.h>
 #include <errno.h>
+#include <assert.h>
 #include <sys/ioctl.h>
 #include <sys/mman.h>
+#ifndef MAP_ANONYMOUS
+# define MAP_ANONYMOUS MAP_ANON
+#endif
 #include <poll.h>
 
+#include <vlc_common.h>
+#include <vlc_demux.h>
+
+#include "v4l2.h"
+
+struct demux_sys_t
+{
+    int fd;
+    vlc_thread_t thread;
+
+    struct buffer_t *bufv;
+    union
+    {
+        uint32_t bufc;
+        uint32_t blocksize;
+    };
+    uint32_t block_flags;
+
+    es_out_id_t *es;
+    vlc_v4l2_ctrl_t *controls;
+    mtime_t start;
+
+#ifdef ZVBI_COMPILED
+    vlc_v4l2_vbi_t *vbi;
+#endif
+};
+
+static void *UserPtrThread (void *);
+static void *MmapThread (void *);
+static void *ReadThread (void *);
 static int DemuxControl( demux_t *, int, va_list );
-static int Demux( demux_t * );
+static int InitVideo (demux_t *, int fd, uint32_t caps);
 
 int DemuxOpen( vlc_object_t *obj )
 {
     demux_t *demux = (demux_t *)obj;
-    demux_sys_t *sys = calloc( 1, sizeof( demux_sys_t ) );
-    if( unlikely(sys == NULL) )
+
+    demux_sys_t *sys = malloc (sizeof (*sys));
+    if (unlikely(sys == NULL))
         return VLC_ENOMEM;
     demux->p_sys = sys;
+#ifdef ZVBI_COMPILED
+    sys->vbi = NULL;
+#endif
 
     ParseMRL( obj, demux->psz_location );
-    sys->i_fd = OpenVideo( obj, sys, true );
-    if( sys->i_fd == -1 )
+
+    char *path = var_InheritString (obj, CFG_PREFIX"dev");
+    if (unlikely(path == NULL))
+        goto error; /* probably OOM */
+
+    uint32_t caps;
+    int fd = OpenDevice (obj, path, &caps);
+    free (path);
+    if (fd == -1)
+        goto error;
+    sys->fd = fd;
+
+    if (InitVideo (demux, fd, caps))
     {
-        free( sys );
-        return VLC_EGENERIC;
+        v4l2_close (fd);
+        goto error;
     }
 
-    demux->pf_demux = Demux;
+    sys->controls = ControlsInit (VLC_OBJECT(demux), fd);
+    sys->start = mdate ();
+    demux->pf_demux = NULL;
     demux->pf_control = DemuxControl;
     demux->info.i_update = 0;
     demux->info.i_title = 0;
     demux->info.i_seekpoint = 0;
     return VLC_SUCCESS;
+error:
+    free (sys);
+    return VLC_EGENERIC;
 }
 
-void DemuxClose( vlc_object_t *obj )
+typedef struct
+{
+    uint32_t v4l2;
+    vlc_fourcc_t vlc;
+    uint32_t red;
+    uint32_t green;
+    uint32_t blue;
+} vlc_v4l2_fmt_t;
+
+/* NOTE: Currently vlc_v4l2_fmt_rank() assumes format are sorted in order of
+ * decreasing preference. */
+static const vlc_v4l2_fmt_t v4l2_fmts[] =
+{
+    /* Planar YUV 4:2:0 */
+    { V4L2_PIX_FMT_YUV420,   VLC_CODEC_I420, 0, 0, 0 },
+    { V4L2_PIX_FMT_YVU420,   VLC_CODEC_YV12, 0, 0, 0 },
+    { V4L2_PIX_FMT_YUV422P,  VLC_CODEC_I422, 0, 0, 0 },
+    /* Packed YUV 4:2:2 */
+    { V4L2_PIX_FMT_YUYV,     VLC_CODEC_YUYV, 0, 0, 0 },
+    { V4L2_PIX_FMT_UYVY,     VLC_CODEC_UYVY, 0, 0, 0 },
+    { V4L2_PIX_FMT_YVYU,     VLC_CODEC_YVYU, 0, 0, 0 },
+    { V4L2_PIX_FMT_VYUY,     VLC_CODEC_VYUY, 0, 0, 0 },
+
+    { V4L2_PIX_FMT_YUV411P,  VLC_CODEC_I411, 0, 0, 0 },
+
+    { V4L2_PIX_FMT_YUV410,   VLC_CODEC_I410, 0, 0, 0 },
+//  { V4L2_PIX_FMT_YVU410     },
+
+//  { V4L2_PIX_FMT_NV24,      },
+//  { V4L2_PIX_FMT_NV42,      },
+//  { V4L2_PIX_FMT_NV16,     VLC_CODEC_NV16, 0, 0, 0 },
+//  { V4L2_PIX_FMT_NV61,     VLC_CODEC_NV61, 0, 0, 0 },
+    { V4L2_PIX_FMT_NV12,     VLC_CODEC_NV12, 0, 0, 0 },
+    { V4L2_PIX_FMT_NV21,     VLC_CODEC_NV21, 0, 0, 0 },
+
+    /* V4L2-documented but VLC-unsupported misc. YUV formats */
+//  { V4L2_PIX_FMT_Y41P       },
+//  { V4L2_PIX_FMT_NV12MT,    },
+//  { V4L2_PIX_FMT_M420,      },
+
+    /* Packed RGB */
+#ifdef WORDS_BIGENDIAN
+    { V4L2_PIX_FMT_RGB32,    VLC_CODEC_RGB32,   0xFF00, 0xFF0000, 0xFF000000 },
+    { V4L2_PIX_FMT_BGR32,    VLC_CODEC_RGB32, 0xFF000000, 0xFF0000,   0xFF00 },
+    { V4L2_PIX_FMT_RGB24,    VLC_CODEC_RGB24,   0xFF0000, 0x00FF00, 0x0000FF },
+    { V4L2_PIX_FMT_BGR24,    VLC_CODEC_RGB24,   0x0000FF, 0x00FF00, 0xFF0000 },
+//  { V4L2_PIX_FMT_BGR666,    },
+//  { V4L2_PIX_FMT_RGB565,    },
+    { V4L2_PIX_FMT_RGB565X,  VLC_CODEC_RGB16,     0x001F,   0x07E0,   0xF800 },
+//  { V4L2_PIX_FMT_RGB555,    },
+    { V4L2_PIX_FMT_RGB555X,  VLC_CODEC_RGB15,     0x001F,   0x03E0,   0x7C00 },
+//  { V4L2_PIX_FMT_RGB444,   VLC_CODEC_RGB12,     0x000F,   0xF000,   0x0F00 },
+#else
+    { V4L2_PIX_FMT_RGB32,    VLC_CODEC_RGB32,   0x0000FF, 0x00FF00, 0xFF0000 },
+    { V4L2_PIX_FMT_BGR32,    VLC_CODEC_RGB32,   0xFF0000, 0x00FF00, 0x0000FF },
+    { V4L2_PIX_FMT_RGB24,    VLC_CODEC_RGB24,   0x0000FF, 0x00FF00, 0xFF0000 },
+    { V4L2_PIX_FMT_BGR24,    VLC_CODEC_RGB24,   0xFF0000, 0x00FF00, 0x0000FF },
+//  { V4L2_PIX_FMT_BGR666,    },
+    { V4L2_PIX_FMT_RGB565,   VLC_CODEC_RGB16,     0x001F,   0x07E0,   0xF800 },
+//  { V4L2_PIX_FMT_RGB565X,   },
+    { V4L2_PIX_FMT_RGB555,   VLC_CODEC_RGB15,     0x001F,   0x03E0,   0x7C00 },
+//  { V4L2_PIX_FMT_RGB555X,   },
+//  { V4L2_PIX_FMT_RGB444,   VLC_CODEC_RGB12,     0x0F00,   0x00F0,   0x000F },
+#endif
+//  { V4L2_PIX_FMT_RGB332,   VLC_CODEC_RGB8,        0xC0,     0x38,     0x07 },
+
+    /* Bayer (sub-sampled RGB). Not supported. */
+//  { V4L2_PIX_FMT_SBGGR16,  }
+//  { V4L2_PIX_FMT_SRGGB12,  }
+//  { V4L2_PIX_FMT_SGRBG12,  }
+//  { V4L2_PIX_FMT_SGBRG12,  }
+//  { V4L2_PIX_FMT_SBGGR12,  }
+//  { V4L2_PIX_FMT_SRGGB10,  }
+//  { V4L2_PIX_FMT_SGRBG10,  }
+//  { V4L2_PIX_FMT_SGBRG10,  }
+//  { V4L2_PIX_FMT_SBGGR10,  }
+//  { V4L2_PIX_FMT_SBGGR8,   }
+//  { V4L2_PIX_FMT_SGBRG8,   }
+//  { V4L2_PIX_FMT_SGRBG8,   }
+//  { V4L2_PIX_FMT_SRGGB8,   }
+
+    /* Compressed data types */
+    { V4L2_PIX_FMT_JPEG,   VLC_CODEC_MJPG, 0, 0, 0 },
+    { V4L2_PIX_FMT_H264,   VLC_CODEC_H264, 0, 0, 0 },
+    /* FIXME: fill p_extra for avc1... */
+//  { V4L2_PIX_FMT_H264_NO_SC, VLC_FOURCC('a','v','c','1'), 0, 0, 0 }
+    { V4L2_PIX_FMT_MPEG4,  VLC_CODEC_MP4V, 0, 0, 0 },
+    { V4L2_PIX_FMT_XVID,   VLC_CODEC_MP4V, 0, 0, 0 },
+    { V4L2_PIX_FMT_H263,   VLC_CODEC_H263, 0, 0, 0 },
+    { V4L2_PIX_FMT_MPEG2,  VLC_CODEC_MPGV, 0, 0, 0 },
+    { V4L2_PIX_FMT_MPEG1,  VLC_CODEC_MPGV, 0, 0, 0 },
+    { V4L2_PIX_FMT_VC1_ANNEX_G, VLC_CODEC_VC1, 0, 0, 0 },
+    { V4L2_PIX_FMT_VC1_ANNEX_L, VLC_CODEC_VC1, 0, 0, 0 },
+    //V4L2_PIX_FMT_MPEG -> use access
+
+    /* Reserved formats */
+    { V4L2_PIX_FMT_MJPEG,   VLC_CODEC_MJPG, 0, 0, 0 },
+    //V4L2_PIX_FMT_DV -> use access
+
+    /* Grey scale */
+//  { V4L2_PIX_FMT_Y16,       },
+//  { V4L2_PIX_FMT_Y12,       },
+//  { V4L2_PIX_FMT_Y10,       },
+//  { V4L2_PIX_FMT_Y10BPACK,  },
+    { V4L2_PIX_FMT_GREY,     VLC_CODEC_GREY, 0, 0, 0 },
+};
+
+static const vlc_v4l2_fmt_t *vlc_from_v4l2_fourcc (uint32_t fourcc)
+{
+     for (size_t i = 0; i < sizeof (v4l2_fmts) / sizeof (v4l2_fmts[0]); i++)
+         if (v4l2_fmts[i].v4l2 == fourcc)
+             return v4l2_fmts + i;
+     return NULL;
+}
+
+static size_t vlc_v4l2_fmt_rank (const vlc_v4l2_fmt_t *fmt)
+{
+    if (fmt == NULL)
+        return SIZE_MAX;
+
+    ptrdiff_t d = fmt - v4l2_fmts;
+    assert (d >= 0);
+    assert (d < (ptrdiff_t)(sizeof (v4l2_fmts) / sizeof (v4l2_fmts[0])));
+    return d;
+}
+
+static vlc_fourcc_t var_InheritFourCC (vlc_object_t *obj, const char *varname)
+{
+    char *str = var_InheritString (obj, varname);
+    if (str == NULL)
+        return 0;
+
+    vlc_fourcc_t fourcc = vlc_fourcc_GetCodecFromString (VIDEO_ES, str);
+    if (fourcc == 0)
+        msg_Err (obj, "invalid codec %s", str);
+    free (str);
+    return fourcc;
+}
+#define var_InheritFourCC(o, v) var_InheritFourCC(VLC_OBJECT(o), v)
+
+static void GetAR (int fd, unsigned *restrict num, unsigned *restrict den)
+{
+    struct v4l2_cropcap cropcap = { .type = V4L2_BUF_TYPE_VIDEO_CAPTURE };
+
+    /* TODO: get CROPCAP only once (see ResetCrop()). */
+    if (v4l2_ioctl (fd, VIDIOC_CROPCAP, &cropcap) < 0)
+    {
+        *num = *den = 1;
+        return;
+    }
+    *num = cropcap.pixelaspect.numerator;
+    *den = cropcap.pixelaspect.denominator;
+}
+
+static int InitVideo (demux_t *demux, int fd, uint32_t caps)
 {
-    demux_t *demux = (demux_t *)obj;
     demux_sys_t *sys = demux->p_sys;
-    int fd = sys->i_fd;
+    v4l2_std_id std;
 
-    /* Stop video capture */
-    switch( sys->io )
+    if (!(caps & V4L2_CAP_VIDEO_CAPTURE))
     {
-        case IO_METHOD_READ:
-            /* Nothing to do */
-            break;
+        msg_Err (demux, "not a video capture device");
+        return -1;
+    }
+
+    if (SetupInput (VLC_OBJECT(demux), fd, &std))
+        return -1;
+
+    /* Picture format negotiation */
+    const vlc_v4l2_fmt_t *selected = NULL;
+    vlc_fourcc_t reqfourcc = var_InheritFourCC (demux, CFG_PREFIX"chroma");
+    bool native = false;
+
+    for (struct v4l2_fmtdesc codec = { .type = V4L2_BUF_TYPE_VIDEO_CAPTURE };
+         v4l2_ioctl (fd, VIDIOC_ENUM_FMT, &codec) >= 0;
+         codec.index++)
+    {   /* Enumerate available chromas */
+        const vlc_v4l2_fmt_t *dsc = vlc_from_v4l2_fourcc (codec.pixelformat);
+
+        msg_Dbg (demux, " %s %s format %4.4s (%4.4s): %s",
+              (codec.flags & V4L2_FMT_FLAG_EMULATED) ? "emulates" : "supports",
+              (codec.flags & V4L2_FMT_FLAG_COMPRESSED) ? "compressed" : "raw",
+                 (char *)&codec.pixelformat,
+                 (dsc != NULL) ? (const char *)&dsc->vlc : "N.A.",
+                 codec.description);
 
-        case IO_METHOD_MMAP:
-        case IO_METHOD_USERPTR:
+        if (dsc == NULL)
+            continue; /* ignore VLC-unsupported codec */
+
+        if (dsc->vlc == reqfourcc)
         {
-            /* NOTE: Some buggy drivers hang if buffers are not unmapped before
-             * streamoff */
-            for( unsigned i = 0; i < sys->i_nbuffers; i++ )
-            {
-                struct v4l2_buffer buf = {
-                    .type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
-                    .memory = ( sys->io == IO_METHOD_USERPTR ) ?
-                    V4L2_MEMORY_USERPTR : V4L2_MEMORY_MMAP,
-                };
-                v4l2_ioctl( fd, VIDIOC_DQBUF, &buf );
-            }
-            enum v4l2_buf_type buf_type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
-            v4l2_ioctl( sys->i_fd, VIDIOC_STREAMOFF, &buf_type );
-            break;
+            msg_Dbg (demux, "  matches the requested format");
+            selected = dsc;
+            break; /* always select the requested format if found */
         }
-    }
 
-    /* Free Video Buffers */
-    if( sys->p_buffers ) {
-        switch( sys->io )
+        if (codec.flags & V4L2_FMT_FLAG_EMULATED)
         {
-        case IO_METHOD_READ:
-            free( sys->p_buffers[0].start );
-            break;
+            if (native)
+                continue; /* ignore emulated format if possible */
+        }
+        else
+            native = true;
 
-        case IO_METHOD_MMAP:
-            for( unsigned i = 0; i < sys->i_nbuffers; ++i )
-                v4l2_munmap( sys->p_buffers[i].start,
-                             sys->p_buffers[i].length );
-            break;
+        if (vlc_v4l2_fmt_rank (dsc) > vlc_v4l2_fmt_rank (selected))
+            continue; /* ignore if rank is worse */
+
+        selected = dsc;
+    }
 
-        case IO_METHOD_USERPTR:
-            for( unsigned i = 0; i < sys->i_nbuffers; ++i )
-               free( sys->p_buffers[i].start );
+    if (selected == NULL)
+    {
+        msg_Err (demux, "cannot negotiate supported video format");
+        return -1;
+    }
+    msg_Dbg (demux, "selected format %4.4s (%4.4s)",
+             (const char *)&selected->v4l2, (const char *)&selected->vlc);
+
+    /* Find best resolution and frame rate available */
+    struct v4l2_format fmt;
+    struct v4l2_streamparm parm;
+    if (SetupFormat (demux, fd, selected->v4l2, &fmt, &parm))
+        return -1;
+
+    /* Print extra info */
+    msg_Dbg (demux, "%d bytes maximum for complete image",
+             fmt.fmt.pix.sizeimage);
+    /* Check interlacing */
+    sys->block_flags = 0;
+    switch (fmt.fmt.pix.field)
+    {
+        case V4L2_FIELD_NONE:
+            msg_Dbg (demux, "Interlacing setting: progressive");
             break;
+        case V4L2_FIELD_TOP:
+            msg_Dbg (demux, "Interlacing setting: top field only");
+            break;
+        case V4L2_FIELD_BOTTOM:
+            msg_Dbg (demux, "Interlacing setting: bottom field only");
+            break;
+        case V4L2_FIELD_INTERLACED:
+            msg_Dbg (demux, "Interlacing setting: interleaved");
+            /*if (NTSC)
+                sys->block_flags = BLOCK_FLAG_BOTTOM_FIELD_FIRST;
+            else*/
+                sys->block_flags = BLOCK_FLAG_TOP_FIELD_FIRST;
+            break;
+        case V4L2_FIELD_SEQ_TB:
+            msg_Dbg (demux, "Interlacing setting: sequential top bottom (TODO)");
+            break;
+        case V4L2_FIELD_SEQ_BT:
+            msg_Dbg (demux, "Interlacing setting: sequential bottom top (TODO)");
+            break;
+        case V4L2_FIELD_ALTERNATE:
+            msg_Dbg (demux, "Interlacing setting: alternate fields (TODO)");
+            fmt.fmt.pix.height *= 2;
+            break;
+        case V4L2_FIELD_INTERLACED_TB:
+            msg_Dbg (demux, "Interlacing setting: interleaved top bottom");
+            sys->block_flags = BLOCK_FLAG_TOP_FIELD_FIRST;
+            break;
+        case V4L2_FIELD_INTERLACED_BT:
+            msg_Dbg (demux, "Interlacing setting: interleaved bottom top");
+            sys->block_flags = BLOCK_FLAG_BOTTOM_FIELD_FIRST;
+            break;
+        default:
+            msg_Warn (demux, "Interlacing setting: unknown type (%d)",
+                      fmt.fmt.pix.field);
+            break;
+    }
+
+    /* Declare our unique elementary (video) stream */
+    es_format_t es_fmt;
+
+    es_format_Init (&es_fmt, VIDEO_ES, selected->vlc);
+    es_fmt.video.i_rmask = selected->red;
+    es_fmt.video.i_gmask = selected->green;
+    es_fmt.video.i_bmask = selected->blue;
+    es_fmt.video.i_width = fmt.fmt.pix.width;
+    es_fmt.video.i_height = fmt.fmt.pix.height;
+    es_fmt.video.i_frame_rate = parm.parm.capture.timeperframe.denominator;
+    es_fmt.video.i_frame_rate_base = parm.parm.capture.timeperframe.numerator;
+    GetAR (fd, &es_fmt.video.i_sar_num, &es_fmt.video.i_sar_den);
+
+    msg_Dbg (demux, "added new video ES %4.4s %ux%u", (char *)&es_fmt.i_codec,
+             es_fmt.video.i_width, es_fmt.video.i_height);
+    msg_Dbg (demux, " frame rate: %u/%u", es_fmt.video.i_frame_rate,
+             es_fmt.video.i_frame_rate_base);
+    msg_Dbg (demux, " aspect ratio: %u/%u", es_fmt.video.i_sar_num,
+             es_fmt.video.i_sar_den);
+    sys->es = es_out_Add (demux->out, &es_fmt);
+
+    /* Init I/O method */
+    void *(*entry) (void *);
+    if (caps & V4L2_CAP_STREAMING)
+    {
+        if (0 /* BROKEN */ && StartUserPtr (VLC_OBJECT(demux), fd) == 0)
+        {
+            /* In principles, mmap() will pad the length to a multiple of the
+             * page size, so there is no need to care. Nevertheless with the
+             * page size, block->i_size can be set optimally. */
+            const long pagemask = sysconf (_SC_PAGE_SIZE) - 1;
+
+            sys->blocksize = (fmt.fmt.pix.sizeimage + pagemask) & ~pagemask;
+            sys->bufv = NULL;
+            entry = UserPtrThread;
+            msg_Dbg (demux, "streaming with %"PRIu32"-bytes user buffers",
+                     sys->blocksize);
+        }
+        else /* fall back to memory map */
+        {
+            sys->bufc = 4;
+            sys->bufv = StartMmap (VLC_OBJECT(demux), fd, &sys->bufc);
+            if (sys->bufv == NULL)
+                return -1;
+            entry = MmapThread;
+            msg_Dbg (demux, "streaming with %"PRIu32" memory-mapped buffers",
+                     sys->bufc);
         }
-        free( sys->p_buffers );
+    }
+    else if (caps & V4L2_CAP_READWRITE)
+    {
+        sys->blocksize = fmt.fmt.pix.sizeimage;
+        sys->bufv = NULL;
+        entry = ReadThread;
+        msg_Dbg (demux, "reading %"PRIu32" bytes at a time", sys->blocksize);
+    }
+    else
+    {
+        msg_Err (demux, "no supported capture method");
+        return -1;
     }
 
-    ControlsDeinit( obj, sys->controls );
-    v4l2_close( fd );
-    free( sys );
-}
+#ifdef ZVBI_COMPILED
+    if (std & V4L2_STD_NTSC_M)
+    {
+        char *vbi_path = var_InheritString (demux, CFG_PREFIX"vbidev");
+        if (vbi_path != NULL)
+            sys->vbi = OpenVBI (demux, vbi_path);
+        free(vbi_path);
+    }
+#endif
 
-static int DemuxControl( demux_t *demux, int query, va_list args )
-{
-    switch( query )
+    if (vlc_clone (&sys->thread, entry, demux, VLC_THREAD_PRIORITY_INPUT))
     {
-        /* Special for access_demux */
-        case DEMUX_CAN_PAUSE:
-        case DEMUX_CAN_SEEK:
-        case DEMUX_CAN_CONTROL_PACE:
-            *va_arg( args, bool * ) = false;
-            return VLC_SUCCESS;
+#ifdef ZVBI_COMPILED
+        if (sys->vbi != NULL)
+            CloseVBI (sys->vbi);
+#endif
+        if (sys->bufv != NULL)
+            StopMmap (sys->fd, sys->bufv, sys->bufc);
+        return -1;
+    }
+    return 0;
+}
 
-        case DEMUX_GET_PTS_DELAY:
-            *va_arg(args,int64_t *) = INT64_C(1000)
-                * var_InheritInteger( demux, "live-caching" );
-            return VLC_SUCCESS;
+void DemuxClose( vlc_object_t *obj )
+{
+    demux_t *demux = (demux_t *)obj;
+    demux_sys_t *sys = demux->p_sys;
 
-        case DEMUX_GET_TIME:
-            *va_arg( args, int64_t * ) = mdate();
-            return VLC_SUCCESS;
+    vlc_cancel (sys->thread);
+    vlc_join (sys->thread, NULL);
+    if (sys->bufv != NULL)
+        StopMmap (sys->fd, sys->bufv, sys->bufc);
+    ControlsDeinit( obj, sys->controls );
+    v4l2_close (sys->fd);
 
-        /* TODO implement others */
-        default:
-            return VLC_EGENERIC;
-    }
+#ifdef ZVBI_COMPILED
+    if (sys->vbi != NULL)
+        CloseVBI (sys->vbi);
+#endif
 
-    return VLC_EGENERIC;
+    free( sys );
 }
 
-/** Gets a frame in read/write mode */
-static block_t *BlockRead( vlc_object_t *obj, int fd, size_t size )
+/** Allocates and queue a user buffer using mmap(). */
+static block_t *UserPtrQueue (vlc_object_t *obj, int fd, size_t length)
 {
-    block_t *block = block_Alloc( size );
-    if( unlikely(block == NULL) )
+    void *ptr = mmap (NULL, length, PROT_READ | PROT_WRITE,
+                      MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+    if (ptr == MAP_FAILED)
+    {
+        msg_Err (obj, "cannot allocate %zu-bytes buffer: %m", length);
         return NULL;
+    }
 
-    ssize_t val = v4l2_read( fd, block->p_buffer, size );
-    if( val == -1 )
+    block_t *block = block_mmap_Alloc (ptr, length);
+    if (unlikely(block == NULL))
     {
-        block_Release( block );
-        switch( errno )
-        {
-            case EAGAIN:
-                return NULL;
-            case EIO: /* could be ignored per specification */
-                /* fall through */
-            default:
-                msg_Err( obj, "cannot read frame: %m" );
-                return NULL;
-        }
+        munmap (ptr, length);
+        return NULL;
+    }
+
+    struct v4l2_buffer buf = {
+        .type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
+        .memory = V4L2_MEMORY_USERPTR,
+        .m = {
+            .userptr = (uintptr_t)ptr,
+        },
+        .length = length,
+    };
+
+    if (v4l2_ioctl (fd, VIDIOC_QBUF, &buf) < 0)
+    {
+        msg_Err (obj, "cannot queue buffer: %m");
+        block_Release (block);
+        return NULL;
     }
-    block->i_buffer = val;
     return block;
 }
 
-static int Demux( demux_t *demux )
+static void *UserPtrThread (void *data)
 {
+    demux_t *demux = data;
     demux_sys_t *sys = demux->p_sys;
-    struct pollfd ufd;
-
-    ufd.fd = sys->i_fd;
-    ufd.events = POLLIN|POLLPRI;
-    /* Wait for data */
-    /* FIXME: remove timeout */
-    while( poll( &ufd, 1, 500 ) == -1 )
-        if( errno != EINTR )
-        {
-            msg_Err( demux, "poll error: %m" );
-            return -1;
-        }
-
-    if( ufd.revents == 0 )
-        return 1;
-
-    block_t *block;
+    int fd = sys->fd;
+    struct pollfd ufd[2];
+    nfds_t numfds = 1;
 
-    if( sys->io == IO_METHOD_READ )
-        block = BlockRead( VLC_OBJECT(demux), ufd.fd, sys->blocksize );
-    else
-        block = GrabVideo( VLC_OBJECT(demux), sys );
-    if( block == NULL )
-        return 1;
-
-    block->i_pts = block->i_dts = mdate();
-    block->i_flags |= sys->i_block_flags;
-    es_out_Control( demux->out, ES_OUT_SET_PCR, block->i_pts );
-    es_out_Send( demux->out, sys->p_es, block );
-    return 1;
-}
+    ufd[0].fd = fd;
+    ufd[0].events = POLLIN;
 
-static float GetMaxFPS( vlc_object_t *obj, int fd, uint32_t pixel_format,
-                        uint32_t width, uint32_t height )
-{
-#ifdef VIDIOC_ENUM_FRAMEINTERVALS
-    /* This is new in Linux 2.6.19 */
-    struct v4l2_frmivalenum fie = {
-        .pixel_format = pixel_format,
-        .width = width,
-        .height = height,
-    };
+    int canc = vlc_savecancel ();
+    for (;;)
+    {
+        struct v4l2_buffer buf = {
+            .type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
+            .memory = V4L2_MEMORY_USERPTR,
+        };
+        block_t *block = UserPtrQueue (VLC_OBJECT(demux), fd, sys->blocksize);
+        if (block == NULL)
+            break;
 
-    if( v4l2_ioctl( fd, VIDIOC_ENUM_FRAMEINTERVALS, &fie ) < 0 )
-        return -1.;
+        /* Wait for data */
+        vlc_restorecancel (canc);
+        block_cleanup_push (block);
+        while (poll (ufd, numfds, -1) == -1)
+           if (errno != EINTR)
+               msg_Err (demux, "poll error: %m");
+        vlc_cleanup_pop ();
+        canc = vlc_savecancel ();
 
-    switch( fie.type )
-    {
-        case V4L2_FRMIVAL_TYPE_DISCRETE:
+        if (v4l2_ioctl (fd, VIDIOC_DQBUF, &buf) < 0)
         {
-            float max = -1.;
-            do
-            {
-                float fps = (float)fie.discrete.denominator
-                          / (float)fie.discrete.numerator;
-                if( fps > max )
-                    max = fps;
-                msg_Dbg( obj, "  discrete frame interval %"PRIu32"/%"PRIu32
-                         " supported",
-                         fie.discrete.numerator, fie.discrete.denominator );
-                fie.index++;
-            } while( v4l2_ioctl( fd, VIDIOC_ENUM_FRAMEINTERVALS, &fie ) >= 0 );
-            return max;
+            msg_Err (demux, "cannot dequeue buffer: %m");
+            block_Release (block);
+            continue;
         }
 
-        case V4L2_FRMIVAL_TYPE_STEPWISE:
-        case V4L2_FRMIVAL_TYPE_CONTINUOUS:
-            msg_Dbg( obj, "  frame intervals from %"PRIu32"/%"PRIu32
-                    "to %"PRIu32"/%"PRIu32" supported",
-                    fie.stepwise.min.numerator, fie.stepwise.min.denominator,
-                    fie.stepwise.max.numerator, fie.stepwise.max.denominator );
-            if( fie.type == V4L2_FRMIVAL_TYPE_STEPWISE )
-                msg_Dbg( obj, "  with %"PRIu32"/%"PRIu32" step",
-                         fie.stepwise.step.numerator,
-                         fie.stepwise.step.denominator );
-            return __MAX( (float)fie.stepwise.max.denominator
-                        / (float)fie.stepwise.max.numerator,
-                          (float)fie.stepwise.min.denominator
-                        / (float)fie.stepwise.min.numerator );
+        assert (block->p_buffer == (void *)buf.m.userptr);
+        block->i_buffer = buf.length;
+        block->i_pts = block->i_dts = GetBufferPTS (&buf);
+        block->i_flags |= sys->block_flags;
+        es_out_Control (demux->out, ES_OUT_SET_PCR, block->i_pts);
+        es_out_Send (demux->out, sys->es, block);
     }
-#endif
-    return -1.;
+    vlc_restorecancel (canc); /* <- hmm, this is purely cosmetic */
+    return NULL;
 }
 
-float GetAbsoluteMaxFrameRate( vlc_object_t *obj, int fd,
-                               uint32_t pixel_format )
+static void *MmapThread (void *data)
 {
-#ifdef VIDIOC_ENUM_FRAMESIZES
-    /* This is new in Linux 2.6.19 */
-    struct v4l2_frmsizeenum fse = {
-        .pixel_format = pixel_format
-    };
+    demux_t *demux = data;
+    demux_sys_t *sys = demux->p_sys;
+    int fd = sys->fd;
+    struct pollfd ufd[2];
+    nfds_t numfds = 1;
+
+    ufd[0].fd = fd;
+    ufd[0].events = POLLIN;
 
-    if( v4l2_ioctl( fd, VIDIOC_ENUM_FRAMESIZES, &fse ) < 0 )
-        return -1.;
+#ifdef ZVBI_COMPILED
+    if (sys->vbi != NULL)
+    {
+        ufd[1].fd = GetFdVBI (sys->vbi);
+        ufd[1].events = POLLIN;
+        numfds++;
+    }
+#endif
 
-    float max = -1.;
-    switch( fse.type )
+    for (;;)
     {
-      case V4L2_FRMSIZE_TYPE_DISCRETE:
-        do
+        /* Wait for data */
+        if (poll (ufd, numfds, -1) == -1)
         {
-            float fps = GetMaxFPS( obj, fd, pixel_format,
-                                   fse.discrete.width, fse.discrete.height );
-            if( fps > max )
-                max = fps;
-            fse.index++;
-        } while( v4l2_ioctl( fd, VIDIOC_ENUM_FRAMESIZES, &fse ) >= 0 );
-        break;
-
-      case V4L2_FRMSIZE_TYPE_STEPWISE:
-      case V4L2_FRMSIZE_TYPE_CONTINUOUS:
-        msg_Dbg( obj, " sizes from %"PRIu32"x%"PRIu32" "
-                 "to %"PRIu32"x%"PRIu32" supported",
-                 fse.stepwise.min_width, fse.stepwise.min_height,
-                 fse.stepwise.max_width, fse.stepwise.max_height );
-        if( fse.type == V4L2_FRMSIZE_TYPE_STEPWISE )
-            msg_Dbg( obj, "  with %"PRIu32"x%"PRIu32" steps",
-                     fse.stepwise.step_width, fse.stepwise.step_height );
-
-        for( uint32_t width =  fse.stepwise.min_width;
-                      width <= fse.stepwise.max_width;
-                      width += fse.stepwise.step_width )
-            for( uint32_t height =  fse.stepwise.min_height;
-                          height <= fse.stepwise.max_width;
-                          height += fse.stepwise.step_height )
+           if (errno != EINTR)
+               msg_Err (demux, "poll error: %m");
+           continue;
+        }
+
+        if( ufd[0].revents )
+        {
+            int canc = vlc_savecancel ();
+            block_t *block = GrabVideo (VLC_OBJECT(demux), fd, sys->bufv);
+            if (block != NULL)
             {
-                float fps = GetMaxFPS( obj, fd, pixel_format, width, height );
-                if( fps > max )
-                    max = fps;
+                block->i_flags |= sys->block_flags;
+                es_out_Control (demux->out, ES_OUT_SET_PCR, block->i_pts);
+                es_out_Send (demux->out, sys->es, block);
             }
-        break;
-    }
-    return max;
-#else
-    return -1.;
+            vlc_restorecancel (canc);
+        }
+#ifdef ZVBI_COMPILED
+        if (sys->vbi != NULL && ufd[1].revents)
+            GrabVBI (demux, sys->vbi);
 #endif
+    }
+
+    assert (0);
 }
 
-void GetMaxDimensions( vlc_object_t *obj, int fd, uint32_t pixel_format,
-                       float fps_min, uint32_t *pwidth, uint32_t *pheight )
+static void *ReadThread (void *data)
 {
-    *pwidth = 0;
-    *pheight = 0;
+    demux_t *demux = data;
+    demux_sys_t *sys = demux->p_sys;
+    int fd = sys->fd;
+    struct pollfd ufd[2];
+    nfds_t numfds = 1;
 
-#ifdef VIDIOC_ENUM_FRAMESIZES
-    /* This is new in Linux 2.6.19 */
-    struct v4l2_frmsizeenum fse = {
-        .pixel_format = pixel_format
-    };
+    ufd[0].fd = fd;
+    ufd[0].events = POLLIN;
 
-    if( v4l2_ioctl( fd, VIDIOC_ENUM_FRAMESIZES, &fse ) < 0 )
-        return;
+#ifdef ZVBI_COMPILED
+    if (sys->vbi != NULL)
+    {
+        ufd[1].fd = GetFdVBI (sys->vbi);
+        ufd[1].events = POLLIN;
+        numfds++;
+    }
+#endif
 
-    switch( fse.type )
+    for (;;)
     {
-      case V4L2_FRMSIZE_TYPE_DISCRETE:
-        do
+        /* Wait for data */
+        if (poll (ufd, numfds, -1) == -1)
         {
-            msg_Dbg( obj, " discrete size %"PRIu32"x%"PRIu32" supported",
-                     fse.discrete.width, fse.discrete.height );
+           if (errno != EINTR)
+               msg_Err (demux, "poll error: %m");
+           continue;
+        }
 
-            float fps = GetMaxFPS( obj, fd, pixel_format,
-                                   fse.discrete.width, fse.discrete.height );
-            if( fps >= fps_min && fse.discrete.width > *pwidth )
+        if( ufd[0].revents )
+        {
+            block_t *block = block_Alloc (sys->blocksize);
+            if (unlikely(block == NULL))
             {
-                *pwidth = fse.discrete.width;
-                *pheight = fse.discrete.height;
+                msg_Err (demux, "read error: %m");
+                v4l2_read (fd, NULL, 0); /* discard frame */
+                continue;
             }
-            fse.index++;
-        }
-        while( v4l2_ioctl( fd, VIDIOC_ENUM_FRAMESIZES, &fse ) >= 0 );
-        break;
-
-      case V4L2_FRMSIZE_TYPE_STEPWISE:
-      case V4L2_FRMSIZE_TYPE_CONTINUOUS:
-        msg_Dbg( obj, " sizes from %"PRIu32"x%"PRIu32" "
-                 "to %"PRIu32"x%"PRIu32" supported",
-                 fse.stepwise.min_width, fse.stepwise.min_height,
-                 fse.stepwise.max_width, fse.stepwise.max_height );
-        if( fse.type == V4L2_FRMSIZE_TYPE_STEPWISE )
-            msg_Dbg( obj, "  with %"PRIu32"x%"PRIu32" steps",
-                     fse.stepwise.step_width, fse.stepwise.step_height );
-
-        for( uint32_t width =  fse.stepwise.min_width;
-                      width <= fse.stepwise.max_width;
-                      width += fse.stepwise.step_width )
-            for( uint32_t height = fse.stepwise.min_height;
-                          height <= fse.stepwise.max_width;
-                          height += fse.stepwise.step_height )
+            block->i_pts = block->i_dts = mdate ();
+            block->i_flags |= sys->block_flags;
+
+            int canc = vlc_savecancel ();
+            ssize_t val = v4l2_read (fd, block->p_buffer, block->i_buffer);
+            if (val != -1)
             {
-                float fps = GetMaxFPS( obj, fd, pixel_format, width, height );
-                if( fps >= fps_min && width > *pwidth )
-                {
-                    *pwidth = width;
-                    *pheight = height;
-                }
+                block->i_buffer = val;
+                es_out_Control (demux->out, ES_OUT_SET_PCR, block->i_pts);
+                es_out_Send (demux->out, sys->es, block);
             }
-        break;
-    }
+            else
+                block_Release (block);
+            vlc_restorecancel (canc);
+        }
+#ifdef ZVBI_COMPILED
+        if (sys->vbi != NULL && ufd[1].revents)
+            GrabVBI (demux, sys->vbi);
 #endif
+    }
+    assert (0);
+}
+
+static int DemuxControl( demux_t *demux, int query, va_list args )
+{
+    demux_sys_t *sys = demux->p_sys;
+
+    switch( query )
+    {
+        /* Special for access_demux */
+        case DEMUX_CAN_PAUSE:
+        case DEMUX_CAN_SEEK:
+        case DEMUX_CAN_CONTROL_PACE:
+            *va_arg( args, bool * ) = false;
+            return VLC_SUCCESS;
+
+        case DEMUX_GET_PTS_DELAY:
+            *va_arg(args,int64_t *) = INT64_C(1000)
+                * var_InheritInteger( demux, "live-caching" );
+            return VLC_SUCCESS;
+
+        case DEMUX_GET_TIME:
+            *va_arg (args, int64_t *) = mdate() - sys->start;
+            return VLC_SUCCESS;
+
+        /* TODO implement others */
+        default:
+            return VLC_EGENERIC;
+    }
+
+    return VLC_EGENERIC;
 }