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
+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
+#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
+
+
+#if QT_CONFIG(linux_v4l)
+#include "qv4l2camera_p.h"
++#include "qv4l2cameradevices_p.h"
+#endif
+
+QT_BEGIN_NAMESPACE
+#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;
+ { QVideoFrameFormat::Format_Invalid, 0 },
+};
+
-+static QVideoFrameFormat::PixelFormat formatForV4L2Format(uint32_t v4l2Format)
++QVideoFrameFormat::PixelFormat formatForV4L2Format(uint32_t v4l2Format)
+{
+ auto *f = formatMap;
+ while (f->v4l2Format) {
+ return QVideoFrameFormat::Format_Invalid;
+}
+
-+static uint32_t v4l2FormatForPixelFormat(QVideoFrameFormat::PixelFormat format)
++uint32_t v4l2FormatForPixelFormat(QVideoFrameFormat::PixelFormat format)
+{
+ auto *f = formatMap;
+ while (f->v4l2Format) {
+ 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)
+{
+
+QV4L2Camera::~QV4L2Camera()
+{
-+ setActive(false);
+ stopCapturing();
+ closeV4L2Fd();
+}
+ resolveCameraFormat({});
+
+ m_active = active;
-+ if (m_active) {
-+ setV4L2CameraFormat();
-+ initMMap();
++ if (m_active)
+ startCapturing();
-+ } else {
++ else
+ stopCapturing();
-+ }
++
+ emit newVideoFrame({});
+
+ emit activeChanged(active);
+{
+ if (m_cameraDevice == camera)
+ return;
-+ if (m_active)
-+ stopCapturing();
+
++ stopCapturing();
+ closeV4L2Fd();
+
+ m_cameraDevice = camera;
+
+ initV4L2Controls();
+
-+ if (m_active) {
-+ setV4L2CameraFormat();
-+ initMMap();
++ if (m_active)
+ startCapturing();
-+ }
+}
+
+bool QV4L2Camera::setCameraFormat(const QCameraFormat &format)
+ if (m_active) {
+ stopCapturing();
+ closeV4L2Fd();
++
+ initV4L2Controls();
-+ setV4L2CameraFormat();
-+ initMMap();
+ startCapturing();
+ }
+
+ 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);
+
+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);
+}
+
+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);
+
+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;
+}
+ ::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);
+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;
+{
+ 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;
+
+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;
+
+bool QV4L2Camera::isWhiteBalanceModeSupported(QCamera::WhiteBalanceMode mode) const
+{
-+ if (v4l2AutoWhiteBalanceSupported && v4l2ColorTemperatureSupported)
++ if (m_v4l2Info.autoWhiteBalanceSupported && m_v4l2Info.colorTemperatureSupported)
+ return true;
+
+ return mode == QCamera::WhiteBalanceAuto;
+
+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);
+
+ ::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)
+ 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;
+
+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;
+ }
+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;
+ }
+
+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;
+ 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;
+ 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;
+ }
+
+ 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
+//
+
+#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
+{
+ 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);
+ 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