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
"qpipewire_audiostream_p.h"
7
#
include
"qpipewire_instance_p.h"
8
#
include
"qpipewire_propertydict_p.h"
9
#
include
"qpipewire_support_p.h"
10
11
#
include
<
QtCore
/
qapplicationstatic
.
h
>
12
#
include
<
QtCore
/
qcoreapplication
.
h
>
13
#
include
<
QtCore
/
qdebug
.
h
>
14
#
include
<
QtCore
/
qsemaphore
.
h
>
15
16
#
include
<
pipewire
/
extensions
/
metadata
.
h
>
17
18
#
include
<
system_error
>
19
20
#
if
__has_include
(
<
spa
/
utils
/
json
.
h
>
)
21
#
include
<
spa
/
utils
/
json
.
h
>
22
#
else
23
#
include
<
QtCore
/
qjsondocument
.
h
>
24
#
include
<
QtCore
/
qjsonvalue
.
h
>
25
#
endif
26
27
#
if
!
PW_CHECK_VERSION
(
0
,
3
,
75
)
28
extern
"C"
{
29
bool
pw_check_library_version(
int
major,
int
minor,
int
micro);
30
}
31
#
endif
32
33
QT_BEGIN_NAMESPACE
34
35
namespace
QtPipeWire
{
36
37
Q_APPLICATION_STATIC
(
QAudioContextManager
,
s_audioContextInstance
)
38
Q_STATIC_LOGGING_CATEGORY
(
lcPipewireRegistry
,
"qt.multimedia.pipewire.registry"
)
39
40
QAudioContextManager
::
QAudioContextManager
():
41
m_libraryInstance
{
42
QPipeWireInstance
::
instance
(),
43
},
44
m_deviceMonitor
{
45
std
::
make_unique
<
QAudioDeviceMonitor
>(),
46
}
47
{
48
prepareEventLoop
();
49
prepareContext
();
50
if
(!
m_context
) {
51
// pipewire server not available
52
return
;
53
}
54
55
connectToPipewireInstance
();
56
if
(!
isConnected
())
57
return
;
58
59
startDeviceMonitor
();
60
startEventLoop
();
61
}
62
63
QAudioContextManager
::~
QAudioContextManager
()
64
{
65
if
(
isConnected
()) {
66
stopListenDefaultMetadataObject
();
67
stopDeviceMonitor
();
68
stopEventLoop
();
69
stopActiveStreams
();
70
}
71
72
m_deviceMonitor
.
reset
();
73
m_registry
.
reset
();
74
m_coreConnection
.
reset
();
75
m_context
.
reset
();
76
m_eventLoop
.
reset
();
77
}
78
79
bool
QAudioContextManager
::
minimumRequirementMet
()
80
{
81
return
pw_check_library_version
(0, 3, 44);
// we require PW_KEY_OBJECT_SERIAL
82
}
83
84
QAudioContextManager
*
QAudioContextManager
::
instance
()
85
{
86
return
s_audioContextInstance
;
87
}
88
89
bool
QAudioContextManager
::
isConnected
()
const
90
{
91
return
bool
(
m_coreConnection
);
92
}
93
94
QAudioDeviceMonitor
&
QAudioContextManager
::
deviceMonitor
()
95
{
96
return
*
instance
()->
m_deviceMonitor
;
97
}
98
99
bool
QAudioContextManager
::
isInPwThreadLoop
()
100
{
101
return
pw_thread_loop_in_thread
(
instance
()->
m_eventLoop
.
get
());
102
}
103
104
pw_loop
*
QAudioContextManager
::
getEventLoop
()
105
{
106
return
pw_thread_loop_get_loop
(
instance
()->
m_eventLoop
.
get
());
107
}
108
109
PwNodeHandle
QAudioContextManager
::
bindNode
(
ObjectId
id
)
110
{
111
return
PwNodeHandle
{
112
reinterpret_cast
<
pw_node
*>(
pw_registry_bind
(
m_registry
.
get
(),
id
.
value
,
113
PW_TYPE_INTERFACE_Node
, PW_VERSION_NODE,
114
sizeof
(
void
*))),
115
};
116
}
117
118
PwMetadataHandle
QAudioContextManager
::
bindMetadata
(
ObjectId
id
)
119
{
120
return
PwMetadataHandle
{
121
reinterpret_cast
<
pw_metadata
*>(
pw_registry_bind
(
m_registry
.
get
(),
id
.
value
,
122
PW_TYPE_INTERFACE_Metadata
,
123
PW_VERSION_METADATA
,
sizeof
(
void
*))),
124
};
125
}
126
127
void
QAudioContextManager
::
syncRegistry
()
128
{
129
using
namespace
std
::
chrono_literals
;
130
CoreEventSyncHelper
syncHelper
;
131
132
auto
syncOrErr
=
syncHelper
.
sync
(
m_coreConnection
.
get
(), 3s);
133
if
(
syncOrErr
==
true
)
134
return
;
135
if
(
syncOrErr
==
false
)
136
qWarning
() <<
"pw_core_sync timed out"
;
137
else
if
(
syncOrErr
.
error
()) {
138
int
err
=
syncOrErr
.
error
();
139
qWarning
() <<
"CoreEventSyncHelper::sync failed:"
<<
make_error_code
(
err
).
message
();
140
}
141
}
142
143
void
QAudioContextManager
::
registerStreamReference
(
std
::
shared_ptr
<
QPipewireAudioStream
>
stream
)
144
{
145
std
::
lock_guard
guard
{
m_activeStreamMutex
};
146
m_activeStreams
.
emplace
(
std
::
move
(
stream
));
147
}
148
149
void
QAudioContextManager
::
unregisterStreamReference
(
150
const
std
::
shared_ptr
<
QPipewireAudioStream
> &
stream
)
151
{
152
std
::
lock_guard
guard
{
m_activeStreamMutex
};
153
m_activeStreams
.
erase
(
stream
);
154
}
155
156
const
PwCoreConnectionHandle
&
QAudioContextManager
::
coreConnection
()
const
157
{
158
return
m_coreConnection
;
159
}
160
161
void
QAudioContextManager
::
prepareEventLoop
()
162
{
163
m_eventLoop
=
PwThreadLoopHandle
{
164
pw_thread_loop_new
(
"QAudioContext"
,
/*props=*/
nullptr
),
165
};
166
if
(!
m_eventLoop
) {
167
qFatal
() <<
"Failed to create pipewire main loop"
<<
make_error_code
().
message
();
168
return
;
169
}
170
}
171
172
void
QAudioContextManager
::
startEventLoop
()
173
{
174
int
status
=
pw_thread_loop_start
(
m_eventLoop
.
get
());
175
if
(
status
< 0)
176
qFatal
() <<
"Failed to start event loop"
<<
make_error_code
(-
status
).
message
();
177
}
178
179
void
QAudioContextManager
::
stopEventLoop
()
180
{
181
pw_thread_loop_stop
(
m_eventLoop
.
get
());
182
}
183
184
void
QAudioContextManager
::
prepareContext
()
185
{
186
PwPropertiesHandle
props
=
makeProperties
({
187
{
PW_KEY_APP_NAME
,
qApp
->
applicationName
().
toUtf8
().
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
qtmultimedia
src
multimedia
pipewire
qpipewire_audiocontextmanager.cpp
Generated on
for Qt by
1.16.1