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_audiocontextmanager.cpp
Go to the documentation of this file.
1
// Copyright (C) 2025 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_audiocontextmanager_p.h"
5
6
#
include
<
QtMultimedia
/
private
/
qpipewire_audiostream_p
.
h
>
7
#
include
<
QtMultimedia
/
private
/
qpipewire_instance_p
.
h
>
8
#
include
<
QtMultimedia
/
private
/
qpipewire_support_p
.
h
>
9
#
include
<
QtCore
/
qapplicationstatic
.
h
>
10
#
include
<
QtCore
/
qdebug
.
h
>
11
#
include
<
QtCore
/
qsemaphore
.
h
>
12
13
#
include
<
pipewire
/
extensions
/
metadata
.
h
>
14
15
#
include
<
system_error
>
16
17
#
if
__has_include
(
<
spa
/
utils
/
json
.
h
>
)
18
#
include
<
spa
/
utils
/
json
.
h
>
19
#
else
20
#
include
<
QtCore
/
qjsondocument
.
h
>
21
#
include
<
QtCore
/
qjsonvalue
.
h
>
22
#
endif
23
24
#
if
!
PW_CHECK_VERSION
(
0
,
3
,
75
)
25
extern
"C"
{
26
bool
pw_check_library_version
(
int
major,
int
minor,
int
micro);
27
}
28
#
endif
29
30
QT_BEGIN_NAMESPACE
31
32
namespace
QtPipeWire
{
33
34
Q_APPLICATION_STATIC
(
QAudioContextManager
,
s_audioContextInstance
)
35
Q_STATIC_LOGGING_CATEGORY
(
lcPipewireRegistry
,
"qt.multimedia.pipewire.registry"
)
36
37
QAudioContextManager
::
QAudioContextManager
():
38
m_pwInstance
{
39
QPipeWireInstance
::
instance
(),
40
},
41
m_deviceMonitor
{
42
std
::
make_unique
<
QAudioDeviceMonitor
>(),
43
}
44
{
45
m_pwInstance
->
runWithEventLoopLock
([&] {
46
auto
connection
=
m_pwInstance
->
connectToDaemon
();
47
if
(!
connection
) {
48
qCWarning
(
lcPipewireRegistry
)
49
<<
"Failed to connect to pipewire instance"
<<
connection
.
error
().
message
();
50
return
;
51
}
52
m_coreConnection
=
std
::
move
(*
connection
);
53
startDeviceMonitor
();
54
});
55
}
56
57
QAudioContextManager
::~
QAudioContextManager
()
58
{
59
if
(
isConnected
()) {
60
stopListenDefaultMetadataObject
();
61
stopDeviceMonitor
();
62
stopActiveStreams
();
63
}
64
65
m_pwInstance
->
runWithEventLoopLock
([&] {
66
m_deviceMonitor
.
reset
();
67
m_registry
.
reset
();
68
m_coreConnection
.
reset
();
69
});
70
}
71
72
bool
QAudioContextManager
::
minimumRequirementMet
()
73
{
74
return
pw_check_library_version
(0, 3, 44);
// we require PW_KEY_OBJECT_SERIAL
75
}
76
77
QAudioContextManager
*
QAudioContextManager
::
instance
()
78
{
79
return
s_audioContextInstance
;
80
}
81
82
bool
QAudioContextManager
::
isConnected
()
const
83
{
84
return
bool
(
m_coreConnection
);
85
}
86
87
QAudioDeviceMonitor
&
QAudioContextManager
::
deviceMonitor
()
88
{
89
return
*
instance
()->
m_deviceMonitor
;
90
}
91
92
bool
QAudioContextManager
::
isInPwThreadLoop
()
93
{
94
return
instance
()->
m_pwInstance
->
isInPwThreadLoop
();
95
}
96
97
pw_loop
*
QAudioContextManager
::
getEventLoop
()
98
{
99
return
instance
()->
m_pwInstance
->
eventLoop
();
100
}
101
102
PwNodeHandle
QAudioContextManager
::
bindNode
(
ObjectId
id
)
103
{
104
return
PwNodeHandle
{
105
reinterpret_cast
<
pw_node
*>(
pw_registry_bind
(
m_registry
.
get
(),
id
.
value
,
106
PW_TYPE_INTERFACE_Node
, PW_VERSION_NODE,
107
sizeof
(
void
*))),
108
};
109
}
110
111
PwMetadataHandle
QAudioContextManager
::
bindMetadata
(
ObjectId
id
)
112
{
113
return
PwMetadataHandle
{
114
reinterpret_cast
<
pw_metadata
*>(
pw_registry_bind
(
m_registry
.
get
(),
id
.
value
,
115
PW_TYPE_INTERFACE_Metadata
,
116
PW_VERSION_METADATA
,
sizeof
(
void
*))),
117
};
118
}
119
120
void
QAudioContextManager
::
syncRegistry
()
121
{
122
using
namespace
std
::
chrono_literals
;
123
CoreEventSyncHelper
syncHelper
;
124
125
auto
syncOrErr
=
syncHelper
.
sync
(
m_coreConnection
.
get
(), 3s);
126
if
(
syncOrErr
==
true
)
127
return
;
128
if
(
syncOrErr
==
false
)
129
qWarning
() <<
"pw_core_sync timed out"
;
130
else
if
(
syncOrErr
.
error
()) {
131
int
err
=
syncOrErr
.
error
();
132
qWarning
() <<
"CoreEventSyncHelper::sync failed:"
<<
make_error_code
(
err
).
message
();
133
}
134
}
135
136
void
QAudioContextManager
::
registerStreamReference
(
std
::
shared_ptr
<
QPipewireAudioStream
>
stream
)
137
{
138
std
::
lock_guard
guard
{
m_activeStreamMutex
};
139
m_activeStreams
.
emplace
(
std
::
move
(
stream
));
140
}
141
142
void
QAudioContextManager
::
unregisterStreamReference
(
143
const
std
::
shared_ptr
<
QPipewireAudioStream
> &
stream
)
144
{
145
std
::
lock_guard
guard
{
m_activeStreamMutex
};
146
m_activeStreams
.
erase
(
stream
);
147
}
148
149
const
PwCoreConnectionHandle
&
QAudioContextManager
::
coreConnection
()
const
150
{
151
return
m_coreConnection
;
152
}
153
154
void
QAudioContextManager
::
objectAddedCb
(
void
*
data
,
uint32_t
id
,
uint32_t
permissions
,
155
const
char
*
type
,
uint32_t
version
,
const
spa_dict
*
props
)
156
{
157
Q_ASSERT
(
isInPwThreadLoop
());
158
159
qCDebug
(
lcPipewireRegistry
) <<
"objectAdded"
<<
id
<<
QString
::
number
(
permissions
, 8) <<
type
160
<<
version
<< *
props
;
161
162
std
::
optional
<
PipewireRegistryType
>
objectType
=
parsePipewireRegistryType
(
type
);
163
if
(!
objectType
) {
164
qCritical
() <<
"object type cannot be parsed:"
<<
type
;
165
return
;
166
}
167
168
if
(!
props
) {
169
qCritical
() <<
"null property received"
;
170
return
;
171
}
172
173
reinterpret_cast
<
QAudioContextManager
*>(
data
)->
objectAdded
(
ObjectId
{
id
},
permissions
,
174
*
objectType
,
version
, *
props
);
175
}
176
177
void
QAudioContextManager
::
objectRemovedCb
(
void
*
data
,
uint32_t
id
)
178
{
179
Q_ASSERT
(
isInPwThreadLoop
());
180
181
qCDebug
(
lcPipewireRegistry
) <<
"objectRemoved"
<<
id
;
182
183
auto
*
self
=
reinterpret_cast
<
QAudioContextManager
*>(
data
);
184
self
->
objectRemoved
(
ObjectId
{
id
});
185
}
186
187
void
QAudioContextManager
::
objectAdded
(
ObjectId
id
,
uint32_t
permissions
,
PipewireRegistryType
type
,
188
uint32_t
version
,
const
spa_dict
&
props
)
189
{
190
switch
(
type
) {
191
case
PipewireRegistryType
::
Device
:
192
case
PipewireRegistryType
::
Node
:
193
return
m_deviceMonitor
->
objectAdded
(
id
,
permissions
,
type
,
version
,
props
);
194
195
case
PipewireRegistryType
::
Metadata
: {
196
const
char
*
name
=
spa_dict_lookup
(&
props
,
PW_KEY_METADATA_NAME
);
197
if
(
name
==
std
::
string_view
(
"default"
))
198
// the "default" metadata will inform us about the "default" device
199
return
startListenDefaultMetadataObject
(
id
,
version
);
200
return
;
201
}
202
203
default
:
204
return
;
205
}
206
}
207
208
void
QAudioContextManager
::
objectRemoved
(
ObjectId
id
)
209
{
210
m_deviceMonitor
->
objectRemoved
(
id
);
211
}
212
213
void
QAudioContextManager
::
startListenDefaultMetadataObject
(
ObjectId
id
,
uint32_t
version
)
214
{
215
if
(
m_defaultMetadataObject
) {
216
qWarning
(
lcPipewireRegistry
) <<
"metadata already registered"
;
217
return
;
218
}
219
220
if
(
version
<
PW_VERSION_METADATA
) {
221
Q_UNLIKELY_BRANCH
;
222
qWarning
(
lcPipewireRegistry
)
223
<<
"metadata version too old, cannot listen to default metadata object"
;
224
return
;
225
}
226
227
static
constexpr
pw_metadata_events
metadata_events
= {
228
.
version
=
PW_VERSION_METADATA_EVENTS
,
229
.
property
= [](
void
*
data
,
uint32_t
subject
,
const
char
*
key
,
const
char
*
type
,
230
const
char
*
value
) ->
int
{
231
auto
*
self
=
reinterpret_cast
<
QAudioContextManager
*>(
data
);
232
233
return
self
->
handleDefaultMetadataObjectEvent
(
234
ObjectId
{
235
subject
,
236
},
237
MetadataRecord
{
238
.
key
=
key
,
239
.
type
=
type
,
240
.
value
=
value
,
241
});
242
},
243
};
244
245
m_defaultMetadataObject
=
bindMetadata
(
id
);
246
if
(!
m_defaultMetadataObject
) {
247
qFatal
() <<
"cannot bind to metadata"
;
248
return
;
249
}
250
251
int
status
=
pw_metadata_add_listener
(
m_defaultMetadataObject
.
get
(),
252
&
m_defaultMetadataObjectListener
, &
metadata_events
,
this
);
253
if
(
status
< 0)
254
qFatal
() <<
"Failed to add listener"
<<
make_error_code
(-
status
).
message
();
255
}
256
257
void
QAudioContextManager
::
stopListenDefaultMetadataObject
()
258
{
259
if
(!
m_defaultMetadataObject
)
260
return
;
261
262
m_pwInstance
->
runWithEventLoopLock
([&] {
263
spa_hook_remove
(&
m_defaultMetadataObjectListener
);
264
m_defaultMetadataObject
.
reset
();
265
});
266
}
267
268
namespace
{
269
270
// parse json object with one "name" member
271
std
::
optional
<
QByteArray
>
jsonParseObjectName
(
const
char
*
json_str
)
272
{
273
#
if
__has_include
(
<
spa
/
utils
/
json
.
h
>
)
274
using
namespace
std
::
string_view_literals
;
275
276
struct
spa_json
json
;
277
spa_json_init
(&
json
,
json_str
,
strlen
(
json_str
));
278
279
struct
spa_json
it
;
280
if
(
spa_json_enter_object
(&
json
, &
it
) > 0) {
281
char
key
[256];
282
while
(
spa_json_get_string
(&
it
,
key
,
sizeof
(
key
)) > 0) {
283
if
(
key
==
"name"sv
) {
284
char
value
[16384];
285
if
(
spa_json_get_string
(&
it
,
value
,
sizeof
(
value
)) >= 0)
286
return
QByteArray
{
value
};
287
}
else
{
288
spa_json_next
(&
it
,
nullptr
);
289
}
290
}
291
}
292
293
return
std
::
nullopt
;
294
#
else
295
// old pipewire does not provide json.h, so we use Qt to parse
296
297
using
namespace
Qt
::
Literals
;
298
299
QByteArray
value
{
json_str
};
300
QJsonDocument
doc
=
QJsonDocument
::
fromJson
(
value
);
301
if
(
doc
.
isNull
()) {
302
qWarning
() <<
"JSON parse error:"
<<
json_str
;
303
return
std
::
nullopt
;
304
}
305
306
QJsonValue
name
=
doc
[u"name"_s];
307
if
(!
name
.
isString
())
308
return
std
::
nullopt
;
309
return
name
.
toString
().
toUtf8
();
310
#
endif
311
}
312
313
}
// namespace
314
315
int
QAudioContextManager
::
handleDefaultMetadataObjectEvent
(
ObjectId
subject
,
316
const
MetadataRecord
&
record
)
317
{
318
using
namespace
std
::
string_view_literals
;
319
320
qCDebug
(
lcPipewireRegistry
) <<
"metadata for"
<<
subject
<<
" - "
<<
record
.
key
<<
record
.
type
321
<<
record
.
value
;
322
323
if
(
subject
!=
ObjectId
{
PW_ID_CORE
})
324
return
0;
325
326
if
(
record
.
key
==
nullptr
) {
327
// "NULL clears all metadata for the subject"
328
m_deviceMonitor
->
setDefaultAudioSource
(
QAudioDeviceMonitor
::
NoDefaultDevice
);
329
m_deviceMonitor
->
setDefaultAudioSink
(
QAudioDeviceMonitor
::
NoDefaultDevice
);
330
return
0;
331
}
332
333
auto
extractName
= [&]() ->
std
::
optional
<
QByteArray
> {
334
if
(
record
.
type
!=
"Spa:String:JSON"sv
)
335
return
std
::
nullopt
;
336
return
jsonParseObjectName
(
record
.
value
);
337
};
338
339
if
(
record
.
key
==
"default.audio.source"sv
) {
340
if
(
record
.
value
) {
341
std
::
optional
<
QByteArray
>
name
=
extractName
();
342
if
(
name
)
343
m_deviceMonitor
->
setDefaultAudioSource
(
std
::
move
(*
name
));
344
}
else
{
345
m_deviceMonitor
->
setDefaultAudioSource
(
QAudioDeviceMonitor
::
NoDefaultDevice
);
346
}
347
348
return
0;
349
}
350
351
if
(
record
.
key
==
"default.audio.sink"sv
) {
352
if
(
record
.
value
) {
353
std
::
optional
<
QByteArray
>
name
=
extractName
();
354
if
(
name
)
355
m_deviceMonitor
->
setDefaultAudioSink
(
std
::
move
(*
name
));
356
}
else
{
357
m_deviceMonitor
->
setDefaultAudioSink
(
QAudioDeviceMonitor
::
NoDefaultDevice
);
358
}
359
return
0;
360
}
361
362
return
0;
363
}
364
365
void
QAudioContextManager
::
stopActiveStreams
()
366
{
367
auto
streams
=
std
::
exchange
(
m_activeStreams
, {});
368
369
for
(
const
auto
&
stream
:
streams
) {
370
stream
->
disconnectStream
();
371
stream
->
resetStream
();
372
}
373
}
374
375
void
QAudioContextManager
::
startDeviceMonitor
()
376
{
377
m_registry
=
PwRegistryHandle
{
378
pw_core_get_registry
(
m_coreConnection
.
get
(), PW_VERSION_REGISTRY,
379
/*user_data_size=*/
sizeof
(
QAudioContextManager
*)),
380
};
381
if
(!
m_registry
) {
382
qFatal
() <<
"Failed to create pipewire registry"
<<
make_error_code
().
message
();
383
return
;
384
}
385
386
spa_zero(
m_registryListener
);
387
388
static
constexpr
struct
pw_registry_events
registry_events
= {
389
.
version
=
PW_VERSION_REGISTRY_EVENTS
,
390
.
global
=
QAudioContextManager
::
objectAddedCb
,
391
.
global_remove
=
QAudioContextManager
::
objectRemovedCb
,
392
};
393
int
status
=
394
pw_registry_add_listener
(
m_registry
.
get
(), &
m_registryListener
, &
registry_events
,
this
);
395
if
(
status
< 0)
396
qFatal
() <<
"Failed to add listener"
<<
make_error_code
(-
status
).
message
();
397
}
398
399
void
QAudioContextManager
::
stopDeviceMonitor
()
400
{
401
if
(!
m_registry
)
402
return
;
403
404
m_pwInstance
->
runWithEventLoopLock
([&] {
405
m_deviceMonitor
->
clearAllObservers
();
406
407
spa_hook_remove
(&
m_registryListener
);
408
m_registry
.
reset
();
409
});
410
}
411
412
}
// namespace QtPipeWire
413
414
QT_END_NAMESPACE
QtPipeWire
Definition
qpipewire_async_support.cpp:10
__has_include
#define __has_include(x)
Definition
qcompilerdetection.h:459
pw_check_library_version
bool pw_check_library_version(int major, int minor, int micro)
qtmultimedia
src
multimedia
pipewire
qpipewire_audiocontextmanager.cpp
Generated on
for Qt by
1.16.1