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 | |
8 | extern "C" { |
9 | #include <libavutil/pixdesc.h> |
10 | } |
11 | |
12 | QT_BEGIN_NAMESPACE |
13 | |
14 | namespace QFFmpeg { |
15 | |
16 | static 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 | |
80 | static auto |
81 | targetSwFormatScoreCalculator(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 | |
90 | static 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 | |
101 | std::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 | |
119 | std::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 | |
155 | AVScore 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 | |
167 | AVRational 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 | |
183 | AVRational 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 | |
204 | QSize 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 | |
218 | int 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 | |
237 | QT_END_NAMESPACE |
238 | |