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