KPipewire

pipewirebaseencodedstream.cpp
1/*
2 SPDX-FileCopyrightText: 2022-2023 Aleix Pol Gonzalez <aleixpol@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
5*/
6
7#include "pipewirebaseencodedstream.h"
8
9#include <logging_libav.h>
10#include <logging_record.h>
11#include <memory>
12#include <va/va.h>
13
14extern "C" {
15#include <libavcodec/codec.h>
16#include <libavutil/log.h>
17}
18#include <unistd.h>
19
20#include <QThread>
21
22#include "pipewireproduce_p.h"
23#include "vaapiutils_p.h"
24
25struct PipeWireEncodedStreamPrivate {
26 uint m_nodeId = 0;
27 std::optional<uint> m_fd;
28 Fraction m_maxFramerate;
29 int m_maxPendingFrames = 50;
30 bool m_active = false;
31 PipeWireBaseEncodedStream::Encoder m_encoder = PipeWireBaseEncodedStream::NoEncoder;
32 std::optional<quint8> m_quality;
33 PipeWireBaseEncodedStream::EncodingPreference m_encodingPreference;
34 PipeWireBaseEncodedStream::State m_state = PipeWireBaseEncodedStream::Idle;
35
36 std::unique_ptr<QThread> m_produceThread;
37 std::unique_ptr<PipeWireProduce> m_produce;
38};
39
40PipeWireBaseEncodedStream::State PipeWireBaseEncodedStream::state() const
41{
42 return d->m_state;
43}
44
45PipeWireBaseEncodedStream::PipeWireBaseEncodedStream(QObject *parent)
46 : QObject(parent)
47 , d(new PipeWireEncodedStreamPrivate)
48{
49 const auto &category = PIPEWIRELIBAV_LOGGING();
50 if (category.isDebugEnabled()) {
51 av_log_set_level(AV_LOG_DEBUG);
52 } else if (category.isInfoEnabled()) {
53 av_log_set_level(AV_LOG_INFO);
54 } else if (category.isWarningEnabled()) {
55 av_log_set_level(AV_LOG_WARNING);
56 } else {
57 av_log_set_level(AV_LOG_ERROR);
58 }
59}
60
61PipeWireBaseEncodedStream::~PipeWireBaseEncodedStream()
62{
63 stop();
64
65 if (d->m_produceThread) {
66 d->m_produceThread->wait();
67 }
68}
69
70void PipeWireBaseEncodedStream::setNodeId(uint nodeId)
71{
72 if (nodeId == d->m_nodeId)
73 return;
74
75 d->m_nodeId = nodeId;
76 Q_EMIT nodeIdChanged(nodeId);
77}
78
79void PipeWireBaseEncodedStream::setFd(uint fd)
80{
81 if (fd == d->m_fd)
82 return;
83
84 if (d->m_fd) {
85 close(*d->m_fd);
86 }
87 d->m_fd = fd;
88 Q_EMIT fdChanged(fd);
89}
90
91Fraction PipeWireBaseEncodedStream::maxFramerate() const
92{
93 if (d->m_maxFramerate) {
94 return d->m_maxFramerate;
95 }
96 return Fraction{60, 1};
97}
98
99void PipeWireBaseEncodedStream::setMaxFramerate(const Fraction &framerate)
100{
101 if (d->m_maxFramerate == framerate) {
102 return;
103 }
104 d->m_maxFramerate = framerate;
105
106 if (d->m_produce) {
107 d->m_produce->setMaxFramerate(d->m_maxFramerate);
108 }
109
110 Q_EMIT maxFramerateChanged();
111}
112
113void PipeWireBaseEncodedStream::setMaxFramerate(quint32 numerator, quint32 denominator)
114{
115 setMaxFramerate({numerator, denominator});
116}
117
118void PipeWireBaseEncodedStream::setMaxPendingFrames(int maxPendingFrames)
119{
120 if (d->m_maxPendingFrames == maxPendingFrames) {
121 return;
122 }
123 if (d->m_produce) {
124 d->m_produce->setMaxPendingFrames(maxPendingFrames);
125 }
126 d->m_maxPendingFrames = maxPendingFrames;
127 Q_EMIT maxPendingFramesChanged();
128}
129
130int PipeWireBaseEncodedStream::maxBufferSize() const
131{
132 return d->m_maxPendingFrames;
133}
134
135void PipeWireBaseEncodedStream::setActive(bool active)
136{
137 if (active) {
138 start();
139 } else {
140 stop();
141
142 if (d->m_produceThread) {
143 d->m_produceThread->wait();
144 }
145 }
146}
147
148void PipeWireBaseEncodedStream::start()
149{
150 if (d->m_nodeId == 0) {
151 qCWarning(PIPEWIRERECORD_LOGGING) << "Cannot start recording on a stream without a node ID";
152 return;
153 }
154
155 if (d->m_produceThread || d->m_state != Idle) {
156 return;
157 }
158
159 if (d->m_encoder == PipeWireBaseEncodedStream::NoEncoder) {
160 d->m_encoder = suggestedEncoders().value(0, NoEncoder);
161 }
162
163 d->m_produceThread = std::make_unique<QThread>();
164 d->m_produceThread->setObjectName("PipeWireProduce::input");
165 d->m_produce = makeProduce();
166 d->m_produce->setQuality(d->m_quality);
167 d->m_produce->setMaxPendingFrames(d->m_maxPendingFrames);
168 d->m_produce->setEncodingPreference(d->m_encodingPreference);
169 d->m_produce->moveToThread(d->m_produceThread.get());
170 d->m_produceThread->start();
171 QMetaObject::invokeMethod(d->m_produce.get(), &PipeWireProduce::initialize, Qt::QueuedConnection);
172
173 connect(d->m_produce.get(), &PipeWireProduce::started, this, [this]() {
174 d->m_active = true;
175 Q_EMIT activeChanged(true);
176 d->m_state = Recording;
177 Q_EMIT stateChanged();
178 });
179
180 connect(d->m_produce.get(), &PipeWireProduce::finished, this, [this]() {
181 d->m_active = false;
182 Q_EMIT activeChanged(false);
183 d->m_state = Idle;
184 Q_EMIT stateChanged();
185 });
186
187 connect(d->m_produceThread.get(), &QThread::finished, this, [this]() {
188 d->m_produce.reset();
189 d->m_produceThread.reset();
190 d->m_nodeId = 0;
191
192 if (d->m_fd) {
193 close(d->m_fd.value());
194 }
195 });
196}
197
198void PipeWireBaseEncodedStream::stop()
199{
200 if (d->m_produceThread) {
201 QMetaObject::invokeMethod(d->m_produce.get(), &PipeWireProduce::deactivate, Qt::QueuedConnection);
202 }
203
204 d->m_state = PipeWireBaseEncodedStream::Rendering;
205 Q_EMIT stateChanged();
206}
207
208std::optional<quint8> PipeWireBaseEncodedStream::quality() const
209{
210 return d->m_quality;
211}
212
213void PipeWireBaseEncodedStream::setQuality(quint8 quality)
214{
215 d->m_quality = quality;
216 if (d->m_produce) {
217 d->m_produce->setQuality(d->m_quality);
218 }
219}
220
221void PipeWireBaseEncodedStream::setEncoder(Encoder encoder)
222{
223 if (d->m_encoder == encoder || !suggestedEncoders().contains(encoder)) {
224 return;
225 }
226 d->m_encoder = encoder;
227 Q_EMIT encoderChanged();
228}
229
230PipeWireBaseEncodedStream::Encoder PipeWireBaseEncodedStream::encoder() const
231{
232 return d->m_encoder;
233}
234
235QList<PipeWireBaseEncodedStream::Encoder> PipeWireBaseEncodedStream::suggestedEncoders() const
236{
237 auto vaapi = VaapiUtils::instance();
238
239 QList<PipeWireBaseEncodedStream::Encoder> ret = {PipeWireBaseEncodedStream::VP8,
240 PipeWireBaseEncodedStream::VP9,
241 PipeWireBaseEncodedStream::H264Main,
242 PipeWireBaseEncodedStream::H264Baseline,
243 PipeWireBaseEncodedStream::WebP,
244 PipeWireBaseEncodedStream::Gif,
245 };
246 auto removeUnavailableEncoders = [&vaapi](const PipeWireBaseEncodedStream::Encoder &encoder) {
247 switch (encoder) {
248 case PipeWireBaseEncodedStream::VP8:
249 if (vaapi->supportsProfile(VAProfileVP8Version0_3) && avcodec_find_encoder_by_name("vp8_vaapi")) {
250 return false;
251 } else {
252 return !avcodec_find_encoder_by_name("libvpx");
253 }
254 case PipeWireBaseEncodedStream::VP9:
255 return !avcodec_find_encoder_by_name("libvpx-vp9");
256 case PipeWireBaseEncodedStream::H264Main:
257 case PipeWireBaseEncodedStream::H264Baseline:
258 if (vaapi->supportsProfile(encoder == PipeWireBaseEncodedStream::H264Main ? VAProfileH264Main : VAProfileH264ConstrainedBaseline)
259 && avcodec_find_encoder_by_name("h264_vaapi")) {
260 return false;
261 } else {
262 return !(avcodec_find_encoder_by_name("libx264") || avcodec_find_encoder_by_name("libopenh264"));
263 }
264 case PipeWireBaseEncodedStream::WebP:
265 return !avcodec_find_encoder_by_name("libwebp");
266 case PipeWireBaseEncodedStream::Gif:
267 return !avcodec_find_encoder_by_name("gif");
268 default:
269 return true;
270 }
271 };
272 ret.removeIf(removeUnavailableEncoders);
273 return ret;
274}
275
276void PipeWireBaseEncodedStream::setEncodingPreference(PipeWireBaseEncodedStream::EncodingPreference preference)
277{
278 d->m_encodingPreference = preference;
279 if (d->m_produce) {
280 d->m_produce->setEncodingPreference(d->m_encodingPreference);
281 }
282}
283
284PipeWireBaseEncodedStream::EncodingPreference PipeWireBaseEncodedStream::encodingPreference()
285{
286 return d->m_encodingPreference;
287}
288
289bool PipeWireBaseEncodedStream::isActive() const
290{
291 return d->m_active;
292}
293
294uint PipeWireBaseEncodedStream::nodeId() const
295{
296 return d->m_nodeId;
297}
298
299uint PipeWireBaseEncodedStream::fd() const
300{
301 return d->m_fd.value_or(0);
302}
303
304#include "moc_pipewirebaseencodedstream.cpp"
const QList< QKeySequence > & close()
Category category(StandardShortcut id)
qsizetype removeIf(Predicate pred)
bool invokeMethod(QObject *context, Functor &&function, FunctorReturnType *ret)
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QueuedConnection
void finished()
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri May 2 2025 12:00:27 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.