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 
0006     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0007 */
0008 
0009 #include "libx264encoder_p.h"
0010 
0011 #include <QSize>
0012 #include <QThread>
0013 
0014 extern "C" {
0015 #include <libavcodec/avcodec.h>
0016 #include <libavfilter/buffersink.h>
0017 #include <libavfilter/buffersrc.h>
0018 #include <libavutil/pixfmt.h>
0019 }
0020 
0021 #include "logging_record.h"
0022 
0023 LibX264Encoder::LibX264Encoder(H264Profile profile, PipeWireProduce *produce)
0024     : SoftwareEncoder(produce)
0025     , m_profile(profile)
0026 {
0027 }
0028 
0029 bool LibX264Encoder::initialize(const QSize &size)
0030 {
0031     createFilterGraph(size);
0032 
0033     auto codec = avcodec_find_encoder_by_name("libx264");
0034     if (!codec) {
0035         qCWarning(PIPEWIRERECORD_LOGGING) << "h264_vaapi codec not found";
0036         return false;
0037     }
0038 
0039     m_avCodecContext = avcodec_alloc_context3(codec);
0040     if (!m_avCodecContext) {
0041         qCWarning(PIPEWIRERECORD_LOGGING) << "Could not allocate video codec context";
0042         return false;
0043     }
0044 
0045     Q_ASSERT(!size.isEmpty());
0046     m_avCodecContext->width = size.width();
0047     m_avCodecContext->height = size.height();
0048     m_avCodecContext->max_b_frames = 0;
0049     m_avCodecContext->gop_size = 100;
0050     m_avCodecContext->pix_fmt = AV_PIX_FMT_YUV420P;
0051     m_avCodecContext->time_base = AVRational{1, 1000};
0052 
0053     if (m_quality) {
0054         m_avCodecContext->global_quality = percentageToAbsoluteQuality(m_quality);
0055     } else {
0056         m_avCodecContext->global_quality = 35;
0057     }
0058 
0059     switch (m_profile) {
0060     case H264Profile::Baseline:
0061         m_avCodecContext->profile = FF_PROFILE_H264_BASELINE;
0062         break;
0063     case H264Profile::Main:
0064         m_avCodecContext->profile = FF_PROFILE_H264_MAIN;
0065         break;
0066     case H264Profile::High:
0067         m_avCodecContext->profile = FF_PROFILE_H264_HIGH;
0068         break;
0069     }
0070 
0071     AVDictionary *options = nullptr;
0072     av_dict_set_int(&options, "threads", qMin(16, QThread::idealThreadCount()), 0);
0073     av_dict_set(&options, "preset", "veryfast", 0);
0074     av_dict_set(&options, "tune-content", "screen", 0);
0075     av_dict_set(&options, "deadline", "realtime", 0);
0076     // In theory a lower number should be faster, but the opposite seems to be true
0077     // av_dict_set(&options, "quality", "40", 0);
0078     // Disable motion estimation, not great while dragging windows but speeds up encoding by an order of magnitude
0079     av_dict_set(&options, "flags", "+mv4", 0);
0080     // Disable in-loop filtering
0081     av_dict_set(&options, "-flags", "+loop", 0);
0082 
0083     if (int result = avcodec_open2(m_avCodecContext, codec, &options); result < 0) {
0084         qCWarning(PIPEWIRERECORD_LOGGING) << "Could not open codec" << av_err2str(result);
0085         return false;
0086     }
0087 
0088     return true;
0089 }
0090 
0091 int LibX264Encoder::percentageToAbsoluteQuality(const std::optional<quint8> &quality)
0092 {
0093     if (!quality) {
0094         return -1;
0095     }
0096 
0097     constexpr int MinQuality = 51 + 6 * 6;
0098     return std::max(1, int(MinQuality - (m_quality.value() / 100.0) * MinQuality));
0099 }