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
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)
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
77
79{
80 return pw_check_library_version(0, 3, 44); // we require PW_KEY_OBJECT_SERIAL
81}
82
87
89{
90 return bool(m_coreConnection);
91}
92
97
102
107
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
125
127{
128 using namespace std::chrono_literals;
130
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
147
154
159
161{
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
172{
174 if (status < 0)
175 qFatal() << "Failed to start event loop" << make_error_code(-status).message();
176}
177
179{
181}
182
184{
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)
bool pw_check_library_version(int major, int minor, int micro)