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_audiosource.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 <QtCore/qcoreapplication.h>
11#include <QtCore/qdebug.h>
12#include <QtCore/qloggingcategory.h>
13
15
16namespace QtPipeWire {
17
19using namespace std::chrono_literals;
20using namespace Qt::Literals;
21
22QPipewireAudioSourceStream::QPipewireAudioSourceStream(QAudioDevice device, const QAudioFormat &format,
23 std::optional<qsizetype> ringbufferSize,
24 QPipewireAudioSource *parent,
25 float volume,
26 std::optional<int32_t> hardwareBufferFrames
27 ):
29 format,
30 },
32 std::move(device),
33 format,
36 volume,
37 },
38 m_parent {
39 parent,
40 }
41{
42 m_xrunNotification = m_xrunOccurred.callOnActivated([this] {
43 if (isStopRequested())
44 return;
45 m_parent->reportXRuns(m_xrunCount.exchange(0));
46 });
47}
48
49QPipewireAudioSourceStream::~QPipewireAudioSourceStream()
50{
51 resetStream();
52}
53
54bool QPipewireAudioSourceStream::start(QIODevice *device)
55{
56 createStream(StreamType::Ringbuffer);
57
58 Q_ASSERT(hasStream());
59 auto sourceNodeSerial = findSourceNodeSerial();
60 if (!sourceNodeSerial) {
61 requestStop();
62 return false;
63 }
64
65 setQIODevice(device);
66
67 bool connected = connectStream(*sourceNodeSerial, SPA_DIRECTION_INPUT);
68 if (!connected) {
69 requestStop();
70 return false;
71 }
72
73 createQIODeviceConnections(device);
74
75 // keep instance alive until PW_STREAM_STATE_UNCONNECTED
76 m_self = shared_from_this();
78
79 return connected;
80}
81
83{
84 QIODevice *device = createRingbufferReaderDevice();
85
86 bool started = start(device);
87 if (!started)
88 return nullptr;
89
90 return device;
91}
92
93bool QPipewireAudioSourceStream::start(AudioCallback &&audioCallback)
94{
95 createStream(StreamType::Callback);
96
97 Q_ASSERT(hasStream());
98 auto sourceNodeSerial = findSourceNodeSerial();
99 if (!sourceNodeSerial) {
100 requestStop();
101 return false;
102 }
103
104 m_audioCallback = std::move(audioCallback);
105
106 bool connected = connectStream(*sourceNodeSerial, SPA_DIRECTION_INPUT);
107 if (!connected) {
108 requestStop();
109 return false;
110 }
111
112 // keep instance alive until PW_STREAM_STATE_UNCONNECTED
113 m_self = shared_from_this();
114 return true;
115}
116
117void QPipewireAudioSourceStream::stop(ShutdownPolicy shutdownPolicy)
118{
119 requestStop();
120
121 // disconnect immediately
122 disconnectStream();
123 unregisterDeviceObserver();
124 disconnectQIODeviceConnections();
125
126 finalizeQIODevice(shutdownPolicy);
127 if (shutdownPolicy == ShutdownPolicy::DiscardRingbuffer) {
128 // Pipewire is asynchronous. So to properly discard the ringbuffer content, we need to wait
129 // for the stream to be stopped before we discard the ringbuffer content
130 bool streamDisconnected = m_streamDisconnected.try_acquire_for(5s);
131 if (!streamDisconnected)
132 qWarning() << "QPipewireAudioSourceStream::stop: m_streamDisconnected semaphore "
133 "timeout. This should not happen";
134 emptyRingbuffer();
135 }
136}
137
139{
140 if (m_parent)
141 m_parent->updateStreamIdle(idle);
142}
143
144void QPipewireAudioSourceStream::createStream(StreamType streamType)
145{
146 auto extraProperties = std::array{
147 spa_dict_item{ PW_KEY_MEDIA_CATEGORY, "Capture" },
148 spa_dict_item{ PW_KEY_MEDIA_ROLE, "Music" },
149 };
150
151 QString applicationName = qApp->applicationName();
152 if (applicationName.isNull())
153 applicationName = u"QPipewireAudioSource"_s;
154
155 QPipewireAudioStream::createStream(extraProperties, m_hardwareBufferFrames,
156 applicationName.toUtf8().constData(), streamType);
157}
158
159std::optional<ObjectSerial> QPipewireAudioSourceStream::findSourceNodeSerial()
160{
161 const QPipewireAudioDevicePrivate *device =
162 QAudioDevicePrivate::handle<QPipewireAudioDevicePrivate>(m_audioDevice);
163
164 QByteArray nodeName = device->nodeName();
165 auto ret = QAudioContextManager::deviceMonitor().findSourceNodeSerial(std::string_view{
166 nodeName.data(),
167 size_t(nodeName.size()),
168 });
169
170 if (!ret)
171 qWarning() << "Cannot find device: " << nodeName;
172 return ret;
173}
174
175void QPipewireAudioSourceStream::disconnectStream()
176{
177 auto self = shared_from_this(); // extend lifetime until this function returns;
178
179 QPipewireAudioStream::disconnectStream();
180
181 QObject::disconnect(m_xrunNotification);
182}
183
185{
186 requestStop();
187}
188
190{
192 if (!b) {
193 qCritical() << "pw_stream_dequeue_buffer failed";
194 return;
195 }
196
197 struct spa_buffer *buf = b->buffer;
198 if (buf->datas[0].data == nullptr) {
199 qWarning() << "pw_stream_dequeue_buffer received null buffer";
200 return;
201 }
202
204 reinterpret_cast<const std::byte *>(buf->datas[0].data),
206 };
207
209
211
215}
216
218{
219 using namespace QtMultimediaPrivate;
221 if (!b) {
222 qCritical() << "pw_stream_dequeue_buffer failed";
223 return;
224 }
225
226 struct spa_buffer *buf = b->buffer;
227 if (buf->datas[0].data == nullptr) {
228 qWarning() << "pw_stream_dequeue_buffer received null buffer";
229 return;
230 }
231
233 reinterpret_cast<const std::byte *>(buf->datas[0].data),
235 };
237
239
242}
243
249
251 const char * /*error*/)
252{
253 switch (state) {
257 m_self.reset();
258 break;
259
260 default:
261 break;
262 }
263}
264
265////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
266// QPipewireAudioSource
267
273
275 = default;
276
277void QPipewireAudioSource::reportXRuns(int numberOfXruns)
278{
279 qDebug() << "XRuns occurred:" << numberOfXruns;
280}
281
282} // namespace QtPipeWire
283
284QT_END_NAMESPACE
void registerStreamReference(std::shared_ptr< QPipewireAudioStream >)
Combined button and popup list for selecting options.
StrongIdType< uint64_t, ObjectSerialTag > ObjectSerial
QPipewireAudioSourceStream(QAudioDevice, const QAudioFormat &, std::optional< qsizetype > ringbufferSize, QPipewireAudioSource *parent, float volume, std::optional< int32_t > hardwareBufferFrames)