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 | |
21 | QT_BEGIN_NAMESPACE |
22 | |
23 | static Q_LOGGING_CATEGORY(ltVideoDevices, "qt.multimedia.gstreamer.videodevices" ); |
24 | |
25 | QGstreamerVideoDevices::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 | |
55 | QGstreamerVideoDevices::~QGstreamerVideoDevices() |
56 | { |
57 | gst_device_monitor_stop(monitor: m_deviceMonitor.get()); |
58 | } |
59 | |
60 | QList<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 | |
129 | void 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 | |
203 | void 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 | |
214 | bool 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 | |
234 | GstDevice *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 | |
241 | QT_END_NAMESPACE |
242 | |