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