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

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