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_audiostream.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_audiostream_p.h"
5
6
#
include
<
QtMultimedia
/
private
/
qaudiosystem_platform_stream_support_p
.
h
>
7
#
include
<
QtMultimedia
/
private
/
qpipewire_audiocontextmanager_p
.
h
>
8
#
include
<
QtMultimedia
/
private
/
qpipewire_spa_pod_support_p
.
h
>
9
10
#
include
<
spa
/
pod
/
builder
.
h
>
11
12
#
if
__has_include
(
<
spa
/
param
/
audio
/
raw
-
utils
.
h
>
)
13
#
include
<
spa
/
param
/
audio
/
raw
-
utils
.
h
>
14
#
else
15
#
include
<
QtMultimedia
/
private
/
qpipewire_spa_compat_p
.
h
>
16
#
endif
17
18
19
#
ifndef
PW_KEY_NODE_FORCE_QUANTUM
20
#
define
PW_KEY_NODE_FORCE_QUANTUM
"node.force-quantum"
21
#
endif
22
23
#
if
!
PW_CHECK_VERSION
(
0
,
3
,
50
)
24
extern
"C"
{
25
int
pw_stream_get_time_n(
struct
pw_stream *stream,
struct
pw_time *time, size_t size);
26
}
27
#
endif
28
29
#
include
<
array
>
30
31
QT_BEGIN_NAMESPACE
32
33
namespace
QtPipeWire
{
34
35
QPipewireAudioStream
::
QPipewireAudioStream
(
const
QAudioFormat &format) :
m_format
{
format
}
36
{
37
}
38
39
QPipewireAudioStream
::~
QPipewireAudioStream
() =
default
;
40
41
void
QPipewireAudioStream
::
createStream
(QSpan<spa_dict_item> extraProperties,
42
std::optional<NativePeriodFrames> nativePeriodFrames,
43
const
char
*streamName,
StreamType
type)
44
{
45
stream_events
.version = PW_VERSION_STREAM_EVENTS;
46
47
switch
(type) {
48
case
StreamType
::
Ringbuffer
:
49
stream_events
.process = [](
void
*userData) {
50
reinterpret_cast
<
QPipewireAudioStream
*>(userData)
->
processRingbuffer
(
)
;
51
};
52
break
;
53
case
StreamType
::
Callback
:
54
stream_events
.process = [](
void
*userData) {
55
reinterpret_cast
<
QPipewireAudioStream
*>(userData)
->
processCallback
(
)
;
56
};
57
break
;
58
default
:
59
Q_UNREACHABLE_RETURN();
60
};
61
62
stream_events
.state_changed = [](
void
*userData, pw_stream_state old, pw_stream_state state,
63
const
char
*error) {
64
reinterpret_cast
<
QPipewireAudioStream
*>(userData)
->
stateChanged
(
old
,
state
,
error
)
;
65
};
66
67
std::vector<spa_dict_item> properties{
68
{ PW_KEY_MEDIA_TYPE,
"Audio"
},
69
};
70
properties.insert(properties.end(), extraProperties.begin(), extraProperties.end());
71
72
std::string quantumStr;
73
if
(nativePeriodFrames) {
74
quantumStr = std::to_string(qToUnderlying(*nativePeriodFrames));
75
properties.push_back({
76
PW_KEY_NODE_FORCE_QUANTUM
,
77
quantumStr.data(),
78
});
79
}
80
81
QAudioContextManager::withEventLoopLock([&] {
82
m_stream = PwStreamHandle{
83
pw_stream_new_simple(QAudioContextManager::getEventLoop(), streamName,
84
makeProperties(properties).release(), &stream_events,
this
),
85
};
86
});
87
if
(!m_stream)
88
qWarning() <<
"pw_stream_new_simple failed"
<< make_error_code().message();
89
}
90
91
bool
QPipewireAudioStream
::
connectStream
(ObjectSerial target, spa_direction direction)
92
{
93
int
status = QAudioContextManager::withEventLoopLock([&] {
94
std::optional<ObjectId> targetNodeId =
95
QAudioContextManager::deviceMonitor().findObjectId(target);
96
if
(!targetNodeId)
97
return
-ENODEV;
98
99
bool
deviceAlreadyRemoved = registerDeviceObserver(target);
100
if
(!deviceAlreadyRemoved)
101
return
-ENODEV;
102
103
std::array<uint8_t, 1024> buffer;
104
105
QT_WARNING_PUSH
106
QT_WARNING_DISABLE_CLANG(
"-Wmissing-field-initializers"
)
107
struct
spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer.data(), uint32_t(buffer.size()));
108
QT_WARNING_POP
109
spa_audio_info_raw audioInfo = asSpaAudioInfoRaw(m_format);
110
111
std::array<
const
struct
spa_pod *, 1> params{
112
spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &audioInfo),
113
};
114
115
return
pw_stream_connect(
116
m_stream.get(), direction, targetNodeId->value,
117
pw_stream_flags(PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS
118
| PW_STREAM_FLAG_RT_PROCESS | PW_STREAM_FLAG_DONT_RECONNECT),
119
params.data(), params.size());
120
});
121
122
if
(status < 0) {
123
qWarning() <<
"pw_stream_connect failed"
<< make_error_code(-status).message();
124
return
false
;
125
}
126
127
return
true
;
128
}
129
130
void
QPipewireAudioStream
::
suspend
()
131
{
132
int
status = QAudioContextManager::withEventLoopLock([&] {
133
return
pw_stream_set_active(m_stream.get(),
false
);
134
});
135
if
(status < 0)
136
qWarning() <<
"pw_stream_set_active failed"
<< make_error_code(-status).message();
137
}
138
139
void
QPipewireAudioStream
::
resume
()
140
{
141
int
status = QAudioContextManager::withEventLoopLock([&] {
142
m_skipNextTickDiscontinuity =
true
;
143
return
pw_stream_set_active(m_stream.get(),
true
);
144
});
145
if
(status < 0)
146
qWarning() <<
"pw_stream_set_active failed"
<< make_error_code(-status).message();
147
}
148
149
void
QPipewireAudioStream
::
disconnectStream
()
150
{
151
if
(!m_stream)
152
return
;
153
154
int
status = QAudioContextManager::withEventLoopLock([&] {
155
int
status = pw_stream_disconnect(m_stream.get());
156
m_stream = {};
157
return
status;
158
});
159
if
(status < 0)
160
qWarning() <<
"pw_stream_disconnect failed"
<< make_error_code(-status).message();
161
}
162
163
void
QPipewireAudioStream
::
resetStream
()
164
{
165
finalizeStream
(
)
;
// mark the stream as stopped
166
disconnectStream
(
)
;
167
168
if
(!m_deviceRemovalObserver) {
169
m_self = {};
170
return
;
171
}
172
173
QAudioContextManager::withEventLoopLock([&] {
174
if
(m_deviceRemovalObserver)
175
unregisterDeviceObserver();
176
m_self = {};
177
});
178
}
179
180
bool
QPipewireAudioStream
::
hasStream
()
const
181
{
182
return
bool
(m_stream);
183
}
184
185
bool
QPipewireAudioStream
::
registerDeviceObserver
(ObjectSerial nodeSerial)
186
{
187
m_deviceRemovalObserver = std::make_shared<ObjectRemoveObserver>(nodeSerial);
188
QObject::connect(m_deviceRemovalObserver.get(), &ObjectRemoveObserver::objectRemoved,
189
m_deviceRemovalObserver.get(), [
this
] {
190
handleDeviceRemoved();
191
});
192
193
return
QAudioContextManager::deviceMonitor().registerObserver(m_deviceRemovalObserver);
194
}
195
196
void
QPipewireAudioStream
::
unregisterDeviceObserver
()
197
{
198
Q_ASSERT(m_deviceRemovalObserver);
199
QAudioContextManager::deviceMonitor().unregisterObserver(m_deviceRemovalObserver);
200
m_deviceRemovalObserver = {};
201
}
202
203
void
QPipewireAudioStream
::
performXRunDetection
(
uint64_t
framesPerBuffer
)
noexcept
Q_DECL_NONBLOCKING_FUNCTION
204
{
205
// XRun detection does not work well with pause/resume.
206
// disabling for now, since we don't have any public API for notifying the application about
207
// xruns
208
constexpr
bool
runXRunDetection
=
false
;
209
if
(!
runXRunDetection
)
210
return
;
211
212
struct
pw_time
time_info
= {};
213
int
status
=
pw_stream_get_time_n
(
m_stream
.
get
(), &
time_info
,
sizeof
(
pw_time
));
214
if
(
status
< 0) {
215
if
(
pw_check_library_version
(0, 3, 50))
216
return
;
// no xrun detection on ancient pipewire
217
218
qFatal
() <<
"pw_stream_get_time_n failed. This should not happen"
;
219
return
;
220
}
221
222
if
(
m_skipNextTickDiscontinuity
.
load
(
std
::
memory_order_relaxed
)) {
223
// prevent xrun detection to fire after resume, as ticks will continue incrementing
224
m_skipNextTickDiscontinuity
=
false
;
225
}
else
if
(
std
::
abs
(
int64_t
(
m_expectedNextTick
) -
int64_t
(
time_info
.
ticks
)) > 1024) {
226
m_totalNumberOfFrames
=
time_info
.
ticks
;
227
m_xrunCount
+= 1;
228
xrunOccurred
(
m_xrunCount
);
229
}
230
231
// CAVEAT:
232
// counts `ticks` in the device rates, which may be different to the rate the stream is
233
// running in. We therefore cannot do any precise xrun detection with this technique, but
234
// only to a best effort.
235
// TODO: can we use profiler events?
236
double
rateFactor
=
double
(
time_info
.
rate
.
num
) /
time_info
.
rate
.
denom
*
m_format
.
sampleRate
();
237
238
#
if
PW_CHECK_VERSION
(
1
,
1
,
0
)
239
if
(
pw_check_library_version
(1, 1, 0)) {
240
// LATER: rely on time_info.size, once 1.1 is the minimum required version
241
m_expectedNextTick
=
time_info
.
ticks
+ (
time_info
.
size
/
rateFactor
);
242
return
;
243
}
244
#
endif
245
m_expectedNextTick
=
time_info
.
ticks
+ (
framesPerBuffer
/
rateFactor
);
246
}
247
248
void
QPipewireAudioStream
::
addFramesHandled
(
uint64_t
arg
)
249
{
250
m_totalNumberOfFrames
+=
arg
;
251
}
252
253
}
// namespace QtPipeWire
254
255
QT_END_NAMESPACE
QtPipeWire
Definition
qpipewire_async_support.cpp:10
__has_include
#define __has_include(x)
Definition
qcompilerdetection.h:459
PW_KEY_NODE_FORCE_QUANTUM
#define PW_KEY_NODE_FORCE_QUANTUM
Definition
qpipewire_audiostream.cpp:20
QtPipeWire::QPipewireAudioStream
Definition
qpipewire_audiostream_p.h:33
QtPipeWire::QPipewireAudioStream::hasStream
bool hasStream() const
Definition
qpipewire_audiostream.cpp:180
QtPipeWire::QPipewireAudioStream::createStream
void createStream(QSpan< spa_dict_item > extraProperties, std::optional< NativePeriodFrames > nativePeriodFrames, const char *streamName, StreamType=StreamType::Ringbuffer)
Definition
qpipewire_audiostream.cpp:41
QtPipeWire::QPipewireAudioStream::stream_events
pw_stream_events stream_events
Definition
qpipewire_audiostream_p.h:70
QtPipeWire::QPipewireAudioStream::suspend
void suspend()
Definition
qpipewire_audiostream.cpp:130
QtPipeWire::QPipewireAudioStream::resetStream
void resetStream()
Definition
qpipewire_audiostream.cpp:163
QtPipeWire::QPipewireAudioStream::unregisterDeviceObserver
void unregisterDeviceObserver()
Definition
qpipewire_audiostream.cpp:196
QtPipeWire::QPipewireAudioStream::finalizeStream
virtual void finalizeStream()=0
QtPipeWire::QPipewireAudioStream::StreamType
StreamType
Definition
qpipewire_audiostream_p.h:43
QtPipeWire::QPipewireAudioStream::StreamType::Callback
@ Callback
Definition
qpipewire_audiostream_p.h:45
QtPipeWire::QPipewireAudioStream::StreamType::Ringbuffer
@ Ringbuffer
Definition
qpipewire_audiostream_p.h:44
QtPipeWire::QPipewireAudioStream::registerDeviceObserver
bool registerDeviceObserver(ObjectSerial)
Definition
qpipewire_audiostream.cpp:185
QtPipeWire::QPipewireAudioStream::processCallback
virtual void processCallback()=0
QtPipeWire::QPipewireAudioStream::disconnectStream
void disconnectStream()
Definition
qpipewire_audiostream.cpp:149
QtPipeWire::QPipewireAudioStream::QPipewireAudioStream
QPipewireAudioStream(const QAudioFormat &)
Definition
qpipewire_audiostream.cpp:35
QtPipeWire::QPipewireAudioStream::stateChanged
virtual void stateChanged(pw_stream_state oldState, pw_stream_state state, const char *error)=0
QtPipeWire::QPipewireAudioStream::processRingbuffer
virtual void processRingbuffer()=0
QtPipeWire::QPipewireAudioStream::connectStream
bool connectStream(ObjectSerial target, spa_direction)
Definition
qpipewire_audiostream.cpp:91
QtPipeWire::QPipewireAudioStream::resume
void resume()
Definition
qpipewire_audiostream.cpp:139
QtPipeWire::QPipewireAudioStream::~QPipewireAudioStream
~QPipewireAudioStream()
qtmultimedia
src
multimedia
pipewire
qpipewire_audiostream.cpp
Generated on
for Qt by
1.18.0