File indexing completed on 2024-05-05 05:30:19

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