File indexing completed on 2024-05-19 09:23:10

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