summaryrefslogtreecommitdiffstats
path: root/src/plugins/multimedia/ffmpeg/qv4l2cameradevices.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/multimedia/ffmpeg/qv4l2cameradevices.cpp')
-rw-r--r--src/plugins/multimedia/ffmpeg/qv4l2cameradevices.cpp182
1 files changed, 182 insertions, 0 deletions
diff --git a/src/plugins/multimedia/ffmpeg/qv4l2cameradevices.cpp b/src/plugins/multimedia/ffmpeg/qv4l2cameradevices.cpp
new file mode 100644
index 000000000..e450cf7bc
--- /dev/null
+++ b/src/plugins/multimedia/ffmpeg/qv4l2cameradevices.cpp
@@ -0,0 +1,182 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qv4l2cameradevices_p.h"
+#include "qv4l2filedescriptor_p.h"
+#include "qv4l2camera_p.h"
+
+#include <private/qcameradevice_p.h>
+#include <private/qcore_unix_p.h>
+
+#include <qdir.h>
+#include <qfile.h>
+#include <qdebug.h>
+#include <qloggingcategory.h>
+
+#include <linux/videodev2.h>
+
+QT_BEGIN_NAMESPACE
+
+static Q_LOGGING_CATEGORY(qLcV4L2CameraDevices, "qt.multimedia.ffmpeg.v4l2cameradevices");
+
+static bool areCamerasEqual(QList<QCameraDevice> a, QList<QCameraDevice> b)
+{
+ auto areCamerasDataEqual = [](const QCameraDevice &a, const QCameraDevice &b) {
+ Q_ASSERT(QCameraDevicePrivate::handle(a));
+ Q_ASSERT(QCameraDevicePrivate::handle(b));
+ return *QCameraDevicePrivate::handle(a) == *QCameraDevicePrivate::handle(b);
+ };
+
+ return std::equal(a.cbegin(), a.cend(), b.cbegin(), b.cend(), areCamerasDataEqual);
+}
+
+QV4L2CameraDevices::QV4L2CameraDevices(QPlatformMediaIntegration *integration)
+ : QPlatformVideoDevices(integration)
+{
+ m_deviceWatcher.addPath(QLatin1String("/dev"));
+ connect(&m_deviceWatcher, &QFileSystemWatcher::directoryChanged, this,
+ &QV4L2CameraDevices::checkCameras);
+ doCheckCameras();
+}
+
+QList<QCameraDevice> QV4L2CameraDevices::videoDevices() const
+{
+ return m_cameras;
+}
+
+void QV4L2CameraDevices::checkCameras()
+{
+ if (doCheckCameras())
+ emit videoInputsChanged();
+}
+
+bool QV4L2CameraDevices::doCheckCameras()
+{
+ QList<QCameraDevice> newCameras;
+
+ QDir dir(QLatin1String("/dev"));
+ const auto devices = dir.entryList(QDir::System);
+
+ bool first = true;
+
+ for (auto device : devices) {
+ // qCDebug(qLcV4L2Camera) << "device:" << device;
+ if (!device.startsWith(QLatin1String("video")))
+ continue;
+
+ QByteArray file = QFile::encodeName(dir.filePath(device));
+ const int fd = open(file.constData(), O_RDONLY);
+ if (fd < 0)
+ continue;
+
+ auto fileCloseGuard = qScopeGuard([fd]() { close(fd); });
+
+ v4l2_fmtdesc formatDesc = {};
+
+ struct v4l2_capability cap;
+ if (xioctl(fd, VIDIOC_QUERYCAP, &cap) < 0)
+ continue;
+
+ if (cap.device_caps & V4L2_CAP_META_CAPTURE)
+ continue;
+ if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE))
+ continue;
+ if (!(cap.capabilities & V4L2_CAP_STREAMING))
+ continue;
+
+ auto camera = std::make_unique<QCameraDevicePrivate>();
+
+ camera->id = file;
+ camera->description = QString::fromUtf8((const char *)cap.card);
+ qCDebug(qLcV4L2CameraDevices) << "found camera" << camera->id << camera->description;
+
+ formatDesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+
+ while (!xioctl(fd, VIDIOC_ENUM_FMT, &formatDesc)) {
+ auto pixelFmt = formatForV4L2Format(formatDesc.pixelformat);
+ qCDebug(qLcV4L2CameraDevices) << " " << pixelFmt;
+
+ if (pixelFmt == QVideoFrameFormat::Format_Invalid) {
+ ++formatDesc.index;
+ continue;
+ }
+
+ qCDebug(qLcV4L2CameraDevices) << "frame sizes:";
+ v4l2_frmsizeenum frameSize = {};
+ frameSize.pixel_format = formatDesc.pixelformat;
+
+ while (!xioctl(fd, VIDIOC_ENUM_FRAMESIZES, &frameSize)) {
+ QList<QSize> resolutions;
+ if (frameSize.type == V4L2_FRMSIZE_TYPE_DISCRETE) {
+ resolutions.append(QSize(frameSize.discrete.width,
+ frameSize.discrete.height));
+ } else {
+ resolutions.append(QSize(frameSize.stepwise.max_width,
+ frameSize.stepwise.max_height));
+ resolutions.append(QSize(frameSize.stepwise.min_width,
+ frameSize.stepwise.min_height));
+ }
+
+ for (auto resolution : resolutions) {
+ float min = 1e10;
+ float max = 0;
+ auto updateMaxMinFrameRate = [&max, &min](auto discreteFrameRate) {
+ const float rate = float(discreteFrameRate.denominator)
+ / float(discreteFrameRate.numerator);
+ if (rate > max)
+ max = rate;
+ if (rate < min)
+ min = rate;
+ };
+
+ v4l2_frmivalenum frameInterval = {};
+ frameInterval.pixel_format = formatDesc.pixelformat;
+ frameInterval.width = resolution.width();
+ frameInterval.height = resolution.height();
+
+ while (!xioctl(fd, VIDIOC_ENUM_FRAMEINTERVALS, &frameInterval)) {
+ if (frameInterval.type == V4L2_FRMIVAL_TYPE_DISCRETE) {
+ updateMaxMinFrameRate(frameInterval.discrete);
+ } else {
+ updateMaxMinFrameRate(frameInterval.stepwise.max);
+ updateMaxMinFrameRate(frameInterval.stepwise.min);
+ }
+ ++frameInterval.index;
+ }
+
+ qCDebug(qLcV4L2CameraDevices) << " " << resolution << min << max;
+
+ if (min <= max) {
+ auto fmt = std::make_unique<QCameraFormatPrivate>();
+ fmt->pixelFormat = pixelFmt;
+ fmt->resolution = resolution;
+ fmt->minFrameRate = min;
+ fmt->maxFrameRate = max;
+ camera->videoFormats.append(fmt.release()->create());
+ camera->photoResolutions.append(resolution);
+ }
+ }
+ ++frameSize.index;
+ }
+ ++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"