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