File indexing completed on 2024-04-28 09:21:51

0001 /*
0002     SPDX-FileCopyrightText: 2022 Aleix Pol Gonzalez <aleixpol@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0005 */
0006 
0007 #include "pipewirerecord.h"
0008 #include "encoder_p.h"
0009 #include "glhelpers.h"
0010 #include "pipewirerecord_p.h"
0011 #include <logging_record.h>
0012 
0013 #include <QGuiApplication>
0014 #include <QImage>
0015 #include <QPainter>
0016 #include <qpa/qplatformnativeinterface.h>
0017 
0018 #include <KShell>
0019 
0020 #include <unistd.h>
0021 extern "C" {
0022 #include <libavcodec/avcodec.h>
0023 #include <libavformat/avformat.h>
0024 #include <libavutil/timestamp.h>
0025 }
0026 
0027 #undef av_err2str
0028 
0029 #ifdef av_ts2str
0030 #undef av_ts2str
0031 char buf[AV_TS_MAX_STRING_SIZE];
0032 #define av_ts2str(ts) av_ts_make_string(buf, ts)
0033 #endif // av_ts2str
0034 
0035 #ifdef av_ts2timestr
0036 #undef av_ts2timestr
0037 char timebuf[AV_TS_MAX_STRING_SIZE];
0038 #define av_ts2timestr(ts, tb) av_ts_make_time_string(timebuf, ts, tb)
0039 #endif // av_ts2timestr
0040 
0041 static void log_packet(const AVFormatContext *fmt_ctx, const AVPacket *pkt)
0042 {
0043     AVRational *time_base = &fmt_ctx->streams[pkt->stream_index]->time_base;
0044 
0045     qCDebug(PIPEWIRERECORD_LOGGING,
0046             "pts:%s pts_time:%s dts:%s dts_time:%s duration:%s duration_time:%s "
0047             "stream_index:%d",
0048             av_ts2str(pkt->pts),
0049             av_ts2timestr(pkt->pts, time_base),
0050             av_ts2str(pkt->dts),
0051             av_ts2timestr(pkt->dts, time_base),
0052             av_ts2str(pkt->duration),
0053             av_ts2timestr(pkt->duration, time_base),
0054             pkt->stream_index);
0055 }
0056 
0057 PipeWireRecord::PipeWireRecord(QObject *parent)
0058     : PipeWireBaseEncodedStream(parent)
0059     , d(new PipeWireRecordPrivate)
0060 {
0061 }
0062 
0063 PipeWireRecord::~PipeWireRecord() = default;
0064 
0065 void PipeWireRecord::setOutput(const QString &_output)
0066 {
0067     const QString output = KShell::tildeExpand(_output);
0068 
0069     if (d->m_output == output)
0070         return;
0071 
0072     d->m_output = output;
0073     refresh();
0074     Q_EMIT outputChanged(output);
0075 }
0076 
0077 QString PipeWireRecord::output() const
0078 {
0079     return d->m_output;
0080 }
0081 
0082 QString PipeWireRecord::extension() const
0083 {
0084     static QHash<PipeWireBaseEncodedStream::Encoder, QString> s_extensions = {
0085         {PipeWireBaseEncodedStream::H264Main, QStringLiteral("mp4")},
0086         {PipeWireBaseEncodedStream::H264Baseline, QStringLiteral("mp4")},
0087         {PipeWireBaseEncodedStream::VP8, QStringLiteral("webm")},
0088         {PipeWireBaseEncodedStream::VP9, QStringLiteral("webm")},
0089     };
0090     return s_extensions.value(encoder());
0091 }
0092 
0093 PipeWireRecordProduce::PipeWireRecordProduce(PipeWireBaseEncodedStream::Encoder encoder, uint nodeId, uint fd, const Fraction &framerate, const QString &output)
0094     : PipeWireProduce(encoder, nodeId, fd, framerate)
0095     , m_output(output)
0096 {
0097 }
0098 
0099 bool PipeWireRecordProduce::setupFormat()
0100 {
0101     avformat_alloc_output_context2(&m_avFormatContext, nullptr, nullptr, m_output.toUtf8().constData());
0102     if (!m_avFormatContext) {
0103         qCWarning(PIPEWIRERECORD_LOGGING) << "Could not deduce output format from file: using WebM." << m_output;
0104         avformat_alloc_output_context2(&m_avFormatContext, nullptr, "webm", m_output.toUtf8().constData());
0105     }
0106     if (!m_avFormatContext) {
0107         qCDebug(PIPEWIRERECORD_LOGGING) << "could not set stream up";
0108         return false;
0109     }
0110 
0111     const Fraction framerate = m_stream->framerate();
0112     int ret = avio_open(&m_avFormatContext->pb, QFile::encodeName(m_output).constData(), AVIO_FLAG_WRITE);
0113     if (ret < 0) {
0114         qCWarning(PIPEWIRERECORD_LOGGING) << "Could not open" << m_output << av_err2str(ret);
0115         return false;
0116     }
0117 
0118     auto avStream = avformat_new_stream(m_avFormatContext, nullptr);
0119     avStream->start_time = 0;
0120     if (framerate) {
0121         avStream->r_frame_rate.num = framerate.numerator;
0122         avStream->r_frame_rate.den = framerate.denominator;
0123         avStream->avg_frame_rate.num = framerate.numerator;
0124         avStream->avg_frame_rate.den = framerate.denominator;
0125     }
0126 
0127     ret = avcodec_parameters_from_context(avStream->codecpar, m_encoder->avCodecContext());
0128     if (ret < 0) {
0129         qCWarning(PIPEWIRERECORD_LOGGING) << "Error occurred when passing the codec:" << av_err2str(ret);
0130         return false;
0131     }
0132 
0133     ret = avformat_write_header(m_avFormatContext, nullptr);
0134     if (ret < 0) {
0135         qCWarning(PIPEWIRERECORD_LOGGING) << "Error occurred when writing header:" << av_err2str(ret);
0136         return false;
0137     }
0138 
0139     return true;
0140 }
0141 
0142 void PipeWireRecordProduce::processFrame(const PipeWireFrame &frame)
0143 {
0144     PipeWireProduce::processFrame(frame);
0145     if (frame.cursor && !frame.dmabuf && !frame.image && !m_frameWithoutMetadataCursor.isNull()) {
0146         PipeWireFrame frame;
0147         frame.image = m_frameWithoutMetadataCursor;
0148         m_encoder->filterFrame(frame);
0149     }
0150 }
0151 
0152 void PipeWireRecordProduce::aboutToEncode(PipeWireFrame &frame)
0153 {
0154     if (!frame.image) {
0155         return;
0156     }
0157 
0158     if (m_cursor.position && !m_cursor.texture.isNull()) {
0159         auto &image = *frame.image;
0160         // Do not copy the image if it's already ours
0161         if (m_frameWithoutMetadataCursor.cacheKey() != image.cacheKey()) {
0162             m_frameWithoutMetadataCursor = image.copy();
0163         }
0164         QPainter p(&image);
0165         p.drawImage(*m_cursor.position, m_cursor.texture);
0166     }
0167 }
0168 
0169 void PipeWireRecordProduce::processPacket(AVPacket *packet)
0170 {
0171     packet->stream_index = (*m_avFormatContext->streams)->index;
0172     av_packet_rescale_ts(packet, m_encoder->avCodecContext()->time_base, (*m_avFormatContext->streams)->time_base);
0173     log_packet(m_avFormatContext, packet);
0174     auto ret = av_interleaved_write_frame(m_avFormatContext, packet);
0175     if (ret < 0) {
0176         qCWarning(PIPEWIRERECORD_LOGGING) << "Error while writing output packet:" << av_err2str(ret);
0177     }
0178 }
0179 
0180 std::unique_ptr<PipeWireProduce> PipeWireRecord::makeProduce()
0181 {
0182     return std::make_unique<PipeWireRecordProduce>(encoder(), nodeId(), fd(), maxFramerate(), d->m_output);
0183 }
0184 
0185 int64_t PipeWireRecordProduce::framePts(const std::optional<std::chrono::nanoseconds> &presentationTimestamp)
0186 {
0187     const auto current = std::chrono::duration_cast<std::chrono::milliseconds>(*presentationTimestamp).count();
0188     if ((*m_avFormatContext->streams)->start_time == 0) {
0189         (*m_avFormatContext->streams)->start_time = current;
0190     }
0191 
0192     return current - (*m_avFormatContext->streams)->start_time;
0193 }
0194 
0195 void PipeWireRecordProduce::cleanup()
0196 {
0197     // Clear the queue of encoded packets.
0198     m_encoder->receivePacket();
0199 
0200     if (auto result = av_write_trailer(m_avFormatContext); result < 0) {
0201         qCWarning(PIPEWIRERECORD_LOGGING) << "Could not write trailer";
0202     }
0203 
0204     avio_closep(&m_avFormatContext->pb);
0205     avformat_free_context(m_avFormatContext);
0206 }
0207 
0208 #include "moc_pipewirerecord.cpp"
0209 
0210 #include "moc_pipewirerecord_p.cpp"