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() = default;
50
51bool QPipewireAudioSourceStream::start(QIODevice *device)
52{
53 createStream(StreamType::Ringbuffer);
54
55 Q_ASSERT(hasStream());
56 auto sourceNodeSerial = findSourceNodeSerial();
57 if (!sourceNodeSerial) {
58 requestStop();
59 return false;
60 }
61
62 setQIODevice(device);
63
64 bool connected = connectStream(*sourceNodeSerial, SPA_DIRECTION_INPUT);
65 if (!connected) {
66 requestStop();
67 return false;
68 }
69
70 createQIODeviceConnections(device);
71
72 // keep instance alive until PW_STREAM_STATE_UNCONNECTED
73 m_self = shared_from_this();
74 QAudioContextManager::instance()->registerStreamReference(m_self);
75
76 return connected;
77}
78
80{
81 QIODevice *device = createRingbufferReaderDevice();
82
83 bool started = start(device);
84 if (!started)
85 return nullptr;
86
87 return device;
88}
89
90bool QPipewireAudioSourceStream::start(AudioCallback &&audioCallback)
91{
92 createStream(StreamType::Callback);
93
94 Q_ASSERT(hasStream());
95 auto sourceNodeSerial = findSourceNodeSerial();
96 if (!sourceNodeSerial) {
97 requestStop();
98 return false;
99 }
100
101 m_audioCallback = std::move(audioCallback);
102
103 bool connected = connectStream(*sourceNodeSerial, SPA_DIRECTION_INPUT);
104 if (!connected) {
105 requestStop();
106 return false;
107 }
108
109 // keep instance alive until PW_STREAM_STATE_UNCONNECTED
110 m_self = shared_from_this();
111 return true;
112}
113
114void QPipewireAudioSourceStream::stop(ShutdownPolicy shutdownPolicy)
115{
116 requestStop();
117
118 // disconnect immediately
119 disconnectStream();
120 unregisterDeviceObserver();
121 disconnectQIODeviceConnections();
122
123 finalizeQIODevice(shutdownPolicy);
124 if (shutdownPolicy == ShutdownPolicy::DiscardRingbuffer) {
125 // Pipewire is asynchronous. So to properly discard the ringbuffer content, we need to wait
126 // for the stream to be stopped before we discard the ringbuffer content
127 bool streamDisconnected = m_streamDisconnected.try_acquire_for(5s);
128 if (!streamDisconnected)
129 qWarning() << "QPipewireAudioSourceStream::stop: m_streamDisconnected semaphore "
130 "timeout. This should not happen";
131 emptyRingbuffer();
132 }
133}
134
136{
137 if (m_parent)
138 m_parent->updateStreamIdle(idle);
139}
140
141void QPipewireAudioSourceStream::createStream(StreamType streamType)
142{
143 auto extraProperties = std::array{
144 spa_dict_item{ PW_KEY_MEDIA_CATEGORY, "Capture" },
145 spa_dict_item{ PW_KEY_MEDIA_ROLE, "Music" },
146 };
147
148 QString applicationName = qApp->applicationName();
149 if (applicationName.isNull())
150 applicationName = u"QPipewireAudioSource"_s;
151
152 QPipewireAudioStream::createStream(extraProperties, m_hardwareBufferFrames,
153 applicationName.toUtf8().constData(), streamType);
154}
155
156std::optional<ObjectSerial> QPipewireAudioSourceStream::findSourceNodeSerial()
157{
158 const QPipewireAudioDevicePrivate *device =
159 QAudioDevicePrivate::handle<QPipewireAudioDevicePrivate>(m_audioDevice);
160
161 QByteArray nodeName = device->nodeName();
162 auto ret = QAudioContextManager::deviceMonitor().findSourceNodeSerial(std::string_view{
163 nodeName.data(),
164 size_t(nodeName.size()),
165 });
166
167 if (!ret)
168 qWarning() << "Cannot find device: " << nodeName;
169 return ret;
170}
171
172void QPipewireAudioSourceStream::disconnectStream()
173{
174 auto self = shared_from_this(); // extend lifetime until this function returns;
175
176 QPipewireAudioStream::disconnectStream();
177
178 QObject::disconnect(m_xrunNotification);
179}
180
182{
184 if (!b) {
185 qCritical() << "pw_stream_dequeue_buffer failed";
186 return;
187 }
188
189 struct spa_buffer *buf = b->buffer;
190 if (buf->datas[0].data == nullptr) {
191 qWarning() << "pw_stream_dequeue_buffer received null buffer";
192 return;
193 }
194
196 reinterpret_cast<const std::byte *>(buf->datas[0].data),
198 };
199
201
203
207}
208
210{
211 using namespace QtMultimediaPrivate;
213 if (!b) {
214 qCritical() << "pw_stream_dequeue_buffer failed";
215 return;
216 }
217
218 struct spa_buffer *buf = b->buffer;
219 if (buf->datas[0].data == nullptr) {
220 qWarning() << "pw_stream_dequeue_buffer received null buffer";
221 return;
222 }
223
225 reinterpret_cast<const std::byte *>(buf->datas[0].data),
227 };
229
231
234}
235
241
243 const char * /*error*/)
244{
245 switch (state) {
249 m_self.reset();
250 break;
251
252 default:
253 break;
254 }
255}
256
257////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
258// QPipewireAudioSource
259
265
267{
268 qDebug() << "XRuns occurred:" << numberOfXruns;
269}
270
271} // namespace QtPipeWire
272
273QT_END_NAMESPACE
StrongIdType< uint64_t, ObjectSerialTag > ObjectSerial
QPipewireAudioSourceStream(QAudioDevice, const QAudioFormat &, std::optional< qsizetype > ringbufferSize, QPipewireAudioSource *parent, float volume, std::optional< int32_t > hardwareBufferFrames)