diff options
Diffstat (limited to 'src/plugins/multimedia/ffmpeg/qv4l2camera.cpp')
-rw-r--r-- | src/plugins/multimedia/ffmpeg/qv4l2camera.cpp | 624 |
1 files changed, 179 insertions, 445 deletions
diff --git a/src/plugins/multimedia/ffmpeg/qv4l2camera.cpp b/src/plugins/multimedia/ffmpeg/qv4l2camera.cpp index 36ef020db..1ba05364d 100644 --- a/src/plugins/multimedia/ffmpeg/qv4l2camera.cpp +++ b/src/plugins/multimedia/ffmpeg/qv4l2camera.cpp @@ -1,85 +1,23 @@ -/**************************************************************************** -** -** Copyright (C) 2021 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// 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 -QV4L2CameraDevices::QV4L2CameraDevices(QPlatformMediaIntegration *integration) - : QPlatformVideoDevices(integration) -{ - deviceWatcher.addPath(QLatin1String("/dev")); - connect(&deviceWatcher, &QFileSystemWatcher::directoryChanged, this, &QV4L2CameraDevices::checkCameras); - doCheckCameras(); -} - -QList<QCameraDevice> QV4L2CameraDevices::videoDevices() const -{ - return cameras; -} - -void QV4L2CameraDevices::checkCameras() -{ - doCheckCameras(); - videoInputsChanged(); -} +static Q_LOGGING_CATEGORY(qLcV4L2Camera, "qt.multimedia.ffmpeg.v4l2camera"); -const struct { +static const struct { QVideoFrameFormat::PixelFormat fmt; uint32_t v4l2Format; } formatMap[] = { @@ -105,7 +43,7 @@ const struct { { QVideoFrameFormat::Format_Invalid, 0 }, }; -static QVideoFrameFormat::PixelFormat formatForV4L2Format(uint32_t v4l2Format) +QVideoFrameFormat::PixelFormat formatForV4L2Format(uint32_t v4l2Format) { auto *f = formatMap; while (f->v4l2Format) { @@ -116,7 +54,7 @@ static QVideoFrameFormat::PixelFormat formatForV4L2Format(uint32_t v4l2Format) return QVideoFrameFormat::Format_Invalid; } -static uint32_t v4l2FormatForPixelFormat(QVideoFrameFormat::PixelFormat format) +uint32_t v4l2FormatForPixelFormat(QVideoFrameFormat::PixelFormat format) { auto *f = formatMap; while (f->v4l2Format) { @@ -127,177 +65,6 @@ static uint32_t v4l2FormatForPixelFormat(QVideoFrameFormat::PixelFormat format) return 0; } - -void QV4L2CameraDevices::doCheckCameras() -{ - cameras.clear(); - - QDir dir(QLatin1String("/dev")); - const auto devices = dir.entryList(QDir::System); - - bool first = true; - - for (auto device : devices) { -// qDebug() << "device:" << device; - if (!device.startsWith(QLatin1String("video"))) - continue; - - QByteArray file = QFile::encodeName(dir.filePath(device)); - int fd = open(file.constData(), O_RDONLY); - if (fd < 0) - continue; - - QCameraDevicePrivate *camera = nullptr; - v4l2_fmtdesc formatDesc = {}; - - struct v4l2_capability cap; - if (ioctl(fd, VIDIOC_QUERYCAP, &cap) < 0) - goto fail; - - if (cap.device_caps & V4L2_CAP_META_CAPTURE) - goto fail; - if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) - goto fail; - if (!(cap.capabilities & V4L2_CAP_STREAMING)) - goto fail; - - camera = new QCameraDevicePrivate; - camera->id = file; - camera->description = QString::fromUtf8((const char *)cap.card); -// qDebug() << "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); - qDebug() << " " << pixelFmt; - - if (pixelFmt == QVideoFrameFormat::Format_Invalid) { - ++formatDesc.index; - continue; - } - -// qDebug() << "frame sizes:"; - v4l2_frmsizeenum frameSize = {}; - frameSize.pixel_format = formatDesc.pixelformat; - - while (!ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &frameSize)) { - 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)) { - if (frameInterval.type != V4L2_FRMIVAL_TYPE_DISCRETE) - continue; - ++frameInterval.index; - float rate = float(frameInterval.discrete.denominator)/float(frameInterval.discrete.numerator); - if (rate > max) - max = rate; - if (rate < min) - min = rate; - } - -// qDebug() << " " << resolution << min << max; - ++frameSize.index; - - if (min <= max) { - QCameraFormatPrivate *fmt = new QCameraFormatPrivate; - fmt->pixelFormat = pixelFmt; - fmt->resolution = resolution; - fmt->minFrameRate = min; - fmt->maxFrameRate = max; - camera->videoFormats.append(fmt->create()); - camera->photoResolutions.append(resolution); - } - } - - ++formatDesc.index; - } - - // first camera is default - camera->isDefault = first; - first = false; - - cameras.append(camera->create()); - - close(fd); - continue; - - fail: - if (camera) - delete camera; - close(fd); - } -} - -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 : qAsConst(mappedBuffers)) - munmap(b.data, b.size); - mappedBuffers.clear(); -} - QV4L2Camera::QV4L2Camera(QCamera *camera) : QPlatformCamera(camera) { @@ -305,7 +72,6 @@ QV4L2Camera::QV4L2Camera(QCamera *camera) QV4L2Camera::~QV4L2Camera() { - setActive(false); stopCapturing(); closeV4L2Fd(); } @@ -326,13 +92,11 @@ void QV4L2Camera::setActive(bool active) resolveCameraFormat({}); m_active = active; - if (m_active) { - setV4L2CameraFormat(); - initMMap(); + if (m_active) startCapturing(); - } else { + else stopCapturing(); - } + emit newVideoFrame({}); emit activeChanged(active); @@ -342,9 +106,8 @@ void QV4L2Camera::setCamera(const QCameraDevice &camera) { if (m_cameraDevice == camera) return; - if (m_active) - stopCapturing(); + stopCapturing(); closeV4L2Fd(); m_cameraDevice = camera; @@ -352,11 +115,8 @@ void QV4L2Camera::setCamera(const QCameraDevice &camera) initV4L2Controls(); - if (m_active) { - setV4L2CameraFormat(); - initMMap(); + if (m_active) startCapturing(); - } } bool QV4L2Camera::setCameraFormat(const QCameraFormat &format) @@ -370,9 +130,8 @@ bool QV4L2Camera::setCameraFormat(const QCameraFormat &format) if (m_active) { stopCapturing(); closeV4L2Fd(); + initV4L2Controls(); - setV4L2CameraFormat(); - initMMap(); startCapturing(); } @@ -398,31 +157,31 @@ void QV4L2Camera::setFocusMode(QCamera::FocusMode mode) 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); @@ -434,17 +193,17 @@ void QV4L2Camera::setFocusMode(QCamera::FocusMode mode) 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); } @@ -460,7 +219,7 @@ bool QV4L2Camera::isFocusModeSupported(QCamera::FocusMode mode) const 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); @@ -468,7 +227,7 @@ void QV4L2Camera::setFlashMode(QCamera::FlashMode 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; } @@ -479,15 +238,12 @@ bool QV4L2Camera::isFlashReady() const ::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); @@ -496,13 +252,13 @@ void QV4L2Camera::setTorchMode(QCamera::TorchMode 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; @@ -516,15 +272,16 @@ bool QV4L2Camera::isExposureModeSupported(QCamera::ExposureMode mode) const { 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; @@ -552,8 +309,9 @@ int QV4L2Camera::isoSensitivity() const 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; @@ -567,7 +325,7 @@ float QV4L2Camera::exposureTime() const bool QV4L2Camera::isWhiteBalanceModeSupported(QCamera::WhiteBalanceMode mode) const { - if (v4l2AutoWhiteBalanceSupported && v4l2ColorTemperatureSupported) + if (m_v4l2Info.autoWhiteBalanceSupported && m_v4l2Info.colorTemperatureSupported) return true; return mode == QCamera::WhiteBalanceAuto; @@ -600,127 +358,114 @@ void QV4L2Camera::setColorTemperature(int temperature) 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(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); -// qDebug() << "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; + updateError(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); + updateError(QCamera::CameraError, QLatin1String("Cannot open camera")); return; } - qDebug() << "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); @@ -729,50 +474,48 @@ void QV4L2Camera::initV4L2Controls() ::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) @@ -780,15 +523,17 @@ 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; @@ -799,8 +544,8 @@ int QV4L2Camera::setV4L2ColorTemperature(int temperature) 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; } @@ -810,7 +555,7 @@ bool QV4L2Camera::setV4L2Parameter(quint32 id, qint32 value) 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; } @@ -819,8 +564,12 @@ int QV4L2Camera::getV4L2Parameter(quint32 id) const void QV4L2Camera::setV4L2CameraFormat() { + if (m_v4l2Info.formatInitialized || !m_v4l2FileDescriptor) + return; + Q_ASSERT(!m_cameraFormat.isNull()); - qDebug() << "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; @@ -831,9 +580,9 @@ void QV4L2Camera::setV4L2CameraFormat() fmt.fmt.pix.pixelformat = v4l2FormatForPixelFormat(m_cameraFormat.pixelFormat()); fmt.fmt.pix.field = V4L2_FIELD_ANY; - qDebug() << "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; @@ -841,25 +590,29 @@ void QV4L2Camera::setV4L2CameraFormat() 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; } @@ -867,107 +620,88 @@ void QV4L2Camera::setV4L2CameraFormat() streamParam.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; streamParam.parm.capture.capability = V4L2_CAP_TIMEPERFRAME; - int num, den; - qt_real_to_fraction(1./m_cameraFormat.maxFrameRate(), &num, &den); + 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; - } - - QV4L2CameraBuffers::MappedBuffer buffer; - buffer.size = buf.length; - buffer.data = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, - d->v4l2FileDescriptor, buf.m.offset); + qCDebug(qLcV4L2Camera) << "Cannot init V4L2_MEMORY_USERPTR; trying V4L2_MEMORY_MMAP"; - 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); + updateError(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??? - unsigned int 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 setup 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" |