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
5
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)
27extern "C" {
28bool pw_check_library_version(int major, int minor, int micro);
29}
30#endif
31
32QT_BEGIN_NAMESPACE
33
34namespace QtPipeWire {
35
37Q_STATIC_LOGGING_CATEGORY(lcPipewireRegistry, "qt.multimedia.pipewire.registry")
38
42 },
45 }
46{
49 if (!m_context) {
50 // pipewire server not available
51 return;
52 }
53
55 if (!isConnected())
56 return;
57
60}
61
75
77{
78 return pw_check_library_version(0, 3, 44); // we require PW_KEY_OBJECT_SERIAL
79}
80
85
87{
88 return bool(m_coreConnection);
89}
90
95
100
105
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
123
125{
126 using namespace std::chrono_literals;
128
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
145
152
157
159{
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
170{
172 if (status < 0)
173 qFatal() << "Failed to start event loop" << make_error_code(-status).message();
174}
175
177{
179}
180
182{
185 });
186
190 /*user_data_size=*/0),
191 };
192}
193
195{
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
207 const char *type, uint32_t version, const spa_dict *props)
208{
210
211 qCDebug(lcPipewireRegistry) << "objectAdded" << id << QString::number(permissions, 8) << type
212 << version << *props;
213
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
227}
228
230{
232
233 qCDebug(lcPipewireRegistry) << "objectRemoved" << id;
234
235 auto *self = reinterpret_cast<QAudioContextManager *>(data);
237}
238
241{
242 switch (type) {
246
249 if (name == std::string_view("default"))
250 // the "default" metadata will inform us about the "default" device
252 return;
253 }
254
255 default:
256 return;
257 }
258}
259
261{
263}
264
266{
268 qWarning(lcPipewireRegistry) << "metadata already registered";
269 return;
270 }
271
275 << "metadata version too old, cannot listen to default metadata object";
276 return;
277 }
278
279 static constexpr pw_metadata_events 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
286 ObjectId{
287 subject,
288 },
290 .key = key,
291 .type = type,
292 .value = value,
293 });
294 },
295 };
296
299 qFatal() << "cannot bind to metadata";
300 return;
301 }
302
305 if (status < 0)
306 qFatal() << "Failed to add listener" << make_error_code(-status).message();
307}
308
309namespace {
310
311// parse json object with one "name" member
313{
314#if __has_include(<spa/utils/json.h>)
315 using namespace std::string_view_literals;
316
317 struct spa_json json;
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
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
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"
371 return 0;
372 }
373
374 auto extractName = [&]() -> std::optional<QByteArray> {
375 if (record.type != "Spa:String:JSON"sv)
376 return std::nullopt;
378 };
379
380 if (record.key == "default.audio.source"sv) {
381 if (record.value) {
383 if (name)
385 } else {
387 }
388
389 return 0;
390 }
391
392 if (record.key == "default.audio.sink"sv) {
393 if (record.value) {
395 if (name)
397 } else {
399 }
400 return 0;
401 }
402
403 return 0;
404}
405
407{
408 auto streams = std::exchange(m_activeStreams, {});
409
410 for (const auto &stream : streams)
412}
413
415{
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 = {
431 };
432 int status =
434 if (status < 0)
435 qFatal() << "Failed to add listener" << make_error_code(-status).message();
436}
437
438} // namespace QtPipeWire
439
440QT_END_NAMESPACE
#define __has_include(x)