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
qpipewire_cameradevicemonitor.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 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/private/qcameradevice_p.h>
7#include <QtMultimedia/private/qpipewire_async_support_p.h>
8#include <QtMultimedia/private/qpipewire_cameracontextmanager_p.h>
9#include <QtMultimedia/private/qpipewire_cameranode_p.h>
10#include <QtMultimedia/private/qmultimedia_ranges_p.h>
11#include <QtCore/qcoreapplication.h>
12#include <QtCore/qloggingcategory.h>
13#include <QtCore/qthread.h>
14
15#include <spa/param/param.h>
16
17QT_BEGIN_NAMESPACE
18
19namespace QtPipeWire {
20
21Q_STATIC_LOGGING_CATEGORY(lcPipewireCameraMonitor, "qt.multimedia.pipewire.camera.devicemonitor")
22
24{
25 using namespace QtMultimediaPrivate;
26
27 std::vector<CameraNode> result = [&] {
29 return m_nodes | views::values | ranges::to<std::vector>();
30 }();
31
32 ranges::sort(result, [](const CameraNode &lhs, const CameraNode &rhs) {
34 });
35
36 return result;
37}
38
40{
41 if (!QThread::isMainThread())
42 // ensure that device monitor runs on application thread
44}
45
47{
49
50 for (const auto &[id, node] : m_nodes) {
52 return id;
53 }
54
55 return std::nullopt;
56}
57
59{
61
62 for (const auto &[id, node] : m_nodes) {
64 continue;
65
66 // The serial is unambiguous and survives a node name being reused.
69
72
73 return std::nullopt;
74 }
75
76 return std::nullopt;
77}
78
79const PwPropertyDict *
81{
83 if (!deviceId)
84 return nullptr;
85
86 const auto device = m_devices.find(*deviceId);
87 if (device == m_devices.end())
88 return nullptr;
89
90 return &device->second.properties;
91}
92
93namespace {
94
95std::vector<SpaObjectVideoFormat> enumerateNodeFormats(QCameraContextManager &context,
96 ObjectId node)
97{
98 using namespace std::chrono_literals;
99 QPipeWireInstance &pwInstance = context.pwInstance();
100
101 auto formats = std::make_shared<std::vector<SpaObjectVideoFormat>>();
102
103 auto onParam = [formats](int /*seq*/, uint32_t /*id*/, uint32_t /*index*/, uint32_t /*next*/,
104 const spa_pod *param) {
105 if (std::optional<SpaObjectVideoFormat> format = SpaObjectVideoFormat::parse(param))
106 formats->push_back(std::move(*format));
107 };
108
109 std::optional<NodeEventListener> listener;
110
111 pwInstance.runWithEventLoopLock([&] {
112 listener.emplace(context.bindNode(node),
113 NodeEventListener::NodeHandler{
114 {},
115 std::move(onParam),
116 });
117 listener->enumParams(SPA_PARAM_EnumFormat);
118 });
119
120 CoreEventSyncHelper sync;
121 // wait for the enumeration to complete, so that we can return the formats to the caller.
122 auto syncOrErr = sync.sync(context.coreConnection().get(), 3s);
123 if (syncOrErr != true)
124 qCWarning(lcPipewireCameraMonitor) << "timed out enumerating the formats of node" << node;
125
126 // The listener has to go before the formats are handed out, so that no
127 // further param event can append to them.
128 listener.reset();
129
130 return std::move(*formats);
131}
132
133} // namespace
134
136{
139
140 // Make sure everything the server had to say about the graph has arrived
141 // before deciding which cameras there are.
143
145
146 for (const CameraNode &node : cameraNodes()) {
148 if (id.isEmpty())
149 continue;
150
152
154 info->id = id;
158
159 if (info->videoFormats.isEmpty()) {
160 qCDebug(lcPipewireCameraMonitor) << "skipping camera without usable formats" << id;
161 continue;
162 }
163
164 for (const QCameraFormat &format : info->videoFormats) {
167 }
168
169 // PipeWire has no notion of a default video source, unlike
170 // default.audio.source, so the first camera in a stable order it is.
172
174 }
175
176 return result;
177}
178
180 const spa_dict &properties)
181{
182 using namespace QtMultimediaPrivate;
183 bool changed = false;
184
185 switch (type) {
188
191
192 // A device may show up after the nodes that belong to it, so the
193 // descriptions of those nodes can change now.
194 for (auto &[nodeId, node] : m_nodes) {
195 if (getDeviceId(node.properties) == id) {
197 changed = true;
198 }
199 }
200 break;
201 }
202
205 if (!isCameraNode(dict))
206 return;
207
208 qCDebug(lcPipewireCameraMonitor) << "camera node added" << id << cameraDeviceId(dict);
209
211 id,
212 std::move(dict),
214 };
215
219
221 changed = true;
222 break;
223 }
224
225 default:
226 return;
227 }
228
229 if (changed)
231}
232
234{
235 bool changed = false;
236
237 {
239
240 if (m_nodes.erase(id) != 0) {
241 qCDebug(lcPipewireCameraMonitor) << "camera node removed" << id;
242 changed = true;
243 }
244
245 if (m_devices.erase(id) != 0) {
246 // Drop the borrowed description of any node that outlives its
247 // device, rather than keeping stale properties around.
248 for (auto &[nodeId, node] : m_nodes) {
249 if (getDeviceId(node.properties) == id) {
251 changed = true;
252 }
253 }
254 }
255 }
256
257 if (changed)
259}
260
261} // namespace QtPipeWire
262
263QT_END_NAMESPACE
Q_STATIC_LOGGING_CATEGORY(lcPipewireAudioSink, "qt.multimedia.pipewire.audiosink")