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