// 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 #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"