KPipewire

h264vaapiencoder.cpp
1/*
2 SPDX-FileCopyrightText: 2023 Aleix Pol Gonzalez <aleixpol@kde.org>
3 SPDX-FileCopyrightText: 2023 Marco Martin <mart@kde.org>
4 SPDX-FileCopyrightText: 2023 Arjen Hiemstra <ahiemstra@heimr.nl>
5
6 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
7*/
8
9#include "h264vaapiencoder_p.h"
10
11#include <QSize>
12
13extern "C" {
14#include <libavcodec/avcodec.h>
15#include <libavfilter/buffersink.h>
16#include <libavfilter/buffersrc.h>
17}
18
19#include "logging_record.h"
20
21H264VAAPIEncoder::H264VAAPIEncoder(H264Profile profile, PipeWireProduce *produce)
22 : HardwareEncoder(produce)
23 , m_profile(profile)
24{
25}
26
27bool H264VAAPIEncoder::initialize(const QSize &size)
28{
29 if (!createDrmContext(size)) {
30 return false;
31 }
32
33 m_avFilterGraph = avfilter_graph_alloc();
34 if (!m_avFilterGraph) {
35 qCWarning(PIPEWIRERECORD_LOGGING) << "Could not create filter graph";
36 return false;
37 }
38
39 int ret = avfilter_graph_create_filter(&m_inputFilter,
40 avfilter_get_by_name("buffer"),
41 "in",
42 "width=1:height=1:pix_fmt=drm_prime:time_base=1/1",
43 nullptr,
44 m_avFilterGraph);
45 if (ret < 0) {
46 qCWarning(PIPEWIRERECORD_LOGGING) << "Failed to create the buffer filter";
47 return false;
48 }
49
50 auto parameters = av_buffersrc_parameters_alloc();
51 if (!parameters) {
52 qFatal("Failed to allocate memory");
53 }
54
55 parameters->format = AV_PIX_FMT_DRM_PRIME;
56 parameters->width = size.width();
57 parameters->height = size.height();
58 parameters->time_base = {1, 1000};
59 parameters->hw_frames_ctx = m_drmFramesContext;
60
61 av_buffersrc_parameters_set(m_inputFilter, parameters);
62 av_free(parameters);
63 parameters = nullptr;
64
65 ret = avfilter_graph_create_filter(&m_outputFilter, avfilter_get_by_name("buffersink"), "out", nullptr, nullptr, m_avFilterGraph);
66 if (ret < 0) {
67 qCWarning(PIPEWIRERECORD_LOGGING) << "Could not create buffer output filter";
68 return false;
69 }
70
71 auto inputs = avfilter_inout_alloc();
72 if (!inputs) {
73 qFatal("Failed to allocate memory");
74 }
75 inputs->name = av_strdup("in");
76 inputs->filter_ctx = m_inputFilter;
77 inputs->pad_idx = 0;
78 inputs->next = nullptr;
79
80 auto outputs = avfilter_inout_alloc();
81 if (!outputs) {
82 qFatal("Failed to allocate memory");
83 }
84 outputs->name = av_strdup("out");
85 outputs->filter_ctx = m_outputFilter;
86 outputs->pad_idx = 0;
87 outputs->next = nullptr;
88
89 ret = avfilter_graph_parse(m_avFilterGraph, "hwmap=mode=direct:derive_device=vaapi,scale_vaapi=format=nv12:mode=fast", outputs, inputs, NULL);
90 if (ret < 0) {
91 qCWarning(PIPEWIRERECORD_LOGGING) << "Failed creating filter graph";
92 return false;
93 }
94
95 for (auto i = 0u; i < m_avFilterGraph->nb_filters; ++i) {
96 m_avFilterGraph->filters[i]->hw_device_ctx = av_buffer_ref(m_drmContext);
97 }
98
99 ret = avfilter_graph_config(m_avFilterGraph, nullptr);
100 if (ret < 0) {
101 qCWarning(PIPEWIRERECORD_LOGGING) << "Failed configuring filter graph";
102 return false;
103 }
104
105 auto codec = avcodec_find_encoder_by_name("h264_vaapi");
106 if (!codec) {
107 qCWarning(PIPEWIRERECORD_LOGGING) << "h264_vaapi codec not found";
108 return false;
109 }
110
111 m_avCodecContext = avcodec_alloc_context3(codec);
112 if (!m_avCodecContext) {
113 qCWarning(PIPEWIRERECORD_LOGGING) << "Could not allocate video codec context";
114 return false;
115 }
116
117 Q_ASSERT(!size.isEmpty());
118 m_avCodecContext->width = size.width();
119 m_avCodecContext->height = size.height();
120 m_avCodecContext->max_b_frames = 0;
121 m_avCodecContext->gop_size = 100;
122 m_avCodecContext->pix_fmt = AV_PIX_FMT_VAAPI;
123 m_avCodecContext->time_base = AVRational{1, 1000};
124
125 if (m_quality) {
126 m_avCodecContext->global_quality = percentageToAbsoluteQuality(m_quality);
127 } else {
128 m_avCodecContext->global_quality = 35;
129 }
130
131 switch (m_profile) {
132 case H264Profile::Baseline:
133 m_avCodecContext->profile = FF_PROFILE_H264_CONSTRAINED_BASELINE;
134 break;
135 case H264Profile::Main:
136 m_avCodecContext->profile = FF_PROFILE_H264_MAIN;
137 break;
138 case H264Profile::High:
139 m_avCodecContext->profile = FF_PROFILE_H264_HIGH;
140 break;
141 }
142
143 AVDictionary *options = nullptr;
144 // av_dict_set_int(&options, "threads", qMin(16, QThread::idealThreadCount()), 0);
145 applyEncodingPreference(options);
146
147 // Assign the right hardware context for encoding frames.
148 // We rely on FFmpeg for creating the VAAPI hardware context as part of
149 // `avfilter_graph_parse()`. The codec context needs the VAAPI context to be
150 // able to encode properly, so get that from the output filter.
151 m_avCodecContext->hw_frames_ctx = av_buffer_ref(av_buffersink_get_hw_frames_ctx(m_outputFilter));
152
153 if (int result = avcodec_open2(m_avCodecContext, codec, &options); result < 0) {
154 qCWarning(PIPEWIRERECORD_LOGGING) << "Could not open codec" << av_err2str(ret);
155 return false;
156 }
157
158 return true;
159}
160
161int H264VAAPIEncoder::percentageToAbsoluteQuality(const std::optional<quint8> &quality)
162{
163 if (!quality) {
164 return -1;
165 }
166
167 constexpr int MinQuality = 51 + 6 * 6;
168 return std::max(1, int(MinQuality - (m_quality.value() / 100.0) * MinQuality));
169}
170
171void H264VAAPIEncoder::applyEncodingPreference(AVDictionary *options)
172{
173 HardwareEncoder::applyEncodingPreference(options);
174 // Disable motion estimation, not great while dragging windows but speeds up encoding by an order of magnitude
175 av_dict_set(&options, "flags", "+mv4", 0);
176 // Disable in-loop filtering
177 av_dict_set(&options, "-flags", "+loop", 0);
178}
int height() const const
bool isEmpty() const const
int width() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Sat Dec 21 2024 17:05:20 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.