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