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