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