File indexing completed on 2024-05-05 05:30:17

0001 /*
0002     SPDX-FileCopyrightText: 2023 Aleix Pol Gonzalez <aleixpol@kde.org>
0003     SPDX-FileCopyrightText: 2023 Marco Martin <mart@kde.org>
0004     SPDX-FileCopyrightText: 2023 Arjen Hiemstra <ahiemstra@heimr.nl>
0005     SPDX-FileCopyrightText: 2023 Noah Davis <noahadvs@gmail.com>
0006 
0007     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0008 */
0009 
0010 #include "libvpxvp9encoder_p.h"
0011 
0012 #include "pipewireproduce_p.h"
0013 
0014 #include <QSize>
0015 #include <QThread>
0016 
0017 extern "C" {
0018 #include <libavcodec/avcodec.h>
0019 #include <libavfilter/buffersink.h>
0020 #include <libavfilter/buffersrc.h>
0021 #include <libavutil/pixfmt.h>
0022 }
0023 
0024 #include "logging_record.h"
0025 
0026 LibVpxVp9Encoder::LibVpxVp9Encoder(PipeWireProduce *produce)
0027     : SoftwareEncoder(produce)
0028 {
0029 }
0030 
0031 bool LibVpxVp9Encoder::initialize(const QSize &size)
0032 {
0033     createFilterGraph(size);
0034 
0035     auto codec = avcodec_find_encoder_by_name("libvpx-vp9");
0036     if (!codec) {
0037         qCWarning(PIPEWIRERECORD_LOGGING) << "libvpx-vp9 codec not found";
0038         return false;
0039     }
0040 
0041     m_avCodecContext = avcodec_alloc_context3(codec);
0042     if (!m_avCodecContext) {
0043         qCWarning(PIPEWIRERECORD_LOGGING) << "Could not allocate video codec context";
0044         return false;
0045     }
0046 
0047     Q_ASSERT(!size.isEmpty());
0048     m_avCodecContext->width = size.width();
0049     m_avCodecContext->height = size.height();
0050     m_avCodecContext->pix_fmt = AV_PIX_FMT_YUV420P;
0051     m_avCodecContext->time_base = AVRational{1, 1000};
0052 
0053     AVDictionary *options = nullptr;
0054 
0055     // We're probably capturing a screen
0056     av_dict_set(&options, "tune-content", "screen", 0);
0057 
0058     const auto area = size.width() * size.height();
0059     // m_avCodecContext->framerate is not set, so we use m_produce->maxFramerate() instead.
0060     const auto maxFramerate = m_produce->maxFramerate();
0061     const auto fps = qreal(maxFramerate.numerator) / std::max(quint32(1), maxFramerate.denominator);
0062 
0063     m_avCodecContext->gop_size = fps * 2;
0064 
0065     // TODO: Make bitrate depend on the framerate? More frames is more data.
0066     // maxFramerate can apparently be changed while recording, so keep that in mind.
0067     m_avCodecContext->bit_rate = std::round(area * 2);
0068     m_avCodecContext->rc_min_rate = std::round(area);
0069     m_avCodecContext->rc_max_rate = std::round(area * 3);
0070 
0071     m_avCodecContext->rc_buffer_size = m_avCodecContext->bit_rate;
0072 
0073     // Lower crf is higher quality. Max 0, min 63. libvpx-vp9 doesn't use global_quality.
0074     int crf = 31;
0075     if (m_quality) {
0076         crf = percentageToAbsoluteQuality(m_quality);
0077     }
0078     av_dict_set_int(&options, "crf", crf, 0);
0079     m_avCodecContext->qmin = std::clamp(crf / 2, 0, crf);
0080     m_avCodecContext->qmax = std::clamp(qRound(crf * 1.5), crf, 63);
0081 
0082     // 0-4 are for Video-On-Demand with the good or best deadline.
0083     // Don't use best, it's not worth it.
0084     // 5-8 are for streaming with the realtime deadline.
0085     // Lower is higher quality.
0086     int cpuUsed = 5 + std::max(1, int(3 - std::round(m_quality.value_or(50) / 100.0 * 3)));
0087     av_dict_set_int(&options, "cpu-used", cpuUsed, 0);
0088     av_dict_set(&options, "deadline", "realtime", 0);
0089 
0090     m_avCodecContext->thread_count = QThread::idealThreadCount();
0091 
0092     // The value is interpreted as being equivalent to log2(realNumberOfColumns),
0093     // so 3 is 8 columns. 6 is the max amount of columns. 2 is the max amount of rows.
0094     av_dict_set(&options, "tile-columns", "6", 0);
0095     av_dict_set(&options, "tile-rows", "2", 0);
0096     // This should make things faster, but it only seems to consume 100MB more RAM.
0097     // av_dict_set(&options, "row-mt", "1", 0);
0098     av_dict_set(&options, "frame-parallel", "1", 0);
0099 
0100     if (int result = avcodec_open2(m_avCodecContext, codec, &options); result < 0) {
0101         qCWarning(PIPEWIRERECORD_LOGGING) << "Could not open codec" << av_err2str(result);
0102         return false;
0103     }
0104 
0105     return true;
0106 }
0107 
0108 int LibVpxVp9Encoder::percentageToAbsoluteQuality(const std::optional<quint8> &quality)
0109 {
0110     if (!quality) {
0111         return -1;
0112     }
0113 
0114     constexpr int MinQuality = 63;
0115     return std::max(1, int(MinQuality - (m_quality.value() / 100.0) * MinQuality));
0116 }