Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Toggle main menu visibility
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
4
#
include
"qpipewire_cameradevicemonitor_p.h"
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
17
QT_BEGIN_NAMESPACE
18
19
namespace
QtPipeWire
{
20
21
Q_STATIC_LOGGING_CATEGORY
(
lcPipewireCameraMonitor
,
"qt.multimedia.pipewire.camera.devicemonitor"
)
22
23
std
::
vector
<
QCameraDeviceMonitor
::
CameraNode
>
QCameraDeviceMonitor
::
cameraNodes
()
const
24
{
25
using
namespace
QtMultimediaPrivate
;
26
27
std
::
vector
<
CameraNode
>
result
= [&] {
28
std
::
lock_guard
guard
{
m_mutex
};
29
return
m_nodes
|
views
::
values
|
ranges
::
to
<
std
::
vector
>();
30
}();
31
32
ranges
::
sort
(
result
, [](
const
CameraNode
&
lhs
,
const
CameraNode
&
rhs
) {
33
return
cameraDeviceId
(
lhs
.
properties
) <
cameraDeviceId
(
rhs
.
properties
);
34
});
35
36
return
result
;
37
}
38
39
QCameraDeviceMonitor
::
QCameraDeviceMonitor
()
40
{
41
if
(!
QThread
::
isMainThread
())
42
// ensure that device monitor runs on application thread
43
moveToThread
(
qApp
->
thread
());
44
}
45
46
std
::
optional
<
ObjectId
>
QCameraDeviceMonitor
::
findCameraNode
(
QByteArrayView
deviceId
)
const
47
{
48
std
::
lock_guard
guard
{
m_mutex
};
49
50
for
(
const
auto
&[
id
,
node
] :
m_nodes
) {
51
if
(
cameraDeviceId
(
node
.
properties
) ==
deviceId
)
52
return
id
;
53
}
54
55
return
std
::
nullopt
;
56
}
57
58
std
::
optional
<
QByteArray
>
QCameraDeviceMonitor
::
findTargetObject
(
QByteArrayView
deviceId
)
const
59
{
60
std
::
lock_guard
guard
{
m_mutex
};
61
62
for
(
const
auto
&[
id
,
node
] :
m_nodes
) {
63
if
(
cameraDeviceId
(
node
.
properties
) !=
deviceId
)
64
continue
;
65
66
// The serial is unambiguous and survives a node name being reused.
67
if
(
std
::
optional
<
ObjectSerial
>
serial
=
getObjectSerial
(
node
.
properties
))
68
return
QByteArray
::
number
(
qulonglong
(
serial
->
value
));
69
70
if
(
std
::
optional
<
std
::
string_view
>
nodeName
=
getNodeName
(
node
.
properties
))
71
return
QByteArray
(
nodeName
->
data
(),
qsizetype
(
nodeName
->
size
()));
72
73
return
std
::
nullopt
;
74
}
75
76
return
std
::
nullopt
;
77
}
78
79
const
PwPropertyDict
*
80
QCameraDeviceMonitor
::
findDeviceProperties
(
const
PwPropertyDict
&
nodeProperties
)
const
81
{
82
const
std
::
optional
<
ObjectId
>
deviceId
=
getDeviceId
(
nodeProperties
);
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
93
namespace
{
94
95
std::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
135
QList
<
QCameraDevice
>
QCameraDeviceMonitor
::
cameraDevices
()
136
{
137
QCameraContextManager
&
context
= *
QCameraContextManager
::
instance
();
138
Q_ASSERT
(!
context
.
isInPwThreadLoop
());
139
140
// Make sure everything the server had to say about the graph has arrived
141
// before deciding which cameras there are.
142
context
.
syncRegistry
();
143
144
QList
<
QCameraDevice
>
result
;
145
146
for
(
const
CameraNode
&
node
:
cameraNodes
()) {
147
const
QByteArray
id
=
cameraDeviceId
(
node
.
properties
);
148
if
(
id
.
isEmpty
())
149
continue
;
150
151
const
std
::
vector
<
SpaObjectVideoFormat
>
formats
=
enumerateNodeFormats
(
context
,
node
.
id
);
152
153
auto
info
=
std
::
make_unique
<
QCameraDevicePrivate
>();
154
info
->
id
=
id
;
155
info
->
description
=
cameraDescription
(
node
.
properties
,
node
.
deviceProperties
);
156
info
->
position
=
cameraPosition
(
node
.
properties
);
157
info
->
videoFormats
=
toCameraFormats
(
formats
);
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
) {
165
if
(!
info
->
photoResolutions
.
contains
(
format
.
resolution
()))
166
info
->
photoResolutions
.
push_back
(
format
.
resolution
());
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.
171
info
->
isDefault
=
result
.
isEmpty
();
172
173
result
.
push_back
(
QCameraDevicePrivate
::
create
(
std
::
move
(
info
)));
174
}
175
176
return
result
;
177
}
178
179
void
QCameraDeviceMonitor
::
objectAdded
(
ObjectId
id
,
PipewireRegistryType
type
,
180
const
spa_dict
&
properties
)
181
{
182
using
namespace
QtMultimediaPrivate
;
183
bool
changed
=
false
;
184
185
switch
(
type
) {
186
case
PipewireRegistryType
::
Device
: {
187
PwPropertyDict
dict
=
toPropertyDict
(
properties
);
188
189
std
::
lock_guard
guard
{
m_mutex
};
190
m_devices
[
id
] =
DeviceRecord
{
std
::
move
(
dict
) };
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
) {
196
node
.
deviceProperties
=
m_devices
[
id
].
properties
;
197
changed
=
true
;
198
}
199
}
200
break
;
201
}
202
203
case
PipewireRegistryType
::
Node
: {
204
PwPropertyDict
dict
=
toPropertyDict
(
properties
);
205
if
(!
isCameraNode
(
dict
))
206
return
;
207
208
qCDebug
(
lcPipewireCameraMonitor
) <<
"camera node added"
<<
id
<<
cameraDeviceId
(
dict
);
209
210
CameraNode
node
{
211
id
,
212
std
::
move
(
dict
),
213
PwPropertyDict
{},
214
};
215
216
std
::
lock_guard
guard
{
m_mutex
};
217
if
(
const
PwPropertyDict
*
deviceProperties
=
findDeviceProperties
(
node
.
properties
))
218
node
.
deviceProperties
= *
deviceProperties
;
219
220
m_nodes
.
insert_or_assign
(
id
,
std
::
move
(
node
));
221
changed
=
true
;
222
break
;
223
}
224
225
default
:
226
return
;
227
}
228
229
if
(
changed
)
230
emit
camerasChanged
();
231
}
232
233
void
QCameraDeviceMonitor
::
objectRemoved
(
ObjectId
id
)
234
{
235
bool
changed
=
false
;
236
237
{
238
std
::
lock_guard
guard
{
m_mutex
};
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
) {
250
node
.
deviceProperties
.
clear
();
251
changed
=
true
;
252
}
253
}
254
}
255
}
256
257
if
(
changed
)
258
emit
camerasChanged
();
259
}
260
261
}
// namespace QtPipeWire
262
263
QT_END_NAMESPACE
QtPipeWire
Definition
qpipewire_async_support.cpp:10
QtPipeWire::Q_STATIC_LOGGING_CATEGORY
Q_STATIC_LOGGING_CATEGORY(lcPipewireAudioSink, "qt.multimedia.pipewire.audiosink")
std
[33]
Definition
src_corelib_tools_qhash.cpp:421
qtmultimedia
src
multimedia
pipewire
qpipewire_cameradevicemonitor.cpp
Generated on
for Qt by
1.18.0