KPipewire

pipewiresourcestream.cpp
1/*
2 SPDX-FileCopyrightText: 2018-2020 Red Hat Inc
3 SPDX-FileCopyrightText: 2020-2021 Aleix Pol Gonzalez <aleixpol@kde.org>
4 SPDX-FileContributor: Jan Grulich <jgrulich@redhat.com>
5
6 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
7*/
8
9#include "pipewiresourcestream.h"
10#include "glhelpers.h"
11#include "logging.h"
12#include "pipewirecore_p.h"
13#include "pwhelpers.h"
14#include "vaapiutils_p.h"
15
16#include <libdrm/drm_fourcc.h>
17#include <spa/utils/result.h>
18#include <sys/ioctl.h>
19#include <sys/mman.h>
20#include <unistd.h>
21
22#include <QGuiApplication>
23#include <QOpenGLTexture>
24#include <QSocketNotifier>
25#include <QThread>
26#include <QVersionNumber>
27#include <qpa/qplatformnativeinterface.h>
28
29#include <KLocalizedString>
30
31#include <EGL/egl.h>
32#include <EGL/eglext.h>
33
34#undef Status
35
36#if !PW_CHECK_VERSION(0, 3, 29)
37#define SPA_POD_PROP_FLAG_MANDATORY (1u << 3)
38#endif
39#if !PW_CHECK_VERSION(0, 3, 33)
40#define SPA_POD_PROP_FLAG_DONT_FIXATE (1u << 4)
41#endif
42
43#define CURSOR_BPP 4
44#define CURSOR_META_SIZE(w, h) (sizeof(struct spa_meta_cursor) + sizeof(struct spa_meta_bitmap) + w * h * CURSOR_BPP)
45
46pw_stream_events pwStreamEvents = {};
47
48struct PipeWireSourceStreamPrivate
49{
51 pw_stream *pwStream = nullptr;
52 spa_hook streamListener;
53
54 uint32_t pwNodeId = 0;
55 std::optional<std::chrono::nanoseconds> m_currentPresentationTimestamp;
56
57 QAtomicInt m_stopped = false;
58 pw_stream_state m_state = PW_STREAM_STATE_UNCONNECTED;
59
60 spa_video_info_raw videoFormat;
61 QString m_error;
62 bool m_allowDmaBuf = true;
63 bool m_usingDmaBuf = false;
64
65 QHash<spa_video_format, QList<uint64_t>> m_availableModifiers;
66 spa_source *m_renegotiateEvent = nullptr;
67
68 bool m_withDamage = false;
69 Fraction maxFramerate;
70
71 PipeWireSourceStream::UsageHint usageHint = PipeWireSourceStream::UsageHint::Render;
72};
73
74static const QVersionNumber pwClientVersion = QVersionNumber::fromString(QString::fromUtf8(pw_get_library_version()));
75static const QVersionNumber kDmaBufMinVersion = {0, 3, 24};
76static const QVersionNumber kDmaBufModifierMinVersion = {0, 3, 33};
77static const QVersionNumber kDropSingleModifierMinVersion = {0, 3, 40};
78
79uint32_t PipeWireSourceStream::spaVideoFormatToDrmFormat(spa_video_format spa_format)
80{
81 switch (spa_format) {
82 case SPA_VIDEO_FORMAT_RGBA:
83 return DRM_FORMAT_ABGR8888;
84 case SPA_VIDEO_FORMAT_RGBx:
85 return DRM_FORMAT_XBGR8888;
86 case SPA_VIDEO_FORMAT_BGRA:
87 return DRM_FORMAT_ARGB8888;
88 case SPA_VIDEO_FORMAT_BGRx:
89 return DRM_FORMAT_XRGB8888;
90 case SPA_VIDEO_FORMAT_BGR:
91 return DRM_FORMAT_BGR888;
92 case SPA_VIDEO_FORMAT_RGB:
93 return DRM_FORMAT_RGB888;
94 case SPA_VIDEO_FORMAT_xBGR:
95 return DRM_FORMAT_RGBX8888;
96 case SPA_VIDEO_FORMAT_ABGR:
97 return DRM_FORMAT_RGBA8888;
98 case SPA_VIDEO_FORMAT_GRAY8:
99 return DRM_FORMAT_R8;
100 default:
101 qCWarning(PIPEWIRE_LOGGING) << "cannot convert spa format to fourcc" << spa_format;
102 return DRM_FORMAT_INVALID;
103 }
104}
105
106static QString drmFormatName(uint32_t format)
107{
108 return QString::asprintf("%c%c%c%c %s-endian (0x%08x)",
109 QLatin1Char(format & 0xff).toLatin1(),
110 QLatin1Char((format >> 8) & 0xff).toLatin1(),
111 QLatin1Char((format >> 16) & 0xff).toLatin1(),
112 QLatin1Char((format >> 24) & 0x7f).toLatin1(),
113 format & DRM_FORMAT_BIG_ENDIAN ? "big" : "little",
114 format);
115}
116
117spa_video_format drmFormatToSpaVideoFormat(uint32_t drm_format)
118{
119 switch (drm_format) {
120 case DRM_FORMAT_ABGR8888:
121 return SPA_VIDEO_FORMAT_RGBA;
122 case DRM_FORMAT_XBGR8888:
123 return SPA_VIDEO_FORMAT_RGBx;
124 case DRM_FORMAT_ARGB8888:
125 return SPA_VIDEO_FORMAT_BGRA;
126 case DRM_FORMAT_XRGB8888:
127 return SPA_VIDEO_FORMAT_BGRx;
128 case DRM_FORMAT_BGR888:
129 return SPA_VIDEO_FORMAT_BGR;
130 case DRM_FORMAT_RGB888:
131 return SPA_VIDEO_FORMAT_RGB;
132 case DRM_FORMAT_YUYV:
133 return SPA_VIDEO_FORMAT_YUY2;
134 case DRM_FORMAT_R8:
135 return SPA_VIDEO_FORMAT_GRAY8;
136 default:
137 qCWarning(PIPEWIRE_LOGGING) << "cannot convert drm format to spa" << drmFormatName(drm_format);
138 return SPA_VIDEO_FORMAT_UNKNOWN;
139 }
140}
141
142static QHash<spa_video_format, QList<uint64_t>> queryDmaBufModifiers(EGLDisplay display, const QList<spa_video_format> &formats, PipeWireSourceStream::UsageHint usageHint)
143{
145 ret.reserve(formats.size());
146 const bool hasEglImageDmaBufImportExt = epoxy_has_egl_extension(display, "EGL_EXT_image_dma_buf_import");
147 static auto eglQueryDmaBufModifiersEXT = (PFNEGLQUERYDMABUFMODIFIERSEXTPROC)eglGetProcAddress("eglQueryDmaBufModifiersEXT");
148 static auto eglQueryDmaBufFormatsEXT = (PFNEGLQUERYDMABUFFORMATSEXTPROC)eglGetProcAddress("eglQueryDmaBufFormatsEXT");
149
150 EGLint count = 0;
151 EGLBoolean successFormats = eglQueryDmaBufFormatsEXT(display, 0, nullptr, &count);
152
153 QList<uint32_t> drmFormats(count);
154 successFormats &= eglQueryDmaBufFormatsEXT(display, count, reinterpret_cast<EGLint *>(drmFormats.data()), &count);
155 if (!successFormats)
156 qCWarning(PIPEWIRE_LOGGING) << "Failed to query DMA-BUF formats.";
157
158 const QList<uint64_t> mods = hasEglImageDmaBufImportExt ? QList<uint64_t>{DRM_FORMAT_MOD_INVALID} : QList<uint64_t>{};
159 if (!eglQueryDmaBufFormatsEXT || !eglQueryDmaBufModifiersEXT || !hasEglImageDmaBufImportExt || !successFormats) {
160 for (spa_video_format format : formats) {
161 ret[format] = mods;
162 }
163 return ret;
164 }
165
166 for (spa_video_format format : formats) {
167 uint32_t drm_format = PipeWireSourceStream::spaVideoFormatToDrmFormat(format);
168 if (drm_format == DRM_FORMAT_INVALID) {
169 qCDebug(PIPEWIRE_LOGGING) << "Failed to find matching DRM format." << format;
170 continue;
171 }
172
173 if (std::find(drmFormats.begin(), drmFormats.end(), drm_format) == drmFormats.end()) {
174 qCDebug(PIPEWIRE_LOGGING) << "Format " << drmFormatName(drm_format) << " not supported for modifiers.";
175 ret[format] = mods;
176 continue;
177 }
178
179 successFormats = eglQueryDmaBufModifiersEXT(display, drm_format, 0, nullptr, nullptr, &count);
180 if (!successFormats) {
181 qCWarning(PIPEWIRE_LOGGING) << "Failed to query DMA-BUF modifier count.";
182 ret[format] = mods;
183 continue;
184 }
185
186 QList<uint64_t> queriedModifiers(count);
187 QList<EGLBoolean> externalOnly(count);
188 if (count > 0) {
189 if (!eglQueryDmaBufModifiersEXT(display, drm_format, count, queriedModifiers.data(), externalOnly.data(), &count)) {
190 qCWarning(PIPEWIRE_LOGGING) << "Failed to query DMA-BUF modifiers.";
191 }
192 }
193
194 QList<uint64_t> usableModifiers;
195 usableModifiers.reserve(count + 1);
196 if (usageHint == PipeWireSourceStream::UsageHint::EncodeHardware) {
197 auto vaapi = VaapiUtils::instance();
198 for (int i = 0; i < queriedModifiers.size(); ++i) {
199 if (externalOnly[i]) {
200 continue;
201 }
202 const uint64_t modifier = queriedModifiers[i];
203 if (vaapi->supportsModifier(drm_format, modifier)) {
204 usableModifiers.append(modifier);
205 }
206 }
207 } else {
208 for (int i = 0; i < queriedModifiers.size(); ++i) {
209 if (!externalOnly[i]) {
210 usableModifiers.append(queriedModifiers[i]);
211 }
212 }
213 }
214
215 if (!usableModifiers.isEmpty()) {
216 // Support modifier-less buffers
217 usableModifiers.push_back(DRM_FORMAT_MOD_INVALID);
218 }
219
220 ret[format] = usableModifiers;
221 }
222 return ret;
223}
224
225void PipeWireSourceStream::onStreamStateChanged(void *data, pw_stream_state old, pw_stream_state state, const char *error_message)
226{
227 PipeWireSourceStream *pw = static_cast<PipeWireSourceStream *>(data);
228 qCDebug(PIPEWIRE_LOGGING) << "state changed" << pw_stream_state_as_string(old) << "->" << pw_stream_state_as_string(state) << error_message;
229 pw->d->m_state = state;
230 Q_EMIT pw->stateChanged(state, old);
231
232 switch (state) {
233 case PW_STREAM_STATE_ERROR:
234 qCWarning(PIPEWIRE_LOGGING) << "Stream error: " << error_message;
235 break;
236 case PW_STREAM_STATE_PAUSED:
237 Q_EMIT pw->streamReady();
238 break;
239 case PW_STREAM_STATE_STREAMING:
240 Q_EMIT pw->startStreaming();
241 break;
242 case PW_STREAM_STATE_CONNECTING:
243 break;
244 case PW_STREAM_STATE_UNCONNECTED:
245 if (!pw->d->m_stopped) {
246 Q_EMIT pw->stopStreaming();
247 }
248 break;
249 }
250}
251
252void PipeWireSourceStream::onRenegotiate(void *data, uint64_t)
253{
254 PipeWireSourceStream *pw = static_cast<PipeWireSourceStream *>(data);
255 uint8_t buffer[4096];
256 spa_pod_builder podBuilder = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
257 auto params = pw->createFormatsParams(podBuilder);
258 pw_stream_update_params(pw->d->pwStream, params.data(), params.size());
259}
260
261void PipeWireSourceStream::renegotiateModifierFailed(spa_video_format format, quint64 modifier)
262{
263 if (d->pwCore->serverVersion() >= kDropSingleModifierMinVersion) {
264 const int removed = d->m_availableModifiers[format].removeAll(modifier);
265 if (removed == 0) {
266 d->m_allowDmaBuf = false;
267 }
268 } else {
269 d->m_allowDmaBuf = false;
270 }
271 qCDebug(PIPEWIRE_LOGGING) << "renegotiating, modifier didn't work" << format << modifier << "now only offering" << d->m_availableModifiers[format].count();
272 pw_loop_signal_event(d->pwCore->loop(), d->m_renegotiateEvent);
273}
274
275static spa_pod *
276buildFormat(spa_pod_builder *builder, spa_video_format format, const QList<uint64_t> &modifiers, bool withDontFixate, const Fraction &requestedMaxFramerate)
277{
278 spa_pod_frame f[2];
279 const spa_rectangle pw_min_screen_bounds{1, 1};
280 const spa_rectangle pw_max_screen_bounds{UINT32_MAX, UINT32_MAX};
281
282 spa_pod_builder_push_object(builder, &f[0], SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat);
283 spa_pod_builder_add(builder, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), 0);
284 spa_pod_builder_add(builder, SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), 0);
285 spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_format, SPA_POD_Id(format), 0);
286 spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle(&pw_min_screen_bounds, &pw_min_screen_bounds, &pw_max_screen_bounds), 0);
287 if (requestedMaxFramerate) {
288 auto defFramerate = SPA_FRACTION(0, 1);
289 auto minFramerate = SPA_FRACTION(1, 1);
290 auto maxFramerate = SPA_FRACTION(requestedMaxFramerate.numerator, requestedMaxFramerate.denominator);
291 spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&defFramerate), 0);
292 spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_maxFramerate, SPA_POD_CHOICE_RANGE_Fraction(&maxFramerate, &minFramerate, &maxFramerate), 0);
293 } else {
294 auto defFramerate = SPA_FRACTION(0, 1);
295 auto maxFramerate = SPA_FRACTION(1200, 1);
296 spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_framerate, SPA_POD_CHOICE_RANGE_Fraction(&defFramerate, &defFramerate, &maxFramerate), 0);
297 }
298
299 if (!modifiers.isEmpty()) {
300 // SPA_POD_PROP_FLAG_DONT_FIXATE can be used with PipeWire >= 0.3.33
301 if (withDontFixate) {
302 spa_pod_builder_prop(builder, SPA_FORMAT_VIDEO_modifier, SPA_POD_PROP_FLAG_MANDATORY | SPA_POD_PROP_FLAG_DONT_FIXATE);
303 } else {
304 spa_pod_builder_prop(builder, SPA_FORMAT_VIDEO_modifier, SPA_POD_PROP_FLAG_MANDATORY);
305 }
306 spa_pod_builder_push_choice(builder, &f[1], SPA_CHOICE_Enum, 0);
307 // mofifiers from the array
308 for (auto it = modifiers.begin(); it != modifiers.end(); it++) {
309 spa_pod_builder_long(builder, *it);
310 if (it == modifiers.begin()) {
311 spa_pod_builder_long(builder, *it);
312 }
313 }
314 spa_pod_builder_pop(builder, &f[1]);
315 }
316
317 return static_cast<spa_pod *>(spa_pod_builder_pop(builder, &f[0]));
318}
319
320static const int videoDamageRegionCount = 16;
321
322void PipeWireSourceStream::onStreamParamChanged(void *data, uint32_t id, const struct spa_pod *format)
323{
324 if (!format || id != SPA_PARAM_Format) {
325 return;
326 }
327
328 PipeWireSourceStream *pw = static_cast<PipeWireSourceStream *>(data);
329 spa_format_video_raw_parse(format, &pw->d->videoFormat);
330
331 uint8_t paramsBuffer[1024];
332 spa_pod_builder pod_builder = SPA_POD_BUILDER_INIT(paramsBuffer, sizeof(paramsBuffer));
333
334 // When SPA_FORMAT_VIDEO_modifier is present we can use DMA-BUFs as
335 // the server announces support for it.
336 // See https://github.com/PipeWire/pipewire/blob/master/doc/dma-buf.dox
337
338 pw->d->m_usingDmaBuf = pw->d->m_allowDmaBuf && spa_pod_find_prop(format, nullptr, SPA_FORMAT_VIDEO_modifier);
339 Q_ASSERT(pw->d->m_allowDmaBuf || !pw->d->m_usingDmaBuf);
340 const auto bufferTypes =
341 pw->d->m_usingDmaBuf ? (1 << SPA_DATA_DmaBuf) | (1 << SPA_DATA_MemFd) | (1 << SPA_DATA_MemPtr) : (1 << SPA_DATA_MemFd) | (1 << SPA_DATA_MemPtr);
342
344 (spa_pod *)spa_pod_builder_add_object(&pod_builder,
345 SPA_TYPE_OBJECT_ParamBuffers,
346 SPA_PARAM_Buffers,
347 SPA_PARAM_BUFFERS_buffers,
348 SPA_POD_CHOICE_RANGE_Int(3, 2, 16),
349 SPA_PARAM_BUFFERS_align,
350 SPA_POD_Int(16),
351 SPA_PARAM_BUFFERS_dataType,
352 SPA_POD_CHOICE_FLAGS_Int(bufferTypes)),
353 (spa_pod *)spa_pod_builder_add_object(&pod_builder,
354 SPA_TYPE_OBJECT_ParamMeta,
355 SPA_PARAM_Meta,
356 SPA_PARAM_META_type,
357 SPA_POD_Id(SPA_META_Header),
358 SPA_PARAM_META_size,
359 SPA_POD_Int(sizeof(struct spa_meta_header))),
360 (spa_pod *)spa_pod_builder_add_object(&pod_builder,
361 SPA_TYPE_OBJECT_ParamMeta,
362 SPA_PARAM_Meta,
363 SPA_PARAM_META_type,
364 SPA_POD_Id(SPA_META_Cursor),
365 SPA_PARAM_META_size,
366 SPA_POD_CHOICE_RANGE_Int(CURSOR_META_SIZE(64, 64), CURSOR_META_SIZE(1, 1), CURSOR_META_SIZE(1024, 1024))),
367 };
368
369 if (pw->d->m_withDamage) {
370 params.append((spa_pod *)spa_pod_builder_add_object(&pod_builder,
371 SPA_TYPE_OBJECT_ParamMeta,
372 SPA_PARAM_Meta,
373 SPA_PARAM_META_type,
374 SPA_POD_Id(SPA_META_VideoDamage),
375 SPA_PARAM_META_size,
376 SPA_POD_CHOICE_RANGE_Int(sizeof(struct spa_meta_region) * videoDamageRegionCount,
377 sizeof(struct spa_meta_region) * 1,
378 sizeof(struct spa_meta_region) * videoDamageRegionCount)));
379 }
380
381 pw_stream_update_params(pw->d->pwStream, params.data(), params.count());
382 Q_EMIT pw->streamParametersChanged();
383}
384
385static void onProcess(void *data)
386{
387 PipeWireSourceStream *stream = static_cast<PipeWireSourceStream *>(data);
388 stream->process();
389}
390
391PipeWireFrameData::PipeWireFrameData(spa_video_format format, void *data, QSize size, qint32 stride, PipeWireFrameCleanupFunction *cleanup)
392 : format(format)
393 , data(data)
394 , size(size)
395 , stride(stride)
396 , cleanup(cleanup)
397{
398 cleanup->ref();
399}
400
401PipeWireFrameData::~PipeWireFrameData()
402{
403 PipeWireFrameCleanupFunction::unref(cleanup);
404}
405
406QSize PipeWireSourceStream::size() const
407{
408 return QSize(d->videoFormat.size.width, d->videoFormat.size.height);
409}
410
411pw_stream_state PipeWireSourceStream::state() const
412{
413 return d->m_state;
414}
415
416std::optional< std::chrono::nanoseconds > PipeWireSourceStream::currentPresentationTimestamp() const
417{
418 return d->m_currentPresentationTimestamp;
419}
420
421QString PipeWireSourceStream::error() const
422{
423 return d->m_error;
424}
425
426PipeWireSourceStream::PipeWireSourceStream(QObject *parent)
427 : QObject(parent)
428 , d(new PipeWireSourceStreamPrivate)
429{
430 pwStreamEvents.version = PW_VERSION_STREAM_EVENTS;
431 pwStreamEvents.process = &onProcess;
432 pwStreamEvents.state_changed = &PipeWireSourceStream::onStreamStateChanged;
433 pwStreamEvents.param_changed = &PipeWireSourceStream::onStreamParamChanged;
434 pwStreamEvents.destroy = &PipeWireSourceStream::onDestroy;
435}
436
437PipeWireSourceStream::~PipeWireSourceStream()
438{
439 d->m_stopped = true;
440 if (d->m_renegotiateEvent) {
441 pw_loop_destroy_source(d->pwCore->loop(), d->m_renegotiateEvent);
442 }
443 if (d->pwStream) {
444 pw_stream_destroy(d->pwStream);
445 }
446}
447
448Fraction PipeWireSourceStream::framerate() const
449{
450 if (d->pwStream) {
451 return {d->videoFormat.max_framerate.num, d->videoFormat.max_framerate.denom};
452 }
453
454 return {0, 1};
455}
456
457void PipeWireSourceStream::setMaxFramerate(const Fraction &framerate)
458{
459 d->maxFramerate = framerate;
460
461 if (d->pwStream) {
462 pw_loop_signal_event(d->pwCore->loop(), d->m_renegotiateEvent);
463 }
464}
465
466uint PipeWireSourceStream::nodeId()
467{
468 return d->pwNodeId;
469}
470
471PipeWireSourceStream::UsageHint PipeWireSourceStream::usageHint() const
472{
473 return d->usageHint;
474}
475
476void PipeWireSourceStream::setUsageHint(UsageHint hint)
477{
478 d->usageHint = hint;
479}
480
481QList<const spa_pod *> PipeWireSourceStream::createFormatsParams(spa_pod_builder podBuilder)
482{
483 const auto pwServerVersion = d->pwCore->serverVersion();
484 static constexpr auto formats = {
485 SPA_VIDEO_FORMAT_RGBx,
486 SPA_VIDEO_FORMAT_RGBA,
487 SPA_VIDEO_FORMAT_BGRx,
488 SPA_VIDEO_FORMAT_BGRA,
489 SPA_VIDEO_FORMAT_RGB,
490 SPA_VIDEO_FORMAT_BGR,
491 SPA_VIDEO_FORMAT_xBGR,
492 SPA_VIDEO_FORMAT_ABGR,
493 SPA_VIDEO_FORMAT_GRAY8,
494 };
496 params.reserve(formats.size() * 2);
497 const EGLDisplay display = static_cast<EGLDisplay>(QGuiApplication::platformNativeInterface()->nativeResourceForIntegration("egldisplay"));
498
499 d->m_allowDmaBuf = d->m_allowDmaBuf && (pwServerVersion.isNull() || (pwClientVersion >= kDmaBufMinVersion && pwServerVersion >= kDmaBufMinVersion));
500 const bool withDontFixate = d->m_allowDmaBuf && (pwServerVersion.isNull() || (pwClientVersion >= kDmaBufModifierMinVersion && pwServerVersion >= kDmaBufModifierMinVersion));
501
502 if (!d->m_allowDmaBuf && d->usageHint == UsageHint::EncodeHardware) {
503 qCWarning(PIPEWIRE_LOGGING) << "DMABUF is unsupported but hardware encoding is requested, which requires DMABUF import. This will not work correctly.";
504 }
505
506 if (d->m_availableModifiers.isEmpty()) {
507 static const auto availableModifiers = queryDmaBufModifiers(display, formats, d->usageHint);
508 d->m_availableModifiers = availableModifiers;
509 }
510
511 for (auto it = d->m_availableModifiers.constBegin(), itEnd = d->m_availableModifiers.constEnd(); it != itEnd; ++it) {
512 if (d->m_allowDmaBuf && !it->isEmpty()) {
513 params += buildFormat(&podBuilder, it.key(), it.value(), withDontFixate, d->maxFramerate);
514 }
515
516 params += buildFormat(&podBuilder, it.key(), {}, withDontFixate, d->maxFramerate);
517 }
518
519 // BUG 492400: Workaround for pipewire < 0.3.49 https://github.com/PipeWire/pipewire/commit/8646117374df6fa3b73f63f9b35cda78a6aaa2f4
520 params.removeAll(nullptr);
521 return params;
522}
523
524bool PipeWireSourceStream::createStream(uint nodeid, int fd)
525{
526 d->m_availableModifiers.clear();
527 d->pwCore = PipeWireCore::fetch(fd);
528 if (!d->pwCore->error().isEmpty()) {
529 qCDebug(PIPEWIRE_LOGGING) << "received error while creating the stream" << d->pwCore->error();
530 d->m_error = d->pwCore->error();
531 return false;
532 }
533
534 connect(d->pwCore.data(), &PipeWireCore::pipewireFailed, this, &PipeWireSourceStream::coreFailed);
535
536 if (objectName().isEmpty()) {
537 setObjectName(QStringLiteral("plasma-screencast-%1").arg(nodeid));
538 }
539
540 const auto pwServerVersion = d->pwCore->serverVersion();
541 d->pwStream = pw_stream_new(**d->pwCore, objectName().toUtf8().constData(), nullptr);
542 d->pwNodeId = nodeid;
543 pw_stream_add_listener(d->pwStream, &d->streamListener, &pwStreamEvents, this);
544
545 d->m_renegotiateEvent = pw_loop_add_event(d->pwCore->loop(), onRenegotiate, this);
546
547 uint8_t buffer[4096];
548 spa_pod_builder podBuilder = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
549 auto params = createFormatsParams(podBuilder);
550 pw_stream_flags s = (pw_stream_flags)(PW_STREAM_FLAG_DONT_RECONNECT | PW_STREAM_FLAG_AUTOCONNECT);
551 if (pw_stream_connect(d->pwStream, PW_DIRECTION_INPUT, d->pwNodeId, s, params.data(), params.size()) != 0) {
552 qCWarning(PIPEWIRE_LOGGING) << "Could not connect to stream";
553 pw_stream_destroy(d->pwStream);
554 d->pwStream = nullptr;
555 return false;
556 }
557 qCDebug(PIPEWIRE_LOGGING) << "created successfully" << nodeid;
558 return true;
559}
560
561void PipeWireSourceStream::handleFrame(struct pw_buffer *buffer)
562{
563 spa_buffer *spaBuffer = buffer->buffer;
564
565 PipeWireFrame frame;
566 frame.format = d->videoFormat.format;
567
568 struct spa_meta_header *header = (struct spa_meta_header *)spa_buffer_find_meta_data(spaBuffer, SPA_META_Header, sizeof(*header));
569 if (header) {
570 if (header->flags & SPA_META_HEADER_FLAG_CORRUPTED) {
571 qCDebug(PIPEWIRE_LOGGING) << "buffer is corrupt";
572 return;
573 }
574
575 d->m_currentPresentationTimestamp = std::chrono::nanoseconds(header->pts);
576 frame.presentationTimestamp = std::chrono::nanoseconds(header->pts);
577 frame.sequential = header->seq;
578 } else {
579 d->m_currentPresentationTimestamp = std::chrono::steady_clock::now().time_since_epoch();
580 frame.presentationTimestamp = d->m_currentPresentationTimestamp;
581 }
582
583 if (spa_meta *vd = spa_buffer_find_meta(spaBuffer, SPA_META_VideoDamage)) {
584 frame.damage = QRegion();
585 spa_meta_region *mr;
586 spa_meta_for_each(mr, vd)
587 {
588 *frame.damage += QRect(mr->region.position.x, mr->region.position.y, mr->region.size.width, mr->region.size.height);
589 }
590 }
591
592 { // process cursor
593 struct spa_meta_cursor *cursor = static_cast<struct spa_meta_cursor *>(spa_buffer_find_meta_data(spaBuffer, SPA_META_Cursor, sizeof(*cursor)));
594 if (cursor && spa_meta_cursor_is_valid(cursor)) {
595 struct spa_meta_bitmap *bitmap = nullptr;
596
597 if (cursor->bitmap_offset)
598 bitmap = SPA_MEMBER(cursor, cursor->bitmap_offset, struct spa_meta_bitmap);
599
600 QImage cursorTexture;
601 if (bitmap && bitmap->size.width > 0 && bitmap->size.height > 0) {
602 const size_t bufferSize = bitmap->stride * bitmap->size.height * 4;
603 void *bufferData = malloc(bufferSize);
604 memcpy(bufferData, SPA_MEMBER(bitmap, bitmap->offset, uint8_t), bufferSize);
605 cursorTexture = PWHelpers::SpaBufferToQImage(static_cast<const uchar *>(bufferData),
606 bitmap->size.width,
607 bitmap->size.height,
608 bitmap->stride,
609 spa_video_format(bitmap->format),
610 new PipeWireFrameCleanupFunction([bufferData] {
611 free(bufferData);
612 }));
613 }
614 frame.cursor = {{cursor->position.x, cursor->position.y}, {cursor->hotspot.x, cursor->hotspot.y}, cursorTexture};
615 }
616 }
617
618 if (spaBuffer->datas->chunk->flags == SPA_CHUNK_FLAG_CORRUPTED) {
619 // do not get a frame
620 qCDebug(PIPEWIRE_LOGGING) << "skipping empty buffer" << spaBuffer->datas->chunk->size << spaBuffer->datas->chunk->flags;
621 } else if (spaBuffer->datas->type == SPA_DATA_MemFd) {
622 if (spaBuffer->datas->chunk->size == 0) {
623 qCDebug(PIPEWIRE_LOGGING) << "skipping empty memfd buffer";
624 } else {
625 const uint32_t mapEnd = spaBuffer->datas->maxsize + spaBuffer->datas->mapoffset;
626 uint8_t *map = static_cast<uint8_t *>(mmap(nullptr, mapEnd, PROT_READ, MAP_PRIVATE, spaBuffer->datas->fd, 0));
627
628 if (map == MAP_FAILED) {
629 qCWarning(PIPEWIRE_LOGGING) << "Failed to mmap the memory: " << strerror(errno);
630 return;
631 }
632 auto cleanup = [map, mapEnd]() {
633 munmap(map, mapEnd);
634 };
635 frame.dataFrame = std::make_shared<PipeWireFrameData>(d->videoFormat.format,
636 map,
637 QSize(d->videoFormat.size.width, d->videoFormat.size.height),
638 spaBuffer->datas->chunk->stride,
639 new PipeWireFrameCleanupFunction(cleanup));
640 }
641 } else if (spaBuffer->datas->type == SPA_DATA_DmaBuf) {
642 DmaBufAttributes attribs;
643 attribs.planes.reserve(spaBuffer->n_datas);
644 attribs.format = spaVideoFormatToDrmFormat(d->videoFormat.format);
645 attribs.modifier = d->videoFormat.modifier;
646 attribs.width = d->videoFormat.size.width;
647 attribs.height = d->videoFormat.size.height;
648
649 for (uint i = 0; i < spaBuffer->n_datas; ++i) {
650 const auto &data = spaBuffer->datas[i];
651
652 DmaBufPlane plane;
653 plane.fd = data.fd;
654 plane.stride = data.chunk->stride;
655 plane.offset = data.chunk->offset;
656 attribs.planes += plane;
657 }
658 Q_ASSERT(!attribs.planes.isEmpty());
659 frame.dmabuf = attribs;
660 } else if (spaBuffer->datas->type == SPA_DATA_MemPtr) {
661 if (spaBuffer->datas->chunk->size == 0) {
662 qCDebug(PIPEWIRE_LOGGING) << "skipping empty memptr buffer";
663 } else {
664 frame.dataFrame = std::make_shared<PipeWireFrameData>(d->videoFormat.format,
665 spaBuffer->datas->data,
666 QSize(d->videoFormat.size.width, d->videoFormat.size.height),
667 spaBuffer->datas->chunk->stride,
668 nullptr);
669 }
670 } else {
671 if (spaBuffer->datas->type == SPA_ID_INVALID) {
672 qCWarning(PIPEWIRE_LOGGING) << "invalid buffer type";
673 } else {
674 qCWarning(PIPEWIRE_LOGGING) << "unsupported buffer type" << spaBuffer->datas->type;
675 }
676 frame.dataFrame = {};
677 }
678
679 Q_EMIT frameReceived(frame);
680}
681
682void PipeWireSourceStream::coreFailed(const QString &errorMessage)
683{
684 qCDebug(PIPEWIRE_LOGGING) << "received error message" << errorMessage;
685 d->m_error = errorMessage;
686 Q_EMIT stopStreaming();
687}
688
689void PipeWireSourceStream::process()
690{
691#if !PW_CHECK_VERSION(0, 3, 73)
692 if (Q_UNLIKELY(!d->pwStream)) {
693 // Assuming it's caused by https://gitlab.freedesktop.org/pipewire/pipewire/-/issues/3314
694 qCDebug(PIPEWIRE_LOGGING) << "stream was terminated before processing buffer";
695 return;
696 }
697#endif
698
699 pw_buffer *buf = pw_stream_dequeue_buffer(d->pwStream);
700 if (!buf) {
701 qCDebug(PIPEWIRE_LOGGING) << "out of buffers";
702 return;
703 }
704
705 handleFrame(buf);
706
707 pw_stream_queue_buffer(d->pwStream, buf);
708}
709
710void PipeWireSourceStream::setActive(bool active)
711{
712 Q_ASSERT(d->pwStream);
713 pw_stream_set_active(d->pwStream, active);
714}
715
716void PipeWireSourceStream::setDamageEnabled(bool withDamage)
717{
718 d->m_withDamage = withDamage;
719}
720
721bool PipeWireSourceStream::usingDmaBuf() const
722{
723 return d->m_usingDmaBuf;
724}
725
726bool PipeWireSourceStream::allowDmaBuf() const
727{
728 return d->m_allowDmaBuf;
729}
730
731void PipeWireSourceStream::setAllowDmaBuf(bool allowed)
732{
733 d->m_allowDmaBuf = allowed;
734}
735
736void PipeWireSourceStream::onDestroy(void *data)
737{
738 // When PipeWire restarts the stream will auto-delete. Make sure we don't have dangling pointers!
739 auto pw = static_cast<PipeWireSourceStream *>(data);
740 pw->d->pwStream = nullptr;
741}
742
743#include "moc_pipewiresourcestream.cpp"
The to track the lifetime of a pipewire frame.
Definition pwhelpers.h:25
KCALUTILS_EXPORT QString errorMessage(const KCalendarCore::Exception &exception)
QAction * hint(const QObject *recvr, const char *slot, QObject *parent)
void reserve(qsizetype size)
void append(QList< T > &&value)
iterator begin()
pointer data()
iterator end()
bool isEmpty() const const
void push_back(parameter_type value)
qsizetype removeAll(const AT &t)
void reserve(qsizetype size)
qsizetype size() const const
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void setObjectName(QAnyStringView name)
QString asprintf(const char *cformat,...)
QString fromUtf8(QByteArrayView str)
QFuture< void > map(Iterator begin, Iterator end, MapFunctor &&function)
qsizetype count() const const
QVersionNumber fromString(QAnyStringView string, qsizetype *suffixIndex)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:15:17 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.