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