/* SPDX-FileCopyrightText: 2023 Aleix Pol Gonzalez SPDX-FileCopyrightText: 2023 Marco Martin SPDX-FileCopyrightText: 2023 Arjen Hiemstra SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL */ #include "libx264encoder_p.h" #include #include extern "C" { #include #include #include #include } #include "logging_record.h" using namespace Qt::StringLiterals; LibX264Encoder::LibX264Encoder(H264Profile profile, PipeWireProduce *produce) : SoftwareEncoder(produce) , m_profile(profile) { // Adjust the filter graph to ensure we are using an even frame size using a // pad filter. Otherwise the size adjustment below will insert a row/column // of garbage instead of black. m_filterGraphToParse = u"pad=ceil(iw/2)*2:ceil(ih/2)*2,format=pix_fmts=yuv420p"_s; } bool LibX264Encoder::initialize(const QSize &size) { createFilterGraph(size); auto codec = avcodec_find_encoder_by_name("libx264"); if (!codec) { qCWarning(PIPEWIRERECORD_LOGGING) << "libx264 codec not found"; return false; } m_avCodecContext = avcodec_alloc_context3(codec); if (!m_avCodecContext) { qCWarning(PIPEWIRERECORD_LOGGING) << "Could not allocate video codec context"; return false; } Q_ASSERT(!size.isEmpty()); // Important: libx264 rejects streams with sizes that are not even. So to // ensure we don't get errors, we need to ensure the size we set here is // even. We also insert a pad filter into the filter chain above to ensure // we don't end up padding with garbage. m_avCodecContext->width = std::ceil(size.width() / 2) * 2; m_avCodecContext->height = std::ceil(size.height() / 2) * 2; m_avCodecContext->max_b_frames = 0; m_avCodecContext->gop_size = 100; m_avCodecContext->pix_fmt = AV_PIX_FMT_YUV420P; m_avCodecContext->time_base = AVRational{1, 1000}; if (m_quality) { m_avCodecContext->global_quality = percentageToAbsoluteQuality(m_quality); } else { m_avCodecContext->global_quality = 35; } switch (m_profile) { case H264Profile::Baseline: m_avCodecContext->profile = FF_PROFILE_H264_BASELINE; break; case H264Profile::Main: m_avCodecContext->profile = FF_PROFILE_H264_MAIN; break; case H264Profile::High: m_avCodecContext->profile = FF_PROFILE_H264_HIGH; break; } AVDictionary *options = nullptr; av_dict_set_int(&options, "threads", qMin(16, QThread::idealThreadCount()), 0); applyEncodingPreference(options); if (int result = avcodec_open2(m_avCodecContext, codec, &options); result < 0) { qCWarning(PIPEWIRERECORD_LOGGING) << "Could not open codec" << av_err2str(result); return false; } return true; } int LibX264Encoder::percentageToAbsoluteQuality(const std::optional &quality) { if (!quality) { return -1; } constexpr int MinQuality = 51 + 6 * 6; return std::max(1, int(MinQuality - (m_quality.value() / 100.0) * MinQuality)); } void LibX264Encoder::applyEncodingPreference(AVDictionary *options) { SoftwareEncoder::applyEncodingPreference(options); // Disable motion estimation, not great while dragging windows but speeds up encoding by an order of magnitude av_dict_set(&options, "flags", "+mv4", 0); // Disable in-loop filtering av_dict_set(&options, "-flags", "+loop", 0); }