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_support_p.h>
9#include <QtCore/qapplicationstatic.h>
10#include <QtCore/qdebug.h>
11#include <QtCore/qsemaphore.h>
12
13#include <pipewire/extensions/metadata.h>
14
15#include <system_error>
16
17#if __has_include(<spa/utils/json.h>)
18# include <spa/utils/json.h>
19#else
20# include <QtCore/qjsondocument.h>
21# include <QtCore/qjsonvalue.h>
22#endif
23
24#if !PW_CHECK_VERSION(0, 3, 75)
25extern "C" {
26bool pw_check_library_version(int major, int minor, int micro);
27}
28#endif
29
30QT_BEGIN_NAMESPACE
31
32namespace QtPipeWire {
33
35Q_STATIC_LOGGING_CATEGORY(lcPipewireRegistry, "qt.multimedia.pipewire.registry")
36
56
71
73{
74 return pw_check_library_version(0, 3, 44); // we require PW_KEY_OBJECT_SERIAL
75}
76
81
83{
84 return bool(m_coreConnection);
85}
86
91
96
101
103{
104 return PwNodeHandle{
105 reinterpret_cast<pw_node *>(pw_registry_bind(m_registry.get(), id.value,
106 PW_TYPE_INTERFACE_Node, PW_VERSION_NODE,
107 sizeof(void *))),
108 };
109}
110
119
121{
122 using namespace std::chrono_literals;
124
126 if (syncOrErr == true)
127 return;
128 if (syncOrErr == false)
129 qWarning() << "pw_core_sync timed out";
130 else if (syncOrErr.error()) {
131 int err = syncOrErr.error();
132 qWarning() << "CoreEventSyncHelper::sync failed:" << make_error_code(err).message();
133 }
134}
135
141
148
153
155 const char *type, uint32_t version, const spa_dict *props)
156{
158
159 qCDebug(lcPipewireRegistry) << "objectAdded" << id << QString::number(permissions, 8) << type
160 << version << *props;
161
163 if (!objectType) {
164 qCritical() << "object type cannot be parsed:" << type;
165 return;
166 }
167
168 if (!props) {
169 qCritical() << "null property received";
170 return;
171 }
172
175}
176
178{
180
181 qCDebug(lcPipewireRegistry) << "objectRemoved" << id;
182
183 auto *self = reinterpret_cast<QAudioContextManager *>(data);
185}
186
189{
190 switch (type) {
194
197 if (name == std::string_view("default"))
198 // the "default" metadata will inform us about the "default" device
200 return;
201 }
202
203 default:
204 return;
205 }
206}
207
209{
211}
212
214{
216 qWarning(lcPipewireRegistry) << "metadata already registered";
217 return;
218 }
219
223 << "metadata version too old, cannot listen to default metadata object";
224 return;
225 }
226
227 static constexpr pw_metadata_events metadata_events = {
229 .property = [](void *data, uint32_t subject, const char *key, const char *type,
230 const char *value) -> int {
231 auto *self = reinterpret_cast<QAudioContextManager *>(data);
232
234 ObjectId{
235 subject,
236 },
238 .key = key,
239 .type = type,
240 .value = value,
241 });
242 },
243 };
244
247 qFatal() << "cannot bind to metadata";
248 return;
249 }
250
253 if (status < 0)
254 qFatal() << "Failed to add listener" << make_error_code(-status).message();
255}
256
258{
260 return;
261
265 });
266}
267
268namespace {
269
270// parse json object with one "name" member
272{
273#if __has_include(<spa/utils/json.h>)
274 using namespace std::string_view_literals;
275
276 struct spa_json json;
278
279 struct spa_json it;
280 if (spa_json_enter_object(&json, &it) > 0) {
281 char key[256];
282 while (spa_json_get_string(&it, key, sizeof(key)) > 0) {
283 if (key == "name"sv) {
284 char value[16384];
285 if (spa_json_get_string(&it, value, sizeof(value)) >= 0)
286 return QByteArray{ value };
287 } else {
288 spa_json_next(&it, nullptr);
289 }
290 }
291 }
292
293 return std::nullopt;
294#else
295 // old pipewire does not provide json.h, so we use Qt to parse
296
297 using namespace Qt::Literals;
298
301 if (doc.isNull()) {
302 qWarning() << "JSON parse error:" << json_str;
303 return std::nullopt;
304 }
305
306 QJsonValue name = doc[u"name"_s];
307 if (!name.isString())
308 return std::nullopt;
309 return name.toString().toUtf8();
310#endif
311}
312
313} // namespace
314
316 const MetadataRecord &record)
317{
318 using namespace std::string_view_literals;
319
320 qCDebug(lcPipewireRegistry) << "metadata for" << subject << " - " << record.key << record.type
321 << record.value;
322
323 if (subject != ObjectId{ PW_ID_CORE })
324 return 0;
325
326 if (record.key == nullptr) {
327 // "NULL clears all metadata for the subject"
330 return 0;
331 }
332
333 auto extractName = [&]() -> std::optional<QByteArray> {
334 if (record.type != "Spa:String:JSON"sv)
335 return std::nullopt;
337 };
338
339 if (record.key == "default.audio.source"sv) {
340 if (record.value) {
342 if (name)
344 } else {
346 }
347
348 return 0;
349 }
350
351 if (record.key == "default.audio.sink"sv) {
352 if (record.value) {
354 if (name)
356 } else {
358 }
359 return 0;
360 }
361
362 return 0;
363}
364
366{
367 auto streams = std::exchange(m_activeStreams, {});
368
369 for (const auto &stream : streams) {
372 }
373}
374
376{
378 pw_core_get_registry(m_coreConnection.get(), PW_VERSION_REGISTRY,
379 /*user_data_size=*/sizeof(QAudioContextManager *)),
380 };
381 if (!m_registry) {
382 qFatal() << "Failed to create pipewire registry" << make_error_code().message();
383 return;
384 }
385
386 spa_zero(m_registryListener);
387
388 static constexpr struct pw_registry_events registry_events = {
392 };
393 int status =
395 if (status < 0)
396 qFatal() << "Failed to add listener" << make_error_code(-status).message();
397}
398
400{
401 if (!m_registry)
402 return;
403
406
409 });
410}
411
412} // namespace QtPipeWire
413
414QT_END_NAMESPACE
#define __has_include(x)
bool pw_check_library_version(int major, int minor, int micro)