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"