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
4#include "qffmpegvideoencoderutils_p.h"
5#include "qffmpegcodecstorage_p.h"
6#include "private/qmultimediautils_p.h"
7
8extern "C" {
9#include <libavutil/pixdesc.h>
10}
11
12QT_BEGIN_NAMESPACE
13
14namespace QFFmpeg {
15
16static AVScore calculateTargetSwFormatScore(const AVPixFmtDescriptor *sourceSwFormatDesc,
17 AVPixelFormat fmt,
18 const AVPixelFormatSet &prohibitedFormats)
19{
20 // determine the format used by the encoder.
21 // We prefer YUV420 based formats such as NV12 or P010. Selection trues to find the best
22 // matching format for the encoder depending on the bit depth of the source format
23
24 if (prohibitedFormats.count(x: fmt))
25 return NotSuitableAVScore;
26
27 const auto *desc = av_pix_fmt_desc_get(pix_fmt: fmt);
28 if (!desc)
29 return NotSuitableAVScore;
30
31 if (desc->flags & AV_PIX_FMT_FLAG_HWACCEL)
32 // we really don't want HW accelerated formats here
33 return NotSuitableAVScore;
34
35 AVScore score = DefaultAVScore;
36
37 if (desc == sourceSwFormatDesc)
38 // prefer exact matches
39 score += 10;
40
41 const int sourceBpp = av_get_bits_per_pixel(pixdesc: sourceSwFormatDesc);
42 const int bpp = av_get_bits_per_pixel(pixdesc: desc);
43
44 // we want formats with the same bpp
45 if (bpp == sourceBpp)
46 score += 100;
47 else if (bpp < sourceBpp)
48 score -= 100 + (sourceBpp - bpp);
49
50 // Add a slight preference for 4:2:0 formats.
51 // TODO: shouldn't we compare withc sourceSwFormatDesc->log2_chroma_h
52 // and sourceSwFormatDesc->log2_chroma_w ?
53 if (desc->log2_chroma_h == 1)
54 score += 1;
55 if (desc->log2_chroma_w == 1)
56 score += 1;
57
58#ifdef Q_OS_ANDROID
59 // Add a slight preference for NV12 on Android
60 // as it's supported better than other 4:2:0 formats
61 if (fmt == AV_PIX_FMT_NV12)
62 score += 1;
63#endif
64
65 if (desc->flags & AV_PIX_FMT_FLAG_BE)
66 score -= 10;
67 if (desc->flags & AV_PIX_FMT_FLAG_PAL)
68 // we don't want paletted formats
69 score -= 10000;
70 if (desc->flags & AV_PIX_FMT_FLAG_RGB)
71 // we don't want RGB formats
72 score -= 1000;
73 // qCDebug(qLcVideoFrameEncoder)
74 // << "checking format" << fmt << Qt::hex << desc->flags << desc->comp[0].depth
75 // << desc->log2_chroma_h << desc->log2_chroma_w << "score:" << score;
76
77 return score;
78}
79
80static auto
81targetSwFormatScoreCalculator(AVPixelFormat sourceFormat,
82 std::reference_wrapper<const AVPixelFormatSet> prohibitedFormats)
83{
84 const auto sourceSwFormatDesc = av_pix_fmt_desc_get(pix_fmt: sourceFormat);
85 return [=](AVPixelFormat fmt) {
86 return calculateTargetSwFormatScore(sourceSwFormatDesc, fmt, prohibitedFormats);
87 };
88}
89
90static bool isHwFormatAcceptedByCodec(AVPixelFormat pixFormat)
91{
92 switch (pixFormat) {
93 case AV_PIX_FMT_MEDIACODEC:
94 // Mediacodec doesn't accept AV_PIX_FMT_MEDIACODEC (QTBUG-116836)
95 return false;
96 default:
97 return true;
98 }
99}
100
101std::optional<AVPixelFormat> findTargetSWFormat(AVPixelFormat sourceSWFormat, const Codec &codec,
102 const HWAccel &accel,
103 const AVPixelFormatSet &prohibitedFormats)
104{
105 auto scoreCalculator = targetSwFormatScoreCalculator(sourceFormat: sourceSWFormat, prohibitedFormats);
106
107 const auto constraints = accel.constraints();
108 if (constraints && constraints->valid_sw_formats) {
109 QSpan<const AVPixelFormat> formats = makeSpan(values: constraints->valid_sw_formats);
110 return findBestAVValue(values: formats, calculateScore: scoreCalculator);
111 }
112
113 // Some codecs, e.g. mediacodec, don't expose constraints, let's find the format in
114 // codec->pix_fmts (avcodec_get_supported_config with AV_CODEC_CONFIG_PIX_FORMAT since n7.1)
115 const auto pixelFormats = codec.pixelFormats();
116 return findBestAVValue(values: pixelFormats, calculateScore: scoreCalculator);
117}
118
119std::optional<AVPixelFormat> findTargetFormat(AVPixelFormat sourceFormat,
120 AVPixelFormat sourceSWFormat, const Codec &codec,
121 const HWAccel *accel,
122 const AVPixelFormatSet &prohibitedFormats)
123{
124 Q_UNUSED(sourceFormat);
125
126 if (accel) {
127 const auto hwFormat = accel->hwFormat();
128
129 // TODO: handle codec->capabilities & AV_CODEC_CAP_HARDWARE here
130 if (!isHwFormatAcceptedByCodec(pixFormat: hwFormat) || prohibitedFormats.count(x: hwFormat))
131 return findTargetSWFormat(sourceSWFormat, codec, accel: *accel, prohibitedFormats);
132
133 const auto constraints = accel->constraints();
134 if (constraints && hasValue(range: makeSpan(values: constraints->valid_hw_formats), value: hwFormat))
135 return hwFormat;
136
137 // Some codecs, don't expose constraints,
138 // let's find the format in codec->pix_fmts (avcodec_get_supported_config with
139 // AV_CODEC_CONFIG_PIX_FORMAT since n7.1) and hw_config
140 if (isAVFormatSupported(codec, format: hwFormat))
141 return hwFormat;
142 }
143
144 const auto pixelFormats = codec.pixelFormats();
145 if (pixelFormats.empty()) {
146 qWarning() << "Codec pix formats are undefined, it's likely to behave incorrectly";
147
148 return sourceSWFormat;
149 }
150
151 auto swScoreCalculator = targetSwFormatScoreCalculator(sourceFormat: sourceSWFormat, prohibitedFormats);
152 return findBestAVValue(values: pixelFormats, calculateScore: swScoreCalculator);
153}
154
155AVScore findSWFormatScores(const Codec &codec, AVPixelFormat sourceSWFormat)
156{
157 const auto pixelFormats = codec.pixelFormats();
158 if (pixelFormats.empty())
159 // codecs without pixel formats are suspicious
160 return MinAVScore;
161
162 AVPixelFormatSet emptySet;
163 auto formatScoreCalculator = targetSwFormatScoreCalculator(sourceFormat: sourceSWFormat, prohibitedFormats: emptySet);
164 return findBestAVValueWithScore(values: pixelFormats, calculateScore: formatScoreCalculator).score;
165}
166
167AVRational adjustFrameRate(QSpan<const AVRational> supportedRates, qreal requestedRate)
168{
169 auto calcScore = [requestedRate](const AVRational &rate) {
170 // relative comparison
171 return qMin(a: requestedRate * rate.den, b: qreal(rate.num))
172 / qMax(a: requestedRate * rate.den, b: qreal(rate.num));
173 };
174
175 const auto result = findBestAVValue(values: supportedRates, calculateScore: calcScore);
176 if (result && result->num && result->den)
177 return *result;
178
179 const auto [num, den] = qRealToFraction(value: requestedRate);
180 return { .num: num, .den: den };
181}
182
183AVRational adjustFrameTimeBase(QSpan<const AVRational> supportedRates, AVRational frameRate)
184{
185 // TODO: user-specified frame rate might be required.
186 if (!supportedRates.empty()) {
187 auto hasFrameRate = [&]() {
188 for (AVRational rate : supportedRates)
189 if (rate.den == frameRate.den && rate.num == frameRate.num)
190 return true;
191
192 return false;
193 };
194
195 Q_ASSERT(hasFrameRate());
196
197 return { .num: frameRate.den, .den: frameRate.num };
198 }
199
200 constexpr int TimeScaleFactor = 1000; // Allows not to follow fixed rate
201 return { .num: frameRate.den, .den: frameRate.num * TimeScaleFactor };
202}
203
204QSize adjustVideoResolution(const Codec &codec, QSize requestedResolution)
205{
206#ifdef Q_OS_WINDOWS
207 // TODO: investigate, there might be more encoders not supporting odd resolution
208 if (codec.name() == u"h264_mf") {
209 auto makeEven = [](int size) { return size & ~1; };
210 return QSize(makeEven(requestedResolution.width()), makeEven(requestedResolution.height()));
211 }
212#else
213 Q_UNUSED(codec);
214#endif
215 return requestedResolution;
216}
217
218int getScaleConversionType(const QSize &sourceSize, const QSize &targetSize)
219{
220 int conversionType = SWS_FAST_BILINEAR;
221
222#ifdef Q_OS_ANDROID
223 // On Android, use SWS_BICUBIC for upscaling if least one dimension is upscaled
224 // to avoid a crash caused by ff_hcscale_fast_c with SWS_FAST_BILINEAR.
225 if (targetSize.width() > sourceSize.width() || targetSize.height() > sourceSize.height())
226 conversionType = SWS_BICUBIC;
227#else
228 Q_UNUSED(sourceSize);
229 Q_UNUSED(targetSize);
230#endif
231
232 return conversionType;
233}
234
235} // namespace QFFmpeg
236
237QT_END_NAMESPACE
238

source code of qtmultimedia/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegvideoencoderutils.cpp