[vlc-commits] [Git][videolan/vlc][master] 19 commits: v4l2/access: reorder to avoid forward declarations

Hugo Beauzée-Luyssen (@chouquette) gitlab at videolan.org
Wed Mar 9 21:12:29 UTC 2022



Hugo Beauzée-Luyssen pushed to branch master at VideoLAN / VLC


Commits:
be82b409 by Rémi Denis-Courmont at 2022-03-09T20:59:25+00:00
v4l2/access: reorder to avoid forward declarations

No functional changes.

- - - - -
6b2c06fa by Rémi Denis-Courmont at 2022-03-09T20:59:25+00:00
v4l2/access: missing static qualifier

- - - - -
908fc55b by Rémi Denis-Courmont at 2022-03-09T20:59:25+00:00
v4l2/access: rewrap

- - - - -
8bbae6a0 by Rémi Denis-Courmont at 2022-03-09T20:59:25+00:00
v4l2/demux: reorder to avoid forward declarations

No functional changes.

- - - - -
c08707dd by Rémi Denis-Courmont at 2022-03-09T20:59:25+00:00
v4l2: move video format setup to common code

- - - - -
f156ec45 by Rémi Denis-Courmont at 2022-03-09T20:59:25+00:00
v4l2: add muxed formats to the list

- - - - -
1139073d by Rémi Denis-Courmont at 2022-03-09T20:59:25+00:00
v4l2/access: use common video format initialisation

- - - - -
41384fd9 by Rémi Denis-Courmont at 2022-03-09T20:59:25+00:00
v4l2: privatise two functions

- - - - -
84dcd742 by Rémi Denis-Courmont at 2022-03-09T20:59:25+00:00
v4l2: report user buffer not supported

User pointers and memory map modes are identified by the same device
capability flag (STREAMING). If user pointers are not supported (and
they typically are not for physical hardware), then REQBUFS returns
EINVAL.

If the error is EINVAL, printing the error message "Invalid arguments"
gives the wrong impression that VLC is misuing the V4L API. If the
error is something else, then it is a real error, so should be printed
as such.

- - - - -
a90c64b9 by Rémi Denis-Courmont at 2022-03-09T20:59:25+00:00
v4l2/demux: enable user pointer mode

This was not so much broken as previously poorly documented.

- - - - -
e935d3b8 by Rémi Denis-Courmont at 2022-03-09T20:59:25+00:00
v4l2: define structure for buffer pool

- - - - -
0b08ccf4 by Rémi Denis-Courmont at 2022-03-09T20:59:25+00:00
v4l2: split format and buffer code

- - - - -
acebb0b6 by Rémi Denis-Courmont at 2022-03-09T20:59:25+00:00
v4l2: pointer buffers back to their pool

This will be used later on.

- - - - -
c9cf4518 by Rémi Denis-Courmont at 2022-03-09T20:59:25+00:00
v4l2: make mmap-buffers actual VLC data blocks

- - - - -
43dc39db by Rémi Denis-Courmont at 2022-03-09T20:59:25+00:00
v4l2: add FD to buffer pool

This will be used later on.

Note that the FD is still stored in demux_sys_t for the cases where the
pool is not created, i.e. user pointer and read-write capture modes.

- - - - -
4080349b by Rémi Denis-Courmont at 2022-03-09T20:59:25+00:00
v4l2: track in-flight buffers

This tracks which buffers have been dequeued and not yet requeued.
When the capture ends, only buffers which were still queued will be
freed. The pool is freed whence all buffers have been freed.

This also automatically requeues a buffer when it is freed as long as
the capture has not been terminated.

- - - - -
77462e9b by Rémi Denis-Courmont at 2022-03-09T20:59:25+00:00
v4l2: make buffer release thread-safe

This is so that blocks can be released from outside the demuxer.

- - - - -
2d3e556b by Rémi Denis-Courmont at 2022-03-09T20:59:25+00:00
v4l2: increase buffer count

- - - - -
0c4a68b1 by Rémi Denis-Courmont at 2022-03-09T20:59:25+00:00
v4l2: do not copy dequeued buffer

...unless we are running out and have no other option left.
This provides zero-copy video capture in most cases.

Note though that the rawvideo decoder will still copy frames to adjust
pitches and transfer from capture buffers to picture buffers (even if
the pitches do not need adjustments).

- - - - -


6 changed files:

- modules/access/Makefile.am
- modules/access/v4l2/access.c
- + modules/access/v4l2/buffers.c
- modules/access/v4l2/demux.c
- modules/access/v4l2/v4l2.h
- modules/access/v4l2/video.c


Changes:

=====================================
modules/access/Makefile.am
=====================================
@@ -147,6 +147,7 @@ libv4l2_plugin_la_SOURCES = \
 	access/v4l2/linux/v4l2-common.h \
 	access/v4l2/linux/v4l2-controls.h \
 	access/v4l2/v4l2.c \
+	access/v4l2/buffers.c \
 	access/v4l2/video.c \
 	access/v4l2/vbi.c \
 	access/v4l2/demux.c \


=====================================
modules/access/v4l2/access.c
=====================================
@@ -33,6 +33,7 @@
 
 #include <vlc_common.h>
 #include <vlc_access.h>
+#include <vlc_es.h>
 #include <vlc_interrupt.h>
 
 #include "v4l2.h"
@@ -40,165 +41,14 @@
 typedef struct
 {
     int fd;
+    struct vlc_v4l2_buffers *pool;
+    uint32_t blocksize;
     uint32_t block_flags;
-    union
-    {
-        uint32_t bufc;
-        uint32_t blocksize;
-    };
-    struct buffer_t *bufv;
     vlc_v4l2_ctrl_t *controls;
 } access_sys_t;
 
-static block_t *MMapBlock (stream_t *, bool *);
-static block_t *ReadBlock (stream_t *, bool *);
-static int AccessControl( stream_t *, int, va_list );
-static int InitVideo(stream_t *, int, uint32_t);
-
-int AccessOpen( vlc_object_t *obj )
-{
-    stream_t *access = (stream_t *)obj;
-
-    if( access->b_preparsing )
-        return VLC_EGENERIC;
-
-    access_sys_t *sys = calloc (1, sizeof (*sys));
-    if( unlikely(sys == NULL) )
-        return VLC_ENOMEM;
-    access->p_sys = sys;
-
-    ParseMRL( obj, access->psz_location );
-
-    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 (access, fd, caps))
-    {
-        v4l2_close (fd);
-        goto error;
-    }
-
-    sys->controls = ControlsInit(vlc_object_parent(obj), fd);
-    access->pf_seek = NULL;
-    access->pf_control = AccessControl;
-    return VLC_SUCCESS;
-error:
-    free (sys);
-    return VLC_EGENERIC;
-}
-
-int InitVideo (stream_t *access, int fd, uint32_t caps)
-{
-    access_sys_t *sys = access->p_sys;
-
-    if (!(caps & V4L2_CAP_VIDEO_CAPTURE))
-    {
-        msg_Err (access, "not a video capture device");
-        return -1;
-    }
-
-    v4l2_std_id std;
-    if (SetupInput (VLC_OBJECT(access), fd, &std))
-        return -1;
-
-    /* NOTE: The V4L access_demux expects a VLC FOURCC as "chroma". It is used to set the
-     * es_format_t structure correctly. However, the V4L access (*here*) has no use for a
-     * VLC FOURCC and expects a V4L2 format directly instead. That is confusing :-( */
-    uint32_t pixfmt = 0;
-    char *fmtstr = var_InheritString (access, CFG_PREFIX"chroma");
-    if (fmtstr != NULL && strlen (fmtstr) <= 4)
-    {
-        memcpy (&pixfmt, fmtstr, strlen (fmtstr));
-        free (fmtstr);
-    }
-    else
-    /* Use the default^Wprevious format if none specified */
-    {
-        struct v4l2_format fmt = { .type = V4L2_BUF_TYPE_VIDEO_CAPTURE };
-        if (v4l2_ioctl (fd, VIDIOC_G_FMT, &fmt) < 0)
-        {
-            msg_Err (access, "cannot get default format: %s",
-                     vlc_strerror_c(errno));
-            return -1;
-        }
-        pixfmt = fmt.fmt.pix.pixelformat;
-    }
-    msg_Dbg (access, "selected format %4.4s", (const char *)&pixfmt);
-
-    struct v4l2_format fmt;
-    struct v4l2_streamparm parm;
-    if (SetupFormat (access, fd, pixfmt, &fmt, &parm))
-        return -1;
-
-    msg_Dbg (access, "%"PRIu32" bytes for complete image", fmt.fmt.pix.sizeimage);
-    /* Check interlacing */
-    switch (fmt.fmt.pix.field)
-    {
-        case V4L2_FIELD_INTERLACED:
-            msg_Dbg (access, "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_INTERLACED_TB:
-            msg_Dbg (access, "Interlacing setting: interleaved top bottom" );
-            sys->block_flags = BLOCK_FLAG_TOP_FIELD_FIRST;
-            break;
-        case V4L2_FIELD_INTERLACED_BT:
-            msg_Dbg (access, "Interlacing setting: interleaved bottom top" );
-            sys->block_flags = BLOCK_FLAG_BOTTOM_FIELD_FIRST;
-            break;
-        default:
-            break;
-    }
-
-    /* Init I/O method */
-    if (caps & V4L2_CAP_STREAMING)
-    {
-        sys->bufc = 4;
-        sys->bufv = StartMmap (VLC_OBJECT(access), fd, &sys->bufc);
-        if (sys->bufv == NULL)
-            return -1;
-        access->pf_block = MMapBlock;
-    }
-    else if (caps & V4L2_CAP_READWRITE)
-    {
-        sys->blocksize = fmt.fmt.pix.sizeimage;
-        sys->bufv = NULL;
-        access->pf_block = ReadBlock;
-    }
-    else
-    {
-        msg_Err (access, "no supported capture method");
-        return -1;
-    }
-
-    return 0;
-}
-
-void AccessClose( vlc_object_t *obj )
-{
-    stream_t *access = (stream_t *)obj;
-    access_sys_t *sys = access->p_sys;
-
-    if (sys->bufv != NULL)
-        StopMmap (sys->fd, sys->bufv, sys->bufc);
-    ControlsDeinit(vlc_object_parent(obj), sys->controls);
-    v4l2_close (sys->fd);
-    free( sys );
-}
-
 /* Wait for data */
-static int AccessPoll (stream_t *access)
+static int AccessPoll(stream_t *access)
 {
     access_sys_t *sys = access->p_sys;
     struct pollfd ufd;
@@ -206,19 +56,18 @@ static int AccessPoll (stream_t *access)
     ufd.fd = sys->fd;
     ufd.events = POLLIN;
 
-    return vlc_poll_i11e (&ufd, 1, -1);
+    return vlc_poll_i11e(&ufd, 1, -1);
 }
 
-
-static block_t *MMapBlock (stream_t *access, bool *restrict eof)
+static block_t *MMapBlock(stream_t *access, bool *restrict eof)
 {
     access_sys_t *sys = access->p_sys;
 
-    if (AccessPoll (access))
+    if (AccessPoll(access))
         return NULL;
 
-    block_t *block = GrabVideo (VLC_OBJECT(access), sys->fd, sys->bufv);
-    if( block != NULL )
+    block_t *block = GrabVideo(VLC_OBJECT(access), sys->pool);
+    if (block != NULL)
     {
         block->i_pts = block->i_dts = vlc_tick_now();
         block->i_flags |= sys->block_flags;
@@ -227,22 +76,22 @@ static block_t *MMapBlock (stream_t *access, bool *restrict eof)
     return block;
 }
 
-static block_t *ReadBlock (stream_t *access, bool *restrict eof)
+static block_t *ReadBlock(stream_t *access, bool *restrict eof)
 {
     access_sys_t *sys = access->p_sys;
 
-    if (AccessPoll (access))
+    if (AccessPoll(access))
         return NULL;
 
-    block_t *block = block_Alloc (sys->blocksize);
+    block_t *block = block_Alloc(sys->blocksize);
     if (unlikely(block == NULL))
         return NULL;
 
-    ssize_t val = v4l2_read (sys->fd, block->p_buffer, block->i_buffer);
+    ssize_t val = v4l2_read(sys->fd, block->p_buffer, block->i_buffer);
     if (val < 0)
     {
-        block_Release (block);
-        msg_Err (access, "cannot read buffer: %s", vlc_strerror_c(errno));
+        block_Release(block);
+        msg_Err(access, "cannot read buffer: %s", vlc_strerror_c(errno));
         *eof = true;
         return NULL;
     }
@@ -251,20 +100,20 @@ static block_t *ReadBlock (stream_t *access, bool *restrict eof)
     return block;
 }
 
-static int AccessControl( stream_t *access, int query, va_list args )
+static int AccessControl(stream_t *access, int query, va_list args)
 {
-    switch( query )
+    switch (query)
     {
         case STREAM_CAN_SEEK:
         case STREAM_CAN_FASTSEEK:
         case STREAM_CAN_PAUSE:
         case STREAM_CAN_CONTROL_PACE:
-            *va_arg( args, bool* ) = false;
+            *va_arg(args, bool *) = false;
             break;
 
         case STREAM_GET_PTS_DELAY:
             *va_arg(args,vlc_tick_t *) = VLC_TICK_FROM_MS(
-                var_InheritInteger( access, "live-caching" ) );
+                var_InheritInteger(access, "live-caching"));
             break;
 
         case STREAM_SET_PAUSE_STATE:
@@ -277,3 +126,85 @@ static int AccessControl( stream_t *access, int query, va_list args )
     }
     return VLC_SUCCESS;
 }
+
+void AccessClose(vlc_object_t *obj)
+{
+    stream_t *access = (stream_t *)obj;
+    access_sys_t *sys = access->p_sys;
+
+    if (sys->pool != NULL)
+        StopMmap(sys->pool);
+    ControlsDeinit(vlc_object_parent(obj), sys->controls);
+    v4l2_close(sys->fd);
+    free(sys);
+}
+
+static int InitVideo(stream_t *access, int fd, uint32_t caps)
+{
+    access_sys_t *sys = access->p_sys;
+    es_format_t es_fmt;
+
+    if (SetupVideo(VLC_OBJECT(access), fd, caps, &es_fmt, &sys->blocksize,
+                   &sys->block_flags))
+        return -1;
+
+    /* Init I/O method */
+    if (caps & V4L2_CAP_STREAMING)
+    {
+        sys->pool = StartMmap (VLC_OBJECT(access), fd, 16);
+        if (sys->pool == NULL)
+            return -1;
+        access->pf_block = MMapBlock;
+    }
+    else if (caps & V4L2_CAP_READWRITE)
+    {
+        access->pf_block = ReadBlock;
+    }
+    else
+    {
+        msg_Err (access, "no supported capture method");
+        return -1;
+    }
+
+    return 0;
+}
+
+int AccessOpen(vlc_object_t *obj)
+{
+    stream_t *access = (stream_t *)obj;
+
+    if (access->b_preparsing)
+        return VLC_EGENERIC;
+
+    access_sys_t *sys = calloc(1, sizeof (*sys));
+    if (unlikely(sys == NULL))
+        return VLC_ENOMEM;
+    access->p_sys = sys;
+
+    ParseMRL(obj, access->psz_location);
+
+    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(access, fd, caps))
+    {
+        v4l2_close(fd);
+        goto error;
+    }
+
+    sys->controls = ControlsInit(vlc_object_parent(obj), fd);
+    access->pf_seek = NULL;
+    access->pf_control = AccessControl;
+    return VLC_SUCCESS;
+error:
+    free(sys);
+    return VLC_EGENERIC;
+}


=====================================
modules/access/v4l2/buffers.c
=====================================
@@ -0,0 +1,278 @@
+/*****************************************************************************
+ * buffers.c: Video4Linux2 input module for vlc
+ *****************************************************************************
+ * Copyright (C) 2002-2009 VLC authors and VideoLAN
+ * Copyright (C) 2011-2012 Rémi Denis-Courmont
+ *
+ * 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 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 Lesser General Public License for more details.
+ *
+ * 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 <assert.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+
+#include <vlc_common.h>
+#include <vlc_block.h>
+
+#include "v4l2.h"
+
+vlc_tick_t GetBufferPTS(const struct v4l2_buffer *buf)
+{
+    vlc_tick_t pts;
+
+    switch (buf->flags & V4L2_BUF_FLAG_TIMESTAMP_MASK)
+    {
+        case V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC:
+            pts = vlc_tick_from_timeval(&buf->timestamp);
+            break;
+        case V4L2_BUF_FLAG_TIMESTAMP_UNKNOWN:
+        default:
+            pts = vlc_tick_now();
+            break;
+    }
+    return pts;
+}
+
+static void ReleaseBuffer(block_t *block)
+{
+    struct vlc_v4l2_buffer *buf = container_of(block, struct vlc_v4l2_buffer,
+                                               block);
+    struct vlc_v4l2_buffers *pool = buf->pool;
+    uint32_t index = buf - pool->bufs;
+    struct v4l2_buffer buf_req = {
+        .type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
+        .memory = V4L2_MEMORY_MMAP,
+        .index = index,
+    };
+    uint32_t mask;
+    int fd;
+
+    vlc_mutex_lock(&pool->lock);
+    mask = atomic_fetch_and_explicit(&pool->inflight, ~(1U << index),
+                                     memory_order_relaxed);
+    fd = pool->fd;
+    vlc_mutex_unlock(&pool->lock);
+    assert(mask & (1U << index));
+
+    assert(mask & (1U << index));
+
+    if (likely(fd >= 0)) {
+        /* Requeue the freed buffer */
+        v4l2_ioctl(pool->fd, VIDIOC_QBUF, &buf_req);
+        return;
+    }
+
+    v4l2_munmap(block->p_start, block->i_size);
+
+    if (vlc_popcount(mask) == 1) /* last active buffer? */
+        free(pool);
+}
+
+static const struct vlc_block_callbacks vlc_v4l2_buffer_cbs = {
+    ReleaseBuffer,
+};
+
+block_t *GrabVideo(vlc_object_t *demux, struct vlc_v4l2_buffers *restrict pool)
+{
+    int fd = pool->fd;
+    struct v4l2_buffer buf_req = {
+        .type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
+        .memory = V4L2_MEMORY_MMAP,
+    };
+    uint32_t mask;
+
+    /* Wait for next frame */
+    if (v4l2_ioctl(fd, VIDIOC_DQBUF, &buf_req) < 0)
+    {
+        switch (errno)
+        {
+            case EAGAIN:
+                return NULL;
+            case EIO:
+                /* Could ignore EIO, see spec. */
+                /* fall through */
+            default:
+                msg_Err(demux, "dequeue error: %s", vlc_strerror_c(errno));
+                return NULL;
+        }
+    }
+
+    assert(buf_req.index < pool->count);
+    mask = atomic_fetch_or_explicit(&pool->inflight, 1U << buf_req.index,
+                                    memory_order_relaxed);
+
+    struct vlc_v4l2_buffer *buf = pool->bufs + buf_req.index;
+    block_t *block = &buf->block;
+    /* Reinitialise the buffer */
+    block->p_buffer = block->p_start;
+    assert(buf_req.bytesused <= block->i_size);
+    block->i_buffer = buf_req.bytesused;
+    block->p_next = NULL;
+
+    if ((size_t)vlc_popcount(mask) == pool->count - 1) {
+        /* Running out of buffers! Memory copy forced. */
+        block = block_Duplicate(block);
+        block_Release(&buf->block);
+    }
+
+    block->i_pts = block->i_dts = GetBufferPTS(&buf_req);
+    return block;
+}
+
+/**
+ * Allocates memory-mapped buffers, queues them and start streaming.
+ * @param n requested buffers count
+ * @return array of allocated buffers (use free()), or NULL on error.
+ */
+struct vlc_v4l2_buffers *StartMmap(vlc_object_t *obj, int fd, unsigned int n)
+{
+    struct vlc_v4l2_buffers *pool;
+    struct v4l2_requestbuffers req = {
+        .count = n,
+        .type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
+        .memory = V4L2_MEMORY_MMAP,
+    };
+
+    if (v4l2_ioctl(fd, VIDIOC_REQBUFS, &req) < 0)
+    {
+        msg_Err(obj, "cannot allocate buffers: %s", vlc_strerror_c(errno));
+        return NULL;
+    }
+
+    if (req.count < 2)
+    {
+        msg_Err(obj, "cannot allocate enough buffers");
+        return NULL;
+    }
+
+    pool = malloc(sizeof (*pool) + req.count * sizeof (pool->bufs[0]));
+    if (unlikely(pool == NULL))
+        return NULL;
+
+    pool->fd = fd;
+    pool->inflight = 0;
+    pool->count = 0;
+    vlc_mutex_init(&pool->lock);
+
+    while (pool->count < req.count)
+    {
+        struct vlc_v4l2_buffer *const buf = pool->bufs + pool->count;
+        struct v4l2_buffer buf_req = {
+            .type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
+            .memory = V4L2_MEMORY_MMAP,
+            .index = pool->count,
+        };
+
+        if (v4l2_ioctl(fd, VIDIOC_QUERYBUF, &buf_req) < 0)
+        {
+            msg_Err(obj, "cannot query buffer %zu: %s", pool->count,
+                    vlc_strerror_c(errno));
+            goto error;
+        }
+
+        void *base = v4l2_mmap(NULL, buf_req.length, PROT_READ | PROT_WRITE,
+                               MAP_SHARED, fd, buf_req.m.offset);
+        if (base == MAP_FAILED)
+        {
+            msg_Err(obj, "cannot map buffer %"PRIu32": %s", buf_req.index,
+                    vlc_strerror_c(errno));
+            goto error;
+        }
+
+        block_Init(&buf->block, &vlc_v4l2_buffer_cbs, base, buf_req.length);
+        buf->pool = pool;
+        pool->count++;
+
+        /* Some drivers refuse to queue buffers before they are mapped. Bug? */
+        if (v4l2_ioctl(fd, VIDIOC_QBUF, &buf_req) < 0)
+        {
+            msg_Err(obj, "cannot queue buffer %"PRIu32": %s", buf_req.index,
+                     vlc_strerror_c(errno));
+            goto error;
+        }
+    }
+
+    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+    if (v4l2_ioctl(fd, VIDIOC_STREAMON, &type) < 0)
+    {
+        msg_Err (obj, "cannot start streaming: %s", vlc_strerror_c(errno));
+        goto error;
+    }
+    return pool;
+error:
+    StopMmap(pool);
+    return NULL;
+}
+
+void StopMmap(struct vlc_v4l2_buffers *pool)
+{
+    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+    const size_t count = pool->count;
+    uint32_t unused;
+
+    /* STREAMOFF implicitly dequeues all buffers */
+    v4l2_ioctl(pool->fd, VIDIOC_STREAMOFF, &type);
+
+    vlc_mutex_lock(&pool->lock);
+    pool->fd = -1;
+    unused = (~atomic_load_explicit(&pool->inflight, memory_order_relaxed))
+             & ((UINT64_C(1) << count) - 1);
+    atomic_fetch_or_explicit(&pool->inflight, unused, memory_order_relaxed);
+    vlc_mutex_unlock(&pool->lock);
+
+    for (size_t i = 0; i < count; i++)
+        if (unused & (1u << i))
+            block_Release(&pool->bufs[i].block);
+    /* Pool is freed whence all buffers are released (possibly here) */
+}
+
+/**
+ * Allocates user pointer buffers, and start streaming.
+ */
+int StartUserPtr(vlc_object_t *obj, int fd)
+{
+    struct v4l2_requestbuffers reqbuf = {
+        .type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
+        .memory = V4L2_MEMORY_USERPTR,
+        .count = 2,
+    };
+
+    if (v4l2_ioctl(fd, VIDIOC_REQBUFS, &reqbuf) < 0)
+    {
+        if (errno != EINVAL)
+            msg_Err(obj, "cannot reserve user buffers: %s",
+                    vlc_strerror_c(errno));
+        else
+            msg_Dbg(obj, "user buffers not supported");
+        return -1;
+    }
+    if (v4l2_ioctl(fd, VIDIOC_STREAMON, &reqbuf.type) < 0)
+    {
+        msg_Err(obj, "cannot start streaming: %s", vlc_strerror_c(errno));
+        return -1;
+    }
+    return 0;
+}


=====================================
modules/access/v4l2/demux.c
=====================================
@@ -47,12 +47,8 @@ typedef struct
     int fd;
     vlc_thread_t thread;
 
-    struct buffer_t *bufv;
-    union
-    {
-        uint32_t bufc;
-        uint32_t blocksize;
-    };
+    struct vlc_v4l2_buffers *pool;
+    uint32_t blocksize;
     uint32_t block_flags;
 
     es_out_id_t *es;
@@ -65,596 +61,22 @@ typedef struct
 #endif
 } demux_sys_t;
 
-static void *UserPtrThread (void *);
-static void *MmapThread (void *);
-static void *ReadThread (void *);
-static int DemuxControl( demux_t *, int, va_list );
-static int InitVideo (demux_t *, int fd, uint32_t caps);
-
-int DemuxOpen( vlc_object_t *obj )
-{
-    demux_t *demux = (demux_t *)obj;
-    if (demux->out == NULL)
-        return VLC_EGENERIC;
-
-    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 );
-
-    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))
-    {
-        v4l2_close (fd);
-        goto error;
-    }
-
-    sys->controls = ControlsInit(vlc_object_parent(obj), fd);
-    sys->start = vlc_tick_now ();
-    demux->pf_demux = NULL;
-    demux->pf_control = DemuxControl;
-    return VLC_SUCCESS;
-error:
-    free (sys);
-    return VLC_EGENERIC;
-}
-
-typedef struct
-{
-    uint32_t v4l2;
-    vlc_fourcc_t vlc;
-    uint8_t bpp; /**< Bytes per pixel (first plane) */
-    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, 1, 0, 0, 0 },
-    { V4L2_PIX_FMT_YVU420,  VLC_CODEC_YV12, 1, 0, 0, 0 },
-    { V4L2_PIX_FMT_YUV422P, VLC_CODEC_I422, 1, 0, 0, 0 },
-    /* Packed YUV 4:2:2 */
-    { V4L2_PIX_FMT_YUYV,    VLC_CODEC_YUYV, 2, 0, 0, 0 },
-    { V4L2_PIX_FMT_UYVY,    VLC_CODEC_UYVY, 2, 0, 0, 0 },
-    { V4L2_PIX_FMT_YVYU,    VLC_CODEC_YVYU, 2, 0, 0, 0 },
-    { V4L2_PIX_FMT_VYUY,    VLC_CODEC_VYUY, 2, 0, 0, 0 },
-
-    { V4L2_PIX_FMT_YUV411P, VLC_CODEC_I411, 1, 0, 0, 0 },
-
-    { V4L2_PIX_FMT_YUV410,  VLC_CODEC_I410, 1, 0, 0, 0 },
-//  { V4L2_PIX_FMT_YVU410     },
-
-    { V4L2_PIX_FMT_NV24,    VLC_CODEC_NV24, 1, 0, 0, 0 },
-    { V4L2_PIX_FMT_NV42,    VLC_CODEC_NV42, 1, 0, 0, 0 },
-    { V4L2_PIX_FMT_NV16,    VLC_CODEC_NV16, 1, 0, 0, 0 },
-    { V4L2_PIX_FMT_NV61,    VLC_CODEC_NV61, 1, 0, 0, 0 },
-    { V4L2_PIX_FMT_NV12,    VLC_CODEC_NV12, 1, 0, 0, 0 },
-    { V4L2_PIX_FMT_NV21,    VLC_CODEC_NV21, 1, 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, 4, 0xFF00, 0xFF0000, 0xFF000000 },
-    { V4L2_PIX_FMT_BGR32,   VLC_CODEC_RGB32, 4, 0xFF000000, 0xFF0000, 0xFF00 },
-    { V4L2_PIX_FMT_RGB24,   VLC_CODEC_RGB24, 3, 0xFF0000, 0x00FF00, 0x0000FF },
-    { V4L2_PIX_FMT_BGR24,   VLC_CODEC_RGB24, 3, 0x0000FF, 0x00FF00, 0xFF0000 },
-//  { V4L2_PIX_FMT_BGR666,    },
-//  { V4L2_PIX_FMT_RGB565,    },
-    { V4L2_PIX_FMT_RGB565X, VLC_CODEC_RGB16, 2,  0x001F,   0x07E0,   0xF800 },
-//  { V4L2_PIX_FMT_RGB555,    },
-    { V4L2_PIX_FMT_RGB555X, VLC_CODEC_RGB15, 2,  0x001F,   0x03E0,   0x7C00 },
-//  { V4L2_PIX_FMT_RGB444,  VLC_CODEC_RGB12, 2,  0x000F,   0xF000,   0x0F00 },
-#else
-    { V4L2_PIX_FMT_RGB32,   VLC_CODEC_RGB32, 4, 0x0000FF, 0x00FF00, 0xFF0000 },
-    { V4L2_PIX_FMT_BGR32,   VLC_CODEC_RGB32, 4, 0xFF0000, 0x00FF00, 0x0000FF },
-    { V4L2_PIX_FMT_RGB24,   VLC_CODEC_RGB24, 3, 0x0000FF, 0x00FF00, 0xFF0000 },
-    { V4L2_PIX_FMT_BGR24,   VLC_CODEC_RGB24, 3, 0xFF0000, 0x00FF00, 0x0000FF },
-//  { V4L2_PIX_FMT_BGR666,    },
-    { V4L2_PIX_FMT_RGB565,  VLC_CODEC_RGB16, 2,   0x001F,   0x07E0,   0xF800 },
-//  { V4L2_PIX_FMT_RGB565X,   },
-    { V4L2_PIX_FMT_RGB555,  VLC_CODEC_RGB15, 2,   0x001F,   0x03E0,   0x7C00 },
-//  { V4L2_PIX_FMT_RGB555X,   },
-//  { V4L2_PIX_FMT_RGB444,  VLC_CODEC_RGB12, 2,   0x0F00,   0x00F0,   0x000F },
-#endif
-//  { V4L2_PIX_FMT_RGB332,  VLC_CODEC_RGB8,  1,      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, 0 },
-    { V4L2_PIX_FMT_H264,    VLC_CODEC_H264, 0, 0, 0, 0 },
-    /* FIXME: fill p_extra for avc1... */
-//  { V4L2_PIX_FMT_H264_NO_SC, VLC_FOURCC('a','v','c','1'), 0, 0, 0, 0 }
-    { V4L2_PIX_FMT_MPEG4,   VLC_CODEC_MP4V, 0, 0, 0, 0 },
-    { V4L2_PIX_FMT_XVID,    VLC_CODEC_MP4V, 0, 0, 0, 0 },
-    { V4L2_PIX_FMT_H263,    VLC_CODEC_H263, 0, 0, 0, 0 },
-    { V4L2_PIX_FMT_MPEG2,   VLC_CODEC_MPGV, 0, 0, 0, 0 },
-    { V4L2_PIX_FMT_MPEG1,   VLC_CODEC_MPGV, 0, 0, 0, 0 },
-    { V4L2_PIX_FMT_VC1_ANNEX_G, VLC_CODEC_VC1, 0, 0, 0, 0 },
-    { V4L2_PIX_FMT_VC1_ANNEX_L, VLC_CODEC_VC1, 0, 0, 0, 0 },
-    //V4L2_PIX_FMT_MPEG -> use access
-
-    /* Reserved formats */
-    { V4L2_PIX_FMT_MJPEG,   VLC_CODEC_MJPG, 0, 0, 0, 0 },
-    //V4L2_PIX_FMT_DV -> use access
-
-    /* Grey scale */
-    { V4L2_PIX_FMT_Y16,     VLC_CODEC_GREY_16L, 2, 0, 0, 0 },
-    { V4L2_PIX_FMT_Y12,     VLC_CODEC_GREY_12L, 2, 0, 0, 0 },
-    { V4L2_PIX_FMT_Y10,     VLC_CODEC_GREY_10L, 2, 0, 0, 0 },
-//  { V4L2_PIX_FMT_Y10BPACK,  },
-    { V4L2_PIX_FMT_GREY,    VLC_CODEC_GREY, 1, 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_sys_t *sys = demux->p_sys;
-    v4l2_std_id std;
-
-    if (!(caps & V4L2_CAP_VIDEO_CAPTURE))
-    {
-        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);
-
-        if (dsc == NULL)
-            continue; /* ignore VLC-unsupported codec */
-
-        if (dsc->vlc == reqfourcc)
-        {
-            msg_Dbg (demux, "  matches the requested format");
-            selected = dsc;
-            break; /* always select the requested format if found */
-        }
-
-        if (codec.flags & V4L2_FMT_FLAG_EMULATED)
-        {
-            if (native)
-                continue; /* ignore emulated format if possible */
-        }
-        else
-            native = true;
-
-        if (vlc_v4l2_fmt_rank (dsc) > vlc_v4l2_fmt_rank (selected))
-            continue; /* ignore if rank is worse */
-
-        selected = dsc;
-    }
-
-    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");
-            sys->block_flags = BLOCK_FLAG_TOP_FIELD_FIRST|BLOCK_FLAG_SINGLE_FIELD;
-            break;
-        case V4L2_FIELD_BOTTOM:
-            msg_Dbg (demux, "Interlacing setting: bottom field only");
-            sys->block_flags = BLOCK_FLAG_BOTTOM_FIELD_FIRST|BLOCK_FLAG_SINGLE_FIELD;
-            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_chroma = 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_visible_width = fmt.fmt.pix.width;
-    if (fmt.fmt.pix.bytesperline != 0 && selected->bpp != 0)
-        es_fmt.video.i_width = fmt.fmt.pix.bytesperline / selected->bpp;
-    else
-        es_fmt.video.i_width = fmt.fmt.pix.width;
-    es_fmt.video.i_visible_height =
-    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);
-
-    sys->interval = vlc_tick_from_samples(
-        parm.parm.capture.timeperframe.numerator,
-        parm.parm.capture.timeperframe.denominator);
-
-    msg_Dbg (demux, "color primaries: %u", fmt.fmt.pix.colorspace);
-    switch (fmt.fmt.pix.colorspace)
-    {
-        case V4L2_COLORSPACE_DEFAULT:
-            break;
-        case V4L2_COLORSPACE_SMPTE170M:
-            es_fmt.video.primaries = COLOR_PRIMARIES_BT601_525;
-            es_fmt.video.transfer = TRANSFER_FUNC_BT709;
-            es_fmt.video.space = COLOR_SPACE_BT601;
-            break;
-        case V4L2_COLORSPACE_SMPTE240M: /* not supported */
-            break;
-        case V4L2_COLORSPACE_REC709:
-            es_fmt.video.primaries = COLOR_PRIMARIES_BT709;
-            es_fmt.video.transfer = TRANSFER_FUNC_BT709;
-            es_fmt.video.space = COLOR_SPACE_BT709;
-            break;
-        case V4L2_COLORSPACE_470_SYSTEM_M:
-            es_fmt.video.transfer = TRANSFER_FUNC_BT709;
-            es_fmt.video.space = COLOR_SPACE_BT601;
-            break;
-        case V4L2_COLORSPACE_470_SYSTEM_BG:
-            es_fmt.video.primaries = COLOR_PRIMARIES_BT601_625;
-            es_fmt.video.transfer = TRANSFER_FUNC_BT709;
-            es_fmt.video.space = COLOR_SPACE_BT601;
-            break;
-        case V4L2_COLORSPACE_JPEG:
-            es_fmt.video.primaries = COLOR_PRIMARIES_SRGB;
-            es_fmt.video.transfer = TRANSFER_FUNC_SRGB;
-            es_fmt.video.space = COLOR_SPACE_BT601;
-            es_fmt.video.color_range = COLOR_RANGE_FULL;
-            break;
-        case V4L2_COLORSPACE_SRGB:
-            es_fmt.video.primaries = COLOR_PRIMARIES_SRGB;
-            es_fmt.video.transfer = TRANSFER_FUNC_SRGB;
-            es_fmt.video.space = COLOR_SPACE_UNDEF; /* sYCC unsupported */
-            break;
-        case V4L2_COLORSPACE_ADOBERGB: /* not supported */
-            es_fmt.video.space = COLOR_SPACE_BT601;
-            break;
-        case V4L2_COLORSPACE_BT2020:
-            es_fmt.video.primaries = COLOR_PRIMARIES_BT2020;
-            es_fmt.video.transfer = TRANSFER_FUNC_BT2020;
-            es_fmt.video.space = COLOR_SPACE_BT2020;
-            break;
-        case V4L2_COLORSPACE_RAW:
-            es_fmt.video.transfer = TRANSFER_FUNC_LINEAR;
-            break;
-        case V4L2_COLORSPACE_DCI_P3:
-            es_fmt.video.primaries = COLOR_PRIMARIES_DCI_P3;
-            es_fmt.video.transfer = TRANSFER_FUNC_UNDEF;
-            es_fmt.video.space = COLOR_SPACE_BT2020;
-            break;
-        default:
-            msg_Warn (demux, "unknown color space %u", fmt.fmt.pix.colorspace);
-            break;
-    }
-
-    msg_Dbg (demux, "transfer function: %u", fmt.fmt.pix.xfer_func);
-    switch (fmt.fmt.pix.xfer_func)
-    {
-        case V4L2_XFER_FUNC_DEFAULT:
-            /* If transfer function is default, the transfer function is
-             * inferred from the colorspace value for backward compatibility.
-             * See V4L2 documentation for details. */
-            break;
-        case V4L2_XFER_FUNC_709:
-            es_fmt.video.transfer = TRANSFER_FUNC_BT709;
-            break;
-        case V4L2_XFER_FUNC_SRGB:
-            es_fmt.video.transfer = TRANSFER_FUNC_SRGB;
-            break;
-        case V4L2_XFER_FUNC_ADOBERGB:
-        case V4L2_XFER_FUNC_SMPTE240M:
-            es_fmt.video.transfer = TRANSFER_FUNC_UNDEF;
-            break;
-        case V4L2_XFER_FUNC_NONE:
-            es_fmt.video.transfer = TRANSFER_FUNC_LINEAR;
-            break;
-        case V4L2_XFER_FUNC_DCI_P3:
-        case V4L2_XFER_FUNC_SMPTE2084:
-            es_fmt.video.transfer = TRANSFER_FUNC_UNDEF;
-            break;
-        default:
-            msg_Warn (demux, "unknown transfer function %u",
-                      fmt.fmt.pix.xfer_func);
-            break;
-    }
-
-    msg_Dbg (demux, "YCbCr encoding: %u", fmt.fmt.pix.ycbcr_enc);
-    switch (fmt.fmt.pix.ycbcr_enc)
-    {
-        case V4L2_YCBCR_ENC_DEFAULT:
-            /* Same as transfer function - use color space value */
-            break;
-        case V4L2_YCBCR_ENC_601:
-            es_fmt.video.space = COLOR_SPACE_BT601;
-            break;
-        case V4L2_YCBCR_ENC_709:
-            es_fmt.video.space = COLOR_SPACE_BT709;
-            break;
-        case V4L2_YCBCR_ENC_XV601:
-        case V4L2_YCBCR_ENC_XV709:
-        case V4L2_YCBCR_ENC_SYCC:
-            break;
-        case V4L2_YCBCR_ENC_BT2020:
-            es_fmt.video.space = COLOR_SPACE_BT2020;
-            break;
-        case V4L2_YCBCR_ENC_BT2020_CONST_LUM:
-        case V4L2_YCBCR_ENC_SMPTE240M:
-            break;
-        default:
-            msg_Err (demux, "unknown YCbCr encoding: %u",
-                     fmt.fmt.pix.ycbcr_enc);
-            break;
-    }
-
-    msg_Dbg (demux, "quantization: %u", fmt.fmt.pix.quantization);
-    switch (fmt.fmt.pix.quantization)
-    {
-        case V4L2_QUANTIZATION_DEFAULT:
-            break;
-        case V4L2_QUANTIZATION_FULL_RANGE:
-            es_fmt.video.color_range = COLOR_RANGE_FULL;
-            break;
-        case V4L2_QUANTIZATION_LIM_RANGE:
-            es_fmt.video.color_range = COLOR_RANGE_LIMITED;
-            break;
-        default:
-            msg_Err (demux, "unknown quantization: %u",
-                     fmt.fmt.pix.quantization);
-            break;
-    }
-
-    msg_Dbg (demux, "added new video ES %4.4s %ux%u (%ux%u)",
-             (char *)&es_fmt.i_codec,
-             es_fmt.video.i_visible_width, es_fmt.video.i_visible_height,
-             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);
-        }
-    }
-    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;
-    }
-
-#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
-
-    if (vlc_clone (&sys->thread, entry, demux, VLC_THREAD_PRIORITY_INPUT))
-    {
-#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;
-}
-
-void DemuxClose( vlc_object_t *obj )
-{
-    demux_t *demux = (demux_t *)obj;
-    demux_sys_t *sys = demux->p_sys;
-
-    vlc_cancel (sys->thread);
-    vlc_join (sys->thread, NULL);
-    if (sys->bufv != NULL)
-        StopMmap (sys->fd, sys->bufv, sys->bufc);
-    ControlsDeinit(vlc_object_parent(obj), sys->controls);
-    v4l2_close (sys->fd);
-
-#ifdef ZVBI_COMPILED
-    if (sys->vbi != NULL)
-        CloseVBI (sys->vbi);
-#endif
-
-    free( sys );
-}
-
 /** Allocates and queue a user buffer using mmap(). */
-static block_t *UserPtrQueue (vlc_object_t *obj, int fd, size_t length)
+static block_t *UserPtrQueue(vlc_object_t *obj, int fd, size_t length)
 {
-    void *ptr = mmap (NULL, length, PROT_READ | PROT_WRITE,
-                      MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+    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: %s", length,
-                 vlc_strerror_c(errno));
+        msg_Err(obj, "cannot allocate %zu-bytes buffer: %s", length,
+                vlc_strerror_c(errno));
         return NULL;
     }
 
-    block_t *block = block_mmap_Alloc (ptr, length);
+    block_t *block = block_mmap_Alloc(ptr, length);
     if (unlikely(block == NULL))
     {
-        munmap (ptr, length);
+        munmap(ptr, length);
         return NULL;
     }
 
@@ -667,16 +89,16 @@ static block_t *UserPtrQueue (vlc_object_t *obj, int fd, size_t length)
         .length = length,
     };
 
-    if (v4l2_ioctl (fd, VIDIOC_QBUF, &buf) < 0)
+    if (v4l2_ioctl(fd, VIDIOC_QBUF, &buf) < 0)
     {
-        msg_Err (obj, "cannot queue buffer: %s", vlc_strerror_c(errno));
-        block_Release (block);
+        msg_Err(obj, "cannot queue buffer: %s", vlc_strerror_c(errno));
+        block_Release(block);
         return NULL;
     }
     return block;
 }
 
-static void *UserPtrThread (void *data)
+static void *UserPtrThread(void *data)
 {
     demux_t *demux = data;
     demux_sys_t *sys = demux->p_sys;
@@ -687,46 +109,46 @@ static void *UserPtrThread (void *data)
     ufd[0].fd = fd;
     ufd[0].events = POLLIN;
 
-    int canc = vlc_savecancel ();
+    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);
+        block_t *block = UserPtrQueue(VLC_OBJECT(demux), fd, sys->blocksize);
         if (block == NULL)
             break;
 
         /* Wait for data */
-        vlc_restorecancel (canc);
-        block_cleanup_push (block);
-        while (poll (ufd, numfds, -1) == -1)
+        vlc_restorecancel(canc);
+        block_cleanup_push(block);
+        while (poll(ufd, numfds, -1) == -1)
            if (errno != EINTR)
-               msg_Err (demux, "poll error: %s", vlc_strerror_c(errno));
-        vlc_cleanup_pop ();
-        canc = vlc_savecancel ();
+               msg_Err(demux, "poll error: %s", vlc_strerror_c(errno));
+        vlc_cleanup_pop();
+        canc = vlc_savecancel();
 
-        if (v4l2_ioctl (fd, VIDIOC_DQBUF, &buf) < 0)
+        if (v4l2_ioctl(fd, VIDIOC_DQBUF, &buf) < 0)
         {
-            msg_Err (demux, "cannot dequeue buffer: %s",
-                     vlc_strerror_c(errno));
-            block_Release (block);
+            msg_Err(demux, "cannot dequeue buffer: %s",
+                    vlc_strerror_c(errno));
+            block_Release(block);
             continue;
         }
 
-        assert (block->p_buffer == (void *)buf.m.userptr);
+        assert(block->p_buffer == (void *)buf.m.userptr);
         block->i_buffer = buf.length;
-        block->i_pts = block->i_dts = GetBufferPTS (&buf);
+        block->i_pts = block->i_dts = GetBufferPTS(&buf);
         block->i_flags |= sys->block_flags;
         es_out_SetPCR(demux->out, block->i_pts);
-        es_out_Send (demux->out, sys->es, block);
+        es_out_Send(demux->out, sys->es, block);
     }
-    vlc_restorecancel (canc); /* <- hmm, this is purely cosmetic */
+    vlc_restorecancel(canc); /* <- hmm, this is purely cosmetic */
     return NULL;
 }
 
-static void *MmapThread (void *data)
+static void *MmapThread(void *data)
 {
     demux_t *demux = data;
     demux_sys_t *sys = demux->p_sys;
@@ -740,7 +162,7 @@ static void *MmapThread (void *data)
 #ifdef ZVBI_COMPILED
     if (sys->vbi != NULL)
     {
-        ufd[1].fd = GetFdVBI (sys->vbi);
+        ufd[1].fd = GetFdVBI(sys->vbi);
         ufd[1].events = POLLIN;
         numfds++;
     }
@@ -749,35 +171,35 @@ static void *MmapThread (void *data)
     for (;;)
     {
         /* Wait for data */
-        if (poll (ufd, numfds, -1) == -1)
+        if (poll(ufd, numfds, -1) == -1)
         {
            if (errno != EINTR)
-               msg_Err (demux, "poll error: %s", vlc_strerror_c(errno));
+               msg_Err(demux, "poll error: %s", vlc_strerror_c(errno));
            continue;
         }
 
-        if( ufd[0].revents )
+        if (ufd[0].revents)
         {
-            int canc = vlc_savecancel ();
-            block_t *block = GrabVideo (VLC_OBJECT(demux), fd, sys->bufv);
+            int canc = vlc_savecancel();
+            block_t *block = GrabVideo(VLC_OBJECT(demux), sys->pool);
             if (block != NULL)
             {
                 block->i_flags |= sys->block_flags;
                 es_out_SetPCR(demux->out, block->i_pts);
-                es_out_Send (demux->out, sys->es, block);
+                es_out_Send(demux->out, sys->es, block);
             }
-            vlc_restorecancel (canc);
+            vlc_restorecancel(canc);
         }
 #ifdef ZVBI_COMPILED
         if (sys->vbi != NULL && ufd[1].revents)
-            GrabVBI (demux, sys->vbi);
+            GrabVBI(demux, sys->vbi);
 #endif
     }
 
-    vlc_assert_unreachable ();
+    vlc_assert_unreachable();
 }
 
-static void *ReadThread (void *data)
+static void *ReadThread(void *data)
 {
     demux_t *demux = data;
     demux_sys_t *sys = demux->p_sys;
@@ -791,7 +213,7 @@ static void *ReadThread (void *data)
 #ifdef ZVBI_COMPILED
     if (sys->vbi != NULL)
     {
-        ufd[1].fd = GetFdVBI (sys->vbi);
+        ufd[1].fd = GetFdVBI(sys->vbi);
         ufd[1].events = POLLIN;
         numfds++;
     }
@@ -800,43 +222,43 @@ static void *ReadThread (void *data)
     for (;;)
     {
         /* Wait for data */
-        if (poll (ufd, numfds, -1) == -1)
+        if (poll(ufd, numfds, -1) == -1)
         {
            if (errno != EINTR)
-               msg_Err (demux, "poll error: %s", vlc_strerror_c(errno));
+               msg_Err(demux, "poll error: %s", vlc_strerror_c(errno));
            continue;
         }
 
-        if( ufd[0].revents )
+        if (ufd[0].revents)
         {
-            block_t *block = block_Alloc (sys->blocksize);
+            block_t *block = block_Alloc(sys->blocksize);
             if (unlikely(block == NULL))
             {
-                msg_Err (demux, "read error: %s", vlc_strerror_c(errno));
-                v4l2_read (fd, NULL, 0); /* discard frame */
+                msg_Err(demux, "read error: %s", vlc_strerror_c(errno));
+                v4l2_read(fd, NULL, 0); /* discard frame */
                 continue;
             }
-            block->i_pts = block->i_dts = vlc_tick_now ();
+            block->i_pts = block->i_dts = vlc_tick_now();
             block->i_flags |= sys->block_flags;
 
-            int canc = vlc_savecancel ();
-            ssize_t val = v4l2_read (fd, block->p_buffer, block->i_buffer);
+            int canc = vlc_savecancel();
+            ssize_t val = v4l2_read(fd, block->p_buffer, block->i_buffer);
             if (val != -1)
             {
                 block->i_buffer = val;
                 es_out_SetPCR(demux->out, block->i_pts);
-                es_out_Send (demux->out, sys->es, block);
+                es_out_Send(demux->out, sys->es, block);
             }
             else
-                block_Release (block);
-            vlc_restorecancel (canc);
+                block_Release(block);
+            vlc_restorecancel(canc);
         }
 #ifdef ZVBI_COMPILED
         if (sys->vbi != NULL && ufd[1].revents)
-            GrabVBI (demux, sys->vbi);
+            GrabVBI(demux, sys->vbi);
 #endif
     }
-    vlc_assert_unreachable ();
+    vlc_assert_unreachable();
 }
 
 static int DemuxControl( demux_t *demux, int query, va_list args )
@@ -873,3 +295,142 @@ static int DemuxControl( demux_t *demux, int query, va_list args )
 
     return VLC_EGENERIC;
 }
+
+static int InitVideo (demux_t *demux, int fd, uint32_t caps)
+{
+    demux_sys_t *sys = demux->p_sys;
+    es_format_t es_fmt;
+
+    if (SetupVideo(VLC_OBJECT(demux), fd, caps, &es_fmt,
+                   &sys->blocksize, &sys->block_flags))
+        return -1;
+    if (es_fmt.i_codec == 0)
+        return -1; /* defer to access */
+
+    sys->interval = vlc_tick_from_samples(es_fmt.video.i_frame_rate,
+                                          es_fmt.video.i_frame_rate_base);
+    sys->es = es_out_Add (demux->out, &es_fmt);
+
+    /* Init I/O method */
+    void *(*entry) (void *);
+    if (caps & V4L2_CAP_STREAMING)
+    {
+        if (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->pool = NULL;
+            sys->blocksize = (sys->blocksize + pagemask) & ~pagemask;
+            entry = UserPtrThread;
+            msg_Dbg (demux, "streaming with %"PRIu32"-bytes user buffers",
+                     sys->blocksize);
+        }
+        else /* fall back to memory map */
+        {
+            sys->pool = StartMmap(VLC_OBJECT(demux), fd, 16);
+            if (sys->pool == NULL)
+                return -1;
+            entry = MmapThread;
+            msg_Dbg(demux, "streaming with %zu memory-mapped buffers",
+                    sys->pool->count);
+        }
+    }
+    else if (caps & V4L2_CAP_READWRITE)
+    {
+        sys->pool = NULL;
+        entry = ReadThread;
+        msg_Dbg (demux, "reading %"PRIu32" bytes at a time", sys->blocksize);
+    }
+    else
+    {
+        msg_Err (demux, "no supported capture method");
+        return -1;
+    }
+
+#ifdef ZVBI_COMPILED
+    {
+        char *vbi_path = var_InheritString (demux, CFG_PREFIX"vbidev");
+        if (vbi_path != NULL)
+            sys->vbi = OpenVBI (demux, vbi_path);
+        free(vbi_path);
+    }
+#endif
+
+    if (vlc_clone (&sys->thread, entry, demux, VLC_THREAD_PRIORITY_INPUT))
+    {
+#ifdef ZVBI_COMPILED
+        if (sys->vbi != NULL)
+            CloseVBI (sys->vbi);
+#endif
+        if (sys->pool != NULL)
+            StopMmap(sys->pool);
+        return -1;
+    }
+    return 0;
+}
+
+void DemuxClose( vlc_object_t *obj )
+{
+    demux_t *demux = (demux_t *)obj;
+    demux_sys_t *sys = demux->p_sys;
+
+    vlc_cancel (sys->thread);
+    vlc_join (sys->thread, NULL);
+    if (sys->pool != NULL)
+        StopMmap(sys->pool);
+    ControlsDeinit(vlc_object_parent(obj), sys->controls);
+    v4l2_close (sys->fd);
+
+#ifdef ZVBI_COMPILED
+    if (sys->vbi != NULL)
+        CloseVBI (sys->vbi);
+#endif
+
+    free( sys );
+}
+
+int DemuxOpen( vlc_object_t *obj)
+{
+    demux_t *demux = (demux_t *)obj;
+    if (demux->out == NULL)
+        return VLC_EGENERIC;
+
+    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);
+
+    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))
+    {
+        v4l2_close(fd);
+        goto error;
+    }
+
+    sys->controls = ControlsInit(vlc_object_parent(obj), fd);
+    sys->start = vlc_tick_now();
+    demux->pf_demux = NULL;
+    demux->pf_control = DemuxControl;
+    return VLC_SUCCESS;
+error:
+    free(sys);
+    return VLC_EGENERIC;
+}


=====================================
modules/access/v4l2/v4l2.h
=====================================
@@ -37,10 +37,19 @@ extern int (*v4l2_munmap) (void *, size_t);
 
 typedef struct vlc_v4l2_ctrl vlc_v4l2_ctrl_t;
 
-struct buffer_t
-{
-    void *  start;
-    size_t  length;
+#include <vlc_block.h>
+
+struct vlc_v4l2_buffer {
+    block_t block;
+    struct vlc_v4l2_buffers *pool;
+};
+
+struct vlc_v4l2_buffers {
+    int fd;
+    _Atomic uint32_t inflight;
+    vlc_mutex_t lock;
+    size_t count;
+    struct vlc_v4l2_buffer bufs[];
 };
 
 /* v4l2.c */
@@ -49,19 +58,16 @@ int OpenDevice (vlc_object_t *, const char *, uint32_t *);
 v4l2_std_id var_InheritStandard (vlc_object_t *, const char *);
 
 /* video.c */
-int SetupInput (vlc_object_t *, int fd, v4l2_std_id *std);
-int SetupFormat (vlc_object_t *, int, uint32_t,
-                 struct v4l2_format *, struct v4l2_streamparm *);
-#define SetupFormat(o,fd,fcc,fmt,p) \
-        SetupFormat(VLC_OBJECT(o),fd,fcc,fmt,p)
 int SetupTuner (vlc_object_t *, int fd, uint32_t);
+int SetupVideo(vlc_object_t *, int fd, uint32_t,
+               es_format_t *, uint32_t *, uint32_t *);
 
 int StartUserPtr (vlc_object_t *, int);
-struct buffer_t *StartMmap (vlc_object_t *, int, uint32_t *);
-void StopMmap (int, struct buffer_t *, uint32_t);
+struct vlc_v4l2_buffers *StartMmap(vlc_object_t *, int, unsigned int);
+void StopMmap(struct vlc_v4l2_buffers *);
 
 vlc_tick_t GetBufferPTS (const struct v4l2_buffer *);
-block_t* GrabVideo (vlc_object_t *, int, const struct buffer_t *);
+block_t* GrabVideo(vlc_object_t *, struct vlc_v4l2_buffers *);
 
 #ifdef ZVBI_COMPILED
 /* vbi.c */


=====================================
modules/access/v4l2/video.c
=====================================
@@ -31,10 +31,10 @@
 #include <assert.h>
 #include <errno.h>
 #include <sys/ioctl.h>
-#include <sys/mman.h>
 
 #include <vlc_common.h>
 #include <vlc_block.h>
+#include <vlc_es.h>
 
 #include "v4l2.h"
 
@@ -244,7 +244,7 @@ static int ResetCrop (vlc_object_t *obj, int fd)
     return 0;
 }
 
-int SetupInput (vlc_object_t *obj, int fd, v4l2_std_id *std)
+static int SetupInput(vlc_object_t *obj, int fd, v4l2_std_id *std)
 {
     struct v4l2_input input;
 
@@ -407,7 +407,6 @@ static int FindMaxRate (vlc_object_t *obj, int fd,
     return 0;
 }
 
-#undef SetupFormat
 /**
  * Finds the best possible frame rate and resolution.
  * @param fourcc pixel format
@@ -415,9 +414,9 @@ static int FindMaxRate (vlc_object_t *obj, int fd,
  * @param parm V4L2 capture streaming parameters [OUT]
  * @return 0 on success, -1 on failure.
  */
-int SetupFormat (vlc_object_t *obj, int fd, uint32_t fourcc,
-                 struct v4l2_format *restrict fmt,
-                 struct v4l2_streamparm *restrict parm)
+static int SetupFormat(vlc_object_t *obj, int fd, uint32_t fourcc,
+                       struct v4l2_format *restrict fmt,
+                       struct v4l2_streamparm *restrict parm)
 {
     memset (fmt, 0, sizeof (*fmt));
     fmt->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
@@ -566,177 +565,440 @@ int SetupFormat (vlc_object_t *obj, int fd, uint32_t fourcc,
     return 0;
 }
 
-vlc_tick_t GetBufferPTS (const struct v4l2_buffer *buf)
+typedef struct
 {
-    vlc_tick_t pts;
+    uint32_t v4l2;
+    vlc_fourcc_t vlc; /**< VLC FOURCC, 0 if muxed bitstream */
+    uint8_t bpp; /**< Bytes per pixel (first plane) */
+    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, 1, 0, 0, 0 },
+    { V4L2_PIX_FMT_YVU420,  VLC_CODEC_YV12, 1, 0, 0, 0 },
+    { V4L2_PIX_FMT_YUV422P, VLC_CODEC_I422, 1, 0, 0, 0 },
+    /* Packed YUV 4:2:2 */
+    { V4L2_PIX_FMT_YUYV,    VLC_CODEC_YUYV, 2, 0, 0, 0 },
+    { V4L2_PIX_FMT_UYVY,    VLC_CODEC_UYVY, 2, 0, 0, 0 },
+    { V4L2_PIX_FMT_YVYU,    VLC_CODEC_YVYU, 2, 0, 0, 0 },
+    { V4L2_PIX_FMT_VYUY,    VLC_CODEC_VYUY, 2, 0, 0, 0 },
+
+    { V4L2_PIX_FMT_YUV411P, VLC_CODEC_I411, 1, 0, 0, 0 },
+
+    { V4L2_PIX_FMT_YUV410,  VLC_CODEC_I410, 1, 0, 0, 0 },
+//  { V4L2_PIX_FMT_YVU410     },
+
+    { V4L2_PIX_FMT_NV24,    VLC_CODEC_NV24, 1, 0, 0, 0 },
+    { V4L2_PIX_FMT_NV42,    VLC_CODEC_NV42, 1, 0, 0, 0 },
+    { V4L2_PIX_FMT_NV16,    VLC_CODEC_NV16, 1, 0, 0, 0 },
+    { V4L2_PIX_FMT_NV61,    VLC_CODEC_NV61, 1, 0, 0, 0 },
+    { V4L2_PIX_FMT_NV12,    VLC_CODEC_NV12, 1, 0, 0, 0 },
+    { V4L2_PIX_FMT_NV21,    VLC_CODEC_NV21, 1, 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, 4, 0xFF00, 0xFF0000, 0xFF000000 },
+    { V4L2_PIX_FMT_BGR32,   VLC_CODEC_RGB32, 4, 0xFF000000, 0xFF0000, 0xFF00 },
+    { V4L2_PIX_FMT_RGB24,   VLC_CODEC_RGB24, 3, 0xFF0000, 0x00FF00, 0x0000FF },
+    { V4L2_PIX_FMT_BGR24,   VLC_CODEC_RGB24, 3, 0x0000FF, 0x00FF00, 0xFF0000 },
+//  { V4L2_PIX_FMT_BGR666,    },
+//  { V4L2_PIX_FMT_RGB565,    },
+    { V4L2_PIX_FMT_RGB565X, VLC_CODEC_RGB16, 2,  0x001F,   0x07E0,   0xF800 },
+//  { V4L2_PIX_FMT_RGB555,    },
+    { V4L2_PIX_FMT_RGB555X, VLC_CODEC_RGB15, 2,  0x001F,   0x03E0,   0x7C00 },
+//  { V4L2_PIX_FMT_RGB444,  VLC_CODEC_RGB12, 2,  0x000F,   0xF000,   0x0F00 },
+#else
+    { V4L2_PIX_FMT_RGB32,   VLC_CODEC_RGB32, 4, 0x0000FF, 0x00FF00, 0xFF0000 },
+    { V4L2_PIX_FMT_BGR32,   VLC_CODEC_RGB32, 4, 0xFF0000, 0x00FF00, 0x0000FF },
+    { V4L2_PIX_FMT_RGB24,   VLC_CODEC_RGB24, 3, 0x0000FF, 0x00FF00, 0xFF0000 },
+    { V4L2_PIX_FMT_BGR24,   VLC_CODEC_RGB24, 3, 0xFF0000, 0x00FF00, 0x0000FF },
+//  { V4L2_PIX_FMT_BGR666,    },
+    { V4L2_PIX_FMT_RGB565,  VLC_CODEC_RGB16, 2,   0x001F,   0x07E0,   0xF800 },
+//  { V4L2_PIX_FMT_RGB565X,   },
+    { V4L2_PIX_FMT_RGB555,  VLC_CODEC_RGB15, 2,   0x001F,   0x03E0,   0x7C00 },
+//  { V4L2_PIX_FMT_RGB555X,   },
+//  { V4L2_PIX_FMT_RGB444,  VLC_CODEC_RGB12, 2,   0x0F00,   0x00F0,   0x000F },
+#endif
+//  { V4L2_PIX_FMT_RGB332,  VLC_CODEC_RGB8,  1,      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, 0 },
+    { V4L2_PIX_FMT_H264,    VLC_CODEC_H264, 0, 0, 0, 0 },
+    /* FIXME: fill p_extra for avc1... */
+//  { V4L2_PIX_FMT_H264_NO_SC, VLC_FOURCC('a','v','c','1'), 0, 0, 0, 0 }
+    { V4L2_PIX_FMT_MPEG4,   VLC_CODEC_MP4V, 0, 0, 0, 0 },
+    { V4L2_PIX_FMT_XVID,    VLC_CODEC_MP4V, 0, 0, 0, 0 },
+    { V4L2_PIX_FMT_H263,    VLC_CODEC_H263, 0, 0, 0, 0 },
+    { V4L2_PIX_FMT_MPEG2,   VLC_CODEC_MPGV, 0, 0, 0, 0 },
+    { V4L2_PIX_FMT_MPEG1,   VLC_CODEC_MPGV, 0, 0, 0, 0 },
+    { V4L2_PIX_FMT_VC1_ANNEX_G, VLC_CODEC_VC1, 0, 0, 0, 0 },
+    { V4L2_PIX_FMT_VC1_ANNEX_L, VLC_CODEC_VC1, 0, 0, 0, 0 },
+    { V4L2_PIX_FMT_MPEG,    0,              0, 0, 0, 0 },
+
+    /* Reserved formats */
+    { V4L2_PIX_FMT_MJPEG,   VLC_CODEC_MJPG, 0, 0, 0, 0 },
+    { V4L2_PIX_FMT_DV,      0,              0, 0, 0, 0 },
+
+    /* Grey scale */
+    { V4L2_PIX_FMT_Y16,     VLC_CODEC_GREY_16L, 2, 0, 0, 0 },
+    { V4L2_PIX_FMT_Y12,     VLC_CODEC_GREY_12L, 2, 0, 0, 0 },
+    { V4L2_PIX_FMT_Y10,     VLC_CODEC_GREY_10L, 2, 0, 0, 0 },
+//  { V4L2_PIX_FMT_Y10BPACK,  },
+    { V4L2_PIX_FMT_GREY,    VLC_CODEC_GREY, 1, 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;
+}
 
-    switch (buf->flags & V4L2_BUF_FLAG_TIMESTAMP_MASK)
-    {
-        case V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC:
-            pts = vlc_tick_from_timeval( &buf->timestamp );
-            break;
-        case V4L2_BUF_FLAG_TIMESTAMP_UNKNOWN:
-        default:
-            pts = vlc_tick_now ();
-            break;
-    }
-    return pts;
+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;
 }
 
-/*****************************************************************************
- * GrabVideo: Grab a video frame
- *****************************************************************************/
-block_t *GrabVideo (vlc_object_t *demux, int fd,
-                    const struct buffer_t *restrict bufv)
+static vlc_fourcc_t var_InheritFourCC (vlc_object_t *obj, const char *varname)
 {
-    struct v4l2_buffer buf = {
-        .type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
-        .memory = V4L2_MEMORY_MMAP,
-    };
+    char *str = var_InheritString (obj, varname);
+    if (str == NULL)
+        return 0;
 
-    /* Wait for next frame */
-    if (v4l2_ioctl (fd, VIDIOC_DQBUF, &buf) < 0)
-    {
-        switch (errno)
-        {
-            case EAGAIN:
-                return NULL;
-            case EIO:
-                /* Could ignore EIO, see spec. */
-                /* fall through */
-            default:
-                msg_Err (demux, "dequeue error: %s", vlc_strerror_c(errno));
-                return NULL;
-        }
-    }
+    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)
 
-    /* Copy frame */
-    block_t *block = block_Alloc (buf.bytesused);
-    if (unlikely(block == NULL))
-        return NULL;
-    block->i_pts = block->i_dts = GetBufferPTS (&buf);
-    memcpy (block->p_buffer, bufv[buf.index].start, buf.bytesused);
+static void GetAR (int fd, unsigned *restrict num, unsigned *restrict den)
+{
+    struct v4l2_cropcap cropcap = { .type = V4L2_BUF_TYPE_VIDEO_CAPTURE };
 
-    /* Unlock */
-    if (v4l2_ioctl (fd, VIDIOC_QBUF, &buf) < 0)
+    /* TODO: get CROPCAP only once (see ResetCrop()). */
+    if (v4l2_ioctl (fd, VIDIOC_CROPCAP, &cropcap) < 0)
     {
-        msg_Err (demux, "queue error: %s", vlc_strerror_c(errno));
-        block_Release (block);
-        return NULL;
+        *num = *den = 1;
+        return;
     }
-    return block;
+    *num = cropcap.pixelaspect.numerator;
+    *den = cropcap.pixelaspect.denominator;
 }
 
-/**
- * Allocates user pointer buffers, and start streaming.
- */
-int StartUserPtr (vlc_object_t *obj, int fd)
+int SetupVideo(vlc_object_t *obj, int fd, uint32_t caps,
+               es_format_t *restrict es_fmt,
+               uint32_t *restrict block_size, uint32_t *restrict block_flags)
 {
-    struct v4l2_requestbuffers reqbuf = {
-        .type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
-        .memory = V4L2_MEMORY_USERPTR,
-        .count = 2,
-    };
+    v4l2_std_id std;
 
-    if (v4l2_ioctl (fd, VIDIOC_REQBUFS, &reqbuf) < 0)
+    if (!(caps & V4L2_CAP_VIDEO_CAPTURE))
     {
-        msg_Dbg (obj, "cannot reserve user buffers: %s",
-                 vlc_strerror_c(errno));
+        msg_Err(obj, "not a video capture device");
         return -1;
     }
-    if (v4l2_ioctl (fd, VIDIOC_STREAMON, &reqbuf.type) < 0)
-    {
-        msg_Err (obj, "cannot start streaming: %s", vlc_strerror_c(errno));
+
+    if (SetupInput(obj, fd, &std))
         return -1;
-    }
-    return 0;
-}
 
-/**
- * Allocates memory-mapped buffers, queues them and start streaming.
- * @param n requested buffers count [IN], allocated buffers count [OUT]
- * @return array of allocated buffers (use free()), or NULL on error.
- */
-struct buffer_t *StartMmap (vlc_object_t *obj, int fd, uint32_t *restrict n)
-{
-    struct v4l2_requestbuffers req = {
-        .count = *n,
-        .type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
-        .memory = V4L2_MEMORY_MMAP,
-    };
+    /* Picture format negotiation */
+    const vlc_v4l2_fmt_t *selected = NULL;
+    vlc_fourcc_t reqfourcc = var_InheritFourCC(obj, CFG_PREFIX"chroma");
+    bool native = false;
 
-    if (v4l2_ioctl (fd, VIDIOC_REQBUFS, &req) < 0)
-    {
-        msg_Err (obj, "cannot allocate buffers: %s", vlc_strerror_c(errno));
-        return NULL;
+    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(obj, " %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);
+
+        if (dsc == NULL)
+            continue; /* ignore VLC-unsupported codec */
+
+        if (dsc->vlc == reqfourcc)
+        {
+            msg_Dbg(obj, "  matches the requested format");
+            selected = dsc;
+            break; /* always select the requested format if found */
+        }
+
+        if (codec.flags & V4L2_FMT_FLAG_EMULATED)
+        {
+            if (native)
+                continue; /* ignore emulated format if possible */
+        }
+        else
+            native = true;
+
+        if (vlc_v4l2_fmt_rank (dsc) > vlc_v4l2_fmt_rank(selected))
+            continue; /* ignore if rank is worse */
+
+        selected = dsc;
     }
 
-    if (req.count < 2)
+    if (selected == NULL)
     {
-        msg_Err (obj, "cannot allocate enough buffers");
-        return NULL;
+        msg_Err(obj, "cannot negotiate supported video format");
+        return -1;
     }
+    msg_Dbg(obj, "selected format %4.4s (%4.4s)",
+            (const char *)&selected->v4l2, (const char *)&selected->vlc);
 
-    struct buffer_t *bufv = vlc_alloc (req.count, sizeof (*bufv));
-    if (unlikely(bufv == NULL))
-        return NULL;
+    /* Find best resolution and frame rate available */
+    struct v4l2_format fmt;
+    struct v4l2_streamparm parm;
+    if (SetupFormat(obj, fd, selected->v4l2, &fmt, &parm))
+        return -1;
 
-    uint32_t bufc = 0;
-    while (bufc < req.count)
+    /* Print extra info */
+    msg_Dbg(obj, "%d bytes maximum for complete image",
+            fmt.fmt.pix.sizeimage);
+    /* Check interlacing */
+    *block_flags = 0;
+    switch (fmt.fmt.pix.field)
     {
-        struct v4l2_buffer buf = {
-            .type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
-            .memory = V4L2_MEMORY_MMAP,
-            .index = bufc,
-        };
+        case V4L2_FIELD_NONE:
+            msg_Dbg(obj, "Interlacing setting: progressive");
+            break;
+        case V4L2_FIELD_TOP:
+            msg_Dbg(obj, "Interlacing setting: top field only");
+            *block_flags = BLOCK_FLAG_TOP_FIELD_FIRST
+                           | BLOCK_FLAG_SINGLE_FIELD;
+            break;
+        case V4L2_FIELD_BOTTOM:
+            msg_Dbg(obj, "Interlacing setting: bottom field only");
+            *block_flags = BLOCK_FLAG_BOTTOM_FIELD_FIRST
+                           | BLOCK_FLAG_SINGLE_FIELD;
+            break;
+        case V4L2_FIELD_INTERLACED:
+            msg_Dbg(obj, "Interlacing setting: interleaved");
+            /*if (NTSC)
+                *block_flags = BLOCK_FLAG_BOTTOM_FIELD_FIRST;
+            else*/
+                *block_flags = BLOCK_FLAG_TOP_FIELD_FIRST;
+            break;
+        case V4L2_FIELD_SEQ_TB:
+            msg_Dbg(obj, "Interlacing setting: sequential top bottom (TODO)");
+            break;
+        case V4L2_FIELD_SEQ_BT:
+            msg_Dbg(obj, "Interlacing setting: sequential bottom top (TODO)");
+            break;
+        case V4L2_FIELD_ALTERNATE:
+            msg_Dbg(obj, "Interlacing setting: alternate fields (TODO)");
+            fmt.fmt.pix.height *= 2;
+            break;
+        case V4L2_FIELD_INTERLACED_TB:
+            msg_Dbg(obj, "Interlacing setting: interleaved top bottom");
+            *block_flags = BLOCK_FLAG_TOP_FIELD_FIRST;
+            break;
+        case V4L2_FIELD_INTERLACED_BT:
+            msg_Dbg(obj, "Interlacing setting: interleaved bottom top");
+            *block_flags = BLOCK_FLAG_BOTTOM_FIELD_FIRST;
+            break;
+        default:
+            msg_Warn(obj, "Interlacing setting: unknown type (%d)",
+                     fmt.fmt.pix.field);
+            break;
+    }
 
-        if (v4l2_ioctl (fd, VIDIOC_QUERYBUF, &buf) < 0)
-        {
-            msg_Err (obj, "cannot query buffer %"PRIu32": %s", bufc,
-                     vlc_strerror_c(errno));
-            goto error;
-        }
+    /* Setup our unique elementary (video) stream format */
+    es_format_Init(es_fmt, VIDEO_ES, selected->vlc);
+    es_fmt->video.i_chroma = 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_visible_width = fmt.fmt.pix.width;
+    if (fmt.fmt.pix.bytesperline != 0 && selected->bpp != 0)
+        es_fmt->video.i_width = fmt.fmt.pix.bytesperline / selected->bpp;
+    else
+        es_fmt->video.i_width = fmt.fmt.pix.width;
+    es_fmt->video.i_visible_height =
+    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);
 
-        bufv[bufc].start = v4l2_mmap (NULL, buf.length, PROT_READ | PROT_WRITE,
-                                      MAP_SHARED, fd, buf.m.offset);
-        if (bufv[bufc].start == MAP_FAILED)
-        {
-            msg_Err (obj, "cannot map buffer %"PRIu32": %s", bufc,
-                     vlc_strerror_c(errno));
-            goto error;
-        }
-        bufv[bufc].length = buf.length;
-        bufc++;
+    msg_Dbg(obj, "color primaries: %u", fmt.fmt.pix.colorspace);
+    switch (fmt.fmt.pix.colorspace)
+    {
+        case V4L2_COLORSPACE_DEFAULT:
+            break;
+        case V4L2_COLORSPACE_SMPTE170M:
+            es_fmt->video.primaries = COLOR_PRIMARIES_BT601_525;
+            es_fmt->video.transfer = TRANSFER_FUNC_BT709;
+            es_fmt->video.space = COLOR_SPACE_BT601;
+            break;
+        case V4L2_COLORSPACE_SMPTE240M: /* not supported */
+            break;
+        case V4L2_COLORSPACE_REC709:
+            es_fmt->video.primaries = COLOR_PRIMARIES_BT709;
+            es_fmt->video.transfer = TRANSFER_FUNC_BT709;
+            es_fmt->video.space = COLOR_SPACE_BT709;
+            break;
+        case V4L2_COLORSPACE_470_SYSTEM_M:
+            es_fmt->video.transfer = TRANSFER_FUNC_BT709;
+            es_fmt->video.space = COLOR_SPACE_BT601;
+            break;
+        case V4L2_COLORSPACE_470_SYSTEM_BG:
+            es_fmt->video.primaries = COLOR_PRIMARIES_BT601_625;
+            es_fmt->video.transfer = TRANSFER_FUNC_BT709;
+            es_fmt->video.space = COLOR_SPACE_BT601;
+            break;
+        case V4L2_COLORSPACE_JPEG:
+            es_fmt->video.primaries = COLOR_PRIMARIES_SRGB;
+            es_fmt->video.transfer = TRANSFER_FUNC_SRGB;
+            es_fmt->video.space = COLOR_SPACE_BT601;
+            es_fmt->video.color_range = COLOR_RANGE_FULL;
+            break;
+        case V4L2_COLORSPACE_SRGB:
+            es_fmt->video.primaries = COLOR_PRIMARIES_SRGB;
+            es_fmt->video.transfer = TRANSFER_FUNC_SRGB;
+            es_fmt->video.space = COLOR_SPACE_UNDEF; /* sYCC unsupported */
+            break;
+        case V4L2_COLORSPACE_ADOBERGB: /* not supported */
+            es_fmt->video.space = COLOR_SPACE_BT601;
+            break;
+        case V4L2_COLORSPACE_BT2020:
+            es_fmt->video.primaries = COLOR_PRIMARIES_BT2020;
+            es_fmt->video.transfer = TRANSFER_FUNC_BT2020;
+            es_fmt->video.space = COLOR_SPACE_BT2020;
+            break;
+        case V4L2_COLORSPACE_RAW:
+            es_fmt->video.transfer = TRANSFER_FUNC_LINEAR;
+            break;
+        case V4L2_COLORSPACE_DCI_P3:
+            es_fmt->video.primaries = COLOR_PRIMARIES_DCI_P3;
+            es_fmt->video.transfer = TRANSFER_FUNC_UNDEF;
+            es_fmt->video.space = COLOR_SPACE_BT2020;
+            break;
+        default:
+            msg_Warn(obj, "unknown color space %u", fmt.fmt.pix.colorspace);
+            break;
+    }
 
-        /* Some drivers refuse to queue buffers before they are mapped. Bug? */
-        if (v4l2_ioctl (fd, VIDIOC_QBUF, &buf) < 0)
-        {
-            msg_Err (obj, "cannot queue buffer %"PRIu32": %s", bufc,
-                     vlc_strerror_c(errno));
-            goto error;
-        }
+    msg_Dbg(obj, "transfer function: %u", fmt.fmt.pix.xfer_func);
+    switch (fmt.fmt.pix.xfer_func)
+    {
+        case V4L2_XFER_FUNC_DEFAULT:
+            /* If transfer function is default, the transfer function is
+             * inferred from the colorspace value for backward compatibility.
+             * See V4L2 documentation for details. */
+            break;
+        case V4L2_XFER_FUNC_709:
+            es_fmt->video.transfer = TRANSFER_FUNC_BT709;
+            break;
+        case V4L2_XFER_FUNC_SRGB:
+            es_fmt->video.transfer = TRANSFER_FUNC_SRGB;
+            break;
+        case V4L2_XFER_FUNC_ADOBERGB:
+        case V4L2_XFER_FUNC_SMPTE240M:
+            es_fmt->video.transfer = TRANSFER_FUNC_UNDEF;
+            break;
+        case V4L2_XFER_FUNC_NONE:
+            es_fmt->video.transfer = TRANSFER_FUNC_LINEAR;
+            break;
+        case V4L2_XFER_FUNC_DCI_P3:
+        case V4L2_XFER_FUNC_SMPTE2084:
+            es_fmt->video.transfer = TRANSFER_FUNC_UNDEF;
+            break;
+        default:
+            msg_Warn(obj, "unknown transfer function %u",
+                     fmt.fmt.pix.xfer_func);
+            break;
     }
 
-    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
-    if (v4l2_ioctl (fd, VIDIOC_STREAMON, &type) < 0)
+    msg_Dbg(obj, "YCbCr encoding: %u", fmt.fmt.pix.ycbcr_enc);
+    switch (fmt.fmt.pix.ycbcr_enc)
     {
-        msg_Err (obj, "cannot start streaming: %s", vlc_strerror_c(errno));
-        goto error;
+        case V4L2_YCBCR_ENC_DEFAULT:
+            /* Same as transfer function - use color space value */
+            break;
+        case V4L2_YCBCR_ENC_601:
+            es_fmt->video.space = COLOR_SPACE_BT601;
+            break;
+        case V4L2_YCBCR_ENC_709:
+            es_fmt->video.space = COLOR_SPACE_BT709;
+            break;
+        case V4L2_YCBCR_ENC_XV601:
+        case V4L2_YCBCR_ENC_XV709:
+        case V4L2_YCBCR_ENC_SYCC:
+            break;
+        case V4L2_YCBCR_ENC_BT2020:
+            es_fmt->video.space = COLOR_SPACE_BT2020;
+            break;
+        case V4L2_YCBCR_ENC_BT2020_CONST_LUM:
+        case V4L2_YCBCR_ENC_SMPTE240M:
+            break;
+        default:
+            msg_Err(obj, "unknown YCbCr encoding: %u", fmt.fmt.pix.ycbcr_enc);
+            break;
     }
-    *n = bufc;
-    return bufv;
-error:
-    StopMmap (fd, bufv, bufc);
-    return NULL;
-}
 
-void StopMmap (int fd, struct buffer_t *bufv, uint32_t bufc)
-{
-    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+    msg_Dbg(obj, "quantization: %u", fmt.fmt.pix.quantization);
+    switch (fmt.fmt.pix.quantization)
+    {
+        case V4L2_QUANTIZATION_DEFAULT:
+            break;
+        case V4L2_QUANTIZATION_FULL_RANGE:
+            es_fmt->video.color_range = COLOR_RANGE_FULL;
+            break;
+        case V4L2_QUANTIZATION_LIM_RANGE:
+            es_fmt->video.color_range = COLOR_RANGE_LIMITED;
+            break;
+        default:
+            msg_Err(obj, "unknown quantization: %u", fmt.fmt.pix.quantization);
+            break;
+    }
 
-    /* STREAMOFF implicitly dequeues all buffers */
-    v4l2_ioctl (fd, VIDIOC_STREAMOFF, &type);
-    for (uint32_t i = 0; i < bufc; i++)
-        v4l2_munmap (bufv[i].start, bufv[i].length);
-    free (bufv);
+    msg_Dbg(obj, "added new video ES %4.4s %ux%u (%ux%u)",
+            (char *)&es_fmt->i_codec,
+            es_fmt->video.i_visible_width, es_fmt->video.i_visible_height,
+            es_fmt->video.i_width, es_fmt->video.i_height);
+    msg_Dbg(obj, " frame rate: %u/%u", es_fmt->video.i_frame_rate,
+            es_fmt->video.i_frame_rate_base);
+    msg_Dbg(obj, " aspect ratio: %u/%u", es_fmt->video.i_sar_num,
+            es_fmt->video.i_sar_den);
+    *block_size = fmt.fmt.pix.sizeimage;
+    return 0;
 }



View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/29ac127718393bfc88240d6d37a631b9e32726b7...0c4a68b1e2b2eb28ec895aa4d54d48374ed4d47f

-- 
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/29ac127718393bfc88240d6d37a631b9e32726b7...0c4a68b1e2b2eb28ec895aa4d54d48374ed4d47f
You're receiving this email because of your account on code.videolan.org.


VideoLAN code repository instance


More information about the vlc-commits mailing list