Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Toggle main menu visibility
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
4
#
include
"qpipewire_audiosource_p.h"
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
15
QT_BEGIN_NAMESPACE
16
17
namespace
QtPipeWire
{
18
19
using
ShutdownPolicy
=
QPlatformAudioIOStream
::
ShutdownPolicy
;
20
using
namespace
std
::
chrono_literals
;
21
using
namespace
Qt
::
Literals
;
22
23
QPipewireAudioSourceStream
::
QPipewireAudioSourceStream
(QAudioDevice device,
const
QAudioFormat &format,
24
std::optional<qsizetype> ringbufferSize,
25
QPipewireAudioSource *parent,
26
float
volume,
27
std::optional<NativePeriodFrames> nativePeriodFrames
28
):
29
QPipewireAudioStream
{
30
format
,
31
},
32
QPlatformAudioSourceStream
{
33
std
::
move
(
device
),
34
format
,
35
ringbufferSize
,
36
nativePeriodFrames
,
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
50
QPipewireAudioSourceStream
::~QPipewireAudioSourceStream()
51
{
52
Q_ASSERT(!m_xrunOccurred.thread() || m_xrunOccurred.thread()->isCurrentThread());
53
resetStream();
54
}
55
56
bool
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
84
QIODevice
*
QPipewireAudioSourceStream
::
start
()
85
{
86
QIODevice *device = createRingbufferReaderDevice();
87
88
bool
started = start(device);
89
if
(!started)
90
return
nullptr
;
91
92
return
device;
93
}
94
95
bool
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
119
void
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
140
void
QPipewireAudioSourceStream
::
updateStreamIdle
(
bool
idle)
141
{
142
if
(m_parent)
143
m_parent->updateStreamIdle(idle);
144
}
145
146
void
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
161
std::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
177
void
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
186
void
QPipewireAudioSourceStream
::
finalizeStream
()
187
{
188
requestStop();
189
}
190
191
void
QPipewireAudioSourceStream
::
processRingbuffer
()
noexcept
Q_DECL_NONBLOCKING_FUNCTION
192
{
193
struct
pw_buffer
*
b
=
pw_stream_dequeue_buffer
(
m_stream
.
get
());
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
205
QSpan
buffer
{
206
reinterpret_cast
<
const
std
::
byte
*>(
buf
->
datas
[0].
data
),
207
qsizetype
(
buf
->
datas
[0].
chunk
->
size
),
208
};
209
210
int
numberOfFrames
=
m_format
.
framesForBytes
(
buffer
.
size
());
211
212
performXRunDetection
(
numberOfFrames
);
213
214
uint64_t
framesWritten
=
QPlatformAudioSourceStream
::
process
(
buffer
,
numberOfFrames
);
215
addFramesHandled
(
framesWritten
);
216
pw_stream_queue_buffer
(
m_stream
.
get
(),
b
);
217
}
218
219
void
QPipewireAudioSourceStream
::
processCallback
()
noexcept
Q_DECL_NONBLOCKING_FUNCTION
220
{
221
using
namespace
QtMultimediaPrivate
;
222
struct
pw_buffer
*
b
=
pw_stream_dequeue_buffer
(
m_stream
.
get
());
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
234
QSpan
buffer
{
235
reinterpret_cast
<
const
std
::
byte
*>(
buf
->
datas
[0].
data
),
236
qsizetype
(
buf
->
datas
[0].
chunk
->
size
),
237
};
238
int
numberOfFrames
=
m_format
.
framesForBytes
(
buffer
.
size
());
239
240
runAudioCallback
(*
m_audioCallback
,
buffer
,
m_format
,
volume
());
241
242
addFramesHandled
(
numberOfFrames
);
243
pw_stream_queue_buffer
(
m_stream
.
get
(),
b
);
244
}
245
246
void
QPipewireAudioSourceStream
::
handleDeviceRemoved
()
247
{
248
if
(
isStopRequested
())
249
return
;
250
251
requestStop
();
252
disconnectQIODeviceConnections
();
253
QPlatformAudioSourceStream
::
handleIOError
(
std
::
exchange
(
m_parent
,
nullptr
));
254
}
255
256
void
QPipewireAudioSourceStream
::
stateChanged
(
pw_stream_state
/*oldState*/
,
pw_stream_state
state
,
257
const
char
*
/*error*/
)
258
{
259
switch
(
state
) {
260
case
pw_stream_state
::
PW_STREAM_STATE_UNCONNECTED
:
261
m_streamDisconnected
.
release
();
262
QAudioContextManager
::
instance
()->
unregisterStreamReference
(
m_self
);
263
invokeOnAppThread
([
self
=
std
::
move
(
m_self
)] {
264
// drop refcount on app thread
265
});
266
break
;
267
268
default
:
269
break
;
270
}
271
}
272
273
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
274
// QPipewireAudioSource
275
276
QPipewireAudioSource
::
QPipewireAudioSource
(
QAudioDevice
device
,
const
QAudioFormat
&
format
,
277
QObject
*
parent
)
278
:
BaseClass
(
std
::
move
(
device
),
format
,
parent
)
279
{
280
}
281
282
QPipewireAudioSource
::~
QPipewireAudioSource
()
283
=
default
;
284
285
void
QPipewireAudioSource::
reportXRuns
(
int
numberOfXruns)
286
{
287
qDebug() <<
"XRuns occurred:"
<< numberOfXruns;
288
}
289
290
}
// namespace QtPipeWire
291
292
QT_END_NAMESPACE
QtPipeWire::QPipewireAudioSource::reportXRuns
void reportXRuns(int)
Definition
qpipewire_audiosource.cpp:285
QtPipeWire
Definition
qpipewire_async_support.cpp:10
QtPipeWire::QPipewireAudioSourceStream::start
bool start(QIODevice *device)
Definition
qpipewire_audiosource.cpp:56
QtPipeWire::QPipewireAudioSourceStream::finalizeStream
void finalizeStream() override
Definition
qpipewire_audiosource.cpp:186
QtPipeWire::QPipewireAudioSourceStream::stop
void stop(ShutdownPolicy)
Definition
qpipewire_audiosource.cpp:119
QtPipeWire::QPipewireAudioSourceStream::start
bool start(AudioCallback &&)
Definition
qpipewire_audiosource.cpp:95
QtPipeWire::QPipewireAudioSourceStream::updateStreamIdle
void updateStreamIdle(bool idle) override
Definition
qpipewire_audiosource.cpp:140
QtPipeWire::QPipewireAudioSourceStream::SampleFormat
QAudioFormat::SampleFormat SampleFormat
Definition
qpipewire_audiosource_p.h:41
QtPipeWire::QPipewireAudioSourceStream::start
QIODevice * start()
Definition
qpipewire_audiosource.cpp:84
QtPipeWire::QPipewireAudioSourceStream::QPipewireAudioSourceStream
QPipewireAudioSourceStream(QAudioDevice, const QAudioFormat &, std::optional< qsizetype > ringbufferSize, QPipewireAudioSource *parent, float volume, std::optional< NativePeriodFrames > nativePeriodFrames)
Definition
qpipewire_audiosource.cpp:23
qtmultimedia
src
multimedia
pipewire
qpipewire_audiosource.cpp
Generated on
for Qt by
1.18.0