Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qgstreamervideodevices.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 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
5
6#include <QtMultimedia/qmediadevices.h>
7#include <QtMultimedia/private/qcameradevice_p.h>
8#include <QtMultimedia/private/qmultimedia_ranges_p.h>
9#include <QtCore/qloggingcategory.h>
10#include <QtCore/qset.h>
11#include <QtCore/private/quniquehandle_types_p.h>
12
13#include <common/qgst_p.h>
14#include <common/qgst_debug_p.h>
15#include <common/qgstutils_p.h>
16#include <common/qglist_helper_p.h>
17
18#if QT_CONFIG(linux_v4l)
19# include <linux/videodev2.h>
20# include <errno.h>
21#endif
22
24
25namespace ranges = QtMultimediaPrivate::ranges;
26
27Q_STATIC_LOGGING_CATEGORY(ltVideoDevices, "qt.multimedia.gstreamer.videodevices");
28
29QGstreamerVideoDevices::QGstreamerVideoDevices(QPlatformMediaIntegration *integration)
34 },
39 },
40 }
41{
42 gst_device_monitor_add_filter(m_deviceMonitor.get(), "Video/Source", nullptr);
43 gst_device_monitor_set_show_all_devices(m_deviceMonitor.get(), true);
44
45 m_busObserver.installMessageFilter(this);
46 gst_device_monitor_start(m_deviceMonitor.get());
47
48 GList *devices = gst_device_monitor_get_devices(m_deviceMonitor.get());
49
50 for (GstDevice *device : QGstUtils::GListRangeAdaptor<GstDevice *>(devices)) {
51 addDevice(QGstDeviceHandle{
52 device,
53 QGstDeviceHandle::HasRef,
54 });
55 }
56
57 g_list_free(devices);
58}
59
60QGstreamerVideoDevices::~QGstreamerVideoDevices()
61{
62 gst_device_monitor_stop(m_deviceMonitor.get());
63}
64
65QList<QCameraDevice> QGstreamerVideoDevices::findVideoInputs() const
66{
67 QList<QCameraDevice> devices;
68
69 for (const auto &device : m_videoSources) {
70 QCameraDevicePrivate *info = new QCameraDevicePrivate;
71
72 QGString desc{
73 gst_device_get_display_name(device.gstDevice.get()),
74 };
75 info->description = desc.toQString();
76 info->id = device.id;
77
78 QUniqueGstStructureHandle properties{
79 gst_device_get_properties(device.gstDevice.get()),
80 };
81 if (properties) {
82 QGstStructureView view{ properties };
83 auto def = view["is-default"].toBool();
84 info->isDefault = def && *def;
85 }
86
87 if (info->isDefault)
88 devices.prepend(info->create());
89 else
90 devices.append(info->create());
91
92 auto caps = QGstCaps(gst_device_get_caps(device.gstDevice.get()), QGstCaps::HasRef);
93 if (caps) {
94 QList<QCameraFormat> formats;
95 QSet<QSize> photoResolutions;
96
97 int size = caps.size();
98 for (int i = 0; i < size; ++i) {
99 auto cap = caps.at(i);
100 QList<QVideoFrameFormat::PixelFormat> pixelFormats = cap.pixelFormats();
101
102 auto frameRate = cap.frameRateRange();
103
104 if (pixelFormats.isEmpty()) {
105 qCDebug(ltVideoDevices) << "pixel format(s) not supported:" << cap;
106 continue; // skip pixel formats that we don't support
107 }
108
109 auto addFormatForResolution = [&](QSize resolution) {
110 for (QVideoFrameFormat::PixelFormat pixelFormat : std::as_const(pixelFormats)){
111 auto *f = new QCameraFormatPrivate{
112 QSharedData(), pixelFormat, resolution, frameRate.min, frameRate.max,
113 };
114 formats.append(f->create());
115 }
116 photoResolutions.insert(resolution);
117 };
118
119 std::optional<QGRange<QSize>> resolutionRange = cap.resolutionRange();
120 if (resolutionRange) {
121 addFormatForResolution(resolutionRange->min);
122 addFormatForResolution(resolutionRange->max);
123 } else {
124 QSize resolution = cap.resolution();
125 if (resolution.isValid())
126 addFormatForResolution(resolution);
127 }
128 }
129 info->videoFormats = formats;
130 // ### sort resolutions?
131 info->photoResolutions = photoResolutions.values();
132 }
133 }
134 return devices;
135}
136
137void QGstreamerVideoDevices::addDevice(QGstDeviceHandle device)
138{
139 Q_ASSERT(gst_device_has_classes(device.get(), "Video/Source"));
140
141#if QT_CONFIG(linux_v4l)
142 QUniqueGstStructureHandle propertiesHandle{
143 gst_device_get_properties(device.get()),
144 };
145 if (!propertiesHandle.isValid()) {
146 qCDebug(ltVideoDevices) << "Skipping device without extra properties:" << device.get();
147 return;
148 }
149
150 auto properties = QGstStructureView(propertiesHandle.get());
151
152 // Pipewire devices causes infinite futex wait in gst_pipewire_src_change_state after calling
153 // QGstreamerMediaCaptureSession::setCameraActive() with true, so we skip adding them:
154 if (properties.name().contains("pipewire")) {
155 qCDebug(ltVideoDevices) << "Skipping pipewire device:" << device.get();
156 return;
157 }
158
159 // QTBUG-140092: NXP's CSI video device "imx-capture" may fail. Can be skipped via env var:
160 static const bool skipImxCapture = qEnvironmentVariableIsSet("QT_GSTREAMER_SKIP_IMXCAPTURE");
161 if (skipImxCapture) {
162 const char *name = properties["device.product.name"].toString();
163 if (name && std::strstr(name, "imx-capture")) {
164 qWarning() << Q_FUNC_INFO << "Skipping video device with product name" << name;
165 return;
166 }
167 }
168
169 const auto *p = properties["device.path"].toString();
170 if (p) {
171 QUniqueFileDescriptorHandle fd{
172 qt_safe_open(p, O_RDONLY),
173 };
174
175 if (!fd) {
176 qCDebug(ltVideoDevices) << "Cannot open v4l2 device:" << p;
177 return;
178 }
179
180 struct v4l2_capability cap;
181 if (::ioctl(fd.get(), VIDIOC_QUERYCAP, &cap) < 0) {
182 qCWarning(ltVideoDevices)
183 << "ioctl failed: VIDIOC_QUERYCAP" << qt_error_string(errno) << p;
184 return;
185 }
186
187 if (cap.device_caps & V4L2_CAP_META_CAPTURE) {
188 qCDebug(ltVideoDevices) << "V4L2_CAP_META_CAPTURE device detected" << p;
189 return;
190 }
191
192 constexpr uint32_t videoCaptureCapabilities =
193 V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_CAPTURE_MPLANE;
194
195 if (!(cap.capabilities & videoCaptureCapabilities)) {
196 qCDebug(ltVideoDevices)
197 << "not a V4L2_CAP_VIDEO_CAPTURE or V4L2_CAP_VIDEO_CAPTURE_MPLANE device" << p;
198 return;
199 }
200 if (!(cap.capabilities & V4L2_CAP_STREAMING)) {
201 qCDebug(ltVideoDevices) << "not a V4L2_CAP_STREAMING device" << p;
202 return;
203 }
204
205 int index;
206 if (::ioctl(fd.get(), VIDIOC_G_INPUT, &index) < 0) {
207 switch (errno) {
208 case ENOTTY:
209 qCDebug(ltVideoDevices) << "Device does not support VIDIOC_G_INPUT, but it could"
210 "still work" << p;
211 break;
212
213 default:
214 qCWarning(ltVideoDevices)
215 << "ioctl failed: VIDIOC_G_INPUT" << qt_error_string(errno) << p;
216 return;
217 }
218 }
219 } else {
220 qCDebug(ltVideoDevices) << "Video device not a v4l2 device:" << propertiesHandle;
221 }
222#endif
223
224 auto it = ranges::find_if(m_videoSources, [&](const QGstRecordDevice &a) {
225 return a.gstDevice == device;
226 });
227
228 if (it != m_videoSources.end())
229 return;
230
231 m_videoSources.push_back(QGstRecordDevice{
232 std::move(device),
233 QByteArray::number(m_idGenerator),
234 });
235
236 m_idGenerator++;
237
238 onVideoInputsChanged();
239}
240
241void QGstreamerVideoDevices::removeDevice(QGstDeviceHandle device)
242{
243 auto it = ranges::find_if(m_videoSources, [&](const QGstRecordDevice &a) {
244 return a.gstDevice == device;
245 });
246
247 if (it != m_videoSources.end()) {
248 m_videoSources.erase(it);
249 onVideoInputsChanged();
250 }
251}
252
253bool QGstreamerVideoDevices::processBusMessage(const QGstreamerMessage &message)
254{
255 QGstDeviceHandle device;
256
257 switch (message.type()) {
258 case GST_MESSAGE_DEVICE_ADDED:
259 gst_message_parse_device_added(message.message(), &device);
260 addDevice(std::move(device));
261 break;
262 case GST_MESSAGE_DEVICE_REMOVED:
263 gst_message_parse_device_removed(message.message(), &device);
264 removeDevice(std::move(device));
265 break;
266 default:
267 break;
268 }
269
270 return false;
271}
272
273GstDevice *QGstreamerVideoDevices::videoDevice(const QByteArray &id) const
274{
275 auto it = ranges::find_if(m_videoSources, [&](const QGstRecordDevice &a) {
276 return a.id == id;
277 });
278 return it != m_videoSources.end() ? it->gstDevice.get() : nullptr;
279}
280
281QT_END_NAMESPACE
GstDevice * videoDevice(const QByteArray &id) const
bool processBusMessage(const QGstreamerMessage &message) override
QGstreamerVideoDevices(QPlatformMediaIntegration *integration)
void removeDevice(QGstDeviceHandle)
void addDevice(QGstDeviceHandle)
QList< QCameraDevice > findVideoInputs() const override
Combined button and popup list for selecting options.
QT_BEGIN_NAMESPACE Q_STATIC_LOGGING_CATEGORY(lcSynthesizedIterableAccess, "qt.iterable.synthesized", QtWarningMsg)