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/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)
28extern "C" {
29bool pw_check_library_version(int major, int minor, int micro);
30}
31#endif
32
33QT_BEGIN_NAMESPACE
34
35namespace QtPipeWire {
36
38Q_STATIC_LOGGING_CATEGORY(lcPipewireRegistry, "qt.multimedia.pipewire.registry")
39
43 },
46 }
47{
50 if (!m_context) {
51 // pipewire server not available
52 return;
53 }
54
56 if (!isConnected())
57 return;
58
61}
62
78
80{
81 return pw_check_library_version(0, 3, 44); // we require PW_KEY_OBJECT_SERIAL
82}
83
88
90{
91 return bool(m_coreConnection);
92}
93
98
103
108
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
126
128{
129 using namespace std::chrono_literals;
131
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
148
155
160
162{
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
173{
175 if (status < 0)
176 qFatal() << "Failed to start event loop" << make_error_code(-status).message();
177}
178
180{
182}
183
185{
188 });
189
193 /*user_data_size=*/0),
194 };
195}
196
198{
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
210 const char *type, uint32_t version, const spa_dict *props)
211{
213
214 qCDebug(lcPipewireRegistry) << "objectAdded" << id << QString::number(permissions, 8) << type
215 << version << *props;
216
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
230}
231
233{
235
236 qCDebug(lcPipewireRegistry) << "objectRemoved" << id;
237
238 auto *self = reinterpret_cast<QAudioContextManager *>(data);
240}
241
244{
245 switch (type) {
249
252 if (name == std::string_view("default"))
253 // the "default" metadata will inform us about the "default" device
255 return;
256 }
257
258 default:
259 return;
260 }
261}
262
264{
266}
267
269{
271 qWarning(lcPipewireRegistry) << "metadata already registered";
272 return;
273 }
274
278 << "metadata version too old, cannot listen to default metadata object";
279 return;
280 }
281
282 static constexpr pw_metadata_events 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
289 ObjectId{
290 subject,
291 },
293 .key = key,
294 .type = type,
295 .value = value,
296 });
297 },
298 };
299
302 qFatal() << "cannot bind to metadata";
303 return;
304 }
305
308 if (status < 0)
309 qFatal() << "Failed to add listener" << make_error_code(-status).message();
310}
311
313{
315 return;
316
320 });
321}
322
323namespace {
324
325// parse json object with one "name" member
327{
328#if __has_include(<spa/utils/json.h>)
329 using namespace std::string_view_literals;
330
331 struct spa_json json;
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
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
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"
385 return 0;
386 }
387
388 auto extractName = [&]() -> std::optional<QByteArray> {
389 if (record.type != "Spa:String:JSON"sv)
390 return std::nullopt;
392 };
393
394 if (record.key == "default.audio.source"sv) {
395 if (record.value) {
397 if (name)
399 } else {
401 }
402
403 return 0;
404 }
405
406 if (record.key == "default.audio.sink"sv) {
407 if (record.value) {
409 if (name)
411 } else {
413 }
414 return 0;
415 }
416
417 return 0;
418}
419
421{
422 auto streams = std::exchange(m_activeStreams, {});
423
424 for (const auto &stream : streams)
426}
427
429{
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 = {
445 };
446 int status =
448 if (status < 0)
449 qFatal() << "Failed to add listener" << make_error_code(-status).message();
450}
451
453{
454 if (!m_registry)
455 return;
456
460 });
461}
462
463} // namespace QtPipeWire
464
465QT_END_NAMESPACE
#define __has_include(x)