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_audiostream.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
9
10#include <spa/pod/builder.h>
11
12#if __has_include(<spa/param/audio/raw-utils.h>)
13# include <spa/param/audio/raw-utils.h>
14#else
15# include "qpipewire_spa_compat_p.h"
16#endif
17
18
19#ifndef PW_KEY_NODE_FORCE_QUANTUM
20# define PW_KEY_NODE_FORCE_QUANTUM "node.force-quantum"
21#endif
22
23#if !PW_CHECK_VERSION(0, 3, 50)
24extern "C" {
25int pw_stream_get_time_n(struct pw_stream *stream, struct pw_time *time, size_t size);
26}
27#endif
28
29#include <array>
30
31QT_BEGIN_NAMESPACE
32
33namespace QtPipeWire {
34
36{
37}
38
40
41void QPipewireAudioStream::createStream(QSpan<spa_dict_item> extraProperties,
42 std::optional<int32_t> hardwareBufferFrames,
43 const char *streamName, StreamType type)
44{
45 stream_events.version = PW_VERSION_STREAM_EVENTS;
46
47 switch (type) {
49 stream_events.process = [](void *userData) {
50 reinterpret_cast<QPipewireAudioStream *>(userData)->processRingbuffer();
51 };
52 break;
54 stream_events.process = [](void *userData) {
55 reinterpret_cast<QPipewireAudioStream *>(userData)->processCallback();
56 };
57 break;
58 default:
59 Q_UNREACHABLE_RETURN();
60 };
61
62 stream_events.state_changed = [](void *userData, pw_stream_state old, pw_stream_state state,
63 const char *error) {
64 reinterpret_cast<QPipewireAudioStream *>(userData)->stateChanged(old, state, error);
65 };
66
67 std::vector<spa_dict_item> properties{
68 { PW_KEY_MEDIA_TYPE, "Audio" },
69 };
70 properties.insert(properties.end(), extraProperties.begin(), extraProperties.end());
71
72 if (hardwareBufferFrames)
73 properties.push_back({
75 std::to_string(*hardwareBufferFrames).data(),
76 });
77
78 QAudioContextManager::withEventLoopLock([&] {
79 m_stream = PwStreamHandle{
80 pw_stream_new_simple(QAudioContextManager::getEventLoop(), streamName,
81 makeProperties(properties).release(), &stream_events, this),
82 };
83 });
84 if (!m_stream)
85 qWarning() << "pw_stream_new_simple failed" << make_error_code().message();
86}
87
88bool QPipewireAudioStream::connectStream(ObjectSerial target, spa_direction direction)
89{
90 int status = QAudioContextManager::withEventLoopLock([&] {
91 std::optional<ObjectId> targetNodeId =
92 QAudioContextManager::deviceMonitor().findObjectId(target);
93 if (!targetNodeId)
94 return -ENODEV;
95
96 bool deviceAlreadyRemoved = registerDeviceObserver(target);
97 if (!deviceAlreadyRemoved)
98 return -ENODEV;
99
100 std::array<uint8_t, 1024> buffer;
101
102 QT_WARNING_PUSH
103 QT_WARNING_DISABLE_CLANG("-Wmissing-field-initializers")
104 struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer.data(), uint32_t(buffer.size()));
105 QT_WARNING_POP
106 spa_audio_info_raw audioInfo = asSpaAudioInfoRaw(m_format);
107
108 std::array<const struct spa_pod *, 1> params{
109 spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &audioInfo),
110 };
111
112 return pw_stream_connect(
113 m_stream.get(), direction, targetNodeId->value,
114 pw_stream_flags(PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS
115 | PW_STREAM_FLAG_RT_PROCESS | PW_STREAM_FLAG_DONT_RECONNECT),
116 params.data(), params.size());
117 });
118
119 if (status < 0) {
120 qWarning() << "pw_stream_connect failed" << make_error_code(-status).message();
121 return false;
122 }
123
124 return true;
125}
126
128{
129 int status = QAudioContextManager::withEventLoopLock([&] {
130 return pw_stream_set_active(m_stream.get(), false);
131 });
132 if (status < 0)
133 qWarning() << "pw_stream_set_active failed" << make_error_code(-status).message();
134}
135
137{
138 int status = QAudioContextManager::withEventLoopLock([&] {
139 m_skipNextTickDiscontinuity = true;
140 return pw_stream_set_active(m_stream.get(), true);
141 });
142 if (status < 0)
143 qWarning() << "pw_stream_set_active failed" << make_error_code(-status).message();
144}
145
147{
148 int status = QAudioContextManager::withEventLoopLock([&] {
149 return pw_stream_disconnect(m_stream.get());
150 });
151 if (status < 0)
152 qWarning() << "pw_stream_disconnect failed" << make_error_code(-status).message();
153}
154
156{
157 finalizeStream(); // mark the stream as stopped
158 QAudioContextManager::withEventLoopLock([&] {
159 if (m_deviceRemovalObserver)
161 m_stream = {};
162 m_self = {};
163 });
164}
165
167{
168 return bool(m_stream);
169}
170
172{
173 m_deviceRemovalObserver = std::make_shared<ObjectRemoveObserver>(nodeSerial);
174 QObject::connect(m_deviceRemovalObserver.get(), &ObjectRemoveObserver::objectRemoved,
175 m_deviceRemovalObserver.get(), [this] {
176 handleDeviceRemoved();
177 });
178
179 return QAudioContextManager::deviceMonitor().registerObserver(m_deviceRemovalObserver);
180}
181
183{
184 Q_ASSERT(m_deviceRemovalObserver);
186 m_deviceRemovalObserver = {};
187}
188
190{
191 // XRun detection does not work well with pause/resume.
192 // disabling for now, since we don't have any public API for notifying the application about
193 // xruns
194 constexpr bool runXRunDetection = false;
195 if (!runXRunDetection)
196 return;
197
198 struct pw_time time_info = {};
200 if (status < 0) {
201 if (pw_check_library_version(0, 3, 50))
202 return; // no xrun detection on ancient pipewire
203
204 qFatal() << "pw_stream_get_time_n failed. This should not happen";
205 return;
206 }
207
209 // prevent xrun detection to fire after resume, as ticks will continue incrementing
211 } else if (std::abs(int64_t(m_expectedNextTick) - int64_t(time_info.ticks)) > 1024) {
213 m_xrunCount += 1;
215 }
216
217 // CAVEAT:
218 // counts `ticks` in the device rates, which may be different to the rate the stream is
219 // running in. We therefore cannot do any precise xrun detection with this technique, but
220 // only to a best effort.
221 // TODO: can we use profiler events?
223
224#if PW_CHECK_VERSION(1, 1, 0)
225 if (pw_check_library_version(1, 1, 0)) {
226 // LATER: rely on time_info.size, once 1.1 is the minimum required version
228 return;
229 }
230#endif
232}
233
238
239} // namespace QtPipeWire
240
241QT_END_NAMESPACE
void unregisterObserver(const SharedObjectRemoveObserver &)
bool registerObserver(SharedObjectRemoveObserver)
StrongIdType< uint32_t, ObjectIdTag > ObjectId
StrongIdType< uint64_t, ObjectSerialTag > ObjectSerial
std::error_code make_error_code(int errnoValue=errno)
#define __has_include(x)
#define PW_KEY_NODE_FORCE_QUANTUM
int pw_stream_get_time_n(struct pw_stream *stream, struct pw_time *time, size_t size)
void createStream(QSpan< spa_dict_item > extraProperties, std::optional< int32_t > hardwareBufferFrames, const char *streamName, StreamType=StreamType::Ringbuffer)
virtual void stateChanged(pw_stream_state oldState, pw_stream_state state, const char *error)=0
virtual void processRingbuffer()=0
bool connectStream(ObjectSerial target, spa_direction)