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