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 }