7#include "pipewirerecord.h"
10#include "pipewirerecord_p.h"
11#include <logging_record.h>
13#include <QGuiApplication>
21#include <libavcodec/avcodec.h>
22#include <libavformat/avformat.h>
23#include <libavutil/timestamp.h>
30char buf[AV_TS_MAX_STRING_SIZE];
31#define av_ts2str(ts) av_ts_make_string(buf, ts)
36char timebuf[AV_TS_MAX_STRING_SIZE];
37#define av_ts2timestr(ts, tb) av_ts_make_time_string(timebuf, ts, tb)
40static void log_packet(
const AVFormatContext *fmt_ctx,
const AVPacket *pkt)
42 AVRational *time_base = &fmt_ctx->streams[pkt->stream_index]->time_base;
44 qCDebug(PIPEWIRERECORD_LOGGING,
45 "pts:%s pts_time:%s dts:%s dts_time:%s duration:%s duration_time:%s "
48 av_ts2timestr(pkt->pts, time_base),
50 av_ts2timestr(pkt->dts, time_base),
51 av_ts2str(pkt->duration),
52 av_ts2timestr(pkt->duration, time_base),
56PipeWireRecord::PipeWireRecord(
QObject *parent)
57 : PipeWireBaseEncodedStream(parent)
58 , d(new PipeWireRecordPrivate)
62PipeWireRecord::~PipeWireRecord() =
default;
64void PipeWireRecord::setOutput(
const QString &_output)
68 if (d->m_output == output)
72 Q_EMIT outputChanged(output);
75QString PipeWireRecord::output()
const
80QString PipeWireRecord::extension()
const
82 static QHash<PipeWireBaseEncodedStream::Encoder, QString> s_extensions = {
83 {PipeWireBaseEncodedStream::H264Main, QStringLiteral(
"mp4")},
84 {PipeWireBaseEncodedStream::H264Baseline, QStringLiteral(
"mp4")},
85 {PipeWireBaseEncodedStream::VP8, QStringLiteral(
"webm")},
86 {PipeWireBaseEncodedStream::VP9, QStringLiteral(
"webm")},
87 {PipeWireBaseEncodedStream::WebP, QStringLiteral(
"webp")},
88 {PipeWireBaseEncodedStream::Gif, QStringLiteral(
"gif")},
90 return s_extensions.
value(encoder());
93PipeWireRecordProduce::PipeWireRecordProduce(PipeWireBaseEncodedStream::Encoder encoder, uint nodeId, uint fd,
const Fraction &framerate,
const QString &output)
94 : PipeWireProduce(encoder, nodeId, fd, framerate)
99bool PipeWireRecordProduce::setupFormat()
101 avformat_alloc_output_context2(&m_avFormatContext,
nullptr,
nullptr, m_output.toUtf8().constData());
102 if (!m_avFormatContext) {
103 qCWarning(PIPEWIRERECORD_LOGGING) <<
"Could not deduce output format from file: using WebM." << m_output;
104 avformat_alloc_output_context2(&m_avFormatContext,
nullptr,
"webm", m_output.toUtf8().constData());
106 if (!m_avFormatContext) {
107 qCDebug(PIPEWIRERECORD_LOGGING) <<
"could not set stream up";
111 const Fraction framerate = m_stream->framerate();
112 int ret = avio_open(&m_avFormatContext->pb,
QFile::encodeName(m_output).constData(), AVIO_FLAG_WRITE);
114 qCWarning(PIPEWIRERECORD_LOGGING) <<
"Could not open" << m_output << av_err2str(ret);
118 auto avStream = avformat_new_stream(m_avFormatContext,
nullptr);
119 avStream->start_time = 0;
121 avStream->r_frame_rate.num = framerate.numerator;
122 avStream->r_frame_rate.den = framerate.denominator;
123 avStream->avg_frame_rate.num = framerate.numerator;
124 avStream->avg_frame_rate.den = framerate.denominator;
127 ret = avcodec_parameters_from_context(avStream->codecpar, m_encoder->avCodecContext());
129 qCWarning(PIPEWIRERECORD_LOGGING) <<
"Error occurred when passing the codec:" << av_err2str(ret);
133 AVDictionary *options =
nullptr;
134 const auto codecId = m_avFormatContext->oformat->video_codec;
135 if (codecId == AV_CODEC_ID_GIF || codecId == AV_CODEC_ID_WEBP) {
136 av_dict_set_int(&options,
"loop", 0, 0);
138 ret = avformat_write_header(m_avFormatContext, &options);
140 qCWarning(PIPEWIRERECORD_LOGGING) <<
"Error occurred when writing header:" << av_err2str(ret);
147void PipeWireRecordProduce::processFrame(
const PipeWireFrame &frame)
149 PipeWireProduce::processFrame(frame);
150 if (frame.cursor && !frame.dmabuf && !frame.dataFrame && m_frameWithoutMetadataCursor.dataFrame) {
151 m_encoder->filterFrame(m_frameWithoutMetadataCursor);
155void PipeWireRecordProduce::aboutToEncode(PipeWireFrame &frame)
157 if (!frame.dataFrame) {
161 if (m_cursor.position && !m_cursor.texture.isNull()) {
162 auto image = frame.dataFrame->toImage();
164 if (m_frameWithoutMetadataCursor.dataFrame->cleanup != frame.dataFrame->cleanup) {
165 m_frameWithoutMetadataCursor.dataFrame = frame.dataFrame->copy();
168 p.drawImage(*m_cursor.position, m_cursor.texture);
172void PipeWireRecordProduce::processPacket(AVPacket *packet)
174 packet->stream_index = (*m_avFormatContext->streams)->index;
175 av_packet_rescale_ts(packet, m_encoder->avCodecContext()->time_base, (*m_avFormatContext->streams)->time_base);
176 log_packet(m_avFormatContext, packet);
177 auto ret = av_interleaved_write_frame(m_avFormatContext, packet);
179 qCWarning(PIPEWIRERECORD_LOGGING) <<
"Error while writing output packet:" << av_err2str(ret);
183std::unique_ptr<PipeWireProduce> PipeWireRecord::makeProduce()
185 return std::make_unique<PipeWireRecordProduce>(encoder(), nodeId(), fd(), maxFramerate(), d->m_output);
188int64_t PipeWireRecordProduce::framePts(
const std::optional<std::chrono::nanoseconds> &presentationTimestamp)
190 const auto current = std::chrono::duration_cast<std::chrono::milliseconds>(*presentationTimestamp).count();
191 if ((*m_avFormatContext->streams)->start_time == 0) {
192 (*m_avFormatContext->streams)->start_time = current;
195 return current - (*m_avFormatContext->streams)->start_time;
198void PipeWireRecordProduce::cleanup()
200 if (m_avFormatContext) {
201 if (
auto result = av_write_trailer(m_avFormatContext); result < 0) {
202 qCWarning(PIPEWIRERECORD_LOGGING) <<
"Could not write trailer";
205 avio_closep(&m_avFormatContext->pb);
206 avformat_free_context(m_avFormatContext);
210#include "moc_pipewirerecord.cpp"
212#include "moc_pipewirerecord_p.cpp"
KCOREADDONS_EXPORT QString tildeExpand(const QString &path)
QByteArray encodeName(const QString &fileName)
T value(const Key &key) const const