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