From 195fc00e6c6de950aec5eab348ef38e8770cabc8 Mon Sep 17 00:00:00 2001 From: tobtoht Date: Thu, 4 Jan 2024 13:14:55 +0100 Subject: [PATCH] qt: update v4l2 with upstream --- contrib/depends/patches/qt/v4l2.patch | 1389 ++++++++++++++++--------- 1 file changed, 892 insertions(+), 497 deletions(-) diff --git a/contrib/depends/patches/qt/v4l2.patch b/contrib/depends/patches/qt/v4l2.patch index 9f1f418..2a1ced1 100644 --- a/contrib/depends/patches/qt/v4l2.patch +++ b/contrib/depends/patches/qt/v4l2.patch @@ -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 -+#include -+#include +#include -+#include -+#include +#include -+#include -+ -+#include -+#include -+#include -+#include -+#include ++#include +#include -+#include -+ -+#include + ++#include +#include + +QT_BEGIN_NAMESPACE + -+static Q_LOGGING_CATEGORY(qLV4L2Camera, "qt.multimedia.ffmpeg.v4l2camera"); -+ -+static bool areCamerasEqual(QList a, QList 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 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 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(); -+ -+ 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(); -+ 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 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 videoBuffer = new QMemoryVideoBuffer(buffer->data, m_bytesPerLine); ++ QVideoFrame frame(videoBuffer, frameFormat()); + -+// auto textureDesc = QVideoTextureHelper::textureDescription(m_format.pixelFormat()); ++ auto &v4l2Buffer = buffer->v4l2Buffer; + -+ 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; ++ 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(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 (errno == EBUSY) { ++ setCameraBusy(); + return; + } + -+ if (req.count < 2) { -+ qWarning() << "Can't map 2 or more buffers"; -+ return; ++ qCDebug(qLcV4L2Camera) << "Cannot init V4L2_MEMORY_USERPTR; trying V4L2_MEMORY_MMAP"; ++ ++ m_memoryTransfer = makeMMapMemoryTransfer(m_v4l2FileDescriptor); ++ ++ if (!m_memoryTransfer) { ++ qCWarning(qLcV4L2Camera) << "Cannot init v4l2 memory transfer," << qt_error_string(errno); ++ emit error(QCamera::CameraError, QLatin1String("Cannot init V4L2 memory transfer")); + } -+ -+ 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; -+ } -+ -+ 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; -+ } -+ -+ d->mappedBuffers.append(buffer); -+ } -+ +} + +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(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 -+#include -+#include -+ -+#include -+#include -+#include ++#include + +QT_BEGIN_NAMESPACE + -+class QV4L2CameraDevices : public QPlatformVideoDevices ++class QV4L2FileDescriptor; ++class QV4L2MemoryTransfer; ++class QSocketNotifier; ++ ++struct V4L2CameraInfo +{ -+ Q_OBJECT -+public: -+ QV4L2CameraDevices(QPlatformMediaIntegration *integration); ++ bool formatInitialized = false; + -+ QList videoDevices() const override; ++ 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; + -+public Q_SLOTS: -+ void checkCameras(); -+ -+private: -+ bool doCheckCameras(); -+ -+ QList m_cameras; -+ QFileSystemWatcher m_deviceWatcher; ++ 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 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 d; ++private: ++ bool m_active = false; ++ QCameraDevice m_cameraDevice; + -+ 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; ++ std::unique_ptr m_notifier; ++ std::unique_ptr m_memoryTransfer; ++ std::shared_ptr 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 + -+#endif // QFFMPEGCAMERA_H ++#include "qv4l2cameradevices_p.h" ++#include "qv4l2filedescriptor_p.h" ++#include "qv4l2camera_p.h" + ++#include ++#include ++ ++#include ++#include ++#include ++#include ++ ++#include ++ ++QT_BEGIN_NAMESPACE ++ ++static Q_LOGGING_CATEGORY(qLcV4L2CameraDevices, "qt.multimedia.ffmpeg.v4l2cameradevices"); ++ ++static bool areCamerasEqual(QList a, QList 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 QV4L2CameraDevices::videoDevices() const ++{ ++ return m_cameras; ++} ++ ++void QV4L2CameraDevices::checkCameras() ++{ ++ if (doCheckCameras()) ++ emit videoInputsChanged(); ++} ++ ++bool QV4L2CameraDevices::doCheckCameras() ++{ ++ QList 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(); ++ ++ 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(); ++ 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 ++#include ++ ++#include ++ ++QT_BEGIN_NAMESPACE ++ ++class QV4L2CameraDevices : public QPlatformVideoDevices ++{ ++ Q_OBJECT ++public: ++ QV4L2CameraDevices(QPlatformMediaIntegration *integration); ++ ++ QList videoDevices() const override; ++ ++public Q_SLOTS: ++ void checkCameras(); ++ ++private: ++ bool doCheckCameras(); ++ ++private: ++ QList 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 ++#include ++ ++#include ++ ++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 ++ ++// ++// 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 ++#include ++#include ++#include ++ ++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 result( ++ new UserPtrMemoryTransfer(std::move(fileDescriptor), buffersCount, imageSize)); ++ ++ return result->enqueueBuffers() ? std::move(result) : nullptr; ++ } ++ ++ std::optional 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(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(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 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 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 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(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(m_spans.size()); } ++ ++private: ++ using QV4L2MemoryTransfer::QV4L2MemoryTransfer; ++ ++private: ++ std::vector 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 ++#include ++#include ++ ++#include ++ ++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; ++ ++class QV4L2MemoryTransfer ++{ ++public: ++ struct Buffer ++ { ++ v4l2_buffer v4l2Buffer = {}; ++ QByteArray data; ++ }; ++ ++ QV4L2MemoryTransfer(QV4L2FileDescriptorPtr fileDescriptor); ++ ++ virtual ~QV4L2MemoryTransfer(); ++ ++ virtual std::optional 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; ++ ++QV4L2MemoryTransferUPtr makeUserPtrMemoryTransfer(QV4L2FileDescriptorPtr fileDescriptor, ++ quint32 imageSize); ++ ++QV4L2MemoryTransferUPtr makeMMapMemoryTransfer(QV4L2FileDescriptorPtr fileDescriptor); ++ ++QT_END_NAMESPACE ++ ++#endif // QV4L2MEMORYTRANSFER_P_H