File indexing completed on 2024-04-21 16:13:07

0001 /*
0002     SPDX-FileCopyrightText: 2018-2020 Red Hat Inc
0003     SPDX-FileCopyrightText: 2020-2021 Aleix Pol Gonzalez <aleixpol@kde.org>
0004     SPDX-FileContributor: Jan Grulich <jgrulich@redhat.com>
0005 
0006     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0007 */
0008 
0009 #include "pipewiresourcestream.h"
0010 #include "glhelpers.h"
0011 #include "logging.h"
0012 #include "pipewirecore.h"
0013 #include "pwhelpers.h"
0014 
0015 #include <libdrm/drm_fourcc.h>
0016 #include <spa/utils/result.h>
0017 #include <sys/ioctl.h>
0018 #include <sys/mman.h>
0019 #include <unistd.h>
0020 
0021 #include <QGuiApplication>
0022 #include <QOpenGLTexture>
0023 #include <QSocketNotifier>
0024 #include <QThread>
0025 #include <QVersionNumber>
0026 #include <qpa/qplatformnativeinterface.h>
0027 
0028 #include <KLocalizedString>
0029 
0030 #include <EGL/egl.h>
0031 #include <EGL/eglext.h>
0032 
0033 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0034 #include <QtPlatformHeaders/QEGLNativeContext>
0035 #endif
0036 #undef Status
0037 
0038 #if !PW_CHECK_VERSION(0, 3, 29)
0039 #define SPA_POD_PROP_FLAG_MANDATORY (1u << 3)
0040 #endif
0041 #if !PW_CHECK_VERSION(0, 3, 33)
0042 #define SPA_POD_PROP_FLAG_DONT_FIXATE (1u << 4)
0043 #endif
0044 
0045 #define CURSOR_BPP 4
0046 #define CURSOR_META_SIZE(w, h) (sizeof(struct spa_meta_cursor) + sizeof(struct spa_meta_bitmap) + w * h * CURSOR_BPP)
0047 
0048 pw_stream_events pwStreamEvents = {};
0049 
0050 struct PipeWireSourceStreamPrivate
0051 {
0052     QSharedPointer<PipeWireCore> pwCore;
0053     pw_stream *pwStream = nullptr;
0054     spa_hook streamListener;
0055 
0056     uint32_t pwNodeId = 0;
0057     std::optional<std::chrono::nanoseconds> m_currentPresentationTimestamp;
0058 
0059     QAtomicInt m_stopped = false;
0060     pw_stream_state m_state = PW_STREAM_STATE_UNCONNECTED;
0061 
0062     spa_video_info_raw videoFormat;
0063     QString m_error;
0064     bool m_allowDmaBuf = true;
0065 
0066     QHash<spa_video_format, QVector<uint64_t>> m_availableModifiers;
0067     spa_source *m_renegotiateEvent = nullptr;
0068 
0069     bool m_withDamage = false;
0070 };
0071 
0072 static const QVersionNumber pwClientVersion = QVersionNumber::fromString(QString::fromUtf8(pw_get_library_version()));
0073 static const QVersionNumber kDmaBufMinVersion = {0, 3, 24};
0074 static const QVersionNumber kDmaBufModifierMinVersion = {0, 3, 33};
0075 static const QVersionNumber kDropSingleModifierMinVersion = {0, 3, 40};
0076 
0077 uint32_t PipeWireSourceStream::spaVideoFormatToDrmFormat(spa_video_format spa_format)
0078 {
0079     switch (spa_format) {
0080     case SPA_VIDEO_FORMAT_RGBA:
0081         return DRM_FORMAT_ABGR8888;
0082     case SPA_VIDEO_FORMAT_RGBx:
0083         return DRM_FORMAT_XBGR8888;
0084     case SPA_VIDEO_FORMAT_BGRA:
0085         return DRM_FORMAT_ARGB8888;
0086     case SPA_VIDEO_FORMAT_BGRx:
0087         return DRM_FORMAT_XRGB8888;
0088     case SPA_VIDEO_FORMAT_BGR:
0089         return DRM_FORMAT_BGR888;
0090     case SPA_VIDEO_FORMAT_RGB:
0091         return DRM_FORMAT_RGB888;
0092     default:
0093         qCWarning(PIPEWIRE_LOGGING) << "unknown QImage format" << spa_format;
0094         return DRM_FORMAT_INVALID;
0095     }
0096 }
0097 
0098 static QHash<spa_video_format, QVector<uint64_t>> queryDmaBufModifiers(EGLDisplay display, const QVector<spa_video_format> &formats)
0099 {
0100     QHash<spa_video_format, QVector<uint64_t>> ret;
0101     ret.reserve(formats.size());
0102     const bool hasEglImageDmaBufImportExt = epoxy_has_egl_extension(display, "EGL_EXT_image_dma_buf_import");
0103     static auto eglQueryDmaBufModifiersEXT = (PFNEGLQUERYDMABUFMODIFIERSEXTPROC)eglGetProcAddress("eglQueryDmaBufModifiersEXT");
0104     static auto eglQueryDmaBufFormatsEXT = (PFNEGLQUERYDMABUFFORMATSEXTPROC)eglGetProcAddress("eglQueryDmaBufFormatsEXT");
0105 
0106     EGLint count = 0;
0107     EGLBoolean successFormats = eglQueryDmaBufFormatsEXT(display, 0, nullptr, &count);
0108 
0109     QVector<uint32_t> drmFormats(count);
0110     successFormats &= eglQueryDmaBufFormatsEXT(display, count, reinterpret_cast<EGLint *>(drmFormats.data()), &count);
0111     if (!successFormats)
0112         qCWarning(PIPEWIRE_LOGGING) << "Failed to query DMA-BUF formats.";
0113 
0114     const QVector<uint64_t> mods = hasEglImageDmaBufImportExt ? QVector<uint64_t>{DRM_FORMAT_MOD_INVALID} : QVector<uint64_t>{};
0115     if (!eglQueryDmaBufFormatsEXT || !eglQueryDmaBufModifiersEXT || !hasEglImageDmaBufImportExt || !successFormats) {
0116         for (spa_video_format format : formats) {
0117             ret[format] = mods;
0118         }
0119         return ret;
0120     }
0121 
0122     for (spa_video_format format : formats) {
0123         uint32_t drm_format = PipeWireSourceStream::spaVideoFormatToDrmFormat(format);
0124         if (drm_format == DRM_FORMAT_INVALID) {
0125             qCDebug(PIPEWIRE_LOGGING) << "Failed to find matching DRM format." << format;
0126             break;
0127         }
0128 
0129         if (std::find(drmFormats.begin(), drmFormats.end(), drm_format) == drmFormats.end()) {
0130             qCDebug(PIPEWIRE_LOGGING) << "Format " << drm_format << " not supported for modifiers.";
0131             ret[format] = mods;
0132             break;
0133         }
0134 
0135         successFormats = eglQueryDmaBufModifiersEXT(display, drm_format, 0, nullptr, nullptr, &count);
0136         if (!successFormats) {
0137             qCWarning(PIPEWIRE_LOGGING) << "Failed to query DMA-BUF modifier count.";
0138             ret[format] = mods;
0139             break;
0140         }
0141 
0142         QVector<uint64_t> modifiers(count);
0143         if (count > 0) {
0144             if (!eglQueryDmaBufModifiersEXT(display, drm_format, count, modifiers.data(), nullptr, &count)) {
0145                 qCWarning(PIPEWIRE_LOGGING) << "Failed to query DMA-BUF modifiers.";
0146             }
0147         }
0148 
0149         // Support modifier-less buffers
0150         modifiers.push_back(DRM_FORMAT_MOD_INVALID);
0151         ret[format] = modifiers;
0152     }
0153     return ret;
0154 }
0155 
0156 void PipeWireSourceStream::onStreamStateChanged(void *data, pw_stream_state old, pw_stream_state state, const char *error_message)
0157 {
0158     PipeWireSourceStream *pw = static_cast<PipeWireSourceStream *>(data);
0159     qCDebug(PIPEWIRE_LOGGING) << "state changed" << pw_stream_state_as_string(old) << "->" << pw_stream_state_as_string(state) << error_message;
0160     pw->d->m_state = state;
0161     Q_EMIT pw->stateChanged(state, old);
0162 
0163     switch (state) {
0164     case PW_STREAM_STATE_ERROR:
0165         qCWarning(PIPEWIRE_LOGGING) << "Stream error: " << error_message;
0166         break;
0167     case PW_STREAM_STATE_PAUSED:
0168         Q_EMIT pw->streamReady();
0169         break;
0170     case PW_STREAM_STATE_STREAMING:
0171         Q_EMIT pw->startStreaming();
0172         break;
0173     case PW_STREAM_STATE_CONNECTING:
0174         break;
0175     case PW_STREAM_STATE_UNCONNECTED:
0176         if (!pw->d->m_stopped) {
0177             Q_EMIT pw->stopStreaming();
0178         }
0179         break;
0180     }
0181 }
0182 
0183 void PipeWireSourceStream::onRenegotiate(void *data, uint64_t)
0184 {
0185     PipeWireSourceStream *pw = static_cast<PipeWireSourceStream *>(data);
0186     uint8_t buffer[4096];
0187     spa_pod_builder podBuilder = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
0188     auto params = pw->createFormatsParams(podBuilder);
0189     pw_stream_update_params(pw->d->pwStream, params.data(), params.size());
0190 }
0191 
0192 void PipeWireSourceStream::renegotiateModifierFailed(spa_video_format format, quint64 modifier)
0193 {
0194     if (d->pwCore->serverVersion() >= kDropSingleModifierMinVersion) {
0195         const int removed = d->m_availableModifiers[format].removeAll(modifier);
0196         if (removed == 0) {
0197             d->m_allowDmaBuf = false;
0198         }
0199     } else {
0200         d->m_allowDmaBuf = false;
0201     }
0202     qCDebug(PIPEWIRE_LOGGING) << "renegotiating, modifier didn't work" << format << modifier << "now only offering" << d->m_availableModifiers[format].count();
0203     pw_loop_signal_event(d->pwCore->loop(), d->m_renegotiateEvent);
0204 }
0205 
0206 static spa_pod *buildFormat(spa_pod_builder *builder, spa_video_format format, const QVector<uint64_t> &modifiers, bool withDontFixate)
0207 {
0208     spa_pod_frame f[2];
0209     const spa_rectangle pw_min_screen_bounds{1, 1};
0210     const spa_rectangle pw_max_screen_bounds{UINT32_MAX, UINT32_MAX};
0211 
0212     spa_pod_builder_push_object(builder, &f[0], SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat);
0213     spa_pod_builder_add(builder, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), 0);
0214     spa_pod_builder_add(builder, SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), 0);
0215     spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_format, SPA_POD_Id(format), 0);
0216     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);
0217 
0218     if (modifiers.size() == 1 && modifiers[0] == DRM_FORMAT_MOD_INVALID) {
0219         // we only support implicit modifiers, use shortpath to skip fixation phase
0220         spa_pod_builder_prop(builder, SPA_FORMAT_VIDEO_modifier, SPA_POD_PROP_FLAG_MANDATORY);
0221         spa_pod_builder_long(builder, modifiers[0]);
0222     } else if (modifiers.size()) {
0223         // SPA_POD_PROP_FLAG_DONT_FIXATE can be used with PipeWire >= 0.3.33
0224         if (withDontFixate) {
0225             spa_pod_builder_prop(builder, SPA_FORMAT_VIDEO_modifier, SPA_POD_PROP_FLAG_MANDATORY | SPA_POD_PROP_FLAG_DONT_FIXATE);
0226         } else {
0227             spa_pod_builder_prop(builder, SPA_FORMAT_VIDEO_modifier, SPA_POD_PROP_FLAG_MANDATORY);
0228         }
0229         spa_pod_builder_push_choice(builder, &f[1], SPA_CHOICE_Enum, 0);
0230         // mofifiers from the array
0231         for (auto it = modifiers.begin(); it != modifiers.end(); it++) {
0232             spa_pod_builder_long(builder, *it);
0233             if (it == modifiers.begin()) {
0234                 spa_pod_builder_long(builder, *it);
0235             }
0236         }
0237         spa_pod_builder_pop(builder, &f[1]);
0238     }
0239 
0240     return static_cast<spa_pod *>(spa_pod_builder_pop(builder, &f[0]));
0241 }
0242 
0243 static const int videoDamageRegionCount = 16;
0244 
0245 void PipeWireSourceStream::onStreamParamChanged(void *data, uint32_t id, const struct spa_pod *format)
0246 {
0247     if (!format || id != SPA_PARAM_Format) {
0248         return;
0249     }
0250 
0251     PipeWireSourceStream *pw = static_cast<PipeWireSourceStream *>(data);
0252     spa_format_video_raw_parse(format, &pw->d->videoFormat);
0253 
0254     uint8_t paramsBuffer[1024];
0255     spa_pod_builder pod_builder = SPA_POD_BUILDER_INIT(paramsBuffer, sizeof(paramsBuffer));
0256 
0257     // When SPA_FORMAT_VIDEO_modifier is present we can use DMA-BUFs as
0258     // the server announces support for it.
0259     // See https://github.com/PipeWire/pipewire/blob/master/doc/dma-buf.dox
0260 
0261     const auto bufferTypes = pw->d->m_allowDmaBuf && spa_pod_find_prop(format, nullptr, SPA_FORMAT_VIDEO_modifier)
0262         ? (1 << SPA_DATA_DmaBuf) | (1 << SPA_DATA_MemFd) | (1 << SPA_DATA_MemPtr)
0263         : (1 << SPA_DATA_MemFd) | (1 << SPA_DATA_MemPtr);
0264 
0265     QVarLengthArray<const spa_pod *> params = {
0266         (spa_pod *)spa_pod_builder_add_object(&pod_builder,
0267                                               SPA_TYPE_OBJECT_ParamBuffers,
0268                                               SPA_PARAM_Buffers,
0269                                               SPA_PARAM_BUFFERS_buffers,
0270                                               SPA_POD_CHOICE_RANGE_Int(16, 2, 16),
0271                                               SPA_PARAM_BUFFERS_align,
0272                                               SPA_POD_Int(16),
0273                                               SPA_PARAM_BUFFERS_dataType,
0274                                               SPA_POD_CHOICE_FLAGS_Int(bufferTypes)),
0275         (spa_pod *)spa_pod_builder_add_object(&pod_builder,
0276                                               SPA_TYPE_OBJECT_ParamMeta,
0277                                               SPA_PARAM_Meta,
0278                                               SPA_PARAM_META_type,
0279                                               SPA_POD_Id(SPA_META_Header),
0280                                               SPA_PARAM_META_size,
0281                                               SPA_POD_Int(sizeof(struct spa_meta_header))),
0282         (spa_pod *)spa_pod_builder_add_object(&pod_builder,
0283                                               SPA_TYPE_OBJECT_ParamMeta,
0284                                               SPA_PARAM_Meta,
0285                                               SPA_PARAM_META_type,
0286                                               SPA_POD_Id(SPA_META_Cursor),
0287                                               SPA_PARAM_META_size,
0288                                               SPA_POD_CHOICE_RANGE_Int(CURSOR_META_SIZE(64, 64), CURSOR_META_SIZE(1, 1), CURSOR_META_SIZE(1024, 1024))),
0289     };
0290 
0291     if (pw->d->m_withDamage) {
0292         params.append((spa_pod *)spa_pod_builder_add_object(&pod_builder,
0293                                                             SPA_TYPE_OBJECT_ParamMeta,
0294                                                             SPA_PARAM_Meta,
0295                                                             SPA_PARAM_META_type,
0296                                                             SPA_POD_Id(SPA_META_VideoDamage),
0297                                                             SPA_PARAM_META_size,
0298                                                             SPA_POD_CHOICE_RANGE_Int(sizeof(struct spa_meta_region) * videoDamageRegionCount,
0299                                                                                      sizeof(struct spa_meta_region) * 1,
0300                                                                                      sizeof(struct spa_meta_region) * videoDamageRegionCount)));
0301     }
0302 
0303     pw_stream_update_params(pw->d->pwStream, params.data(), params.count());
0304     Q_EMIT pw->streamParametersChanged();
0305 }
0306 
0307 static void onProcess(void *data)
0308 {
0309     PipeWireSourceStream *stream = static_cast<PipeWireSourceStream *>(data);
0310     stream->process();
0311 }
0312 
0313 QSize PipeWireSourceStream::size() const
0314 {
0315     return QSize(d->videoFormat.size.width, d->videoFormat.size.height);
0316 }
0317 
0318 pw_stream_state PipeWireSourceStream::state() const
0319 {
0320     return d->m_state;
0321 }
0322 
0323 std::optional< std::chrono::nanoseconds > PipeWireSourceStream::currentPresentationTimestamp() const
0324 {
0325     return d->m_currentPresentationTimestamp;
0326 }
0327 
0328 QString PipeWireSourceStream::error() const
0329 {
0330     return d->m_error;
0331 }
0332 
0333 PipeWireSourceStream::PipeWireSourceStream(QObject *parent)
0334     : QObject(parent)
0335     , d(new PipeWireSourceStreamPrivate)
0336 {
0337     qRegisterMetaType<QVector<DmaBufPlane>>();
0338 
0339     pwStreamEvents.version = PW_VERSION_STREAM_EVENTS;
0340     pwStreamEvents.process = &onProcess;
0341     pwStreamEvents.state_changed = &PipeWireSourceStream::onStreamStateChanged;
0342     pwStreamEvents.param_changed = &PipeWireSourceStream::onStreamParamChanged;
0343 }
0344 
0345 PipeWireSourceStream::~PipeWireSourceStream()
0346 {
0347     d->m_stopped = true;
0348     if (d->m_renegotiateEvent) {
0349         pw_loop_destroy_source(d->pwCore->loop(), d->m_renegotiateEvent);
0350     }
0351     if (d->pwStream) {
0352         pw_stream_destroy(d->pwStream);
0353     }
0354 }
0355 
0356 Fraction PipeWireSourceStream::framerate() const
0357 {
0358     if (d->pwStream) {
0359         return {d->videoFormat.max_framerate.num, d->videoFormat.max_framerate.denom};
0360     }
0361 
0362     return {0, 1};
0363 }
0364 
0365 uint PipeWireSourceStream::nodeId()
0366 {
0367     return d->pwNodeId;
0368 }
0369 
0370 QVector<const spa_pod *> PipeWireSourceStream::createFormatsParams(spa_pod_builder podBuilder)
0371 {
0372     const auto pwServerVersion = d->pwCore->serverVersion();
0373     const QVector<spa_video_format> formats =
0374         {SPA_VIDEO_FORMAT_RGBx, SPA_VIDEO_FORMAT_RGBA, SPA_VIDEO_FORMAT_BGRx, SPA_VIDEO_FORMAT_BGRA, SPA_VIDEO_FORMAT_RGB, SPA_VIDEO_FORMAT_BGR};
0375     QVector<const spa_pod *> params;
0376     params.reserve(formats.size() * 2);
0377     const EGLDisplay display = static_cast<EGLDisplay>(QGuiApplication::platformNativeInterface()->nativeResourceForIntegration("egldisplay"));
0378 
0379     d->m_allowDmaBuf = pwServerVersion.isNull() || (pwClientVersion >= kDmaBufMinVersion && pwServerVersion >= kDmaBufMinVersion);
0380     const bool withDontFixate = pwServerVersion.isNull() || (pwClientVersion >= kDmaBufModifierMinVersion && pwServerVersion >= kDmaBufModifierMinVersion);
0381 
0382     if (d->m_availableModifiers.isEmpty()) {
0383         d->m_availableModifiers = queryDmaBufModifiers(display, formats);
0384     }
0385 
0386     for (auto it = d->m_availableModifiers.constBegin(), itEnd = d->m_availableModifiers.constEnd(); it != itEnd; ++it) {
0387         if (d->m_allowDmaBuf && !it->isEmpty()) {
0388             params += buildFormat(&podBuilder, it.key(), it.value(), withDontFixate);
0389         }
0390 
0391         params += buildFormat(&podBuilder, it.key(), {}, withDontFixate);
0392     }
0393     return params;
0394 }
0395 
0396 bool PipeWireSourceStream::createStream(uint nodeid, int fd)
0397 {
0398     d->m_availableModifiers.clear();
0399     d->pwCore = PipeWireCore::fetch(fd);
0400     if (!d->pwCore->error().isEmpty()) {
0401         qCDebug(PIPEWIRE_LOGGING) << "received error while creating the stream" << d->pwCore->error();
0402         d->m_error = d->pwCore->error();
0403         return false;
0404     }
0405 
0406     connect(d->pwCore.data(), &PipeWireCore::pipewireFailed, this, &PipeWireSourceStream::coreFailed);
0407 
0408     if (objectName().isEmpty()) {
0409         setObjectName(QStringLiteral("plasma-screencast-%1").arg(nodeid));
0410     }
0411 
0412     const auto pwServerVersion = d->pwCore->serverVersion();
0413     d->pwStream = pw_stream_new(**d->pwCore, objectName().toUtf8().constData(), nullptr);
0414     d->pwNodeId = nodeid;
0415     pw_stream_add_listener(d->pwStream, &d->streamListener, &pwStreamEvents, this);
0416 
0417     d->m_renegotiateEvent = pw_loop_add_event(d->pwCore->loop(), onRenegotiate, this);
0418 
0419     uint8_t buffer[4096];
0420     spa_pod_builder podBuilder = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
0421     auto params = createFormatsParams(podBuilder);
0422     pw_stream_flags s = (pw_stream_flags)(PW_STREAM_FLAG_DONT_RECONNECT | PW_STREAM_FLAG_AUTOCONNECT);
0423     if (pw_stream_connect(d->pwStream, PW_DIRECTION_INPUT, d->pwNodeId, s, params.data(), params.size()) != 0) {
0424         qCWarning(PIPEWIRE_LOGGING) << "Could not connect to stream";
0425         pw_stream_destroy(d->pwStream);
0426         d->pwStream = nullptr;
0427         return false;
0428     }
0429     qCDebug(PIPEWIRE_LOGGING) << "created successfully" << nodeid;
0430     return true;
0431 }
0432 
0433 void PipeWireSourceStream::handleFrame(struct pw_buffer *buffer)
0434 {
0435     spa_buffer *spaBuffer = buffer->buffer;
0436 
0437     PipeWireFrame frame;
0438     frame.format = d->videoFormat.format;
0439 
0440     struct spa_meta_header *h = (struct spa_meta_header *)spa_buffer_find_meta_data(spaBuffer, SPA_META_Header, sizeof(*h));
0441     if (h) {
0442         d->m_currentPresentationTimestamp = std::chrono::nanoseconds(h->pts);
0443         frame.presentationTimestamp = std::chrono::nanoseconds(h->pts);
0444         frame.sequential = h->seq;
0445     } else {
0446         using namespace std::chrono;
0447         auto now = system_clock::now();
0448         d->m_currentPresentationTimestamp = time_point_cast<nanoseconds>(now).time_since_epoch();
0449     }
0450 
0451     if (spa_meta *vd = spa_buffer_find_meta(spaBuffer, SPA_META_VideoDamage)) {
0452         frame.damage = QRegion();
0453         spa_meta_region *mr;
0454         spa_meta_for_each(mr, vd)
0455         {
0456             *frame.damage += QRect(mr->region.position.x, mr->region.position.y, mr->region.size.width, mr->region.size.height);
0457         }
0458     }
0459 
0460     { // process cursor
0461         struct spa_meta_cursor *cursor = static_cast<struct spa_meta_cursor *>(spa_buffer_find_meta_data(spaBuffer, SPA_META_Cursor, sizeof(*cursor)));
0462         if (cursor && spa_meta_cursor_is_valid(cursor)) {
0463             struct spa_meta_bitmap *bitmap = nullptr;
0464 
0465             if (cursor->bitmap_offset)
0466                 bitmap = SPA_MEMBER(cursor, cursor->bitmap_offset, struct spa_meta_bitmap);
0467 
0468             QImage cursorTexture;
0469             if (bitmap && bitmap->size.width > 0 && bitmap->size.height > 0) {
0470                 const uint8_t *bitmap_data = SPA_MEMBER(bitmap, bitmap->offset, uint8_t);
0471                 cursorTexture =
0472                     PWHelpers::SpaBufferToQImage(bitmap_data, bitmap->size.width, bitmap->size.height, bitmap->stride, spa_video_format(bitmap->format));
0473             }
0474             frame.cursor = {{cursor->position.x, cursor->position.y}, {cursor->hotspot.x, cursor->hotspot.y}, cursorTexture};
0475         } else {
0476             frame.cursor = {{}, {}, {}};
0477         }
0478     }
0479 
0480     if (spaBuffer->datas->chunk->size == 0 || spaBuffer->datas->chunk->flags == SPA_CHUNK_FLAG_CORRUPTED) {
0481         // do not get a frame
0482     } else if (spaBuffer->datas->type == SPA_DATA_MemFd) {
0483         if (spaBuffer->datas->chunk->size == 0)
0484             return;
0485 
0486         uint8_t *map =
0487             static_cast<uint8_t *>(mmap(nullptr, spaBuffer->datas->maxsize + spaBuffer->datas->mapoffset, PROT_READ, MAP_PRIVATE, spaBuffer->datas->fd, 0));
0488 
0489         if (map == MAP_FAILED) {
0490             qCWarning(PIPEWIRE_LOGGING) << "Failed to mmap the memory: " << strerror(errno);
0491             return;
0492         }
0493         QImage img =
0494             PWHelpers::SpaBufferToQImage(map, d->videoFormat.size.width, d->videoFormat.size.height, spaBuffer->datas->chunk->stride, d->videoFormat.format);
0495         frame.image = img.copy();
0496 
0497         munmap(map, spaBuffer->datas->maxsize + spaBuffer->datas->mapoffset);
0498     } else if (spaBuffer->datas->type == SPA_DATA_DmaBuf) {
0499         DmaBufAttributes attribs;
0500         attribs.planes.reserve(spaBuffer->n_datas);
0501         attribs.format = spaVideoFormatToDrmFormat(d->videoFormat.format);
0502         attribs.modifier = d->videoFormat.modifier;
0503         attribs.width = d->videoFormat.size.width;
0504         attribs.height = d->videoFormat.size.height;
0505 
0506         for (uint i = 0; i < spaBuffer->n_datas; ++i) {
0507             const auto &data = spaBuffer->datas[i];
0508 
0509             DmaBufPlane plane;
0510             plane.fd = data.fd;
0511             plane.stride = data.chunk->stride;
0512             plane.offset = data.chunk->offset;
0513             attribs.planes += plane;
0514         }
0515         Q_ASSERT(!attribs.planes.isEmpty());
0516         frame.dmabuf = attribs;
0517     } else if (spaBuffer->datas->type == SPA_DATA_MemPtr) {
0518         frame.image = PWHelpers::SpaBufferToQImage(static_cast<uint8_t *>(spaBuffer->datas->data),
0519                                                    d->videoFormat.size.width,
0520                                                    d->videoFormat.size.height,
0521                                                    spaBuffer->datas->chunk->stride,
0522                                                    d->videoFormat.format);
0523     } else {
0524         if (spaBuffer->datas->type == SPA_ID_INVALID)
0525             qWarning() << "invalid buffer type";
0526         else
0527             qWarning() << "unsupported buffer type" << spaBuffer->datas->type;
0528         QImage errorImage(200, 200, QImage::Format_ARGB32_Premultiplied);
0529         errorImage.fill(Qt::red);
0530         frame.image = errorImage;
0531     }
0532 
0533     Q_EMIT frameReceived(frame);
0534 }
0535 
0536 void PipeWireSourceStream::coreFailed(const QString &errorMessage)
0537 {
0538     qCDebug(PIPEWIRE_LOGGING) << "received error message" << errorMessage;
0539     d->m_error = errorMessage;
0540     Q_EMIT stopStreaming();
0541 }
0542 
0543 void PipeWireSourceStream::process()
0544 {
0545     pw_buffer *buf = pw_stream_dequeue_buffer(d->pwStream);
0546     if (!buf) {
0547         qCDebug(PIPEWIRE_LOGGING) << "out of buffers";
0548         return;
0549     }
0550 
0551     handleFrame(buf);
0552 
0553     pw_stream_queue_buffer(d->pwStream, buf);
0554 }
0555 
0556 void PipeWireSourceStream::setActive(bool active)
0557 {
0558     Q_ASSERT(d->pwStream);
0559     pw_stream_set_active(d->pwStream, active);
0560 }
0561 
0562 void PipeWireSourceStream::setDamageEnabled(bool withDamage)
0563 {
0564     d->m_withDamage = withDamage;
0565 }