11#include <QtCore/qcoreapplication.h>
12#include <QtCore/qdebug.h>
13#include <QtCore/qsemaphore.h>
15#include <pipewire/extensions/metadata.h>
17#include <system_error>
20# include <spa/utils/json.h>
22# include <QtCore/qjsondocument.h>
23# include <QtCore/qjsonvalue.h>
26#if !PW_CHECK_VERSION
(0
, 3
, 75
)
28bool pw_check_library_version(
int major,
int minor,
int micro);
55 connectToPipewireInstance();
70 m_deviceMonitor.reset();
72 m_coreConnection.reset();
84 return s_audioContextInstance;
89 return bool(m_coreConnection);
99 return pw_thread_loop_in_thread(
instance()->m_eventLoop.get());
104 return pw_thread_loop_get_loop(
instance()->m_eventLoop.get());
110 reinterpret_cast<pw_node *>(pw_registry_bind(m_registry.get(), id.value,
111 PW_TYPE_INTERFACE_Node, PW_VERSION_NODE,
118 return PwMetadataHandle{
119 reinterpret_cast<pw_metadata *>(pw_registry_bind(m_registry.get(), id.value,
120 PW_TYPE_INTERFACE_Metadata,
121 PW_VERSION_METADATA,
sizeof(
void *))),
127 using namespace std::chrono_literals;
130 auto syncOrErr = syncHelper.sync(m_coreConnection.get(), 3s);
131 if (syncOrErr ==
true)
133 if (syncOrErr ==
false)
134 qWarning() <<
"pw_core_sync timed out";
135 else if (syncOrErr.error()) {
136 int err = syncOrErr.error();
137 qWarning() <<
"CoreEventSyncHelper::sync failed:" <<
make_error_code(err
).message();
143 std::lock_guard guard{ m_activeStreamMutex };
144 m_activeStreams.emplace(std::move(stream));
150 std::lock_guard guard{ m_activeStreamMutex };
151 m_activeStreams.erase(stream);
156 return m_coreConnection;
161 m_eventLoop = PwThreadLoopHandle{
162 pw_thread_loop_new(
"QAudioContext",
nullptr),
165 qFatal() <<
"Failed to create pipewire main loop" <<
make_error_code().message();
172 int status = pw_thread_loop_start(m_eventLoop.get());
179 pw_thread_loop_stop(m_eventLoop.get());
184 PwPropertiesHandle props = makeProperties({
185 { PW_KEY_APP_NAME, qApp->applicationName().toUtf8().data() },
188 Q_ASSERT(m_eventLoop);
189 m_context = PwContextHandle{
190 pw_context_new(pw_thread_loop_get_loop(m_eventLoop.get()), props.release(),
197 Q_ASSERT(m_eventLoop && m_context);
198 m_coreConnection = PwCoreConnectionHandle{
199 pw_context_connect(m_context.get(),
nullptr,
203 if (!m_coreConnection)
204 qInfo() <<
"Failed to connect to pipewire instance" <<
make_error_code().message();
208 const char *type, uint32_t version,
const spa_dict *props)
212 qCDebug(lcPipewireRegistry) <<
"objectAdded" << id << QString::number(permissions, 8) << type
213 << version << *props;
217 qCritical() <<
"object type cannot be parsed:" << type;
222 qCritical() <<
"null property received";
227 *objectType, version, *props);
234 qCDebug(lcPipewireRegistry) <<
"objectRemoved" << id;
237 self->objectRemoved(ObjectId{ id });
241 uint32_t version,
const spa_dict &props)
244 case PipewireRegistryType::Device:
245 case PipewireRegistryType::Node:
246 return m_deviceMonitor->objectAdded(id, permissions, type, version, props);
249 const char *name = spa_dict_lookup(&props, PW_KEY_METADATA_NAME);
250 if (name == std::string_view(
"default"))
252 return startListenDefaultMetadataObject(id, version);
263 m_deviceMonitor->objectRemoved(id);
268 if (m_defaultMetadataObject) {
269 qWarning(lcPipewireRegistry) <<
"metadata already registered";
273 if (version < PW_VERSION_METADATA) {
275 qWarning(lcPipewireRegistry)
276 <<
"metadata version too old, cannot listen to default metadata object";
280 static constexpr pw_metadata_events metadata_events = {
281 .version = PW_VERSION_METADATA_EVENTS,
282 .property = [](
void *data, uint32_t subject,
const char *key,
const char *type,
283 const char *value) ->
int {
284 Q_ASSERT(subject == PW_ID_CORE);
286 auto *self =
reinterpret_cast<QAudioContextManager *>(data);
289 return self->handleDefaultMetadataObjectEvent(MetadataRecord{
297 m_defaultMetadataObject = bindMetadata(id);
298 if (!m_defaultMetadataObject) {
299 qFatal() <<
"cannot bind to metadata";
303 int status = pw_metadata_add_listener(m_defaultMetadataObject.get(),
304 &m_defaultMetadataObjectListener, &metadata_events,
this);
312std::optional<QByteArray> jsonParseObjectName(
const char *json_str)
315 using namespace std::string_view_literals;
317 struct spa_json json;
318 spa_json_init(&json, json_str, strlen(json_str));
321 if (spa_json_enter_object(&json, &it) > 0) {
323 while (spa_json_get_string(&it, key,
sizeof(key)) > 0) {
324 if (key ==
"name"sv) {
326 if (spa_json_get_string(&it, value,
sizeof(value)) >= 0)
327 return QByteArray{ value };
329 spa_json_next(&it,
nullptr);
338 using namespace Qt::Literals;
340 QByteArray value{ json_str };
341 QJsonDocument doc = QJsonDocument::fromJson(value);
343 qWarning() <<
"JSON parse error:" << json_str;
347 QJsonValue name = doc[u"name"_s];
348 if (!name.isString())
350 return name.toString().toUtf8();
358 using namespace std::string_view_literals;
360 qDebug(lcPipewireRegistry) <<
"metadata:" << record.key << record.type << record.value;
362 if (record.key ==
nullptr) {
364 m_deviceMonitor->setDefaultAudioSource(QAudioDeviceMonitor::NoDefaultDevice);
365 m_deviceMonitor->setDefaultAudioSink(QAudioDeviceMonitor::NoDefaultDevice);
369 auto extractName = [&]() -> std::optional<QByteArray> {
370 if (record.type !=
"Spa:String:JSON"sv)
372 return jsonParseObjectName(record.value);
375 if (record.key ==
"default.audio.source"sv) {
377 std::optional<QByteArray> name = extractName();
379 m_deviceMonitor->setDefaultAudioSource(std::move(*name));
381 m_deviceMonitor->setDefaultAudioSource(QAudioDeviceMonitor::NoDefaultDevice);
387 if (record.key ==
"default.audio.sink"sv) {
389 std::optional<QByteArray> name = extractName();
391 m_deviceMonitor->setDefaultAudioSink(std::move(*name));
393 m_deviceMonitor->setDefaultAudioSink(QAudioDeviceMonitor::NoDefaultDevice);
403 auto streams = std::exchange(m_activeStreams, {});
405 for (
const auto &stream : streams)
406 stream->resetStream();
411 m_registry = PwRegistryHandle{
412 pw_core_get_registry(m_coreConnection.get(), PW_VERSION_REGISTRY,
413 sizeof(QAudioContextManager *)),
420 spa_zero(m_registryListener);
422 static constexpr struct pw_registry_events registry_events = {
423 .version = PW_VERSION_REGISTRY_EVENTS,
424 .global = QAudioContextManager::objectAddedCb,
425 .global_remove = QAudioContextManager::objectRemovedCb,
428 pw_registry_add_listener(m_registry.get(), &m_registryListener, ®istry_events,
this);
static bool minimumRequirementMet()
PwNodeHandle bindNode(ObjectId)
static QAudioDeviceMonitor & deviceMonitor()
static QAudioContextManager * instance()
void registerStreamReference(std::shared_ptr< QPipewireAudioStream >)
void unregisterStreamReference(const std::shared_ptr< QPipewireAudioStream > &)
PwMetadataHandle bindMetadata(ObjectId)
static bool isInPwThreadLoop()
static pw_loop * getEventLoop()
const PwCoreConnectionHandle & coreConnection() const
Q_STATIC_LOGGING_CATEGORY(lcPipewireRegistry, "qt.multimedia.pipewire.registry")
Q_GLOBAL_STATIC(QAudioContextManager, s_audioContextInstance)
std::unique_ptr< pw_core, PwCoreConnectionDeleter > PwCoreConnectionHandle
StrongIdType< uint32_t, ObjectIdTag > ObjectId
std::error_code make_error_code(int errnoValue=errno)
bool pw_check_library_version(int major, int minor, int micro)