File indexing completed on 2024-04-28 05:27:33
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.dataFrame && m_frameWithoutMetadataCursor.dataFrame) { 0146 m_encoder->filterFrame(m_frameWithoutMetadataCursor); 0147 } 0148 } 0149 0150 void PipeWireRecordProduce::aboutToEncode(PipeWireFrame &frame) 0151 { 0152 if (!frame.dataFrame) { 0153 return; 0154 } 0155 0156 if (m_cursor.position && !m_cursor.texture.isNull()) { 0157 auto image = frame.dataFrame->toImage(); 0158 // Do not copy the image if it's already ours 0159 if (m_frameWithoutMetadataCursor.dataFrame->cleanup != frame.dataFrame->cleanup) { 0160 m_frameWithoutMetadataCursor.dataFrame = frame.dataFrame->copy(); 0161 } 0162 QPainter p(&image); 0163 p.drawImage(*m_cursor.position, m_cursor.texture); 0164 } 0165 } 0166 0167 void PipeWireRecordProduce::processPacket(AVPacket *packet) 0168 { 0169 packet->stream_index = (*m_avFormatContext->streams)->index; 0170 av_packet_rescale_ts(packet, m_encoder->avCodecContext()->time_base, (*m_avFormatContext->streams)->time_base); 0171 log_packet(m_avFormatContext, packet); 0172 auto ret = av_interleaved_write_frame(m_avFormatContext, packet); 0173 if (ret < 0) { 0174 qCWarning(PIPEWIRERECORD_LOGGING) << "Error while writing output packet:" << av_err2str(ret); 0175 } 0176 } 0177 0178 std::unique_ptr<PipeWireProduce> PipeWireRecord::makeProduce() 0179 { 0180 return std::make_unique<PipeWireRecordProduce>(encoder(), nodeId(), fd(), maxFramerate(), d->m_output); 0181 } 0182 0183 int64_t PipeWireRecordProduce::framePts(const std::optional<std::chrono::nanoseconds> &presentationTimestamp) 0184 { 0185 const auto current = std::chrono::duration_cast<std::chrono::milliseconds>(*presentationTimestamp).count(); 0186 if ((*m_avFormatContext->streams)->start_time == 0) { 0187 (*m_avFormatContext->streams)->start_time = current; 0188 } 0189 0190 return current - (*m_avFormatContext->streams)->start_time; 0191 } 0192 0193 void PipeWireRecordProduce::cleanup() 0194 { 0195 // Clear the queue of encoded packets. 0196 m_encoder->receivePacket(); 0197 0198 if (auto result = av_write_trailer(m_avFormatContext); result < 0) { 0199 qCWarning(PIPEWIRERECORD_LOGGING) << "Could not write trailer"; 0200 } 0201 0202 avio_closep(&m_avFormatContext->pb); 0203 avformat_free_context(m_avFormatContext); 0204 } 0205 0206 #include "moc_pipewirerecord.cpp" 0207 0208 #include "moc_pipewirerecord_p.cpp"