Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qffmpegvideoencoderutils.cpp
Go to the documentation of this file.
1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
5
6#include <QtMultimedia/private/qmultimediautils_p.h>
7#include <QtCore/qoperatingsystemversion.h>
8#include <QtCore/private/qminimalflatset_p.h>
9#include <QtFFmpegMediaPluginImpl/private/qffmpegrecordingengineutils_p.h>
10
11extern "C" {
12#include <libavutil/pixdesc.h>
13}
14
15QT_BEGIN_NAMESPACE
16
17namespace QFFmpeg {
18
19using namespace Qt::Literals;
20
21namespace {
22
23bool is16BitFormat(const AVPixFmtDescriptor *desc)
24{
25 return desc->comp[0].depth == 16;
26}
27
28bool is10BitFormat(const AVPixFmtDescriptor *desc)
29{
30 return desc->comp[0].depth == 10;
31}
32
33bool is8BitFormat(const AVPixFmtDescriptor *desc)
34{
35 return desc->comp[0].depth == 8;
36}
37
38bool is444Format(const AVPixFmtDescriptor *desc)
39{
40 return desc->log2_chroma_h == 0 && desc->log2_chroma_w == 0;
41}
42
43bool is422Format(const AVPixFmtDescriptor *desc)
44{
45 return desc->log2_chroma_h == 1 && desc->log2_chroma_w == 0;
46}
47
48bool is420Format(const AVPixFmtDescriptor *desc)
49{
50 return desc->log2_chroma_h == 1 && desc->log2_chroma_w == 1;
51}
52
53bool isGreyFormat(const AVPixFmtDescriptor *desc)
54{
55 return desc->nb_components == 1;
56}
57
58AVScore scoreTargetSwFormat(const AVPixFmtDescriptor *sourceSwFormatDesc, AVPixelFormat fmt)
59{
60 // determine the format used by the encoder.
61 // We prefer YUV420 based formats such as NV12 or P010. Selection trues to find the best
62 // matching format for the encoder depending on the bit depth of the source format
63
64 const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(fmt);
65 if (!desc)
66 return NotSuitableAVScore;
67
68 if (desc->flags & AV_PIX_FMT_FLAG_HWACCEL)
69 // we really don't want HW accelerated formats here
70 return NotSuitableAVScore;
71
72 AVScore score = DefaultAVScore;
73
74 if (desc == sourceSwFormatDesc)
75 // prefer exact matches
76 score += 10;
77
78 const int sourceBpp = av_get_bits_per_pixel(sourceSwFormatDesc);
79 const int bpp = av_get_bits_per_pixel(desc);
80
81 // we want formats with the same bpp
82 if (bpp == sourceBpp)
83 score += 100;
84 else if (bpp < sourceBpp)
85 score -= 100 + (sourceBpp - bpp);
86
87 // pessimize 10 and 16 bit formats if the source format is 8 bit
88 if (is8BitFormat(sourceSwFormatDesc)) {
89 if (is10BitFormat(desc))
90 score -= 100;
91 else if (is16BitFormat(desc))
92 score -= 200;
93 }
94
95 // Add a slight preference for 4:2:0 formats.
96 if (is420Format(desc))
97 score += 2;
98 else if (is422Format(desc))
99 score += 1;
100 else if (is444Format(desc))
101 score -= 1;
102
103 if constexpr (QOperatingSystemVersion::currentType() == QOperatingSystemVersion::Android) {
104 // Add a slight preference for NV12 on Android
105 // as it's supported better than other 4:2:0 formats
106 if (fmt == AV_PIX_FMT_NV12)
107 score += 1;
108 }
109
110 if (isGreyFormat(desc) && !isGreyFormat(sourceSwFormatDesc)) // we don't want greyscale formats
111 return AVScore::NotSuitableAVScore;
112
113 if (desc->flags & AV_PIX_FMT_FLAG_BE) // we don't want big endian formats
114 score -= 10;
115 if (desc->flags & AV_PIX_FMT_FLAG_PAL) // we don't want paletted formats
116 score -= 10000;
117 if (desc->flags & AV_PIX_FMT_FLAG_RGB) // we don't want RGB formats
118 score -= 1000;
119
120 return score;
121}
122
123auto targetSwFormatScoreCalculator(AVPixelFormat sourceFormat)
124{
125 const auto sourceSwFormatDesc = av_pix_fmt_desc_get(sourceFormat);
126 return [=](AVPixelFormat fmt) {
127 return scoreTargetSwFormat(sourceSwFormatDesc, fmt);
128 };
129}
130
131bool isHwFormatAcceptedByCodec(AVPixelFormat pixFormat)
132{
133 switch (pixFormat) {
134 case AV_PIX_FMT_MEDIACODEC:
135 // Mediacodec doesn't accept AV_PIX_FMT_MEDIACODEC (QTBUG-116836)
136 return false;
137 default:
138 return true;
139 }
140}
141
142} // namespace
143
144std::optional<AVPixelFormat> findTargetSWFormat(AVPixelFormat sourceSWFormat, const Codec &codec,
145 const HWAccel &accel,
146 const AVPixelFormatSet &prohibitedFormats)
147{
148 using namespace QtMultimediaPrivate;
149
150 auto scoreTargetSwFormat = targetSwFormatScoreCalculator(sourceSWFormat);
151
152 const auto constraints = accel.constraints();
153 if (constraints && constraints->valid_sw_formats) {
154
155 const auto validSWFormatsForHWAccel =
156 makeSpan(constraints->valid_sw_formats) | ranges::to<QMinimalFlatSet>();
157
158 const auto codecPixelFormats = codec.pixelFormats();
159 auto validCodecPixelFormats = views::filter(codecPixelFormats, [&](AVPixelFormat fmt) {
160 if (!validSWFormatsForHWAccel.contains(fmt))
161 return false;
162
163 return !prohibitedFormats.count(fmt);
164 });
165
166 if constexpr (false) {
167 qDebug() << "validSWFormats" << (validSWFormatsForHWAccel | ranges::to<std::vector>())
168 << "scoredPixelFormats"
169 << (validCodecPixelFormats | views::transform([&](auto arg) {
170 return std::pair(arg, scoreTargetSwFormat(arg));
171 }) | ranges::to<std::vector>());
172 }
173
174 std::optional bestPixelFormat =
175 findBestAVValue(validCodecPixelFormats, scoreTargetSwFormat);
176 if (bestPixelFormat)
177 return bestPixelFormat;
178 }
179
180 // Some codecs, e.g. mediacodec, don't expose constraints, let's find the format in
181 // codec->pix_fmts (avcodec_get_supported_config with AV_CODEC_CONFIG_PIX_FORMAT since n7.1)
182 const auto codecPixelFormats = codec.pixelFormats();
183 auto pixelFormats = views::filter(codecPixelFormats, [&](AVPixelFormat fmt) {
184 return !prohibitedFormats.count(fmt);
185 });
186
187 return findBestAVValue(pixelFormats, scoreTargetSwFormat);
188}
189
190std::optional<AVPixelFormat> findTargetFormat(AVPixelFormat sourceSWFormat, const Codec &codec,
191 const HWAccel *accel,
192 const AVPixelFormatSet &prohibitedFormats)
193{
194 using namespace QtMultimediaPrivate;
195
196 if (accel) {
197 const auto hwFormat = accel->hwFormat();
198
199 // TODO: handle codec->capabilities & AV_CODEC_CAP_HARDWARE here
200 if (!isHwFormatAcceptedByCodec(hwFormat) || prohibitedFormats.count(hwFormat))
201 return findTargetSWFormat(sourceSWFormat, codec, *accel, prohibitedFormats);
202
203 const auto constraints = accel->constraints();
204 if (constraints && ranges::contains(makeSpan(constraints->valid_hw_formats), hwFormat))
205 return hwFormat;
206
207 // Some codecs, don't expose constraints,
208 // let's find the format in codec->pix_fmts (avcodec_get_supported_config with
209 // AV_CODEC_CONFIG_PIX_FORMAT since n7.1) and hw_config
210 if (isAVFormatSupported(codec, hwFormat))
211 return hwFormat;
212 }
213
214 const auto pixelFormats = codec.pixelFormats();
215 if (pixelFormats.empty()) {
216 qWarning() << "Codec pix formats are undefined, it's likely to behave incorrectly";
217
218 return sourceSWFormat;
219 }
220
221 auto candidatePixelFormats = views::filter(pixelFormats, [&](AVPixelFormat fmt) {
222 return !prohibitedFormats.count(fmt);
223 });
224
225 auto swScoreCalculator = targetSwFormatScoreCalculator(sourceSWFormat);
226 return findBestAVValue(candidatePixelFormats, swScoreCalculator);
227}
228
229AVScore findSWFormatScores(const Codec &codec, AVPixelFormat sourceSWFormat)
230{
231 const auto pixelFormats = codec.pixelFormats();
232 if (pixelFormats.empty())
233 // codecs without pixel formats are suspicious
234 return MinAVScore;
235
236 auto formatScoreCalculator = targetSwFormatScoreCalculator(sourceSWFormat);
237 std::optional bestFormatWithScore =
238 findBestAVValueWithScore(pixelFormats, formatScoreCalculator);
239 if (bestFormatWithScore)
240 return bestFormatWithScore->score;
241 else
242 return MinAVScore;
243}
244
245AVRational adjustFrameRate(QSpan<const AVRational> supportedRates, qreal settingsRate,
246 qreal sourceRate)
247{
248 qreal preferredRate = 0.;
249 if (settingsRate > 0)
250 preferredRate = settingsRate;
251 else if (sourceRate > 0)
252 preferredRate = sourceRate;
253 else if (supportedRates.empty())
254 preferredRate = 0.;
255 else
256 preferredRate = qreal(DefaultVideoFrameRate);
257
258 auto calcScore = [preferredRate](const AVRational &rate) {
259 // relative comparison
260 return qMin(preferredRate * rate.den, qreal(rate.num))
261 / qMax(preferredRate * rate.den, qreal(rate.num));
262 };
263
264 const auto result = findBestAVValue(supportedRates, calcScore);
265 if (result && result->num && result->den)
266 return *result;
267
268 const auto [num, den] = qRealToFraction(preferredRate);
269 return { num, den };
270}
271
272AVRational adjustFrameTimeBase(QSpan<const AVRational> supportedRates, AVRational frameRate,
273 bool isFixedRate)
274{
275 // TODO: user-specified frame rate might be required.
276 if (!supportedRates.empty()) {
277 auto hasFrameRate = [&]() {
278 for (AVRational rate : supportedRates)
279 if (rate.den == frameRate.den && rate.num == frameRate.num)
280 return true;
281
282 return false;
283 };
284
285 Q_ASSERT(hasFrameRate());
286
287 return { frameRate.den, frameRate.num };
288 }
289
290 if (isFixedRate)
291 return { frameRate.den, frameRate.num };
292
293 constexpr int TimeScaleFactor = 1000; // Allows not to follow fixed rate
294 return { frameRate.den, frameRate.num * TimeScaleFactor };
295}
296
297QSize adjustVideoResolution(const Codec &codec, QSize requestedResolution)
298{
299 if constexpr (QOperatingSystemVersion::currentType() == QOperatingSystemVersion::Windows) {
300 // TODO: investigate, there might be more encoders not supporting odd resolution
301 if (codec.name() == "h264_mf"_L1) {
302 auto makeEven = [](int size) { return size & ~1; };
303 return QSize(makeEven(requestedResolution.width()), makeEven(requestedResolution.height()));
304 }
305 }
306 return requestedResolution;
307}
308
309SwsFlags getScaleConversionType(const QSize &sourceSize, const QSize &targetSize)
310{
311 SwsFlags conversionType = SWS_FAST_BILINEAR;
312
313 if constexpr (QOperatingSystemVersion::currentType() == QOperatingSystemVersion::Android) {
314 // On Android, use SWS_BICUBIC for upscaling if least one dimension is upscaled
315 // to avoid a crash caused by ff_hcscale_fast_c with SWS_FAST_BILINEAR.
316 if (targetSize.width() > sourceSize.width() || targetSize.height() > sourceSize.height())
317 conversionType = SWS_BICUBIC;
318 }
319
320 return conversionType;
321}
322
323} // namespace QFFmpeg
324
325QT_END_NAMESPACE
std::optional< AVPixelFormat > findTargetFormat(AVPixelFormat sourceSWFormat, const Codec &codec, const HWAccel *accel, const AVPixelFormatSet &prohibitedFormats)
AVScore findSWFormatScores(const Codec &codec, AVPixelFormat sourceSWFormat)
SwsFlags getScaleConversionType(const QSize &sourceSize, const QSize &targetSize)
QSize adjustVideoResolution(const Codec &codec, QSize requestedResolution)
QT_MANGLE_NAMESPACE(QMacScreenCaptureStreamDelegate) QMacScreenCaptureStreamDelegate
AVRational adjustFrameRate(QSpan< const AVRational > supportedRates, qreal settingsRate, qreal sourceRate)
adjustFrameRate resolves the effective frame rate to negotiate with a codec.
AVRational adjustFrameTimeBase(QSpan< const AVRational > supportedRates, AVRational frameRate, bool isFixedRate)
adjustFrameTimeBase gets adjusted timebase by a list of supported frame rates and an already adjusted...
std::optional< AVPixelFormat > findTargetSWFormat(AVPixelFormat sourceSWFormat, const Codec &codec, const HWAccel &accel, const AVPixelFormatSet &prohibitedFormats)