1// Copyright (C) 2023 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qv4l2cameradevices_p.h"
5#include "qv4l2filedescriptor_p.h"
6#include "qv4l2camera_p.h"
7
8#include <private/qcameradevice_p.h>
9#include <private/qcore_unix_p.h>
10
11#include <qdir.h>
12#include <qfile.h>
13#include <qdebug.h>
14#include <qloggingcategory.h>
15
16#include <linux/videodev2.h>
17
18QT_BEGIN_NAMESPACE
19
20static Q_LOGGING_CATEGORY(qLcV4L2CameraDevices, "qt.multimedia.ffmpeg.v4l2cameradevices");
21
22static bool areCamerasEqual(QList<QCameraDevice> a, QList<QCameraDevice> b)
23{
24 auto areCamerasDataEqual = [](const QCameraDevice &a, const QCameraDevice &b) {
25 Q_ASSERT(QCameraDevicePrivate::handle(a));
26 Q_ASSERT(QCameraDevicePrivate::handle(b));
27 return *QCameraDevicePrivate::handle(device: a) == *QCameraDevicePrivate::handle(device: b);
28 };
29
30 return std::equal(first1: a.cbegin(), last1: a.cend(), first2: b.cbegin(), last2: b.cend(), binary_pred: areCamerasDataEqual);
31}
32
33QV4L2CameraDevices::QV4L2CameraDevices(QPlatformMediaIntegration *integration)
34 : QPlatformVideoDevices(integration)
35{
36 m_deviceWatcher.addPath(file: QLatin1String("/dev"));
37 connect(sender: &m_deviceWatcher, signal: &QFileSystemWatcher::directoryChanged, context: this,
38 slot: &QV4L2CameraDevices::checkCameras);
39 doCheckCameras();
40}
41
42QList<QCameraDevice> QV4L2CameraDevices::videoDevices() const
43{
44 return m_cameras;
45}
46
47void QV4L2CameraDevices::checkCameras()
48{
49 if (doCheckCameras())
50 emit videoInputsChanged();
51}
52
53bool QV4L2CameraDevices::doCheckCameras()
54{
55 QList<QCameraDevice> newCameras;
56
57 QDir dir(QLatin1String("/dev"));
58 const auto devices = dir.entryList(filters: QDir::System);
59
60 bool first = true;
61
62 for (auto device : devices) {
63 // qCDebug(qLcV4L2Camera) << "device:" << device;
64 if (!device.startsWith(s: QLatin1String("video")))
65 continue;
66
67 QByteArray file = QFile::encodeName(fileName: dir.filePath(fileName: device));
68 const int fd = open(file: file.constData(), O_RDONLY);
69 if (fd < 0)
70 continue;
71
72 auto fileCloseGuard = qScopeGuard(f: [fd]() { close(fd: fd); });
73
74 v4l2_fmtdesc formatDesc = {};
75
76 struct v4l2_capability cap;
77 if (xioctl(fd, VIDIOC_QUERYCAP, arg: &cap) < 0)
78 continue;
79
80 if (cap.device_caps & V4L2_CAP_META_CAPTURE)
81 continue;
82 if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE))
83 continue;
84 if (!(cap.capabilities & V4L2_CAP_STREAMING))
85 continue;
86
87 auto camera = std::make_unique<QCameraDevicePrivate>();
88
89 camera->id = file;
90 camera->description = QString::fromUtf8(utf8: (const char *)cap.card);
91 qCDebug(qLcV4L2CameraDevices) << "found camera" << camera->id << camera->description;
92
93 formatDesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
94
95 while (!xioctl(fd, VIDIOC_ENUM_FMT, arg: &formatDesc)) {
96 auto pixelFmt = formatForV4L2Format(v4l2Format: formatDesc.pixelformat);
97 qCDebug(qLcV4L2CameraDevices) << " " << pixelFmt;
98
99 if (pixelFmt == QVideoFrameFormat::Format_Invalid) {
100 ++formatDesc.index;
101 continue;
102 }
103
104 qCDebug(qLcV4L2CameraDevices) << "frame sizes:";
105 v4l2_frmsizeenum frameSize = {};
106 frameSize.pixel_format = formatDesc.pixelformat;
107
108 while (!xioctl(fd, VIDIOC_ENUM_FRAMESIZES, arg: &frameSize)) {
109 QList<QSize> resolutions;
110 if (frameSize.type == V4L2_FRMSIZE_TYPE_DISCRETE) {
111 resolutions.append(t: QSize(frameSize.discrete.width,
112 frameSize.discrete.height));
113 } else {
114 resolutions.append(t: QSize(frameSize.stepwise.max_width,
115 frameSize.stepwise.max_height));
116 resolutions.append(t: QSize(frameSize.stepwise.min_width,
117 frameSize.stepwise.min_height));
118 }
119
120 for (auto resolution : resolutions) {
121 float min = 1e10;
122 float max = 0;
123 auto updateMaxMinFrameRate = [&max, &min](auto discreteFrameRate) {
124 const float rate = float(discreteFrameRate.denominator)
125 / float(discreteFrameRate.numerator);
126 if (rate > max)
127 max = rate;
128 if (rate < min)
129 min = rate;
130 };
131
132 v4l2_frmivalenum frameInterval = {};
133 frameInterval.pixel_format = formatDesc.pixelformat;
134 frameInterval.width = resolution.width();
135 frameInterval.height = resolution.height();
136
137 while (!xioctl(fd, VIDIOC_ENUM_FRAMEINTERVALS, arg: &frameInterval)) {
138 if (frameInterval.type == V4L2_FRMIVAL_TYPE_DISCRETE) {
139 updateMaxMinFrameRate(frameInterval.discrete);
140 } else {
141 updateMaxMinFrameRate(frameInterval.stepwise.max);
142 updateMaxMinFrameRate(frameInterval.stepwise.min);
143 }
144 ++frameInterval.index;
145 }
146
147 qCDebug(qLcV4L2CameraDevices) << " " << resolution << min << max;
148
149 if (min <= max) {
150 auto fmt = std::make_unique<QCameraFormatPrivate>();
151 fmt->pixelFormat = pixelFmt;
152 fmt->resolution = resolution;
153 fmt->minFrameRate = min;
154 fmt->maxFrameRate = max;
155 camera->videoFormats.append(t: fmt.release()->create());
156 camera->photoResolutions.append(t: resolution);
157 }
158 }
159 ++frameSize.index;
160 }
161 ++formatDesc.index;
162 }
163
164 if (camera->videoFormats.empty())
165 continue;
166
167 // first camera is default
168 camera->isDefault = std::exchange(obj&: first, new_val: false);
169
170 newCameras.append(t: camera.release()->create());
171 }
172
173 if (areCamerasEqual(a: m_cameras, b: newCameras))
174 return false;
175
176 m_cameras = std::move(newCameras);
177 return true;
178}
179
180QT_END_NAMESPACE
181
182#include "moc_qv4l2cameradevices_p.cpp"
183

source code of qtmultimedia/src/plugins/multimedia/ffmpeg/qv4l2cameradevices.cpp