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"