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 | |
18 | QT_BEGIN_NAMESPACE |
19 | |
20 | static Q_LOGGING_CATEGORY(qLcV4L2CameraDevices, "qt.multimedia.ffmpeg.v4l2cameradevices" ); |
21 | |
22 | static 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 | |
33 | QV4L2CameraDevices::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 | |
42 | QList<QCameraDevice> QV4L2CameraDevices::videoDevices() const |
43 | { |
44 | return m_cameras; |
45 | } |
46 | |
47 | void QV4L2CameraDevices::checkCameras() |
48 | { |
49 | if (doCheckCameras()) |
50 | emit videoInputsChanged(); |
51 | } |
52 | |
53 | bool 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 | |
180 | QT_END_NAMESPACE |
181 | |
182 | #include "moc_qv4l2cameradevices_p.cpp" |
183 | |