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
4#include "qgstreamervideodevices_p.h"
5
6#include <QtMultimedia/qmediadevices.h>
7#include <QtMultimedia/private/qcameradevice_p.h>
8#include <QtCore/qloggingcategory.h>
9
10#include <common/qgst_p.h>
11#include <common/qgst_debug_p.h>
12#include <common/qgstutils_p.h>
13#include <common/qglist_helper_p.h>
14
15#if QT_CONFIG(linux_v4l)
16# include <linux/videodev2.h>
17# include <errno.h>
18#endif
19
20QT_BEGIN_NAMESPACE
21
22static Q_LOGGING_CATEGORY(ltVideoDevices, "qt.multimedia.gstreamer.videodevices");
23
24QGstreamerVideoDevices::QGstreamerVideoDevices(QPlatformMediaIntegration *integration)
25 : QPlatformVideoDevices(integration),
26 m_deviceMonitor{
27 gst_device_monitor_new(),
28 },
29 m_busObserver{
30 QGstBusHandle{
31 gst_device_monitor_get_bus(monitor: m_deviceMonitor.get()),
32 QGstBusHandle::HasRef,
33 },
34 }
35{
36 gst_device_monitor_add_filter(monitor: m_deviceMonitor.get(), classes: "Video/Source", caps: nullptr);
37
38 m_busObserver.installMessageFilter(this);
39 gst_device_monitor_start(monitor: m_deviceMonitor.get());
40
41 GList *devices = gst_device_monitor_get_devices(monitor: m_deviceMonitor.get());
42
43 for (GstDevice *device : QGstUtils::GListRangeAdaptor<GstDevice *>(devices)) {
44 addDevice(QGstDeviceHandle{
45 device,
46 QGstDeviceHandle::HasRef,
47 });
48 }
49
50 g_list_free(list: devices);
51}
52
53QGstreamerVideoDevices::~QGstreamerVideoDevices()
54{
55 gst_device_monitor_stop(monitor: m_deviceMonitor.get());
56}
57
58QList<QCameraDevice> QGstreamerVideoDevices::videoDevices() const
59{
60 QList<QCameraDevice> devices;
61
62 for (const auto &device : m_videoSources) {
63 QCameraDevicePrivate *info = new QCameraDevicePrivate;
64
65 QGString desc{
66 gst_device_get_display_name(device: device.gstDevice.get()),
67 };
68 info->description = desc.toQString();
69 info->id = device.id;
70
71 QUniqueGstStructureHandle properties{
72 gst_device_get_properties(device: device.gstDevice.get()),
73 };
74 if (properties) {
75 QGstStructureView view{ properties };
76 auto def = view["is-default"].toBool();
77 info->isDefault = def && *def;
78 }
79
80 if (info->isDefault)
81 devices.prepend(t: info->create());
82 else
83 devices.append(t: info->create());
84
85 auto caps = QGstCaps(gst_device_get_caps(device: device.gstDevice.get()), QGstCaps::HasRef);
86 if (caps) {
87 QList<QCameraFormat> formats;
88 QSet<QSize> photoResolutions;
89
90 int size = caps.size();
91 for (int i = 0; i < size; ++i) {
92 auto cap = caps.at(index: i);
93 auto pixelFormat = cap.pixelFormat();
94 auto frameRate = cap.frameRateRange();
95
96 if (pixelFormat == QVideoFrameFormat::PixelFormat::Format_Invalid) {
97 qCDebug(ltVideoDevices) << "pixel format not supported:" << cap;
98 continue; // skip pixel formats that we don't support
99 }
100
101 auto addFormatForResolution = [&](QSize resolution) {
102 auto *f = new QCameraFormatPrivate{
103 QSharedData(), .pixelFormat: pixelFormat, .resolution: resolution, .minFrameRate: frameRate.min, .maxFrameRate: frameRate.max,
104 };
105 formats.append(t: f->create());
106 photoResolutions.insert(value: resolution);
107 };
108
109 std::optional<QGRange<QSize>> resolutionRange = cap.resolutionRange();
110 if (resolutionRange) {
111 addFormatForResolution(resolutionRange->min);
112 addFormatForResolution(resolutionRange->max);
113 } else {
114 QSize resolution = cap.resolution();
115 if (resolution.isValid())
116 addFormatForResolution(resolution);
117 }
118 }
119 info->videoFormats = formats;
120 // ### sort resolutions?
121 info->photoResolutions = photoResolutions.values();
122 }
123 }
124 return devices;
125}
126
127void QGstreamerVideoDevices::addDevice(QGstDeviceHandle device)
128{
129 Q_ASSERT(gst_device_has_classes(device.get(), "Video/Source"));
130
131#if QT_CONFIG(linux_v4l)
132 QUniqueGstStructureHandle structureHandle{
133 gst_device_get_properties(device: device.get()),
134 };
135
136 const auto *p = QGstStructureView(structureHandle.get())["device.path"].toString();
137 if (p) {
138 QFileDescriptorHandle fd{
139 qt_safe_open(pathname: p, O_RDONLY),
140 };
141
142 if (!fd) {
143 qCDebug(ltVideoDevices) << "Cannot open v4l2 device:" << p;
144 return;
145 }
146
147 struct v4l2_capability cap;
148 if (::ioctl(fd: fd.get(), VIDIOC_QUERYCAP, &cap) < 0) {
149 qCWarning(ltVideoDevices)
150 << "ioctl failed: VIDIOC_QUERYCAP" << qt_error_string(errno) << p;
151 return;
152 }
153
154 if (cap.device_caps & V4L2_CAP_META_CAPTURE) {
155 qCDebug(ltVideoDevices) << "V4L2_CAP_META_CAPTURE device detected" << p;
156 return;
157 }
158 if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
159 qCDebug(ltVideoDevices) << "not a V4L2_CAP_VIDEO_CAPTURE device" << p;
160 return;
161 }
162 if (!(cap.capabilities & V4L2_CAP_STREAMING)) {
163 qCDebug(ltVideoDevices) << "not a V4L2_CAP_STREAMING device" << p;
164 return;
165 }
166
167 int index;
168 if (::ioctl(fd: fd.get(), VIDIOC_G_INPUT, &index) < 0) {
169 switch (errno) {
170 case ENOTTY:
171 qCDebug(ltVideoDevices) << "device does not have video inputs" << p;
172 return;
173
174 default:
175 qCWarning(ltVideoDevices)
176 << "ioctl failed: VIDIOC_G_INPUT" << qt_error_string(errno) << p;
177 return;
178 }
179 }
180 } else {
181 qCDebug(ltVideoDevices) << "Video device not a v4l2 device:" << structureHandle;
182 }
183#endif
184
185 auto it = std::find_if(first: m_videoSources.begin(), last: m_videoSources.end(),
186 pred: [&](const QGstRecordDevice &a) { return a.gstDevice == device; });
187
188 if (it != m_videoSources.end())
189 return;
190
191 m_videoSources.push_back(x: QGstRecordDevice{
192 .gstDevice: std::move(device),
193 .id: QByteArray::number(m_idGenerator),
194 });
195 emit videoInputsChanged();
196 m_idGenerator++;
197}
198
199void QGstreamerVideoDevices::removeDevice(QGstDeviceHandle device)
200{
201 auto it = std::find_if(first: m_videoSources.begin(), last: m_videoSources.end(),
202 pred: [&](const QGstRecordDevice &a) { return a.gstDevice == device; });
203
204 if (it != m_videoSources.end()) {
205 m_videoSources.erase(position: it);
206 emit videoInputsChanged();
207 }
208}
209
210bool QGstreamerVideoDevices::processBusMessage(const QGstreamerMessage &message)
211{
212 QGstDeviceHandle device;
213
214 switch (message.type()) {
215 case GST_MESSAGE_DEVICE_ADDED:
216 gst_message_parse_device_added(message: message.message(), device: &device);
217 addDevice(device: std::move(device));
218 break;
219 case GST_MESSAGE_DEVICE_REMOVED:
220 gst_message_parse_device_removed(message: message.message(), device: &device);
221 removeDevice(device: std::move(device));
222 break;
223 default:
224 break;
225 }
226
227 return false;
228}
229
230GstDevice *QGstreamerVideoDevices::videoDevice(const QByteArray &id) const
231{
232 auto it = std::find_if(first: m_videoSources.begin(), last: m_videoSources.end(),
233 pred: [&](const QGstRecordDevice &a) { return a.id == id; });
234 return it != m_videoSources.end() ? it->gstDevice.get() : nullptr;
235}
236
237QT_END_NAMESPACE
238

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

source code of qtmultimedia/src/plugins/multimedia/gstreamer/qgstreamervideodevices.cpp