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