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 "h264vaapiencoder_p.h"
0010 
0011 #include <QSize>
0012 
0013 extern "C" {
0014 #include <libavcodec/avcodec.h>
0015 #include <libavfilter/buffersink.h>
0016 #include <libavfilter/buffersrc.h>
0017 }
0018 
0019 #include "logging_record.h"
0020 
0021 H264VAAPIEncoder::H264VAAPIEncoder(H264Profile profile, PipeWireProduce *produce)
0022     : HardwareEncoder(produce)
0023     , m_profile(profile)
0024 {
0025 }
0026 
0027 bool H264VAAPIEncoder::initialize(const QSize &size)
0028 {
0029     if (!createDrmContext(size)) {
0030         return false;
0031     }
0032 
0033     m_avFilterGraph = avfilter_graph_alloc();
0034     if (!m_avFilterGraph) {
0035         qCWarning(PIPEWIRERECORD_LOGGING) << "Could not create filter graph";
0036         return false;
0037     }
0038 
0039     int ret = avfilter_graph_create_filter(&m_inputFilter,
0040                                            avfilter_get_by_name("buffer"),
0041                                            "in",
0042                                            "width=1:height=1:pix_fmt=drm_prime:time_base=1/1",
0043                                            nullptr,
0044                                            m_avFilterGraph);
0045     if (ret < 0) {
0046         qCWarning(PIPEWIRERECORD_LOGGING) << "Failed to create the buffer filter";
0047         return false;
0048     }
0049 
0050     auto parameters = av_buffersrc_parameters_alloc();
0051     if (!parameters) {
0052         qFatal("Failed to allocate memory");
0053     }
0054 
0055     parameters->format = AV_PIX_FMT_DRM_PRIME;
0056     parameters->width = size.width();
0057     parameters->height = size.height();
0058     parameters->time_base = {1, 1000};
0059     parameters->hw_frames_ctx = m_drmFramesContext;
0060 
0061     av_buffersrc_parameters_set(m_inputFilter, parameters);
0062     av_free(parameters);
0063     parameters = nullptr;
0064 
0065     ret = avfilter_graph_create_filter(&m_outputFilter, avfilter_get_by_name("buffersink"), "out", nullptr, nullptr, m_avFilterGraph);
0066     if (ret < 0) {
0067         qCWarning(PIPEWIRERECORD_LOGGING) << "Could not create buffer output filter";
0068         return false;
0069     }
0070 
0071     auto inputs = avfilter_inout_alloc();
0072     if (!inputs) {
0073         qFatal("Failed to allocate memory");
0074     }
0075     inputs->name = av_strdup("in");
0076     inputs->filter_ctx = m_inputFilter;
0077     inputs->pad_idx = 0;
0078     inputs->next = nullptr;
0079 
0080     auto outputs = avfilter_inout_alloc();
0081     if (!outputs) {
0082         qFatal("Failed to allocate memory");
0083     }
0084     outputs->name = av_strdup("out");
0085     outputs->filter_ctx = m_outputFilter;
0086     outputs->pad_idx = 0;
0087     outputs->next = nullptr;
0088 
0089     ret = avfilter_graph_parse(m_avFilterGraph, "hwmap=mode=direct:derive_device=vaapi,scale_vaapi=format=nv12:mode=fast", outputs, inputs, NULL);
0090     if (ret < 0) {
0091         qCWarning(PIPEWIRERECORD_LOGGING) << "Failed creating filter graph";
0092         return false;
0093     }
0094 
0095     for (auto i = 0u; i < m_avFilterGraph->nb_filters; ++i) {
0096         m_avFilterGraph->filters[i]->hw_device_ctx = av_buffer_ref(m_drmContext);
0097     }
0098 
0099     ret = avfilter_graph_config(m_avFilterGraph, nullptr);
0100     if (ret < 0) {
0101         qCWarning(PIPEWIRERECORD_LOGGING) << "Failed configuring filter graph";
0102         return false;
0103     }
0104 
0105     auto codec = avcodec_find_encoder_by_name("h264_vaapi");
0106     if (!codec) {
0107         qCWarning(PIPEWIRERECORD_LOGGING) << "h264_vaapi codec not found";
0108         return false;
0109     }
0110 
0111     m_avCodecContext = avcodec_alloc_context3(codec);
0112     if (!m_avCodecContext) {
0113         qCWarning(PIPEWIRERECORD_LOGGING) << "Could not allocate video codec context";
0114         return false;
0115     }
0116 
0117     Q_ASSERT(!size.isEmpty());
0118     m_avCodecContext->width = size.width();
0119     m_avCodecContext->height = size.height();
0120     m_avCodecContext->max_b_frames = 0;
0121     m_avCodecContext->gop_size = 100;
0122     m_avCodecContext->pix_fmt = AV_PIX_FMT_VAAPI;
0123     m_avCodecContext->time_base = AVRational{1, 1000};
0124 
0125     if (m_quality) {
0126         m_avCodecContext->global_quality = percentageToAbsoluteQuality(m_quality);
0127     } else {
0128         m_avCodecContext->global_quality = 35;
0129     }
0130 
0131     switch (m_profile) {
0132     case H264Profile::Baseline:
0133         m_avCodecContext->profile = FF_PROFILE_H264_CONSTRAINED_BASELINE;
0134         break;
0135     case H264Profile::Main:
0136         m_avCodecContext->profile = FF_PROFILE_H264_MAIN;
0137         break;
0138     case H264Profile::High:
0139         m_avCodecContext->profile = FF_PROFILE_H264_HIGH;
0140         break;
0141     }
0142 
0143     AVDictionary *options = nullptr;
0144     // av_dict_set_int(&options, "threads", qMin(16, QThread::idealThreadCount()), 0);
0145     av_dict_set(&options, "preset", "veryfast", 0);
0146     av_dict_set(&options, "tune-content", "screen", 0);
0147     av_dict_set(&options, "deadline", "realtime", 0);
0148     // In theory a lower number should be faster, but the opposite seems to be true
0149     // av_dict_set(&options, "quality", "40", 0);
0150     // Disable motion estimation, not great while dragging windows but speeds up encoding by an order of magnitude
0151     av_dict_set(&options, "flags", "+mv4", 0);
0152     // Disable in-loop filtering
0153     av_dict_set(&options, "-flags", "+loop", 0);
0154 
0155     m_avCodecContext->hw_frames_ctx = av_buffer_ref(m_outputFilter->inputs[0]->hw_frames_ctx);
0156 
0157     if (int result = avcodec_open2(m_avCodecContext, codec, &options); result < 0) {
0158         qCWarning(PIPEWIRERECORD_LOGGING) << "Could not open codec" << av_err2str(ret);
0159         return false;
0160     }
0161 
0162     return true;
0163 }
0164 
0165 int H264VAAPIEncoder::percentageToAbsoluteQuality(const std::optional<quint8> &quality)
0166 {
0167     if (!quality) {
0168         return -1;
0169     }
0170 
0171     constexpr int MinQuality = 51 + 6 * 6;
0172     return std::max(1, int(MinQuality - (m_quality.value() / 100.0) * MinQuality));
0173 }