Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package kpipewire for openSUSE:Factory checked in at 2023-04-05 21:26:51 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/kpipewire (Old) and /work/SRC/openSUSE:Factory/.kpipewire.new.19717 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "kpipewire" Wed Apr 5 21:26:51 2023 rev:12 rq:1077311 version:5.27.4 Changes: -------- --- /work/SRC/openSUSE:Factory/kpipewire/kpipewire.changes 2023-03-17 17:01:33.568830693 +0100 +++ /work/SRC/openSUSE:Factory/.kpipewire.new.19717/kpipewire.changes 2023-04-05 21:34:44.906257984 +0200 @@ -1,0 +2,20 @@ +Tue Apr 4 15:05:35 UTC 2023 - Fabian Vogt <[email protected]> + +- Update to 5.27.4 + * New bugfix release + * For more details please see: + * https://kde.org/announcements/plasma/5/5.27.4 +- Changes since 5.27.3: + * source: Handle BGRA buffers gracefully + * record: Only create the sws_context when necessary + * record: Use a good amount of threads as recommended by QThread + * record: Make sure we process all the frames before leaving + * record: Improve packet fetching + * Use a different API call to make importing DmaBufs work on Nvidia (kde#448839) + * options to disable motion estimation and in-loop filtering + * record: Refactor thread distribution + * record: Allocate SwsContext only when necessary + * recording: Allocate frames when we render + * recording: Extend the encoders API + +------------------------------------------------------------------- Old: ---- kpipewire-5.27.3.tar.xz kpipewire-5.27.3.tar.xz.sig New: ---- kpipewire-5.27.4.tar.xz kpipewire-5.27.4.tar.xz.sig ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ kpipewire.spec ++++++ --- /var/tmp/diff_new_pack.PQhYhO/_old 2023-04-05 21:34:45.398260793 +0200 +++ /var/tmp/diff_new_pack.PQhYhO/_new 2023-04-05 21:34:45.410260861 +0200 @@ -21,7 +21,7 @@ %{!?_plasma5_bugfix: %global _plasma5_bugfix %{version}} %bcond_without released Name: kpipewire -Version: 5.27.3 +Version: 5.27.4 Release: 0 Summary: PipeWire integration for KDE Plasma License: LGPL-2.0-only AND LGPL-3.0-only ++++++ kpipewire-5.27.3.tar.xz -> kpipewire-5.27.4.tar.xz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kpipewire-5.27.3/CMakeLists.txt new/kpipewire-5.27.4/CMakeLists.txt --- old/kpipewire-5.27.3/CMakeLists.txt 2023-03-14 13:23:59.000000000 +0100 +++ new/kpipewire-5.27.4/CMakeLists.txt 2023-04-04 12:38:03.000000000 +0200 @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.16) project(KPipewire) -set(PROJECT_VERSION "5.27.3") +set(PROJECT_VERSION "5.27.4") set(PROJECT_VERSION_MAJOR 5) set(KF5_MIN_VERSION "5.102.0") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kpipewire-5.27.3/po/de/kpipewire5.po new/kpipewire-5.27.4/po/de/kpipewire5.po --- old/kpipewire-5.27.3/po/de/kpipewire5.po 2023-03-14 13:23:59.000000000 +0100 +++ new/kpipewire-5.27.4/po/de/kpipewire5.po 2023-04-04 12:38:03.000000000 +0200 @@ -16,7 +16,7 @@ "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=n != 1;\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Lokalize 21.12.2\n" #: pipewirecore.cpp:86 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kpipewire-5.27.3/po/zh_CN/kpipewire5.po new/kpipewire-5.27.4/po/zh_CN/kpipewire5.po --- old/kpipewire-5.27.3/po/zh_CN/kpipewire5.po 2023-03-14 13:23:59.000000000 +0100 +++ new/kpipewire-5.27.4/po/zh_CN/kpipewire5.po 2023-04-04 12:38:03.000000000 +0200 @@ -3,7 +3,7 @@ "Project-Id-Version: kdeorg\n" "Report-Msgid-Bugs-To: https://bugs.kde.org\n" "POT-Creation-Date: 2022-05-31 00:52+0000\n" -"PO-Revision-Date: 2023-03-11 04:55\n" +"PO-Revision-Date: 2023-03-27 12:02\n" "Last-Translator: \n" "Language-Team: Chinese Simplified\n" "Language: zh_CN\n" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kpipewire-5.27.3/src/CMakeLists.txt new/kpipewire-5.27.4/src/CMakeLists.txt --- old/kpipewire-5.27.3/src/CMakeLists.txt 2023-03-14 13:23:59.000000000 +0100 +++ new/kpipewire-5.27.4/src/CMakeLists.txt 2023-04-04 12:38:03.000000000 +0200 @@ -42,6 +42,7 @@ pipewiresourceitem.cpp pipewiresourcestream.cpp glhelpers.cpp + pwhelpers.cpp ${kpipewire_SRCS} ) @@ -120,6 +121,8 @@ epoxy::epoxy Libdrm::Libdrm Qt::GuiPrivate ) +target_compile_definitions(KPipeWireRecord INTERFACE -DKPW_WITH_SUGGESTED=1) + ecm_generate_headers(KPipeWireRecord_HEADERS HEADER_NAMES PipeWireRecord diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kpipewire-5.27.3/src/dmabufhandler.cpp new/kpipewire-5.27.4/src/dmabufhandler.cpp --- old/kpipewire-5.27.3/src/dmabufhandler.cpp 2023-03-14 13:23:59.000000000 +0100 +++ new/kpipewire-5.27.4/src/dmabufhandler.cpp 2023-04-04 12:38:03.000000000 +0200 @@ -193,6 +193,7 @@ GLHelpers::initDebugOutput(); // create GL 2D texture for framebuffer GLuint texture; + GLuint fbo; glGenTextures(1, &texture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); @@ -201,8 +202,20 @@ glBindTexture(GL_TEXTURE_2D, texture); glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image); - glGetTexImage(GL_TEXTURE_2D, 0, closestGLType(qimage), GL_UNSIGNED_BYTE, qimage.bits()); + glGenFramebuffers(1, &fbo); + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, + texture, 0); + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + glDeleteFramebuffers(1, &fbo); + glDeleteTextures(1, &texture); + eglDestroyImageKHR(m_egl.display, image); + return false; + } + glReadPixels(0, 0, frame.dmabuf->width, frame.dmabuf->height, closestGLType(qimage), GL_UNSIGNED_BYTE, qimage.bits()); + + glDeleteFramebuffers(1, &fbo); glDeleteTextures(1, &texture); eglDestroyImageKHR(m_egl.display, image); return true; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kpipewire-5.27.3/src/pipewirerecord.cpp new/kpipewire-5.27.4/src/pipewirerecord.cpp --- old/kpipewire-5.27.3/src/pipewirerecord.cpp 2023-03-14 13:23:59.000000000 +0100 +++ new/kpipewire-5.27.4/src/pipewirerecord.cpp 2023-04-04 12:38:03.000000000 +0200 @@ -81,12 +81,34 @@ AVFrame *m_avFrame; }; +static AVPixelFormat convertQImageFormatToAVPixelFormat(QImage::Format format) +{ + // Listing those handed by SpaToQImageFormat + switch (format) { + case QImage::Format_BGR888: + return AV_PIX_FMT_BGR24; + case QImage::Format_RGBX8888: + case QImage::Format_RGBA8888_Premultiplied: + return AV_PIX_FMT_RGBA; + case QImage::Format_RGB32: + return AV_PIX_FMT_RGB32; + default: + qDebug() << "Unexpected pixel format" << format; + return AV_PIX_FMT_RGB32; + } +} + +Q_DECLARE_METATYPE(std::optional<int>); +Q_DECLARE_METATYPE(std::optional<std::chrono::nanoseconds>); + PipeWireRecord::PipeWireRecord(QObject *parent) : QObject(parent) , d(new PipeWireRecordPrivate) { d->m_encoder = "libvpx"; av_log_set_level(AV_LOG_DEBUG); + qRegisterMetaType<std::optional<int>>(); + qRegisterMetaType<std::optional<std::chrono::nanoseconds>>(); } PipeWireRecord::~PipeWireRecord() @@ -208,8 +230,8 @@ disconnect(m_stream.data(), &PipeWireSourceStream::frameReceived, this, &PipeWireRecordProduce::processFrame); if (m_writeThread) { - m_writeThread->drain(); - bool done = QThreadPool::globalInstance()->waitForDone(-1); + m_writeThread->quit(); + bool done = m_writeThread->wait(); Q_ASSERT(done); } @@ -228,6 +250,15 @@ return QStringLiteral("webm"); } +QString PipeWireRecord::currentExtension() const +{ + static QHash<QByteArray, QString> s_extensions = { + {"libx264", QStringLiteral("mp4")}, + {"libvpx", QStringLiteral("webm")}, + }; + return s_extensions.value(d->m_encoder, QStringLiteral("mkv")); +} + void PipeWireRecordProduce::setupStream() { qCDebug(PIPEWIRERECORD_LOGGING) << "Setting up stream"; @@ -265,7 +296,7 @@ m_avCodecContext->width = size.width(); m_avCodecContext->height = size.height(); m_avCodecContext->max_b_frames = 1; - m_avCodecContext->gop_size = 1; + m_avCodecContext->gop_size = 100; if (m_codec->pix_fmts && m_codec->pix_fmts[0] > 0) { m_avCodecContext->pix_fmt = m_codec->pix_fmts[0]; } else { @@ -274,10 +305,17 @@ m_avCodecContext->time_base = AVRational{1, 1000}; AVDictionary *options = nullptr; - av_dict_set_int(&options, "threads", 4, 0); - av_dict_set(&options, "preset", "ultrafast", 0); + av_dict_set_int(&options, "threads", qMin(16, QThread::idealThreadCount()), 0); + av_dict_set(&options, "preset", "veryfast", 0); av_dict_set(&options, "tune-content", "screen", 0); - av_dict_set(&options, "deadline", "good", 0); + av_dict_set(&options, "deadline", "realtime", 0); + // In theory a lower number should be faster, but the opposite seems to be true + av_dict_set(&options, "quality", "40", 0); + av_dict_set(&options, "cpu-used", "6", 0); + // Disable motion estimation, not great while dragging windows but speeds up encoding by an order of magnitude + av_dict_set(&options, "flags", "+mv4", 0); + // Disable in-loop filtering + av_dict_set(&options, "-flags", "+loop", 0); int ret = avcodec_open2(m_avCodecContext, m_codec, &options); if (ret < 0) { @@ -285,13 +323,6 @@ return; } - m_frame.reset(new CustomAVFrame); - ret = m_frame->alloc(m_avCodecContext->width, m_avCodecContext->height, m_avCodecContext->pix_fmt); - if (ret < 0) { - qCWarning(PIPEWIRERECORD_LOGGING) << "Could not allocate raw picture buffer" << av_err2str(ret); - return; - } - ret = avio_open(&m_avFormatContext->pb, QFile::encodeName(m_output).constData(), AVIO_FLAG_WRITE); if (ret < 0) { qCWarning(PIPEWIRERECORD_LOGGING) << "Could not open" << m_output << av_err2str(ret); @@ -318,8 +349,8 @@ } connect(m_stream.data(), &PipeWireSourceStream::frameReceived, this, &PipeWireRecordProduce::processFrame); - m_writeThread = new PipeWireRecordWriteThread(&m_bufferNotEmpty, m_avFormatContext, m_avCodecContext); - QThreadPool::globalInstance()->start(m_writeThread); + m_writeThread = new PipeWireRecordWriteThread(this, m_avFormatContext, m_avCodecContext); + m_writeThread->start(); } void PipeWireRecordProduce::processFrame(const PipeWireFrame &frame) @@ -381,23 +412,6 @@ render(frame); } -static AVPixelFormat convertQImageFormatToAVPixelFormat(QImage::Format format) -{ - // Listing those handed by SpaToQImageFormat - switch (format) { - case QImage::Format_BGR888: - return AV_PIX_FMT_BGR24; - case QImage::Format_RGBX8888: - case QImage::Format_RGBA8888_Premultiplied: - return AV_PIX_FMT_RGBA; - case QImage::Format_RGB32: - return AV_PIX_FMT_RGB32; - default: - qDebug() << "Unexpected pixel format" << format; - return AV_PIX_FMT_RGB32; - } -} - void PipeWireRecordProduce::render(const PipeWireFrame &frame) { Q_ASSERT(!m_frameWithoutMetadataCursor.isNull()); @@ -409,53 +423,7 @@ p.drawImage(*m_cursor.position, m_cursor.texture); } - const std::uint8_t *buffers[] = {image.constBits(), nullptr}; - const int strides[] = {static_cast<int>(image.bytesPerLine()), 0, 0, 0}; - struct SwsContext *sws_context = nullptr; - sws_context = sws_getCachedContext(sws_context, - image.width(), - image.height(), - convertQImageFormatToAVPixelFormat(image.format()), - m_avCodecContext->width, - m_avCodecContext->height, - m_avCodecContext->pix_fmt, - 0, - nullptr, - nullptr, - nullptr); - sws_scale(sws_context, buffers, strides, 0, m_avCodecContext->height, m_frame->m_avFrame->data, m_frame->m_avFrame->linesize); - - if (frame.presentationTimestamp.has_value()) { - const auto current = std::chrono::duration_cast<std::chrono::milliseconds>(*frame.presentationTimestamp).count(); - if ((*m_avFormatContext->streams)->start_time == 0) { - (*m_avFormatContext->streams)->start_time = current; - } - - Q_ASSERT((*m_avFormatContext->streams)->start_time <= current); - m_frame->m_avFrame->pts = current - (*m_avFormatContext->streams)->start_time; - } else { - m_frame->m_avFrame->pts = AV_NOPTS_VALUE; - } - - // Let's add a key frame every 100 frames and also the first frame - if (frame.sequential && (*frame.sequential == 0 || (*frame.sequential - m_lastKeyFrame) > 100)) { - m_frame->m_avFrame->key_frame = 1; - m_lastKeyFrame = *frame.sequential; - } - - if (m_lastPts > 0 && m_frame->m_avFrame->pts <= m_lastPts) { - // Make sure we don't have two frames at the same presentation time - m_frame->m_avFrame->pts = m_lastPts + 1; - } - m_lastPts = m_frame->m_avFrame->pts; - - const int ret = avcodec_send_frame(m_avCodecContext, m_frame->m_avFrame); - // qDebug() << "issued" << m_frame->m_avFrame->pts; - if (ret < 0) { - qCWarning(PIPEWIRERECORD_LOGGING) << "Error sending a frame for encoding:" << av_err2str(ret); - return; - } - m_bufferNotEmpty.wakeAll(); + Q_EMIT producedFrame(image, frame.sequential, frame.presentationTimestamp); } static void log_packet(const AVFormatContext *fmt_ctx, const AVPacket *pkt) @@ -476,7 +444,25 @@ void PipeWireRecord::setEncoder(const QByteArray &encoder) { + if (d->m_encoder == encoder) { + return; + } d->m_encoder = encoder; + Q_EMIT encoderChanged(); +} + +QByteArray PipeWireRecord::encoder() const +{ + return d->m_encoder; +} + +QList<QByteArray> PipeWireRecord::suggestedEncoders() const +{ + QList<QByteArray> ret = {"libvpx", "libx264"}; + std::remove_if(ret.begin(), ret.end(), [](const QByteArray &encoder) { + return !avcodec_find_encoder_by_name(encoder.constData()); + }); + return ret; } QString PipeWireRecord::output() const @@ -499,39 +485,94 @@ return d->m_fd.value_or(0); } -PipeWireRecordWriteThread::PipeWireRecordWriteThread(QWaitCondition *notEmpty, AVFormatContext *avFormatContext, AVCodecContext *avCodecContext) - : QRunnable() +PipeWireRecordWrite::PipeWireRecordWrite(PipeWireRecordProduce *produce, AVFormatContext *avFormatContext, AVCodecContext *avCodecContext) + : QObject() , m_packet(av_packet_alloc()) , m_avFormatContext(avFormatContext) , m_avCodecContext(avCodecContext) - , m_bufferNotEmpty(notEmpty) { + connect(produce, &PipeWireRecordProduce::producedFrame, this, &PipeWireRecordWrite::addFrame); } -PipeWireRecordWriteThread::~PipeWireRecordWriteThread() +PipeWireRecordWrite::~PipeWireRecordWrite() { + int ret = av_write_trailer(m_avFormatContext); + if (ret < 0) { + qCWarning(PIPEWIRERECORD_LOGGING) << "failed to write trailer" << av_err2str(ret); + } av_packet_free(&m_packet); } -void PipeWireRecordWriteThread::run() +PipeWireRecordWriteThread::PipeWireRecordWriteThread(PipeWireRecordProduce *produce, AVFormatContext *avFormatContext, AVCodecContext *avCodecContext) + : QThread(produce) + , m_produce(produce) + , m_avFormatContext(avFormatContext) + , m_avCodecContext(avCodecContext) +{ +} + +void PipeWireRecordWrite::addFrame(const QImage &image, std::optional<int> sequential, std::optional<std::chrono::nanoseconds> presentationTimestamp) { - QMutex mutex; - int ret = 0; - while (true) { + if (!sws_context || m_lastReceivedSize != image.size()) { + sws_context = sws_getCachedContext(sws_context, + image.width(), + image.height(), + convertQImageFormatToAVPixelFormat(image.format()), + m_avCodecContext->width, + m_avCodecContext->height, + m_avCodecContext->pix_fmt, + 0, + nullptr, + nullptr, + nullptr); + } + + CustomAVFrame avFrame; + int ret = avFrame.alloc(m_avCodecContext->width, m_avCodecContext->height, m_avCodecContext->pix_fmt); + if (ret < 0) { + qCWarning(PIPEWIRERECORD_LOGGING) << "Could not allocate raw picture buffer" << av_err2str(ret); + return; + } + const std::uint8_t *buffers[] = {image.constBits(), nullptr}; + const int strides[] = {static_cast<int>(image.bytesPerLine()), 0, 0, 0}; + sws_scale(sws_context, buffers, strides, 0, m_avCodecContext->height, avFrame.m_avFrame->data, avFrame.m_avFrame->linesize); + + if (presentationTimestamp.has_value()) { + const auto current = std::chrono::duration_cast<std::chrono::milliseconds>(*presentationTimestamp).count(); + if ((*m_avFormatContext->streams)->start_time == 0) { + (*m_avFormatContext->streams)->start_time = current; + } + + Q_ASSERT((*m_avFormatContext->streams)->start_time <= current); + avFrame.m_avFrame->pts = current - (*m_avFormatContext->streams)->start_time; + } else { + avFrame.m_avFrame->pts = AV_NOPTS_VALUE; + } + + // Let's add a key frame every 100 frames and also the first frame + if (sequential && (*sequential == 0 || (*sequential - m_lastKeyFrame) > 100)) { + avFrame.m_avFrame->key_frame = 1; + m_lastKeyFrame = *sequential; + } + + if (m_lastPts > 0 && avFrame.m_avFrame->pts <= m_lastPts) { + // Make sure we don't have two frames at the same presentation time + avFrame.m_avFrame->pts = m_lastPts + 1; + } + m_lastPts = avFrame.m_avFrame->pts; + + ret = avcodec_send_frame(m_avCodecContext, avFrame.m_avFrame); + if (ret < 0) { + qCWarning(PIPEWIRERECORD_LOGGING) << "Error sending a frame for encoding:" << av_err2str(ret); + return; + } + for (;;) { ret = avcodec_receive_packet(m_avCodecContext, m_packet); - if (ret == AVERROR_EOF) { - break; - } else if (ret == AVERROR(EAGAIN)) { - if (m_active) { - m_bufferNotEmpty->wait(&mutex); - } else { - int sent = avcodec_send_frame(m_avCodecContext, nullptr); - qCDebug(PIPEWIRERECORD_LOGGING) << "draining" << sent; + if (ret < 0) { + if (ret != AVERROR_EOF && ret != AVERROR(EAGAIN)) { + qCWarning(PIPEWIRERECORD_LOGGING) << "Error encoding a frame: " << av_err2str(ret) << ret; } - continue; - } else if (ret < 0) { - qCWarning(PIPEWIRERECORD_LOGGING) << "Error encoding a frame: " << av_err2str(ret); - continue; + break; } m_packet->stream_index = (*m_avFormatContext->streams)->index; @@ -540,17 +581,30 @@ ret = av_interleaved_write_frame(m_avFormatContext, m_packet); if (ret < 0) { qCWarning(PIPEWIRERECORD_LOGGING) << "Error while writing output packet:" << av_err2str(ret); - continue; } - } - ret = av_write_trailer(m_avFormatContext); - if (ret < 0) { - qCWarning(PIPEWIRERECORD_LOGGING) << "failed to write trailer" << av_err2str(ret); + av_packet_unref(m_packet); } } -void PipeWireRecordWriteThread::drain() +void PipeWireRecordWriteThread::run() { - m_active = false; - m_bufferNotEmpty->wakeAll(); + PipeWireRecordWrite writer(m_produce, m_avFormatContext, m_avCodecContext); + QThread::exec(); + AVPacket *pkt = av_packet_alloc(); + avcodec_send_frame(m_avCodecContext, nullptr); + + for (;;) { + if (avcodec_receive_packet(m_avCodecContext, pkt) < 0) + break; + + pkt->stream_index = (*m_avFormatContext->streams)->index; + av_packet_rescale_ts(pkt, m_avCodecContext->time_base, (*m_avFormatContext->streams)->time_base); + log_packet(m_avFormatContext, pkt); + int ret = av_interleaved_write_frame(m_avFormatContext, pkt); + if (ret < 0) { + qCWarning(PIPEWIRERECORD_LOGGING) << "Error while writing output packet:" << av_err2str(ret); + } + av_packet_unref(pkt); + } + av_packet_free(&pkt); } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kpipewire-5.27.3/src/pipewirerecord.h new/kpipewire-5.27.4/src/pipewirerecord.h --- old/kpipewire-5.27.3/src/pipewirerecord.h 2023-03-14 13:23:59.000000000 +0100 +++ new/kpipewire-5.27.4/src/pipewirerecord.h 2023-04-04 12:38:03.000000000 +0200 @@ -26,6 +26,8 @@ Q_PROPERTY(bool active READ isActive WRITE setActive NOTIFY activeChanged) Q_PROPERTY(QString output READ output WRITE setOutput NOTIFY outputChanged) Q_PROPERTY(State state READ state NOTIFY stateChanged) + Q_PROPERTY(QString extension READ extension NOTIFY encoderChanged) + Q_PROPERTY(QByteArray encoder READ encoder WRITE setEncoder NOTIFY encoderChanged) public: PipeWireRecord(QObject *parent = nullptr); ~PipeWireRecord() override; @@ -57,8 +59,13 @@ * ffmpeg -encoders | grep "^ V" */ void setEncoder(const QByteArray &encoder); + QByteArray encoder() const; - static QString extension(); + /// Returns the encoders that are tested to work, sorted by preference + QList<QByteArray> suggestedEncoders() const; + + QString currentExtension() const; + Q_DECL_DEPRECATED static QString extension(); Q_SIGNALS: void activeChanged(bool active); @@ -67,6 +74,7 @@ void outputChanged(const QString &output); void errorFound(const QString &error); void stateChanged(); + void encoderChanged(); private: void refresh(); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kpipewire-5.27.3/src/pipewirerecord_p.h new/kpipewire-5.27.4/src/pipewirerecord_p.h --- old/kpipewire-5.27.3/src/pipewirerecord_p.h 2023-03-14 13:23:59.000000000 +0100 +++ new/kpipewire-5.27.4/src/pipewirerecord_p.h 2023-04-04 12:38:03.000000000 +0200 @@ -31,27 +31,45 @@ struct AVFormatContext; struct AVPacket; class CustomAVFrame; +class PipeWireRecordProduce; struct gbm_device; -class PipeWireRecordWriteThread : public QRunnable +class PipeWireRecordWrite : public QObject { public: - PipeWireRecordWriteThread(QWaitCondition *notEmpty, AVFormatContext *avFormatContext, AVCodecContext *avCodecContext); - ~PipeWireRecordWriteThread(); + PipeWireRecordWrite(PipeWireRecordProduce *produce, AVFormatContext *avFormatContext, AVCodecContext *avCodecContext); + ~PipeWireRecordWrite(); - void run() override; - void drain(); + void addFrame(const QImage &image, std::optional<int> sequential, std::optional<std::chrono::nanoseconds> presentationTimestamp); private: QAtomicInt m_active = true; AVPacket *m_packet; AVFormatContext *const m_avFormatContext; AVCodecContext *const m_avCodecContext; - QWaitCondition *const m_bufferNotEmpty; + struct SwsContext *sws_context = nullptr; + int64_t m_lastPts = -1; + uint m_lastKeyFrame = 0; + QSize m_lastReceivedSize; +}; + +class PipeWireRecordWriteThread : public QThread +{ +public: + PipeWireRecordWriteThread(PipeWireRecordProduce *produce, AVFormatContext *avFormatContext, AVCodecContext *avCodecContext); + + void run() override; + void drain(); + +private: + PipeWireRecordProduce *const m_produce; + AVFormatContext *const m_avFormatContext; + AVCodecContext *const m_avCodecContext; }; class PipeWireRecordProduce : public QObject { + Q_OBJECT public: PipeWireRecordProduce(const QByteArray &encoder, uint nodeId, uint fd, const QString &output); ~PipeWireRecordProduce() override; @@ -61,6 +79,9 @@ return m_error; } +Q_SIGNALS: + void producedFrame(const QImage &image, std::optional<int> sequential, std::optional<std::chrono::nanoseconds> presentationTimestamp); + private: friend class PipeWireRecordProduceThread; void setupStream(); @@ -78,11 +99,8 @@ QString m_error; PipeWireRecordWriteThread *m_writeThread = nullptr; - QWaitCondition m_bufferNotEmpty; const QByteArray m_encoder; - QScopedPointer<CustomAVFrame> m_frame; - struct { QImage texture; std::optional<QPoint> position; @@ -91,8 +109,6 @@ } m_cursor; QImage m_frameWithoutMetadataCursor; DmaBufHandler m_dmabufHandler; - uint m_lastKeyFrame = 0; - int64_t m_lastPts = -1; QAtomicInt m_deactivated = false; }; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kpipewire-5.27.3/src/pipewiresourcestream.cpp new/kpipewire-5.27.4/src/pipewiresourcestream.cpp --- old/kpipewire-5.27.3/src/pipewiresourcestream.cpp 2023-03-14 13:23:59.000000000 +0100 +++ new/kpipewire-5.27.4/src/pipewiresourcestream.cpp 2023-04-04 12:38:03.000000000 +0200 @@ -10,6 +10,7 @@ #include "glhelpers.h" #include "logging.h" #include "pipewirecore.h" +#include "pwhelpers.h" #include <libdrm/drm_fourcc.h> #include <spa/utils/result.h> @@ -18,11 +19,10 @@ #include <unistd.h> #include <QGuiApplication> -#include <QLoggingCategory> #include <QOpenGLTexture> #include <QSocketNotifier> -#include <QVersionNumber> #include <QThread> +#include <QVersionNumber> #include <qpa/qplatformnativeinterface.h> #include <KLocalizedString> @@ -95,26 +95,6 @@ } } -QImage::Format SpaToQImageFormat(quint32 format) -{ - switch (format) { - case SPA_VIDEO_FORMAT_BGRx: - case SPA_VIDEO_FORMAT_BGRA: - return QImage::Format_RGBA8888_Premultiplied; // TODO: Add BGR to QImage - case SPA_VIDEO_FORMAT_BGR: - return QImage::Format_BGR888; - case SPA_VIDEO_FORMAT_RGBx: - return QImage::Format_RGBX8888; - case SPA_VIDEO_FORMAT_RGB: - return QImage::Format_RGB888; - case SPA_VIDEO_FORMAT_RGBA: - return QImage::Format_RGBA8888_Premultiplied; - default: - qCWarning(PIPEWIRE_LOGGING) << "unknown spa format" << format; - return QImage::Format_RGB32; - } -} - static QHash<spa_video_format, QVector<uint64_t>> queryDmaBufModifiers(EGLDisplay display, const QVector<spa_video_format> &formats) { QHash<spa_video_format, QVector<uint64_t>> ret; @@ -488,7 +468,8 @@ QImage cursorTexture; if (bitmap && bitmap->size.width > 0 && bitmap->size.height > 0) { const uint8_t *bitmap_data = SPA_MEMBER(bitmap, bitmap->offset, uint8_t); - cursorTexture = QImage(bitmap_data, bitmap->size.width, bitmap->size.height, bitmap->stride, SpaToQImageFormat(bitmap->format)); + cursorTexture = + PWHelpers::SpaBufferToQImage(bitmap_data, bitmap->size.width, bitmap->size.height, bitmap->stride, spa_video_format(bitmap->format)); } frame.cursor = {{cursor->position.x, cursor->position.y}, {cursor->hotspot.x, cursor->hotspot.y}, cursorTexture}; } else { @@ -509,7 +490,8 @@ qCWarning(PIPEWIRE_LOGGING) << "Failed to mmap the memory: " << strerror(errno); return; } - QImage img(map, d->videoFormat.size.width, d->videoFormat.size.height, spaBuffer->datas->chunk->stride, SpaToQImageFormat(d->videoFormat.format)); + QImage img = + PWHelpers::SpaBufferToQImage(map, d->videoFormat.size.width, d->videoFormat.size.height, spaBuffer->datas->chunk->stride, d->videoFormat.format); frame.image = img.copy(); munmap(map, spaBuffer->datas->maxsize + spaBuffer->datas->mapoffset); @@ -533,11 +515,11 @@ Q_ASSERT(!attribs.planes.isEmpty()); frame.dmabuf = attribs; } else if (spaBuffer->datas->type == SPA_DATA_MemPtr) { - frame.image = QImage(static_cast<uint8_t *>(spaBuffer->datas->data), - d->videoFormat.size.width, - d->videoFormat.size.height, - spaBuffer->datas->chunk->stride, - SpaToQImageFormat(d->videoFormat.format)); + frame.image = PWHelpers::SpaBufferToQImage(static_cast<uint8_t *>(spaBuffer->datas->data), + d->videoFormat.size.width, + d->videoFormat.size.height, + spaBuffer->datas->chunk->stride, + d->videoFormat.format); } else { if (spaBuffer->datas->type == SPA_ID_INVALID) qWarning() << "invalid buffer type"; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kpipewire-5.27.3/src/pwhelpers.cpp new/kpipewire-5.27.4/src/pwhelpers.cpp --- old/kpipewire-5.27.3/src/pwhelpers.cpp 1970-01-01 01:00:00.000000000 +0100 +++ new/kpipewire-5.27.4/src/pwhelpers.cpp 2023-04-04 12:38:03.000000000 +0200 @@ -0,0 +1,46 @@ +/* + SPDX-FileCopyrightText: 2023 Aleix Pol Gonzalez <[email protected]> + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +#include "pwhelpers.h" +#include "logging.h" + +QImage::Format SpaToQImageFormat(quint32 format) +{ + switch (format) { + case SPA_VIDEO_FORMAT_BGRx: + case SPA_VIDEO_FORMAT_BGRA: + return QImage::Format_RGBA8888_Premultiplied; // TODO: Add BGR to QImage + case SPA_VIDEO_FORMAT_BGR: + return QImage::Format_BGR888; + case SPA_VIDEO_FORMAT_RGBx: + return QImage::Format_RGBX8888; + case SPA_VIDEO_FORMAT_RGB: + return QImage::Format_RGB888; + case SPA_VIDEO_FORMAT_RGBA: + return QImage::Format_RGBA8888_Premultiplied; + default: + qCWarning(PIPEWIRE_LOGGING) << "unknown spa format" << format; + return QImage::Format_RGB32; + } +} + +QImage PWHelpers::SpaBufferToQImage(const uchar *data, int width, int height, qsizetype bytesPerLine, spa_video_format format) +{ + switch (format) { + case SPA_VIDEO_FORMAT_BGRx: + case SPA_VIDEO_FORMAT_BGRA: { + // This is needed because QImage does not support BGRA + // This is obviously a much slower path, it makes sense to avoid it as much as possible + return QImage(data, width, height, bytesPerLine, SpaToQImageFormat(format)).rgbSwapped(); + } + case SPA_VIDEO_FORMAT_BGR: + case SPA_VIDEO_FORMAT_RGBx: + case SPA_VIDEO_FORMAT_RGB: + case SPA_VIDEO_FORMAT_RGBA: + default: + return QImage(data, width, height, bytesPerLine, SpaToQImageFormat(format)); + } +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kpipewire-5.27.3/src/pwhelpers.h new/kpipewire-5.27.4/src/pwhelpers.h --- old/kpipewire-5.27.3/src/pwhelpers.h 1970-01-01 01:00:00.000000000 +0100 +++ new/kpipewire-5.27.4/src/pwhelpers.h 2023-04-04 12:38:03.000000000 +0200 @@ -0,0 +1,21 @@ +/* + SPDX-FileCopyrightText: 2023 Aleix Pol Gonzalez <[email protected]> + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +#pragma once + +#include "pipewiresourcestream.h" +#include <QByteArray> +#include <epoxy/egl.h> +#include <kpipewire_export.h> + +typedef unsigned int GLenum; + +namespace PWHelpers +{ + +KPIPEWIRE_EXPORT QImage SpaBufferToQImage(const uchar *data, int width, int height, qsizetype bytesPerLine, spa_video_format format); + +}
