Revision control

Copy as Markdown

// 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 <QtMultimedia/private/qcameradevice_p.h>
#include <QtMultimedia/private/qmultimediautils_p.h>
#include <QtMultimedia/private/qv4l2camera_p.h>
#include <QtMultimedia/private/qv4l2filedescriptor_p.h>
#include <QtCore/qdebug.h>
#include <QtCore/qdir.h>
#include <QtCore/qfile.h>
#include <QtCore/qloggingcategory.h>
#include <QtCore/private/qcore_unix_p.h>
#include <linux/videodev2.h>
QT_BEGIN_NAMESPACE
namespace QtV4L2 {
Q_STATIC_LOGGING_CATEGORY(qLcV4L2CameraDevices, "qt.multimedia.v4l2.cameradevices");
static bool areCamerasEqual(QSpan<const QCameraDevice> a, QSpan<const 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::findVideoInputs() const
{
return m_cameras;
}
void QV4L2CameraDevices::checkCameras()
{
if (doCheckCameras())
onVideoInputsChanged();
}
bool QV4L2CameraDevices::doCheckCameras()
{
using namespace QtMultimediaPrivate;
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 (qt_safe_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<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 (!qt_safe_ioctl(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 (!qt_safe_ioctl(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 (!qt_safe_ioctl(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;
}
} // namespace QtV4L2
QT_END_NAMESPACE
#include "moc_qv4l2cameradevices_p.cpp"