]> Nutra Git (v2) - gamesguru/feather.git/commitdiff
qt: update v4l2 with upstream
authortobtoht <tob@featherwallet.org>
Thu, 4 Jan 2024 12:14:55 +0000 (13:14 +0100)
committertobtoht <tob@featherwallet.org>
Thu, 4 Jan 2024 12:14:55 +0000 (13:14 +0100)
contrib/depends/patches/qt/v4l2.patch

index 9f1f4189e9123fd7e3ecf87cc9c07ba944f68789..2a1ced100dd1fe64f246a87bbe23533e227755cf 100644 (file)
@@ -13,10 +13,10 @@ index 978710112..1cb2cc730 100644
      add_subdirectory(gstreamer)
 diff --git a/src/plugins/multimedia/v4l2/CMakeLists.txt b/src/plugins/multimedia/v4l2/CMakeLists.txt
 new file mode 100644
-index 000000000..7c7e1a8da
+index 000000000..f20612c29
 --- /dev/null
 +++ b/src/plugins/multimedia/v4l2/CMakeLists.txt
-@@ -0,0 +1,22 @@
+@@ -0,0 +1,24 @@
 +qt_internal_add_plugin(QFFmpegMediaPlugin
 +    OUTPUT_NAME ffmpegmediaplugin
 +    PLUGIN_TYPE multimedia
@@ -37,8 +37,10 @@ index 000000000..7c7e1a8da
 +qt_internal_extend_target(QFFmpegMediaPlugin CONDITION QT_FEATURE_linux_v4l
 +    SOURCES
 +        qv4l2camera.cpp qv4l2camera_p.h
++        qv4l2filedescriptor.cpp qv4l2filedescriptor_p.h
++        qv4l2memorytransfer.cpp qv4l2memorytransfer_p.h
++        qv4l2cameradevices.cpp qv4l2cameradevices_p.h
 +)
-+
 diff --git a/src/plugins/multimedia/v4l2/ffmpeg.json b/src/plugins/multimedia/v4l2/ffmpeg.json
 new file mode 100644
 index 000000000..d8e7e4456
@@ -670,10 +672,10 @@ index 000000000..e34005bbf
 +#endif
 diff --git a/src/plugins/multimedia/v4l2/qffmpegmediaintegration.cpp b/src/plugins/multimedia/v4l2/qffmpegmediaintegration.cpp
 new file mode 100644
-index 000000000..44d81e489
+index 000000000..57a332696
 --- /dev/null
 +++ b/src/plugins/multimedia/v4l2/qffmpegmediaintegration.cpp
-@@ -0,0 +1,123 @@
+@@ -0,0 +1,124 @@
 +// Copyright (C) 2021 The Qt Company Ltd.
 +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
 +
@@ -698,6 +700,7 @@ index 000000000..44d81e489
 +
 +#if QT_CONFIG(linux_v4l)
 +#include "qv4l2camera_p.h"
++#include "qv4l2cameradevices_p.h"
 +#endif
 +
 +QT_BEGIN_NAMESPACE
@@ -1032,68 +1035,28 @@ index 000000000..cbaa810d7
 +#endif
 diff --git a/src/plugins/multimedia/v4l2/qv4l2camera.cpp b/src/plugins/multimedia/v4l2/qv4l2camera.cpp
 new file mode 100644
-index 000000000..b635a7fce
+index 000000000..2086af10d
 --- /dev/null
 +++ b/src/plugins/multimedia/v4l2/qv4l2camera.cpp
-@@ -0,0 +1,949 @@
+@@ -0,0 +1,707 @@
 +// Copyright (C) 2021 The Qt Company Ltd.
 +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
 +
 +#include "qv4l2camera_p.h"
++#include "qv4l2filedescriptor_p.h"
++#include "qv4l2memorytransfer_p.h"
 +
-+#include <qdir.h>
-+#include <qmutex.h>
-+#include <qendian.h>
 +#include <private/qcameradevice_p.h>
-+#include <private/qabstractvideobuffer_p.h>
-+#include <private/qvideotexturehelper_p.h>
 +#include <private/qmultimediautils_p.h>
-+#include <private/qplatformmediadevices_p.h>
-+
-+#include <sys/types.h>
-+#include <sys/stat.h>
-+#include <sys/ioctl.h>
-+#include <unistd.h>
-+#include <fcntl.h>
++#include <private/qmemoryvideobuffer_p.h>
 +#include <private/qcore_unix_p.h>
-+#include <sys/mman.h>
-+
-+#include <linux/videodev2.h>
 +
++#include <qsocketnotifier.h>
 +#include <qloggingcategory.h>
 +
 +QT_BEGIN_NAMESPACE
 +
-+static Q_LOGGING_CATEGORY(qLV4L2Camera, "qt.multimedia.ffmpeg.v4l2camera");
-+
-+static bool areCamerasEqual(QList<QCameraDevice> a, QList<QCameraDevice> b) {
-+    auto areCamerasDataEqual = [](const QCameraDevice& a, const QCameraDevice& b) {
-+        Q_ASSERT(QCameraDevicePrivate::handle(a));
-+        Q_ASSERT(QCameraDevicePrivate::handle(b));
-+        return *QCameraDevicePrivate::handle(a) == *QCameraDevicePrivate::handle(b);
-+    };
-+
-+    return std::equal(a.cbegin(), a.cend(), b.cbegin(), b.cend(), areCamerasDataEqual);
-+}
-+
-+QV4L2CameraDevices::QV4L2CameraDevices(QPlatformMediaIntegration *integration)
-+    : QPlatformVideoDevices(integration)
-+{
-+    m_deviceWatcher.addPath(QLatin1String("/dev"));
-+    connect(&m_deviceWatcher, &QFileSystemWatcher::directoryChanged, this, &QV4L2CameraDevices::checkCameras);
-+    doCheckCameras();
-+}
-+
-+QList<QCameraDevice> QV4L2CameraDevices::videoDevices() const
-+{
-+    return m_cameras;
-+}
-+
-+void QV4L2CameraDevices::checkCameras()
-+{
-+    if (doCheckCameras())
-+        emit videoInputsChanged();
-+}
++static Q_LOGGING_CATEGORY(qLcV4L2Camera, "qt.multimedia.ffmpeg.v4l2camera");
 +
 +static const struct {
 +    QVideoFrameFormat::PixelFormat fmt;
@@ -1121,7 +1084,7 @@ index 000000000..b635a7fce
 +    { QVideoFrameFormat::Format_Invalid,  0                    },
 +};
 +
-+static QVideoFrameFormat::PixelFormat formatForV4L2Format(uint32_t v4l2Format)
++QVideoFrameFormat::PixelFormat formatForV4L2Format(uint32_t v4l2Format)
 +{
 +    auto *f = formatMap;
 +    while (f->v4l2Format) {
@@ -1132,7 +1095,7 @@ index 000000000..b635a7fce
 +    return QVideoFrameFormat::Format_Invalid;
 +}
 +
-+static uint32_t v4l2FormatForPixelFormat(QVideoFrameFormat::PixelFormat format)
++uint32_t v4l2FormatForPixelFormat(QVideoFrameFormat::PixelFormat format)
 +{
 +    auto *f = formatMap;
 +    while (f->v4l2Format) {
@@ -1143,176 +1106,6 @@ index 000000000..b635a7fce
 +    return 0;
 +}
 +
-+
-+bool QV4L2CameraDevices::doCheckCameras()
-+{
-+    QList<QCameraDevice> newCameras;
-+
-+    QDir dir(QLatin1String("/dev"));
-+    const auto devices = dir.entryList(QDir::System);
-+
-+    bool first = true;
-+
-+    for (auto device : devices) {
-+//        qCDebug(qLV4L2Camera) << "device:" << device;
-+        if (!device.startsWith(QLatin1String("video")))
-+            continue;
-+
-+        QByteArray file = QFile::encodeName(dir.filePath(device));
-+        const int fd = open(file.constData(), O_RDONLY);
-+        if (fd < 0)
-+            continue;
-+
-+        auto fileCloseGuard = qScopeGuard([fd](){ close(fd); });
-+
-+        v4l2_fmtdesc formatDesc = {};
-+
-+        struct v4l2_capability cap;
-+        if (ioctl(fd, VIDIOC_QUERYCAP, &cap) < 0)
-+            continue;
-+
-+        if (cap.device_caps & V4L2_CAP_META_CAPTURE)
-+            continue;
-+        if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE))
-+            continue;
-+        if (!(cap.capabilities & V4L2_CAP_STREAMING))
-+            continue;
-+
-+        auto camera = std::make_unique<QCameraDevicePrivate>();
-+
-+        camera->id = file;
-+        camera->description = QString::fromUtf8((const char *)cap.card);
-+//        qCDebug(qLV4L2Camera) << "found camera" << camera->id << camera->description;
-+
-+        formatDesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
-+
-+        while (!ioctl(fd, VIDIOC_ENUM_FMT, &formatDesc)) {
-+            auto pixelFmt = formatForV4L2Format(formatDesc.pixelformat);
-+            qCDebug(qLV4L2Camera) << "    " << pixelFmt;
-+
-+            if (pixelFmt == QVideoFrameFormat::Format_Invalid) {
-+                ++formatDesc.index;
-+                continue;
-+            }
-+
-+//            qCDebug(qLV4L2Camera) << "frame sizes:";
-+            v4l2_frmsizeenum frameSize = {};
-+            frameSize.pixel_format = formatDesc.pixelformat;
-+
-+            while (!ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &frameSize)) {
-+                ++frameSize.index;
-+                if (frameSize.type != V4L2_FRMSIZE_TYPE_DISCRETE)
-+                    continue;
-+
-+                QSize resolution(frameSize.discrete.width, frameSize.discrete.height);
-+                float min = 1e10;
-+                float max = 0;
-+
-+                v4l2_frmivalenum frameInterval = {};
-+                frameInterval.pixel_format = formatDesc.pixelformat;
-+                frameInterval.width = frameSize.discrete.width;
-+                frameInterval.height = frameSize.discrete.height;
-+
-+                while (!ioctl(fd, VIDIOC_ENUM_FRAMEINTERVALS, &frameInterval)) {
-+                    ++frameInterval.index;
-+                    if (frameInterval.type != V4L2_FRMIVAL_TYPE_DISCRETE)
-+                        continue;
-+                    float rate = float(frameInterval.discrete.denominator)/float(frameInterval.discrete.numerator);
-+                    if (rate > max)
-+                        max = rate;
-+                    if (rate < min)
-+                        min = rate;
-+                }
-+
-+//                qCDebug(qLV4L2Camera) << "    " << resolution << min << max;
-+
-+                if (min <= max) {
-+                    auto fmt = std::make_unique<QCameraFormatPrivate>();
-+                    fmt->pixelFormat = pixelFmt;
-+                    fmt->resolution = resolution;
-+                    fmt->minFrameRate = min;
-+                    fmt->maxFrameRate = max;
-+                    camera->videoFormats.append(fmt.release()->create());
-+                    camera->photoResolutions.append(resolution);
-+                }
-+            }
-+
-+            ++formatDesc.index;
-+        }
-+
-+        // first camera is default
-+        camera->isDefault = std::exchange(first, false);
-+
-+        newCameras.append(camera.release()->create());
-+    }
-+
-+    if (areCamerasEqual(m_cameras, newCameras))
-+        return false;
-+
-+    m_cameras = std::move(newCameras);
-+    return true;
-+}
-+
-+class QV4L2VideoBuffer : public QAbstractVideoBuffer
-+{
-+public:
-+    QV4L2VideoBuffer(QV4L2CameraBuffers *d, int index)
-+        : QAbstractVideoBuffer(QVideoFrame::NoHandle, nullptr)
-+        , index(index)
-+        , d(d)
-+    {}
-+    ~QV4L2VideoBuffer()
-+    {
-+        d->release(index);
-+    }
-+
-+    QVideoFrame::MapMode mapMode() const override { return m_mode; }
-+    MapData map(QVideoFrame::MapMode mode) override {
-+        m_mode = mode;
-+        return d->v4l2FileDescriptor >= 0 ? data : MapData{};
-+    }
-+    void unmap() override {
-+        m_mode = QVideoFrame::NotMapped;
-+    }
-+
-+    QVideoFrame::MapMode m_mode = QVideoFrame::NotMapped;
-+    MapData data;
-+    int index = 0;
-+    QExplicitlySharedDataPointer<QV4L2CameraBuffers> d;
-+};
-+
-+QV4L2CameraBuffers::~QV4L2CameraBuffers()
-+{
-+    QMutexLocker locker(&mutex);
-+    Q_ASSERT(v4l2FileDescriptor < 0);
-+    unmapBuffers();
-+}
-+
-+
-+
-+void QV4L2CameraBuffers::release(int index)
-+{
-+    QMutexLocker locker(&mutex);
-+    if (v4l2FileDescriptor < 0 || index >= mappedBuffers.size())
-+        return;
-+
-+    struct v4l2_buffer buf = {};
-+
-+    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
-+    buf.memory = V4L2_MEMORY_MMAP;
-+    buf.index = index;
-+
-+    if (ioctl(v4l2FileDescriptor, VIDIOC_QBUF, &buf) < 0)
-+        qWarning() << "Couldn't release V4L2 buffer" << errno << strerror(errno) << index;
-+}
-+
-+void QV4L2CameraBuffers::unmapBuffers()
-+{
-+    for (const auto &b : std::as_const(mappedBuffers))
-+        munmap(b.data, b.size);
-+    mappedBuffers.clear();
-+}
-+
 +QV4L2Camera::QV4L2Camera(QCamera *camera)
 +    : QPlatformCamera(camera)
 +{
@@ -1320,7 +1113,6 @@ index 000000000..b635a7fce
 +
 +QV4L2Camera::~QV4L2Camera()
 +{
-+    setActive(false);
 +    stopCapturing();
 +    closeV4L2Fd();
 +}
@@ -1341,13 +1133,11 @@ index 000000000..b635a7fce
 +        resolveCameraFormat({});
 +
 +    m_active = active;
-+    if (m_active) {
-+        setV4L2CameraFormat();
-+        initMMap();
++    if (m_active)
 +        startCapturing();
-+    } else {
++    else
 +        stopCapturing();
-+    }
++
 +    emit newVideoFrame({});
 +
 +    emit activeChanged(active);
@@ -1357,9 +1147,8 @@ index 000000000..b635a7fce
 +{
 +    if (m_cameraDevice == camera)
 +        return;
-+    if (m_active)
-+        stopCapturing();
 +
++    stopCapturing();
 +    closeV4L2Fd();
 +
 +    m_cameraDevice = camera;
@@ -1367,11 +1156,8 @@ index 000000000..b635a7fce
 +
 +    initV4L2Controls();
 +
-+    if (m_active) {
-+        setV4L2CameraFormat();
-+        initMMap();
++    if (m_active)
 +        startCapturing();
-+    }
 +}
 +
 +bool QV4L2Camera::setCameraFormat(const QCameraFormat &format)
@@ -1385,9 +1171,8 @@ index 000000000..b635a7fce
 +    if (m_active) {
 +        stopCapturing();
 +        closeV4L2Fd();
++
 +        initV4L2Controls();
-+        setV4L2CameraFormat();
-+        initMMap();
 +        startCapturing();
 +    }
 +
@@ -1413,31 +1198,31 @@ index 000000000..b635a7fce
 +        return;
 +
 +    bool focusDist = supportedFeatures() & QCamera::Feature::FocusDistance;
-+    if (!focusDist && !v4l2RangedFocus)
++    if (!focusDist && !m_v4l2Info.rangedFocus)
 +        return;
 +
 +    switch (mode) {
 +    default:
 +    case QCamera::FocusModeAuto:
 +        setV4L2Parameter(V4L2_CID_FOCUS_AUTO, 1);
-+        if (v4l2RangedFocus)
++        if (m_v4l2Info.rangedFocus)
 +            setV4L2Parameter(V4L2_CID_AUTO_FOCUS_RANGE, V4L2_AUTO_FOCUS_RANGE_AUTO);
 +        break;
 +    case QCamera::FocusModeAutoNear:
 +        setV4L2Parameter(V4L2_CID_FOCUS_AUTO, 1);
-+        if (v4l2RangedFocus)
++        if (m_v4l2Info.rangedFocus)
 +            setV4L2Parameter(V4L2_CID_AUTO_FOCUS_RANGE, V4L2_AUTO_FOCUS_RANGE_MACRO);
 +        else if (focusDist)
-+            setV4L2Parameter(V4L2_CID_FOCUS_ABSOLUTE, v4l2MinFocus);
++            setV4L2Parameter(V4L2_CID_FOCUS_ABSOLUTE, m_v4l2Info.minFocus);
 +        break;
 +    case QCamera::FocusModeAutoFar:
 +        setV4L2Parameter(V4L2_CID_FOCUS_AUTO, 1);
-+        if (v4l2RangedFocus)
++        if (m_v4l2Info.rangedFocus)
 +            setV4L2Parameter(V4L2_CID_AUTO_FOCUS_RANGE, V4L2_AUTO_FOCUS_RANGE_INFINITY);
 +        break;
 +    case QCamera::FocusModeInfinity:
 +        setV4L2Parameter(V4L2_CID_FOCUS_AUTO, 0);
-+        setV4L2Parameter(V4L2_CID_FOCUS_ABSOLUTE, v4l2MaxFocus);
++        setV4L2Parameter(V4L2_CID_FOCUS_ABSOLUTE, m_v4l2Info.maxFocus);
 +        break;
 +    case QCamera::FocusModeManual:
 +        setV4L2Parameter(V4L2_CID_FOCUS_AUTO, 0);
@@ -1449,17 +1234,17 @@ index 000000000..b635a7fce
 +
 +void QV4L2Camera::setFocusDistance(float d)
 +{
-+    int distance = v4l2MinFocus + int((v4l2MaxFocus - v4l2MinFocus)*d);
++    int distance = m_v4l2Info.minFocus + int((m_v4l2Info.maxFocus - m_v4l2Info.minFocus) * d);
 +    setV4L2Parameter(V4L2_CID_FOCUS_ABSOLUTE, distance);
 +    focusDistanceChanged(d);
 +}
 +
 +void QV4L2Camera::zoomTo(float factor, float)
 +{
-+    if (v4l2MaxZoom == v4l2MinZoom)
++    if (m_v4l2Info.maxZoom == m_v4l2Info.minZoom)
 +        return;
 +    factor = qBound(1., factor, 2.);
-+    int zoom = v4l2MinZoom + (factor - 1.)*(v4l2MaxZoom - v4l2MinZoom);
++    int zoom = m_v4l2Info.minZoom + (factor - 1.) * (m_v4l2Info.maxZoom - m_v4l2Info.minZoom);
 +    setV4L2Parameter(V4L2_CID_ZOOM_ABSOLUTE, zoom);
 +    zoomFactorChanged(factor);
 +}
@@ -1475,7 +1260,7 @@ index 000000000..b635a7fce
 +
 +void QV4L2Camera::setFlashMode(QCamera::FlashMode mode)
 +{
-+    if (!v4l2FlashSupported || mode == QCamera::FlashOn)
++    if (!m_v4l2Info.flashSupported || mode == QCamera::FlashOn)
 +        return;
 +    setV4L2Parameter(V4L2_CID_FLASH_LED_MODE, mode == QCamera::FlashAuto ? V4L2_FLASH_LED_MODE_FLASH : V4L2_FLASH_LED_MODE_NONE);
 +    flashModeChanged(mode);
@@ -1483,7 +1268,7 @@ index 000000000..b635a7fce
 +
 +bool QV4L2Camera::isFlashModeSupported(QCamera::FlashMode mode) const
 +{
-+    if (v4l2FlashSupported && mode == QCamera::FlashAuto)
++    if (m_v4l2Info.flashSupported && mode == QCamera::FlashAuto)
 +        return true;
 +    return mode == QCamera::FlashOff;
 +}
@@ -1494,15 +1279,12 @@ index 000000000..b635a7fce
 +    ::memset(&queryControl, 0, sizeof(queryControl));
 +    queryControl.id = V4L2_CID_AUTO_WHITE_BALANCE;
 +
-+    if (::ioctl(d->v4l2FileDescriptor, VIDIOC_QUERYCTRL, &queryControl) == 0)
-+        return true;
-+
-+    return false;
++    return m_v4l2FileDescriptor && m_v4l2FileDescriptor->call(VIDIOC_QUERYCTRL, &queryControl);
 +}
 +
 +void QV4L2Camera::setTorchMode(QCamera::TorchMode mode)
 +{
-+    if (!v4l2TorchSupported || mode == QCamera::TorchOn)
++    if (!m_v4l2Info.torchSupported || mode == QCamera::TorchOn)
 +        return;
 +    setV4L2Parameter(V4L2_CID_FLASH_LED_MODE, mode == QCamera::TorchOn ? V4L2_FLASH_LED_MODE_TORCH : V4L2_FLASH_LED_MODE_NONE);
 +    torchModeChanged(mode);
@@ -1511,13 +1293,13 @@ index 000000000..b635a7fce
 +bool QV4L2Camera::isTorchModeSupported(QCamera::TorchMode mode) const
 +{
 +    if (mode == QCamera::TorchOn)
-+        return v4l2TorchSupported;
++        return m_v4l2Info.torchSupported;
 +    return mode == QCamera::TorchOff;
 +}
 +
 +void QV4L2Camera::setExposureMode(QCamera::ExposureMode mode)
 +{
-+    if (v4l2AutoExposureSupported && v4l2ManualExposureSupported) {
++    if (m_v4l2Info.autoExposureSupported && m_v4l2Info.manualExposureSupported) {
 +        if (mode != QCamera::ExposureAuto && mode != QCamera::ExposureManual)
 +            return;
 +        int value = QCamera::ExposureAuto ? V4L2_EXPOSURE_AUTO : V4L2_EXPOSURE_MANUAL;
@@ -1531,15 +1313,16 @@ index 000000000..b635a7fce
 +{
 +    if (mode == QCamera::ExposureAuto)
 +        return true;
-+    if (v4l2ManualExposureSupported && v4l2AutoExposureSupported)
++    if (m_v4l2Info.manualExposureSupported && m_v4l2Info.autoExposureSupported)
 +        return mode == QCamera::ExposureManual;
 +    return false;
 +}
 +
 +void QV4L2Camera::setExposureCompensation(float compensation)
 +{
-+    if ((v4l2MinExposureAdjustment != 0 || v4l2MaxExposureAdjustment != 0)) {
-+        int value = qBound(v4l2MinExposureAdjustment, (int)(compensation*1000), v4l2MaxExposureAdjustment);
++    if ((m_v4l2Info.minExposureAdjustment != 0 || m_v4l2Info.maxExposureAdjustment != 0)) {
++        int value = qBound(m_v4l2Info.minExposureAdjustment, (int)(compensation * 1000),
++                           m_v4l2Info.maxExposureAdjustment);
 +        setV4L2Parameter(V4L2_CID_AUTO_EXPOSURE_BIAS, value);
 +        exposureCompensationChanged(value/1000.);
 +        return;
@@ -1567,8 +1350,9 @@ index 000000000..b635a7fce
 +
 +void QV4L2Camera::setManualExposureTime(float secs)
 +{
-+    if (v4l2ManualExposureSupported && v4l2AutoExposureSupported) {
-+        int exposure = qBound(v4l2MinExposure, qRound(secs*10000.), v4l2MaxExposure);
++    if (m_v4l2Info.manualExposureSupported && m_v4l2Info.autoExposureSupported) {
++        int exposure =
++                qBound(m_v4l2Info.minExposure, qRound(secs * 10000.), m_v4l2Info.maxExposure);
 +        setV4L2Parameter(V4L2_CID_EXPOSURE_ABSOLUTE, exposure);
 +        exposureTimeChanged(exposure/10000.);
 +        return;
@@ -1582,7 +1366,7 @@ index 000000000..b635a7fce
 +
 +bool QV4L2Camera::isWhiteBalanceModeSupported(QCamera::WhiteBalanceMode mode) const
 +{
-+    if (v4l2AutoWhiteBalanceSupported && v4l2ColorTemperatureSupported)
++    if (m_v4l2Info.autoWhiteBalanceSupported && m_v4l2Info.colorTemperatureSupported)
 +        return true;
 +
 +    return mode == QCamera::WhiteBalanceAuto;
@@ -1615,127 +1399,114 @@ index 000000000..b635a7fce
 +
 +void QV4L2Camera::readFrame()
 +{
-+    if (!d)
-+        return;
++    Q_ASSERT(m_memoryTransfer);
 +
-+    v4l2_buffer buf = {};
-+    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
-+    buf.memory = V4L2_MEMORY_MMAP;
++    auto buffer = m_memoryTransfer->dequeueBuffer();
++    if (!buffer) {
++        qCWarning(qLcV4L2Camera) << "Cannot take buffer";
 +
-+    if (ioctl(d->v4l2FileDescriptor, VIDIOC_DQBUF, &buf) < 0) {
 +        if (errno == ENODEV) {
 +            // camera got removed while being active
 +            stopCapturing();
 +            closeV4L2Fd();
-+            return;
 +        }
-+        if (errno != EAGAIN)
-+            qWarning() << "error calling VIDIOC_DQBUF" << errno << strerror(errno);
++
++        return;
 +    }
 +
-+    Q_ASSERT(qsizetype(buf.index) < d->mappedBuffers.size());
-+    int i = buf.index;
-+
-+//    auto textureDesc = QVideoTextureHelper::textureDescription(m_format.pixelFormat());
-+
-+    QV4L2VideoBuffer *buffer = new QV4L2VideoBuffer(d.get(), i);
-+    buffer->data.nPlanes = 1;
-+    buffer->data.bytesPerLine[0] = bytesPerLine;
-+    buffer->data.data[0] = (uchar *)d->mappedBuffers.at(i).data;
-+    buffer->data.size[0] = d->mappedBuffers.at(i).size;
-+    QVideoFrameFormat fmt(m_cameraFormat.resolution(), m_cameraFormat.pixelFormat());
-+    fmt.setColorSpace(colorSpace);
-+//    qCDebug(qLV4L2Camera) << "got a frame" << d->mappedBuffers.at(i).data << d->mappedBuffers.at(i).size << fmt << i;
-+    QVideoFrame frame(buffer, fmt);
-+
-+    if (firstFrameTime.tv_sec == -1)
-+        firstFrameTime = buf.timestamp;
-+    qint64 secs = buf.timestamp.tv_sec - firstFrameTime.tv_sec;
-+    qint64 usecs = buf.timestamp.tv_usec - firstFrameTime.tv_usec;
++    auto videoBuffer = new QMemoryVideoBuffer(buffer->data, m_bytesPerLine);
++    QVideoFrame frame(videoBuffer, frameFormat());
++
++    auto &v4l2Buffer = buffer->v4l2Buffer;
++
++    if (m_firstFrameTime.tv_sec == -1)
++        m_firstFrameTime = v4l2Buffer.timestamp;
++    qint64 secs = v4l2Buffer.timestamp.tv_sec - m_firstFrameTime.tv_sec;
++    qint64 usecs = v4l2Buffer.timestamp.tv_usec - m_firstFrameTime.tv_usec;
 +    frame.setStartTime(secs*1000000 + usecs);
-+    frame.setEndTime(frame.startTime() + frameDuration);
++    frame.setEndTime(frame.startTime() + m_frameDuration);
 +
 +    emit newVideoFrame(frame);
++
++    if (!m_memoryTransfer->enqueueBuffer(v4l2Buffer.index))
++        qCWarning(qLcV4L2Camera) << "Cannot add buffer";
 +}
 +
 +void QV4L2Camera::setCameraBusy()
 +{
-+    cameraBusy = true;
-+    error(QCamera::CameraError, tr("Camera is in use."));
++    m_cameraBusy = true;
++    emit error(QCamera::CameraError, QLatin1String("Camera is in use"));
 +}
 +
 +void QV4L2Camera::initV4L2Controls()
 +{
-+    v4l2AutoWhiteBalanceSupported = false;
-+    v4l2ColorTemperatureSupported = false;
-+    v4l2RangedFocus = false;
-+    v4l2FlashSupported = false;
-+    v4l2TorchSupported = false;
++    m_v4l2Info = {};
 +    QCamera::Features features;
 +
-+
 +    const QByteArray deviceName = m_cameraDevice.id();
 +    Q_ASSERT(!deviceName.isEmpty());
 +
 +    closeV4L2Fd();
-+    Q_ASSERT(!d);
 +
-+    d = new QV4L2CameraBuffers;
-+
-+    d->v4l2FileDescriptor = qt_safe_open(deviceName.constData(), O_RDWR);
-+    if (d->v4l2FileDescriptor == -1) {
-+        qWarning() << "Unable to open the camera" << deviceName
-+                   << "for read to query the parameter info:" << qt_error_string(errno);
++    const int descriptor = qt_safe_open(deviceName.constData(), O_RDWR);
++    if (descriptor == -1) {
++        qCWarning(qLcV4L2Camera) << "Unable to open the camera" << deviceName
++                                 << "for read to query the parameter info:"
++                                 << qt_error_string(errno);
++        emit error(QCamera::CameraError, QLatin1String("Cannot open camera"));
 +        return;
 +    }
-+    qCDebug(qLV4L2Camera) << "FD=" << d->v4l2FileDescriptor;
++
++    m_v4l2FileDescriptor = std::make_shared<QV4L2FileDescriptor>(descriptor);
++
++    qCDebug(qLcV4L2Camera) << "FD=" << descriptor;
 +
 +    struct v4l2_queryctrl queryControl;
 +    ::memset(&queryControl, 0, sizeof(queryControl));
 +    queryControl.id = V4L2_CID_AUTO_WHITE_BALANCE;
 +
-+    if (::ioctl(d->v4l2FileDescriptor, VIDIOC_QUERYCTRL, &queryControl) == 0) {
-+        v4l2AutoWhiteBalanceSupported = true;
++    if (m_v4l2FileDescriptor->call(VIDIOC_QUERYCTRL, &queryControl)) {
++        m_v4l2Info.autoWhiteBalanceSupported = true;
 +        setV4L2Parameter(V4L2_CID_AUTO_WHITE_BALANCE, true);
 +    }
 +
 +    ::memset(&queryControl, 0, sizeof(queryControl));
 +    queryControl.id = V4L2_CID_WHITE_BALANCE_TEMPERATURE;
-+    if (::ioctl(d->v4l2FileDescriptor, VIDIOC_QUERYCTRL, &queryControl) == 0) {
-+        v4l2MinColorTemp = queryControl.minimum;
-+        v4l2MaxColorTemp = queryControl.maximum;
-+        v4l2ColorTemperatureSupported = true;
++    if (m_v4l2FileDescriptor->call(VIDIOC_QUERYCTRL, &queryControl)) {
++        m_v4l2Info.minColorTemp = queryControl.minimum;
++        m_v4l2Info.maxColorTemp = queryControl.maximum;
++        m_v4l2Info.colorTemperatureSupported = true;
 +        features |= QCamera::Feature::ColorTemperature;
 +    }
 +
 +    ::memset(&queryControl, 0, sizeof(queryControl));
 +    queryControl.id = V4L2_CID_EXPOSURE_AUTO;
-+    if (::ioctl(d->v4l2FileDescriptor, VIDIOC_QUERYCTRL, &queryControl) == 0) {
-+        v4l2AutoExposureSupported = true;
++    if (m_v4l2FileDescriptor->call(VIDIOC_QUERYCTRL, &queryControl)) {
++        m_v4l2Info.autoExposureSupported = true;
 +    }
 +
 +    ::memset(&queryControl, 0, sizeof(queryControl));
 +    queryControl.id = V4L2_CID_EXPOSURE_ABSOLUTE;
-+    if (::ioctl(d->v4l2FileDescriptor, VIDIOC_QUERYCTRL, &queryControl) == 0) {
-+        v4l2ManualExposureSupported = true;
-+        v4l2MinExposure = queryControl.minimum;
-+        v4l2MaxExposure = queryControl.maximum;
++    if (m_v4l2FileDescriptor->call(VIDIOC_QUERYCTRL, &queryControl)) {
++        m_v4l2Info.manualExposureSupported = true;
++        m_v4l2Info.minExposure = queryControl.minimum;
++        m_v4l2Info.maxExposure = queryControl.maximum;
 +        features |= QCamera::Feature::ManualExposureTime;
 +    }
 +
 +    ::memset(&queryControl, 0, sizeof(queryControl));
 +    queryControl.id = V4L2_CID_AUTO_EXPOSURE_BIAS;
-+    if (::ioctl(d->v4l2FileDescriptor, VIDIOC_QUERYCTRL, &queryControl) == 0) {
-+        v4l2MinExposureAdjustment = queryControl.minimum;
-+        v4l2MaxExposureAdjustment = queryControl.maximum;
++    if (m_v4l2FileDescriptor->call(VIDIOC_QUERYCTRL, &queryControl)) {
++        m_v4l2Info.minExposureAdjustment = queryControl.minimum;
++        m_v4l2Info.maxExposureAdjustment = queryControl.maximum;
 +        features |= QCamera::Feature::ExposureCompensation;
 +    }
 +
 +    ::memset(&queryControl, 0, sizeof(queryControl));
 +    queryControl.id = V4L2_CID_ISO_SENSITIVITY_AUTO;
-+    if (::ioctl(d->v4l2FileDescriptor, VIDIOC_QUERYCTRL, &queryControl) == 0) {
++    if (m_v4l2FileDescriptor->call(VIDIOC_QUERYCTRL, &queryControl)) {
 +        queryControl.id = V4L2_CID_ISO_SENSITIVITY;
-+        if (::ioctl(d->v4l2FileDescriptor, VIDIOC_QUERYCTRL, &queryControl) == 0) {
++        if (m_v4l2FileDescriptor->call(VIDIOC_QUERYCTRL, &queryControl)) {
 +            features |= QCamera::Feature::IsoSensitivity;
 +            minIsoChanged(queryControl.minimum);
 +            maxIsoChanged(queryControl.minimum);
@@ -1744,50 +1515,48 @@ index 000000000..b635a7fce
 +
 +    ::memset(&queryControl, 0, sizeof(queryControl));
 +    queryControl.id = V4L2_CID_FOCUS_ABSOLUTE;
-+    if (::ioctl(d->v4l2FileDescriptor, VIDIOC_QUERYCTRL, &queryControl) == 0) {
-+        v4l2MinExposureAdjustment = queryControl.minimum;
-+        v4l2MaxExposureAdjustment = queryControl.maximum;
++    if (m_v4l2FileDescriptor->call(VIDIOC_QUERYCTRL, &queryControl)) {
++        m_v4l2Info.minExposureAdjustment = queryControl.minimum;
++        m_v4l2Info.maxExposureAdjustment = queryControl.maximum;
 +        features |= QCamera::Feature::FocusDistance;
 +    }
 +
 +    ::memset(&queryControl, 0, sizeof(queryControl));
 +    queryControl.id = V4L2_CID_AUTO_FOCUS_RANGE;
-+    if (::ioctl(d->v4l2FileDescriptor, VIDIOC_QUERYCTRL, &queryControl) == 0) {
-+        v4l2RangedFocus = true;
++    if (m_v4l2FileDescriptor->call(VIDIOC_QUERYCTRL, &queryControl)) {
++        m_v4l2Info.rangedFocus = true;
 +    }
 +
 +    ::memset(&queryControl, 0, sizeof(queryControl));
 +    queryControl.id = V4L2_CID_FLASH_LED_MODE;
-+    if (::ioctl(d->v4l2FileDescriptor, VIDIOC_QUERYCTRL, &queryControl) == 0) {
-+        v4l2FlashSupported = queryControl.minimum <= V4L2_FLASH_LED_MODE_FLASH && queryControl.maximum >= V4L2_FLASH_LED_MODE_FLASH;
-+        v4l2TorchSupported = queryControl.minimum <= V4L2_FLASH_LED_MODE_TORCH && queryControl.maximum >= V4L2_FLASH_LED_MODE_TORCH;
++    if (m_v4l2FileDescriptor->call(VIDIOC_QUERYCTRL, &queryControl)) {
++        m_v4l2Info.flashSupported = queryControl.minimum <= V4L2_FLASH_LED_MODE_FLASH
++                && queryControl.maximum >= V4L2_FLASH_LED_MODE_FLASH;
++        m_v4l2Info.torchSupported = queryControl.minimum <= V4L2_FLASH_LED_MODE_TORCH
++                && queryControl.maximum >= V4L2_FLASH_LED_MODE_TORCH;
 +    }
 +
-+    v4l2MinZoom = 0;
-+    v4l2MaxZoom = 0;
 +    ::memset(&queryControl, 0, sizeof(queryControl));
 +    queryControl.id = V4L2_CID_ZOOM_ABSOLUTE;
-+    if (::ioctl(d->v4l2FileDescriptor, VIDIOC_QUERYCTRL, &queryControl) == 0) {
-+        v4l2MinZoom = queryControl.minimum;
-+        v4l2MaxZoom = queryControl.maximum;
++    if (m_v4l2FileDescriptor->call(VIDIOC_QUERYCTRL, &queryControl)) {
++        m_v4l2Info.minZoom = queryControl.minimum;
++        m_v4l2Info.maxZoom = queryControl.maximum;
 +    }
 +    // zoom factors are in arbitrary units, so we simply normalize them to go from 1 to 2
 +    // if they are different
 +    minimumZoomFactorChanged(1);
-+    maximumZoomFactorChanged(v4l2MinZoom != v4l2MaxZoom ? 2 : 1);
++    maximumZoomFactorChanged(m_v4l2Info.minZoom != m_v4l2Info.maxZoom ? 2 : 1);
 +
 +    supportedFeaturesChanged(features);
 +}
 +
 +void QV4L2Camera::closeV4L2Fd()
 +{
-+    if (d && d->v4l2FileDescriptor >= 0) {
-+        QMutexLocker locker(&d->mutex);
-+        d->unmapBuffers();
-+        qt_safe_close(d->v4l2FileDescriptor);
-+        d->v4l2FileDescriptor = -1;
-+    }
-+    d = nullptr;
++    Q_ASSERT(!m_memoryTransfer);
++
++    m_v4l2Info = {};
++    m_cameraBusy = false;
++    m_v4l2FileDescriptor = nullptr;
 +}
 +
 +int QV4L2Camera::setV4L2ColorTemperature(int temperature)
@@ -1795,15 +1564,17 @@ index 000000000..b635a7fce
 +    struct v4l2_control control;
 +    ::memset(&control, 0, sizeof(control));
 +
-+    if (v4l2AutoWhiteBalanceSupported) {
++    if (m_v4l2Info.autoWhiteBalanceSupported) {
 +        setV4L2Parameter(V4L2_CID_AUTO_WHITE_BALANCE, temperature == 0 ? true : false);
 +    } else if (temperature == 0) {
 +        temperature = 5600;
 +    }
 +
-+    if (temperature != 0 && v4l2ColorTemperatureSupported) {
-+        temperature = qBound(v4l2MinColorTemp, temperature, v4l2MaxColorTemp);
-+        if (!setV4L2Parameter(V4L2_CID_WHITE_BALANCE_TEMPERATURE, qBound(v4l2MinColorTemp, temperature, v4l2MaxColorTemp)))
++    if (temperature != 0 && m_v4l2Info.colorTemperatureSupported) {
++        temperature = qBound(m_v4l2Info.minColorTemp, temperature, m_v4l2Info.maxColorTemp);
++        if (!setV4L2Parameter(
++                    V4L2_CID_WHITE_BALANCE_TEMPERATURE,
++                    qBound(m_v4l2Info.minColorTemp, temperature, m_v4l2Info.maxColorTemp)))
 +            temperature = 0;
 +    } else {
 +        temperature = 0;
@@ -1814,8 +1585,8 @@ index 000000000..b635a7fce
 +
 +bool QV4L2Camera::setV4L2Parameter(quint32 id, qint32 value)
 +{
-+    struct v4l2_control control{id, value};
-+    if (::ioctl(d->v4l2FileDescriptor, VIDIOC_S_CTRL, &control) != 0) {
++    v4l2_control control{ id, value };
++    if (!m_v4l2FileDescriptor->call(VIDIOC_S_CTRL, &control)) {
 +        qWarning() << "Unable to set the V4L2 Parameter" << Qt::hex << id << "to" << value << qt_error_string(errno);
 +        return false;
 +    }
@@ -1825,7 +1596,7 @@ index 000000000..b635a7fce
 +int QV4L2Camera::getV4L2Parameter(quint32 id) const
 +{
 +    struct v4l2_control control{id, 0};
-+    if (::ioctl(d->v4l2FileDescriptor, VIDIOC_G_CTRL, &control) != 0) {
++    if (!m_v4l2FileDescriptor->call(VIDIOC_G_CTRL, &control)) {
 +        qWarning() << "Unable to get the V4L2 Parameter" << Qt::hex << id << qt_error_string(errno);
 +        return 0;
 +    }
@@ -1834,8 +1605,12 @@ index 000000000..b635a7fce
 +
 +void QV4L2Camera::setV4L2CameraFormat()
 +{
++    if (m_v4l2Info.formatInitialized || !m_v4l2FileDescriptor)
++        return;
++
 +    Q_ASSERT(!m_cameraFormat.isNull());
-+    qCDebug(qLV4L2Camera) << "XXXXX" << this << m_cameraDevice.id() << m_cameraFormat.pixelFormat() << m_cameraFormat.resolution();
++    qCDebug(qLcV4L2Camera) << "XXXXX" << this << m_cameraDevice.id() << m_cameraFormat.pixelFormat()
++                           << m_cameraFormat.resolution();
 +
 +    v4l2_format fmt = {};
 +    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
@@ -1846,9 +1621,9 @@ index 000000000..b635a7fce
 +    fmt.fmt.pix.pixelformat = v4l2FormatForPixelFormat(m_cameraFormat.pixelFormat());
 +    fmt.fmt.pix.field = V4L2_FIELD_ANY;
 +
-+    qCDebug(qLV4L2Camera) << "setting camera format to" << size;
++    qCDebug(qLcV4L2Camera) << "setting camera format to" << size << fmt.fmt.pix.pixelformat;
 +
-+    if (ioctl(d->v4l2FileDescriptor, VIDIOC_S_FMT, &fmt) < 0) {
++    if (!m_v4l2FileDescriptor->call(VIDIOC_S_FMT, &fmt)) {
 +        if (errno == EBUSY) {
 +            setCameraBusy();
 +            return;
@@ -1856,25 +1631,29 @@ index 000000000..b635a7fce
 +        qWarning() << "Couldn't set video format on v4l2 camera" << strerror(errno);
 +    }
 +
-+    bytesPerLine = fmt.fmt.pix.bytesperline;
++    m_v4l2Info.formatInitialized = true;
++    m_cameraBusy = false;
++
++    m_bytesPerLine = fmt.fmt.pix.bytesperline;
++    m_imageSize = std::max(fmt.fmt.pix.sizeimage, m_bytesPerLine * fmt.fmt.pix.height);
 +
 +    switch (v4l2_colorspace(fmt.fmt.pix.colorspace)) {
 +    default:
 +    case V4L2_COLORSPACE_DCI_P3:
-+        colorSpace = QVideoFrameFormat::ColorSpace_Undefined;
++        m_colorSpace = QVideoFrameFormat::ColorSpace_Undefined;
 +        break;
 +    case V4L2_COLORSPACE_REC709:
-+        colorSpace = QVideoFrameFormat::ColorSpace_BT709;
++        m_colorSpace = QVideoFrameFormat::ColorSpace_BT709;
 +        break;
 +    case V4L2_COLORSPACE_JPEG:
-+        colorSpace = QVideoFrameFormat::ColorSpace_AdobeRgb;
++        m_colorSpace = QVideoFrameFormat::ColorSpace_AdobeRgb;
 +        break;
 +    case V4L2_COLORSPACE_SRGB:
 +        // ##### is this correct???
-+        colorSpace = QVideoFrameFormat::ColorSpace_BT601;
++        m_colorSpace = QVideoFrameFormat::ColorSpace_BT601;
 +        break;
 +    case V4L2_COLORSPACE_BT2020:
-+        colorSpace = QVideoFrameFormat::ColorSpace_BT2020;
++        m_colorSpace = QVideoFrameFormat::ColorSpace_BT2020;
 +        break;
 +    }
 +
@@ -1884,118 +1663,100 @@ index 000000000..b635a7fce
 +    streamParam.parm.capture.capability = V4L2_CAP_TIMEPERFRAME;
 +    auto [num, den] = qRealToFraction(1./m_cameraFormat.maxFrameRate());
 +    streamParam.parm.capture.timeperframe = { (uint)num, (uint)den };
-+    ioctl(d->v4l2FileDescriptor, VIDIOC_S_PARM, &streamParam);
++    m_v4l2FileDescriptor->call(VIDIOC_S_PARM, &streamParam);
 +
-+    frameDuration = 1000000*streamParam.parm.capture.timeperframe.numerator
-+                    /streamParam.parm.capture.timeperframe.denominator;
++    m_frameDuration = 1000000 * streamParam.parm.capture.timeperframe.numerator
++            streamParam.parm.capture.timeperframe.denominator;
 +}
 +
-+void QV4L2Camera::initMMap()
++void QV4L2Camera::initV4L2MemoryTransfer()
 +{
-+    if (cameraBusy)
++    if (m_cameraBusy)
 +        return;
 +
-+    v4l2_requestbuffers req = {};
-+    req.count = 4;
-+    req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
-+    req.memory = V4L2_MEMORY_MMAP;
++    Q_ASSERT(!m_memoryTransfer);
 +
-+    if (ioctl(d->v4l2FileDescriptor, VIDIOC_REQBUFS, &req) < 0) {
-+        if (errno == EBUSY)
-+            setCameraBusy();
-+        qWarning() << "requesting mmap'ed buffers failed" << strerror(errno);
++    m_memoryTransfer = makeUserPtrMemoryTransfer(m_v4l2FileDescriptor, m_imageSize);
++
++    if (m_memoryTransfer)
 +        return;
-+    }
 +
-+    if (req.count < 2) {
-+        qWarning() << "Can't map 2 or more buffers";
++    if (errno == EBUSY) {
++        setCameraBusy();
 +        return;
 +    }
 +
-+    for (uint32_t n = 0; n < req.count; ++n) {
-+        v4l2_buffer buf = {};
-+        buf.index = n;
-+        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
-+        buf.memory = V4L2_MEMORY_MMAP;
-+
-+        if (ioctl(d->v4l2FileDescriptor, VIDIOC_QUERYBUF, &buf) != 0) {
-+            qWarning() << "Can't map buffer" << n;
-+            return;
-+        }
++    qCDebug(qLcV4L2Camera) << "Cannot init V4L2_MEMORY_USERPTR; trying V4L2_MEMORY_MMAP";
 +
-+        QV4L2CameraBuffers::MappedBuffer buffer;
-+        buffer.size = buf.length;
-+        buffer.data = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED,
-+                           d->v4l2FileDescriptor, buf.m.offset);
-+
-+        if (buffer.data == MAP_FAILED) {
-+            qWarning() << "mmap failed" << n << buf.length << buf.m.offset;
-+            return;
-+        }
++    m_memoryTransfer = makeMMapMemoryTransfer(m_v4l2FileDescriptor);
 +
-+        d->mappedBuffers.append(buffer);
++    if (!m_memoryTransfer) {
++        qCWarning(qLcV4L2Camera) << "Cannot init v4l2 memory transfer," << qt_error_string(errno);
++        emit error(QCamera::CameraError, QLatin1String("Cannot init V4L2 memory transfer"));
 +    }
-+
 +}
 +
 +void QV4L2Camera::stopCapturing()
 +{
-+    if (!d)
++    if (!m_memoryTransfer || !m_v4l2FileDescriptor)
 +        return;
 +
-+    delete notifier;
-+    notifier = nullptr;
++    m_notifier = nullptr;
 +
-+    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
-+
-+    if (ioctl(d->v4l2FileDescriptor, VIDIOC_STREAMOFF, &type) < 0) {
++    if (!m_v4l2FileDescriptor->stopStream()) {
++        // TODO: handle the case carefully to avoid possible memory corruption
 +        if (errno != ENODEV)
 +            qWarning() << "failed to stop capture";
 +    }
-+    cameraBusy = false;
++
++    m_memoryTransfer = nullptr;
++    m_cameraBusy = false;
 +}
 +
 +void QV4L2Camera::startCapturing()
 +{
-+    if (cameraBusy)
++    if (!m_v4l2FileDescriptor)
 +        return;
 +
-+    // #### better to use the user data method instead of mmap???
-+    qsizetype i;
++    setV4L2CameraFormat();
++    initV4L2MemoryTransfer();
 +
-+    for (i = 0; i < d->mappedBuffers.size(); ++i) {
-+        v4l2_buffer buf = {};
-+        buf.index = i;
-+        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
-+        buf.memory = V4L2_MEMORY_MMAP;
++    if (m_cameraBusy || !m_memoryTransfer)
++        return;
 +
-+        if (ioctl(d->v4l2FileDescriptor, VIDIOC_QBUF, &buf) < 0) {
-+            qWarning() << "failed to set up mapped buffer";
-+            return;
-+        }
++    if (!m_v4l2FileDescriptor->startStream()) {
++        qWarning() << "Couldn't start v4l2 camera stream";
++        return;
 +    }
-+    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
-+    if (ioctl(d->v4l2FileDescriptor, VIDIOC_STREAMON, &type) < 0)
-+        qWarning() << "failed to start capture";
 +
-+    notifier = new QSocketNotifier(d->v4l2FileDescriptor, QSocketNotifier::Read);
-+    connect(notifier, &QSocketNotifier::activated, this, &QV4L2Camera::readFrame);
++    m_notifier =
++            std::make_unique<QSocketNotifier>(m_v4l2FileDescriptor->get(), QSocketNotifier::Read);
++    connect(m_notifier.get(), &QSocketNotifier::activated, this, &QV4L2Camera::readFrame);
 +
-+    firstFrameTime = { -1, -1 };
++    m_firstFrameTime = { -1, -1 };
++}
++
++QVideoFrameFormat QV4L2Camera::frameFormat() const
++{
++    auto result = QPlatformCamera::frameFormat();
++    result.setColorSpace(m_colorSpace);
++    return result;
 +}
 +
 +QT_END_NAMESPACE
++
++#include "moc_qv4l2camera_p.cpp"
 diff --git a/src/plugins/multimedia/v4l2/qv4l2camera_p.h b/src/plugins/multimedia/v4l2/qv4l2camera_p.h
 new file mode 100644
-index 000000000..f5df691c3
+index 000000000..79cc3cfa9
 --- /dev/null
 +++ b/src/plugins/multimedia/v4l2/qv4l2camera_p.h
-@@ -0,0 +1,159 @@
+@@ -0,0 +1,133 @@
 +// Copyright (C) 2021 The Qt Company Ltd.
 +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
 +
-+#ifndef QFFMPEGCAMERA_H
-+#define QFFMPEGCAMERA_H
++#ifndef QV4L2CAMERA_H
++#define QV4L2CAMERA_H
 +
 +//
 +//  W A R N I N G
@@ -2009,50 +1770,40 @@ index 000000000..f5df691c3
 +//
 +
 +#include <private/qplatformcamera_p.h>
-+#include <private/qplatformvideodevices_p.h>
-+#include <private/qplatformmediaintegration_p.h>
-+
-+#include <qfilesystemwatcher.h>
-+#include <qsocketnotifier.h>
-+#include <qmutex.h>
++#include <sys/time.h>
 +
 +QT_BEGIN_NAMESPACE
 +
-+class QV4L2CameraDevices : public QPlatformVideoDevices
-+{
-+    Q_OBJECT
-+public:
-+    QV4L2CameraDevices(QPlatformMediaIntegration *integration);
-+
-+    QList<QCameraDevice> videoDevices() const override;
-+
-+public Q_SLOTS:
-+    void checkCameras();
-+
-+private:
-+    bool doCheckCameras();
-+
-+    QList<QCameraDevice> m_cameras;
-+    QFileSystemWatcher m_deviceWatcher;
++class QV4L2FileDescriptor;
++class QV4L2MemoryTransfer;
++class QSocketNotifier;
++
++struct V4L2CameraInfo
++{
++    bool formatInitialized = false;
++
++    bool autoWhiteBalanceSupported = false;
++    bool colorTemperatureSupported = false;
++    bool autoExposureSupported = false;
++    bool manualExposureSupported = false;
++    bool flashSupported = false;
++    bool torchSupported = false;
++    qint32 minColorTemp = 5600; // Daylight...
++    qint32 maxColorTemp = 5600;
++    qint32 minExposure = 0;
++    qint32 maxExposure = 0;
++    qint32 minExposureAdjustment = 0;
++    qint32 maxExposureAdjustment = 0;
++    qint32 minFocus = 0;
++    qint32 maxFocus = 0;
++    qint32 rangedFocus = false;
++
++    int minZoom = 0;
++    int maxZoom = 0;
 +};
 +
-+struct QV4L2CameraBuffers
-+{
-+public:
-+    ~QV4L2CameraBuffers();
-+
-+    void release(int index);
-+    void unmapBuffers();
-+
-+    QAtomicInt ref;
-+    QMutex mutex;
-+    struct MappedBuffer {
-+        void *data;
-+        qsizetype size;
-+    };
-+    QList<MappedBuffer> mappedBuffers;
-+    int v4l2FileDescriptor = -1;
-+};
++QVideoFrameFormat::PixelFormat formatForV4L2Format(uint32_t v4l2Format);
++uint32_t v4l2FormatForPixelFormat(QVideoFrameFormat::PixelFormat format);
 +
 +class Q_MULTIMEDIA_EXPORT QV4L2Camera : public QPlatformCamera
 +{
@@ -2095,18 +1846,13 @@ index 000000000..f5df691c3
 +    void setWhiteBalanceMode(QCamera::WhiteBalanceMode /*mode*/) override;
 +    void setColorTemperature(int /*temperature*/) override;
 +
-+    void releaseBuffer(int index);
++    QVideoFrameFormat frameFormat() const override;
 +
 +private Q_SLOTS:
 +    void readFrame();
 +
 +private:
 +    void setCameraBusy();
-+
-+    bool m_active = false;
-+
-+    QCameraDevice m_cameraDevice;
-+
 +    void initV4L2Controls();
 +    void closeV4L2Fd();
 +    int setV4L2ColorTemperature(int temperature);
@@ -2114,39 +1860,688 @@ index 000000000..f5df691c3
 +    int getV4L2Parameter(quint32 id) const;
 +
 +    void setV4L2CameraFormat();
-+    void initMMap();
++    void initV4L2MemoryTransfer();
 +    void startCapturing();
 +    void stopCapturing();
 +
-+    QSocketNotifier *notifier = nullptr;
-+    QExplicitlySharedDataPointer<QV4L2CameraBuffers> d;
-+
-+    bool v4l2AutoWhiteBalanceSupported = false;
-+    bool v4l2ColorTemperatureSupported = false;
-+    bool v4l2AutoExposureSupported = false;
-+    bool v4l2ManualExposureSupported = false;
-+    qint32 v4l2MinColorTemp = 5600; // Daylight...
-+    qint32 v4l2MaxColorTemp = 5600;
-+    qint32 v4l2MinExposure = 0;
-+    qint32 v4l2MaxExposure = 0;
-+    qint32 v4l2MinExposureAdjustment = 0;
-+    qint32 v4l2MaxExposureAdjustment = 0;
-+    qint32 v4l2MinFocus = 0;
-+    qint32 v4l2MaxFocus = 0;
-+    qint32 v4l2RangedFocus = false;
-+    bool v4l2FlashSupported = false;
-+    bool v4l2TorchSupported = false;
-+    int v4l2MinZoom = 0;
-+    int v4l2MaxZoom = 0;
-+    timeval firstFrameTime = {-1, -1};
-+    int bytesPerLine = -1;
-+    QVideoFrameFormat::ColorSpace colorSpace = QVideoFrameFormat::ColorSpace_Undefined;
-+    qint64 frameDuration = -1;
-+    bool cameraBusy = false;
++private:
++    bool m_active = false;
++    QCameraDevice m_cameraDevice;
++
++    std::unique_ptr<QSocketNotifier> m_notifier;
++    std::unique_ptr<QV4L2MemoryTransfer> m_memoryTransfer;
++    std::shared_ptr<QV4L2FileDescriptor> m_v4l2FileDescriptor;
++
++    V4L2CameraInfo m_v4l2Info;
++
++    timeval m_firstFrameTime = { -1, -1 };
++    quint32 m_bytesPerLine = 0;
++    quint32 m_imageSize = 0;
++    QVideoFrameFormat::ColorSpace m_colorSpace = QVideoFrameFormat::ColorSpace_Undefined;
++    qint64 m_frameDuration = -1;
++    bool m_cameraBusy = false;
 +};
 +
 +QT_END_NAMESPACE
 +
++#endif // QV4L2CAMERA_H
+diff --git a/src/plugins/multimedia/v4l2/qv4l2cameradevices.cpp b/src/plugins/multimedia/v4l2/qv4l2cameradevices.cpp
+new file mode 100644
+index 000000000..44abde914
+--- /dev/null
++++ b/src/plugins/multimedia/v4l2/qv4l2cameradevices.cpp
+@@ -0,0 +1,168 @@
++// Copyright (C) 2023 The Qt Company Ltd.
++// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++
++#include "qv4l2cameradevices_p.h"
++#include "qv4l2filedescriptor_p.h"
++#include "qv4l2camera_p.h"
++
++#include <private/qcameradevice_p.h>
++#include <private/qcore_unix_p.h>
++
++#include <qdir.h>
++#include <qfile.h>
++#include <qdebug.h>
++#include <qloggingcategory.h>
++
++#include <linux/videodev2.h>
++
++QT_BEGIN_NAMESPACE
++
++static Q_LOGGING_CATEGORY(qLcV4L2CameraDevices, "qt.multimedia.ffmpeg.v4l2cameradevices");
++
++static bool areCamerasEqual(QList<QCameraDevice> a, QList<QCameraDevice> b)
++{
++    auto areCamerasDataEqual = [](const QCameraDevice &a, const QCameraDevice &b) {
++        Q_ASSERT(QCameraDevicePrivate::handle(a));
++        Q_ASSERT(QCameraDevicePrivate::handle(b));
++        return *QCameraDevicePrivate::handle(a) == *QCameraDevicePrivate::handle(b);
++    };
++
++    return std::equal(a.cbegin(), a.cend(), b.cbegin(), b.cend(), areCamerasDataEqual);
++}
++
++QV4L2CameraDevices::QV4L2CameraDevices(QPlatformMediaIntegration *integration)
++    : QPlatformVideoDevices(integration)
++{
++    m_deviceWatcher.addPath(QLatin1String("/dev"));
++    connect(&m_deviceWatcher, &QFileSystemWatcher::directoryChanged, this,
++            &QV4L2CameraDevices::checkCameras);
++    doCheckCameras();
++}
++
++QList<QCameraDevice> QV4L2CameraDevices::videoDevices() const
++{
++    return m_cameras;
++}
++
++void QV4L2CameraDevices::checkCameras()
++{
++    if (doCheckCameras())
++        emit videoInputsChanged();
++}
++
++bool QV4L2CameraDevices::doCheckCameras()
++{
++    QList<QCameraDevice> newCameras;
++
++    QDir dir(QLatin1String("/dev"));
++    const auto devices = dir.entryList(QDir::System);
++
++    bool first = true;
++
++    for (auto device : devices) {
++        //        qCDebug(qLcV4L2Camera) << "device:" << device;
++        if (!device.startsWith(QLatin1String("video")))
++            continue;
++
++        QByteArray file = QFile::encodeName(dir.filePath(device));
++        const int fd = open(file.constData(), O_RDONLY);
++        if (fd < 0)
++            continue;
++
++        auto fileCloseGuard = qScopeGuard([fd]() { close(fd); });
++
++        v4l2_fmtdesc formatDesc = {};
++
++        struct v4l2_capability cap;
++        if (xioctl(fd, VIDIOC_QUERYCAP, &cap) < 0)
++            continue;
++
++        if (cap.device_caps & V4L2_CAP_META_CAPTURE)
++            continue;
++        if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE))
++            continue;
++        if (!(cap.capabilities & V4L2_CAP_STREAMING))
++            continue;
++
++        auto camera = std::make_unique<QCameraDevicePrivate>();
++
++        camera->id = file;
++        camera->description = QString::fromUtf8((const char *)cap.card);
++        qCDebug(qLcV4L2CameraDevices) << "found camera" << camera->id << camera->description;
++
++        formatDesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
++
++        while (!xioctl(fd, VIDIOC_ENUM_FMT, &formatDesc)) {
++            auto pixelFmt = formatForV4L2Format(formatDesc.pixelformat);
++            qCDebug(qLcV4L2CameraDevices) << "    " << pixelFmt;
++
++            if (pixelFmt == QVideoFrameFormat::Format_Invalid) {
++                ++formatDesc.index;
++                continue;
++            }
++
++            qCDebug(qLcV4L2CameraDevices) << "frame sizes:";
++            v4l2_frmsizeenum frameSize = {};
++            frameSize.pixel_format = formatDesc.pixelformat;
++
++            while (!xioctl(fd, VIDIOC_ENUM_FRAMESIZES, &frameSize)) {
++                ++frameSize.index;
++                if (frameSize.type != V4L2_FRMSIZE_TYPE_DISCRETE)
++                    continue;
++
++                QSize resolution(frameSize.discrete.width, frameSize.discrete.height);
++                float min = 1e10;
++                float max = 0;
++
++                v4l2_frmivalenum frameInterval = {};
++                frameInterval.pixel_format = formatDesc.pixelformat;
++                frameInterval.width = frameSize.discrete.width;
++                frameInterval.height = frameSize.discrete.height;
++
++                while (!xioctl(fd, VIDIOC_ENUM_FRAMEINTERVALS, &frameInterval)) {
++                    ++frameInterval.index;
++                    if (frameInterval.type != V4L2_FRMIVAL_TYPE_DISCRETE)
++                        continue;
++                    float rate = float(frameInterval.discrete.denominator)
++                            / float(frameInterval.discrete.numerator);
++                    if (rate > max)
++                        max = rate;
++                    if (rate < min)
++                        min = rate;
++                }
++
++                qCDebug(qLcV4L2CameraDevices) << "    " << resolution << min << max;
++
++                if (min <= max) {
++                    auto fmt = std::make_unique<QCameraFormatPrivate>();
++                    fmt->pixelFormat = pixelFmt;
++                    fmt->resolution = resolution;
++                    fmt->minFrameRate = min;
++                    fmt->maxFrameRate = max;
++                    camera->videoFormats.append(fmt.release()->create());
++                    camera->photoResolutions.append(resolution);
++                }
++            }
++
++            ++formatDesc.index;
++        }
++
++        if (camera->videoFormats.empty())
++            continue;
++
++        // first camera is default
++        camera->isDefault = std::exchange(first, false);
++
++        newCameras.append(camera.release()->create());
++    }
++
++    if (areCamerasEqual(m_cameras, newCameras))
++        return false;
++
++    m_cameras = std::move(newCameras);
++    return true;
++}
++
++QT_END_NAMESPACE
++
++#include "moc_qv4l2cameradevices_p.cpp"
+diff --git a/src/plugins/multimedia/v4l2/qv4l2cameradevices_p.h b/src/plugins/multimedia/v4l2/qv4l2cameradevices_p.h
+new file mode 100644
+index 000000000..ce424d3b6
+--- /dev/null
++++ b/src/plugins/multimedia/v4l2/qv4l2cameradevices_p.h
+@@ -0,0 +1,46 @@
++// Copyright (C) 2023 The Qt Company Ltd.
++// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++
++#ifndef QV4L2CAMERADEVICES_P_H
++#define QV4L2CAMERADEVICES_P_H
++
++//
++//  W A R N I N G
++//  -------------
++//
++// This file is not part of the Qt API.  It exists purely as an
++// implementation detail.  This header file may change from version to
++// version without notice, or even be removed.
++//
++// We mean it.
++//
++
++#include <private/qplatformvideodevices_p.h>
++#include <private/qplatformmediaintegration_p.h>
++
++#include <qfilesystemwatcher.h>
++
++QT_BEGIN_NAMESPACE
++
++class QV4L2CameraDevices : public QPlatformVideoDevices
++{
++    Q_OBJECT
++public:
++    QV4L2CameraDevices(QPlatformMediaIntegration *integration);
++
++    QList<QCameraDevice> videoDevices() const override;
++
++public Q_SLOTS:
++    void checkCameras();
++
++private:
++    bool doCheckCameras();
++
++private:
++    QList<QCameraDevice> m_cameras;
++    QFileSystemWatcher m_deviceWatcher;
++};
++
++QT_END_NAMESPACE
++
++#endif // QV4L2CAMERADEVICES_P_H
+diff --git a/src/plugins/multimedia/v4l2/qv4l2filedescriptor.cpp b/src/plugins/multimedia/v4l2/qv4l2filedescriptor.cpp
+new file mode 100644
+index 000000000..7f7b099c7
+--- /dev/null
++++ b/src/plugins/multimedia/v4l2/qv4l2filedescriptor.cpp
+@@ -0,0 +1,71 @@
++// Copyright (C) 2023 The Qt Company Ltd.
++// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++
++#include "qv4l2filedescriptor_p.h"
++
++#include <sys/ioctl.h>
++#include <private/qcore_unix_p.h>
++
++#include <linux/videodev2.h>
++
++QT_BEGIN_NAMESPACE
++
++int xioctl(int fd, int request, void *arg)
++{
++    int res;
++
++    do {
++        res = ::ioctl(fd, request, arg);
++    } while (res == -1 && EINTR == errno);
++
++    return res;
++}
++
++QV4L2FileDescriptor::QV4L2FileDescriptor(int descriptor) : m_descriptor(descriptor)
++{
++    Q_ASSERT(descriptor >= 0);
++}
++
++QV4L2FileDescriptor::~QV4L2FileDescriptor()
++{
++    qt_safe_close(m_descriptor);
++}
++
++bool QV4L2FileDescriptor::call(int request, void *arg) const
++{
++    return ::xioctl(m_descriptor, request, arg) >= 0;
++}
++
++bool QV4L2FileDescriptor::requestBuffers(quint32 memoryType, quint32 &buffersCount) const
++{
++    v4l2_requestbuffers req = {};
++    req.count = buffersCount;
++    req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
++    req.memory = memoryType;
++
++    if (!call(VIDIOC_REQBUFS, &req))
++        return false;
++
++    buffersCount = req.count;
++    return true;
++}
++
++bool QV4L2FileDescriptor::startStream()
++{
++    int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
++    if (!call(VIDIOC_STREAMON, &type))
++        return false;
++
++    m_streamStarted = true;
++    return true;
++}
++
++bool QV4L2FileDescriptor::stopStream()
++{
++    int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
++    auto result = call(VIDIOC_STREAMOFF, &type);
++    m_streamStarted = false;
++    return result;
++}
++
++QT_END_NAMESPACE
+diff --git a/src/plugins/multimedia/v4l2/qv4l2filedescriptor_p.h b/src/plugins/multimedia/v4l2/qv4l2filedescriptor_p.h
+new file mode 100644
+index 000000000..1058c7a82
+--- /dev/null
++++ b/src/plugins/multimedia/v4l2/qv4l2filedescriptor_p.h
+@@ -0,0 +1,50 @@
++// Copyright (C) 2023 The Qt Company Ltd.
++// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++
++#ifndef QV4L2FILEDESCRIPTOR_P_H
++#define QV4L2FILEDESCRIPTOR_P_H
++
++#include <private/qtmultimediaglobal_p.h>
++
++//
++//  W A R N I N G
++//  -------------
++//
++// This file is not part of the Qt API.  It exists purely as an
++// implementation detail.  This header file may change from version to
++// version without notice, or even be removed.
++//
++// We mean it.
++//
++
++QT_BEGIN_NAMESPACE
++
++int xioctl(int fd, int request, void *arg);
++
++class QV4L2FileDescriptor
++{
++public:
++    QV4L2FileDescriptor(int descriptor);
++
++    ~QV4L2FileDescriptor();
++
++    bool call(int request, void *arg) const;
++
++    int get() const { return m_descriptor; }
++
++    bool requestBuffers(quint32 memoryType, quint32 &buffersCount) const;
++
++    bool startStream();
++
++    bool stopStream();
++
++    bool streamStarted() const { return m_streamStarted; }
++
++private:
++    int m_descriptor;
++    bool m_streamStarted = false;
++};
++
++QT_END_NAMESPACE
++
++#endif // QV4L2FILEDESCRIPTOR_P_H
+diff --git a/src/plugins/multimedia/v4l2/qv4l2memorytransfer.cpp b/src/plugins/multimedia/v4l2/qv4l2memorytransfer.cpp
+new file mode 100644
+index 000000000..32ee4f8f8
+--- /dev/null
++++ b/src/plugins/multimedia/v4l2/qv4l2memorytransfer.cpp
+@@ -0,0 +1,223 @@
++// Copyright (C) 2023 The Qt Company Ltd.
++// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++
++#include "qv4l2memorytransfer_p.h"
++#include "qv4l2filedescriptor_p.h"
++
++#include <qloggingcategory.h>
++#include <qdebug.h>
++#include <sys/mman.h>
++#include <optional>
++
++QT_BEGIN_NAMESPACE
++
++static Q_LOGGING_CATEGORY(qLcV4L2MemoryTransfer, "qt.multimedia.ffmpeg.v4l2camera.memorytransfer");
++
++namespace {
++
++v4l2_buffer makeV4l2Buffer(quint32 memoryType, quint32 index = 0)
++{
++    v4l2_buffer buf = {};
++    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
++    buf.memory = memoryType;
++    buf.index = index;
++    return buf;
++}
++
++class UserPtrMemoryTransfer : public QV4L2MemoryTransfer
++{
++public:
++    static QV4L2MemoryTransferUPtr create(QV4L2FileDescriptorPtr fileDescriptor, quint32 imageSize)
++    {
++        quint32 buffersCount = 2;
++        if (!fileDescriptor->requestBuffers(V4L2_MEMORY_USERPTR, buffersCount)) {
++            qCWarning(qLcV4L2MemoryTransfer) << "Cannot request V4L2_MEMORY_USERPTR buffers";
++            return {};
++        }
++
++        std::unique_ptr<UserPtrMemoryTransfer> result(
++                new UserPtrMemoryTransfer(std::move(fileDescriptor), buffersCount, imageSize));
++
++        return result->enqueueBuffers() ? std::move(result) : nullptr;
++    }
++
++    std::optional<Buffer> dequeueBuffer() override
++    {
++        auto v4l2Buffer = makeV4l2Buffer(V4L2_MEMORY_USERPTR);
++        if (!fileDescriptor().call(VIDIOC_DQBUF, &v4l2Buffer))
++            return {};
++
++        Q_ASSERT(v4l2Buffer.index < m_byteArrays.size());
++        Q_ASSERT(!m_byteArrays[v4l2Buffer.index].isEmpty());
++
++        return Buffer{ v4l2Buffer, std::move(m_byteArrays[v4l2Buffer.index]) };
++    }
++
++    bool enqueueBuffer(quint32 index) override
++    {
++        Q_ASSERT(index < m_byteArrays.size());
++        Q_ASSERT(m_byteArrays[index].isEmpty());
++
++        auto buf = makeV4l2Buffer(V4L2_MEMORY_USERPTR, index);
++        static_assert(sizeof(decltype(buf.m.userptr)) == sizeof(size_t), "Not compatible sizes");
++
++        m_byteArrays[index] = QByteArray(static_cast<int>(m_imageSize), Qt::Uninitialized);
++
++        buf.m.userptr = (decltype(buf.m.userptr))m_byteArrays[index].data();
++        buf.length = m_byteArrays[index].size();
++
++        if (!fileDescriptor().call(VIDIOC_QBUF, &buf)) {
++            qWarning() << "Couldn't add V4L2 buffer" << errno << strerror(errno) << index;
++            return false;
++        }
++
++        return true;
++    }
++
++    quint32 buffersCount() const override { return static_cast<quint32>(m_byteArrays.size()); }
++
++private:
++    UserPtrMemoryTransfer(QV4L2FileDescriptorPtr fileDescriptor, quint32 buffersCount,
++                          quint32 imageSize)
++        : QV4L2MemoryTransfer(std::move(fileDescriptor)),
++          m_imageSize(imageSize),
++          m_byteArrays(buffersCount)
++    {
++    }
++
++private:
++    quint32 m_imageSize;
++    std::vector<QByteArray> m_byteArrays;
++};
++
++class MMapMemoryTransfer : public QV4L2MemoryTransfer
++{
++public:
++    struct MemorySpan
++    {
++        void *data = nullptr;
++        size_t size = 0;
++        bool inQueue = false;
++    };
++
++    static QV4L2MemoryTransferUPtr create(QV4L2FileDescriptorPtr fileDescriptor)
++    {
++        quint32 buffersCount = 2;
++        if (!fileDescriptor->requestBuffers(V4L2_MEMORY_MMAP, buffersCount)) {
++            qCWarning(qLcV4L2MemoryTransfer) << "Cannot request V4L2_MEMORY_MMAP buffers";
++            return {};
++        }
++
++        std::unique_ptr<MMapMemoryTransfer> result(
++                new MMapMemoryTransfer(std::move(fileDescriptor)));
++
++        return result->init(buffersCount) ? std::move(result) : nullptr;
++    }
++
++    bool init(quint32 buffersCount)
++    {
++        for (quint32 index = 0; index < buffersCount; ++index) {
++            auto buf = makeV4l2Buffer(V4L2_MEMORY_MMAP, index);
++
++            if (!fileDescriptor().call(VIDIOC_QUERYBUF, &buf)) {
++                qWarning() << "Can't map buffer" << index;
++                return false;
++            }
++
++            auto mappedData = mmap(nullptr, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED,
++                                   fileDescriptor().get(), buf.m.offset);
++
++            if (mappedData == MAP_FAILED) {
++                qWarning() << "mmap failed" << index << buf.length << buf.m.offset;
++                return false;
++            }
++
++            m_spans.push_back(MemorySpan{ mappedData, buf.length, false });
++        }
++
++        m_spans.shrink_to_fit();
++
++        return enqueueBuffers();
++    }
++
++    ~MMapMemoryTransfer() override
++    {
++        for (const auto &span : m_spans)
++            munmap(span.data, span.size);
++    }
++
++    std::optional<Buffer> dequeueBuffer() override
++    {
++        auto v4l2Buffer = makeV4l2Buffer(V4L2_MEMORY_MMAP);
++        if (!fileDescriptor().call(VIDIOC_DQBUF, &v4l2Buffer))
++            return {};
++
++        const auto index = v4l2Buffer.index;
++
++        Q_ASSERT(index < m_spans.size());
++
++        auto &span = m_spans[index];
++
++        Q_ASSERT(span.inQueue);
++        span.inQueue = false;
++
++        return Buffer{ v4l2Buffer,
++                       QByteArray(reinterpret_cast<const char *>(span.data), span.size) };
++    }
++
++    bool enqueueBuffer(quint32 index) override
++    {
++        Q_ASSERT(index < m_spans.size());
++        Q_ASSERT(!m_spans[index].inQueue);
++
++        auto buf = makeV4l2Buffer(V4L2_MEMORY_MMAP, index);
++        if (!fileDescriptor().call(VIDIOC_QBUF, &buf))
++            return false;
++
++        m_spans[index].inQueue = true;
++        return true;
++    }
++
++    quint32 buffersCount() const override { return static_cast<quint32>(m_spans.size()); }
++
++private:
++    using QV4L2MemoryTransfer::QV4L2MemoryTransfer;
++
++private:
++    std::vector<MemorySpan> m_spans;
++};
++} // namespace
++
++QV4L2MemoryTransfer::QV4L2MemoryTransfer(QV4L2FileDescriptorPtr fileDescriptor)
++    : m_fileDescriptor(std::move(fileDescriptor))
++{
++    Q_ASSERT(m_fileDescriptor);
++    Q_ASSERT(!m_fileDescriptor->streamStarted());
++}
++
++QV4L2MemoryTransfer::~QV4L2MemoryTransfer()
++{
++    Q_ASSERT(!m_fileDescriptor->streamStarted()); // to avoid possible corruptions
++}
++
++bool QV4L2MemoryTransfer::enqueueBuffers()
++{
++    for (quint32 i = 0; i < buffersCount(); ++i)
++        if (!enqueueBuffer(i))
++            return false;
++
++    return true;
++}
++
++QV4L2MemoryTransferUPtr makeUserPtrMemoryTransfer(QV4L2FileDescriptorPtr fileDescriptor,
++                                                  quint32 imageSize)
++{
++    return UserPtrMemoryTransfer::create(std::move(fileDescriptor), imageSize);
++}
++
++QV4L2MemoryTransferUPtr makeMMapMemoryTransfer(QV4L2FileDescriptorPtr fileDescriptor)
++{
++    return MMapMemoryTransfer::create(std::move(fileDescriptor));
++}
++
++QT_END_NAMESPACE
+diff --git a/src/plugins/multimedia/v4l2/qv4l2memorytransfer_p.h b/src/plugins/multimedia/v4l2/qv4l2memorytransfer_p.h
+new file mode 100644
+index 000000000..6b5e3913f
+--- /dev/null
++++ b/src/plugins/multimedia/v4l2/qv4l2memorytransfer_p.h
+@@ -0,0 +1,66 @@
++// Copyright (C) 2023 The Qt Company Ltd.
++// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++
++#ifndef QV4L2MEMORYTRANSFER_P_H
++#define QV4L2MEMORYTRANSFER_P_H
++
++#include <private/qtmultimediaglobal_p.h>
++#include <qbytearray.h>
++#include <linux/videodev2.h>
++
++#include <memory>
++
++QT_BEGIN_NAMESPACE
++
++//
++//  W A R N I N G
++//  -------------
++//
++// This file is not part of the Qt API.  It exists purely as an
++// implementation detail.  This header file may change from version to
++// version without notice, or even be removed.
++//
++// We mean it.
++//
++
++class QV4L2FileDescriptor;
++using QV4L2FileDescriptorPtr = std::shared_ptr<QV4L2FileDescriptor>;
++
++class QV4L2MemoryTransfer
++{
++public:
++    struct Buffer
++    {
++        v4l2_buffer v4l2Buffer = {};
++        QByteArray data;
++    };
++
++    QV4L2MemoryTransfer(QV4L2FileDescriptorPtr fileDescriptor);
++
++    virtual ~QV4L2MemoryTransfer();
++
++    virtual std::optional<Buffer> dequeueBuffer() = 0;
++
++    virtual bool enqueueBuffer(quint32 index) = 0;
++
++    virtual quint32 buffersCount() const = 0;
++
++protected:
++    bool enqueueBuffers();
++
++    const QV4L2FileDescriptor &fileDescriptor() const { return *m_fileDescriptor; }
++
++private:
++    QV4L2FileDescriptorPtr m_fileDescriptor;
++};
++
++using QV4L2MemoryTransferUPtr = std::unique_ptr<QV4L2MemoryTransfer>;
++
++QV4L2MemoryTransferUPtr makeUserPtrMemoryTransfer(QV4L2FileDescriptorPtr fileDescriptor,
++                                                  quint32 imageSize);
++
++QV4L2MemoryTransferUPtr makeMMapMemoryTransfer(QV4L2FileDescriptorPtr fileDescriptor);
++
++QT_END_NAMESPACE
 +
-+#endif  // QFFMPEGCAMERA_H
-+
++#endif // QV4L2MEMORYTRANSFER_P_H