summaryrefslogtreecommitdiffstats
path: root/src/plugins/multimedia/ffmpeg/qv4l2camera.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/multimedia/ffmpeg/qv4l2camera.cpp')
-rw-r--r--src/plugins/multimedia/ffmpeg/qv4l2camera.cpp707
1 files changed, 707 insertions, 0 deletions
diff --git a/src/plugins/multimedia/ffmpeg/qv4l2camera.cpp b/src/plugins/multimedia/ffmpeg/qv4l2camera.cpp
new file mode 100644
index 000000000..1ba05364d
--- /dev/null
+++ b/src/plugins/multimedia/ffmpeg/qv4l2camera.cpp
@@ -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 <private/qcameradevice_p.h>
+#include <private/qmultimediautils_p.h>
+#include <private/qmemoryvideobuffer_p.h>
+#include <private/qcore_unix_p.h>
+
+#include <qsocketnotifier.h>
+#include <qloggingcategory.h>
+
+QT_BEGIN_NAMESPACE
+
+static Q_LOGGING_CATEGORY(qLcV4L2Camera, "qt.multimedia.ffmpeg.v4l2camera");
+
+static const struct {
+ QVideoFrameFormat::PixelFormat fmt;
+ uint32_t v4l2Format;
+} formatMap[] = {
+ // ### How do we handle V4L2_PIX_FMT_H264 and V4L2_PIX_FMT_MPEG4?
+ { QVideoFrameFormat::Format_YUV420P, V4L2_PIX_FMT_YUV420 },
+ { QVideoFrameFormat::Format_YUV422P, V4L2_PIX_FMT_YUV422P },
+ { QVideoFrameFormat::Format_YUYV, V4L2_PIX_FMT_YUYV },
+ { QVideoFrameFormat::Format_UYVY, V4L2_PIX_FMT_UYVY },
+ { QVideoFrameFormat::Format_XBGR8888, V4L2_PIX_FMT_XBGR32 },
+ { QVideoFrameFormat::Format_XRGB8888, V4L2_PIX_FMT_XRGB32 },
+ { QVideoFrameFormat::Format_ABGR8888, V4L2_PIX_FMT_ABGR32 },
+ { QVideoFrameFormat::Format_ARGB8888, V4L2_PIX_FMT_ARGB32 },
+ { QVideoFrameFormat::Format_BGRX8888, V4L2_PIX_FMT_BGR32 },
+ { QVideoFrameFormat::Format_RGBX8888, V4L2_PIX_FMT_RGB32 },
+ { QVideoFrameFormat::Format_BGRA8888, V4L2_PIX_FMT_BGRA32 },
+ { QVideoFrameFormat::Format_RGBA8888, V4L2_PIX_FMT_RGBA32 },
+ { QVideoFrameFormat::Format_Y8, V4L2_PIX_FMT_GREY },
+ { QVideoFrameFormat::Format_Y16, V4L2_PIX_FMT_Y16 },
+ { QVideoFrameFormat::Format_NV12, V4L2_PIX_FMT_NV12 },
+ { QVideoFrameFormat::Format_NV21, V4L2_PIX_FMT_NV21 },
+ { QVideoFrameFormat::Format_Jpeg, V4L2_PIX_FMT_MJPEG },
+ { QVideoFrameFormat::Format_Jpeg, V4L2_PIX_FMT_JPEG },
+ { QVideoFrameFormat::Format_Invalid, 0 },
+};
+
+QVideoFrameFormat::PixelFormat formatForV4L2Format(uint32_t v4l2Format)
+{
+ auto *f = formatMap;
+ while (f->v4l2Format) {
+ if (f->v4l2Format == v4l2Format)
+ return f->fmt;
+ ++f;
+ }
+ return QVideoFrameFormat::Format_Invalid;
+}
+
+uint32_t v4l2FormatForPixelFormat(QVideoFrameFormat::PixelFormat format)
+{
+ auto *f = formatMap;
+ while (f->v4l2Format) {
+ if (f->fmt == format)
+ return f->v4l2Format;
+ ++f;
+ }
+ return 0;
+}
+
+QV4L2Camera::QV4L2Camera(QCamera *camera)
+ : QPlatformCamera(camera)
+{
+}
+
+QV4L2Camera::~QV4L2Camera()
+{
+ stopCapturing();
+ closeV4L2Fd();
+}
+
+bool QV4L2Camera::isActive() const
+{
+ return m_active;
+}
+
+void QV4L2Camera::setActive(bool active)
+{
+ if (m_active == active)
+ return;
+ if (m_cameraDevice.isNull() && active)
+ return;
+
+ if (m_cameraFormat.isNull())
+ resolveCameraFormat({});
+
+ m_active = active;
+ if (m_active)
+ startCapturing();
+ else
+ stopCapturing();
+
+ emit newVideoFrame({});
+
+ emit activeChanged(active);
+}
+
+void QV4L2Camera::setCamera(const QCameraDevice &camera)
+{
+ if (m_cameraDevice == camera)
+ return;
+
+ stopCapturing();
+ closeV4L2Fd();
+
+ m_cameraDevice = camera;
+ resolveCameraFormat({});
+
+ initV4L2Controls();
+
+ if (m_active)
+ startCapturing();
+}
+
+bool QV4L2Camera::setCameraFormat(const QCameraFormat &format)
+{
+ if (!format.isNull() && !m_cameraDevice.videoFormats().contains(format))
+ return false;
+
+ if (!resolveCameraFormat(format))
+ return true;
+
+ if (m_active) {
+ stopCapturing();
+ closeV4L2Fd();
+
+ initV4L2Controls();
+ startCapturing();
+ }
+
+ return true;
+}
+
+bool QV4L2Camera::resolveCameraFormat(const QCameraFormat &format)
+{
+ auto fmt = format;
+ if (fmt.isNull())
+ fmt = findBestCameraFormat(m_cameraDevice);
+
+ if (fmt == m_cameraFormat)
+ return false;
+
+ m_cameraFormat = fmt;
+ return true;
+}
+
+void QV4L2Camera::setFocusMode(QCamera::FocusMode mode)
+{
+ if (mode == focusMode())
+ return;
+
+ bool focusDist = supportedFeatures() & QCamera::Feature::FocusDistance;
+ if (!focusDist && !m_v4l2Info.rangedFocus)
+ return;
+
+ switch (mode) {
+ default:
+ case QCamera::FocusModeAuto:
+ setV4L2Parameter(V4L2_CID_FOCUS_AUTO, 1);
+ 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 (m_v4l2Info.rangedFocus)
+ setV4L2Parameter(V4L2_CID_AUTO_FOCUS_RANGE, V4L2_AUTO_FOCUS_RANGE_MACRO);
+ else if (focusDist)
+ setV4L2Parameter(V4L2_CID_FOCUS_ABSOLUTE, m_v4l2Info.minFocus);
+ break;
+ case QCamera::FocusModeAutoFar:
+ setV4L2Parameter(V4L2_CID_FOCUS_AUTO, 1);
+ 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, m_v4l2Info.maxFocus);
+ break;
+ case QCamera::FocusModeManual:
+ setV4L2Parameter(V4L2_CID_FOCUS_AUTO, 0);
+ setFocusDistance(focusDistance());
+ break;
+ }
+ focusModeChanged(mode);
+}
+
+void QV4L2Camera::setFocusDistance(float 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 (m_v4l2Info.maxZoom == m_v4l2Info.minZoom)
+ return;
+ factor = qBound(1., factor, 2.);
+ int zoom = m_v4l2Info.minZoom + (factor - 1.) * (m_v4l2Info.maxZoom - m_v4l2Info.minZoom);
+ setV4L2Parameter(V4L2_CID_ZOOM_ABSOLUTE, zoom);
+ zoomFactorChanged(factor);
+}
+
+bool QV4L2Camera::isFocusModeSupported(QCamera::FocusMode mode) const
+{
+ if (supportedFeatures() & QCamera::Feature::FocusDistance &&
+ (mode == QCamera::FocusModeManual || mode == QCamera::FocusModeAutoNear || mode == QCamera::FocusModeInfinity))
+ return true;
+
+ return mode == QCamera::FocusModeAuto;
+}
+
+void QV4L2Camera::setFlashMode(QCamera::FlashMode mode)
+{
+ 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 (m_v4l2Info.flashSupported && mode == QCamera::FlashAuto)
+ return true;
+ return mode == QCamera::FlashOff;
+}
+
+bool QV4L2Camera::isFlashReady() const
+{
+ struct v4l2_queryctrl queryControl;
+ ::memset(&queryControl, 0, sizeof(queryControl));
+ queryControl.id = V4L2_CID_AUTO_WHITE_BALANCE;
+
+ return m_v4l2FileDescriptor && m_v4l2FileDescriptor->call(VIDIOC_QUERYCTRL, &queryControl);
+}
+
+void QV4L2Camera::setTorchMode(QCamera::TorchMode mode)
+{
+ 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 m_v4l2Info.torchSupported;
+ return mode == QCamera::TorchOff;
+}
+
+void QV4L2Camera::setExposureMode(QCamera::ExposureMode mode)
+{
+ 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;
+ setV4L2Parameter(V4L2_CID_EXPOSURE_AUTO, value);
+ exposureModeChanged(mode);
+ return;
+ }
+}
+
+bool QV4L2Camera::isExposureModeSupported(QCamera::ExposureMode mode) const
+{
+ if (mode == QCamera::ExposureAuto)
+ return true;
+ if (m_v4l2Info.manualExposureSupported && m_v4l2Info.autoExposureSupported)
+ return mode == QCamera::ExposureManual;
+ return false;
+}
+
+void QV4L2Camera::setExposureCompensation(float compensation)
+{
+ 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::setManualIsoSensitivity(int iso)
+{
+ if (!(supportedFeatures() & QCamera::Feature::IsoSensitivity))
+ return;
+ setV4L2Parameter(V4L2_CID_ISO_SENSITIVITY_AUTO, iso <= 0 ? V4L2_ISO_SENSITIVITY_AUTO : V4L2_ISO_SENSITIVITY_MANUAL);
+ if (iso > 0) {
+ iso = qBound(minIso(), iso, maxIso());
+ setV4L2Parameter(V4L2_CID_ISO_SENSITIVITY, iso);
+ }
+ return;
+}
+
+int QV4L2Camera::isoSensitivity() const
+{
+ if (!(supportedFeatures() & QCamera::Feature::IsoSensitivity))
+ return -1;
+ return getV4L2Parameter(V4L2_CID_ISO_SENSITIVITY);
+}
+
+void QV4L2Camera::setManualExposureTime(float secs)
+{
+ 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;
+ }
+}
+
+float QV4L2Camera::exposureTime() const
+{
+ return getV4L2Parameter(V4L2_CID_EXPOSURE_ABSOLUTE)/10000.;
+}
+
+bool QV4L2Camera::isWhiteBalanceModeSupported(QCamera::WhiteBalanceMode mode) const
+{
+ if (m_v4l2Info.autoWhiteBalanceSupported && m_v4l2Info.colorTemperatureSupported)
+ return true;
+
+ return mode == QCamera::WhiteBalanceAuto;
+}
+
+void QV4L2Camera::setWhiteBalanceMode(QCamera::WhiteBalanceMode mode)
+{
+ Q_ASSERT(isWhiteBalanceModeSupported(mode));
+
+ int temperature = colorTemperatureForWhiteBalance(mode);
+ int t = setV4L2ColorTemperature(temperature);
+ if (t == 0)
+ mode = QCamera::WhiteBalanceAuto;
+ whiteBalanceModeChanged(mode);
+}
+
+void QV4L2Camera::setColorTemperature(int temperature)
+{
+ if (temperature == 0) {
+ setWhiteBalanceMode(QCamera::WhiteBalanceAuto);
+ return;
+ }
+
+ Q_ASSERT(isWhiteBalanceModeSupported(QCamera::WhiteBalanceManual));
+
+ int t = setV4L2ColorTemperature(temperature);
+ if (t)
+ colorTemperatureChanged(t);
+}
+
+void QV4L2Camera::readFrame()
+{
+ Q_ASSERT(m_memoryTransfer);
+
+ auto buffer = m_memoryTransfer->dequeueBuffer();
+ if (!buffer) {
+ qCWarning(qLcV4L2Camera) << "Cannot take buffer";
+
+ if (errno == ENODEV) {
+ // camera got removed while being active
+ stopCapturing();
+ closeV4L2Fd();
+ }
+
+ return;
+ }
+
+ 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() + m_frameDuration);
+
+ emit newVideoFrame(frame);
+
+ if (!m_memoryTransfer->enqueueBuffer(v4l2Buffer.index))
+ qCWarning(qLcV4L2Camera) << "Cannot add buffer";
+}
+
+void QV4L2Camera::setCameraBusy()
+{
+ m_cameraBusy = true;
+ updateError(QCamera::CameraError, QLatin1String("Camera is in use"));
+}
+
+void QV4L2Camera::initV4L2Controls()
+{
+ m_v4l2Info = {};
+ QCamera::Features features;
+
+ const QByteArray deviceName = m_cameraDevice.id();
+ Q_ASSERT(!deviceName.isEmpty());
+
+ closeV4L2Fd();
+
+ 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);
+ updateError(QCamera::CameraError, QLatin1String("Cannot open camera"));
+ return;
+ }
+
+ 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 (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 (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 (m_v4l2FileDescriptor->call(VIDIOC_QUERYCTRL, &queryControl)) {
+ m_v4l2Info.autoExposureSupported = true;
+ }
+
+ ::memset(&queryControl, 0, sizeof(queryControl));
+ queryControl.id = V4L2_CID_EXPOSURE_ABSOLUTE;
+ 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 (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 (m_v4l2FileDescriptor->call(VIDIOC_QUERYCTRL, &queryControl)) {
+ queryControl.id = V4L2_CID_ISO_SENSITIVITY;
+ 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 (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 (m_v4l2FileDescriptor->call(VIDIOC_QUERYCTRL, &queryControl)) {
+ m_v4l2Info.rangedFocus = true;
+ }
+
+ ::memset(&queryControl, 0, sizeof(queryControl));
+ queryControl.id = V4L2_CID_FLASH_LED_MODE;
+ 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;
+ }
+
+ ::memset(&queryControl, 0, sizeof(queryControl));
+ queryControl.id = V4L2_CID_ZOOM_ABSOLUTE;
+ 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(m_v4l2Info.minZoom != m_v4l2Info.maxZoom ? 2 : 1);
+
+ supportedFeaturesChanged(features);
+}
+
+void QV4L2Camera::closeV4L2Fd()
+{
+ 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 (m_v4l2Info.autoWhiteBalanceSupported) {
+ setV4L2Parameter(V4L2_CID_AUTO_WHITE_BALANCE, temperature == 0 ? true : false);
+ } else if (temperature == 0) {
+ temperature = 5600;
+ }
+
+ 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;
+ }
+
+ return temperature;
+}
+
+bool QV4L2Camera::setV4L2Parameter(quint32 id, qint32 value)
+{
+ 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;
+ }
+ return true;
+}
+
+int QV4L2Camera::getV4L2Parameter(quint32 id) const
+{
+ struct v4l2_control control{id, 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;
+ }
+ return control.value;
+}
+
+void QV4L2Camera::setV4L2CameraFormat()
+{
+ if (m_v4l2Info.formatInitialized || !m_v4l2FileDescriptor)
+ return;
+
+ Q_ASSERT(!m_cameraFormat.isNull());
+ qCDebug(qLcV4L2Camera) << "XXXXX" << this << m_cameraDevice.id() << m_cameraFormat.pixelFormat()
+ << m_cameraFormat.resolution();
+
+ v4l2_format fmt = {};
+ fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+
+ auto size = m_cameraFormat.resolution();
+ fmt.fmt.pix.width = size.width();
+ fmt.fmt.pix.height = size.height();
+ fmt.fmt.pix.pixelformat = v4l2FormatForPixelFormat(m_cameraFormat.pixelFormat());
+ fmt.fmt.pix.field = V4L2_FIELD_ANY;
+
+ qCDebug(qLcV4L2Camera) << "setting camera format to" << size << fmt.fmt.pix.pixelformat;
+
+ if (!m_v4l2FileDescriptor->call(VIDIOC_S_FMT, &fmt)) {
+ if (errno == EBUSY) {
+ setCameraBusy();
+ return;
+ }
+ qWarning() << "Couldn't set video format on v4l2 camera" << strerror(errno);
+ }
+
+ 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:
+ m_colorSpace = QVideoFrameFormat::ColorSpace_Undefined;
+ break;
+ case V4L2_COLORSPACE_REC709:
+ m_colorSpace = QVideoFrameFormat::ColorSpace_BT709;
+ break;
+ case V4L2_COLORSPACE_JPEG:
+ m_colorSpace = QVideoFrameFormat::ColorSpace_AdobeRgb;
+ break;
+ case V4L2_COLORSPACE_SRGB:
+ // ##### is this correct???
+ m_colorSpace = QVideoFrameFormat::ColorSpace_BT601;
+ break;
+ case V4L2_COLORSPACE_BT2020:
+ m_colorSpace = QVideoFrameFormat::ColorSpace_BT2020;
+ break;
+ }
+
+ v4l2_streamparm streamParam = {};
+ streamParam.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+
+ streamParam.parm.capture.capability = V4L2_CAP_TIMEPERFRAME;
+ auto [num, den] = qRealToFraction(1./m_cameraFormat.maxFrameRate());
+ streamParam.parm.capture.timeperframe = { (uint)num, (uint)den };
+ m_v4l2FileDescriptor->call(VIDIOC_S_PARM, &streamParam);
+
+ m_frameDuration = 1000000 * streamParam.parm.capture.timeperframe.numerator
+ / streamParam.parm.capture.timeperframe.denominator;
+}
+
+void QV4L2Camera::initV4L2MemoryTransfer()
+{
+ if (m_cameraBusy)
+ return;
+
+ Q_ASSERT(!m_memoryTransfer);
+
+ m_memoryTransfer = makeUserPtrMemoryTransfer(m_v4l2FileDescriptor, m_imageSize);
+
+ if (m_memoryTransfer)
+ return;
+
+ if (errno == EBUSY) {
+ setCameraBusy();
+ 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);
+ updateError(QCamera::CameraError, QLatin1String("Cannot init V4L2 memory transfer"));
+ }
+}
+
+void QV4L2Camera::stopCapturing()
+{
+ if (!m_memoryTransfer || !m_v4l2FileDescriptor)
+ return;
+
+ m_notifier = nullptr;
+
+ if (!m_v4l2FileDescriptor->stopStream()) {
+ // TODO: handle the case carefully to avoid possible memory corruption
+ if (errno != ENODEV)
+ qWarning() << "failed to stop capture";
+ }
+
+ m_memoryTransfer = nullptr;
+ m_cameraBusy = false;
+}
+
+void QV4L2Camera::startCapturing()
+{
+ if (!m_v4l2FileDescriptor)
+ return;
+
+ setV4L2CameraFormat();
+ initV4L2MemoryTransfer();
+
+ if (m_cameraBusy || !m_memoryTransfer)
+ return;
+
+ if (!m_v4l2FileDescriptor->startStream()) {
+ qWarning() << "Couldn't start v4l2 camera stream";
+ return;
+ }
+
+ m_notifier =
+ std::make_unique<QSocketNotifier>(m_v4l2FileDescriptor->get(), QSocketNotifier::Read);
+ connect(m_notifier.get(), &QSocketNotifier::activated, this, &QV4L2Camera::readFrame);
+
+ 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"