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 | AVPixelFormat findTargetSWFormat(AVPixelFormat sourceSWFormat, const AVCodec *codec, |
102 | const HWAccel &accel, const AVPixelFormatSet &prohibitedFormats) |
103 | { |
104 | auto scoreCalculator = targetSwFormatScoreCalculator(sourceFormat: sourceSWFormat, prohibitedFormats); |
105 | |
106 | const auto constraints = accel.constraints(); |
107 | if (constraints && constraints->valid_sw_formats) |
108 | return findBestAVValue(values: constraints->valid_sw_formats, calculateScore: scoreCalculator).first; |
109 | |
110 | // Some codecs, e.g. mediacodec, don't expose constraints, let's find the format in |
111 | // codec->pix_fmts (avcodec_get_supported_config with AV_CODEC_CONFIG_PIX_FORMAT since n7.1) |
112 | const auto pixelFormats = getCodecPixelFormats(codec); |
113 | if (pixelFormats) |
114 | return findBestAVValue(values: pixelFormats, calculateScore: scoreCalculator).first; |
115 | |
116 | return AV_PIX_FMT_NONE; |
117 | } |
118 | |
119 | AVPixelFormat findTargetFormat(AVPixelFormat sourceFormat, AVPixelFormat sourceSWFormat, |
120 | const AVCodec *codec, const HWAccel *accel, |
121 | const AVPixelFormatSet &prohibitedFormats) |
122 | { |
123 | Q_UNUSED(sourceFormat); |
124 | |
125 | if (accel) { |
126 | const auto hwFormat = accel->hwFormat(); |
127 | |
128 | // TODO: handle codec->capabilities & AV_CODEC_CAP_HARDWARE here |
129 | if (!isHwFormatAcceptedByCodec(pixFormat: hwFormat) || prohibitedFormats.count(x: hwFormat)) |
130 | return findTargetSWFormat(sourceSWFormat, codec, accel: *accel, prohibitedFormats); |
131 | |
132 | const auto constraints = accel->constraints(); |
133 | if (constraints && hasAVValue(fmts: constraints->valid_hw_formats, format: hwFormat)) |
134 | return hwFormat; |
135 | |
136 | // Some codecs, don't expose constraints, |
137 | // let's find the format in codec->pix_fmts (avcodec_get_supported_config with |
138 | // AV_CODEC_CONFIG_PIX_FORMAT since n7.1) and hw_config |
139 | if (isAVFormatSupported(codec, format: hwFormat)) |
140 | return hwFormat; |
141 | } |
142 | |
143 | const auto pixelFormats = getCodecPixelFormats(codec); |
144 | if (!pixelFormats) { |
145 | qWarning() << "Codec pix formats are undefined, it's likely to behave incorrectly" ; |
146 | |
147 | return sourceSWFormat; |
148 | } |
149 | |
150 | auto swScoreCalculator = targetSwFormatScoreCalculator(sourceFormat: sourceSWFormat, prohibitedFormats); |
151 | return findBestAVValue(values: pixelFormats, calculateScore: swScoreCalculator).first; |
152 | } |
153 | |
154 | std::pair<const AVCodec *, HWAccelUPtr> findHwEncoder(AVCodecID codecID, const QSize &resolution) |
155 | { |
156 | auto matchesSizeConstraints = [&resolution](const HWAccel &accel) { |
157 | return accel.matchesSizeContraints(size: resolution); |
158 | }; |
159 | |
160 | // 1st - attempt to find hw accelerated encoder |
161 | auto result = HWAccel::findEncoderWithHwAccel(id: codecID, hwAccelPredicate: matchesSizeConstraints); |
162 | Q_ASSERT(!!result.first == !!result.second); |
163 | |
164 | return result; |
165 | } |
166 | |
167 | AVScore findSWFormatScores(const AVCodec* codec, AVPixelFormat sourceSWFormat) |
168 | { |
169 | const auto pixelFormats = getCodecPixelFormats(codec); |
170 | if (!pixelFormats) |
171 | // codecs without pixel formats are suspicious |
172 | return MinAVScore; |
173 | |
174 | AVPixelFormatSet emptySet; |
175 | auto formatScoreCalculator = targetSwFormatScoreCalculator(sourceFormat: sourceSWFormat, prohibitedFormats: emptySet); |
176 | return findBestAVValue(values: pixelFormats, calculateScore: formatScoreCalculator).second; |
177 | } |
178 | |
179 | const AVCodec *findSwEncoder(AVCodecID codecID, AVPixelFormat sourceSWFormat) |
180 | { |
181 | return findAVEncoder(codecId: codecID, scoresGetter: [sourceSWFormat](const AVCodec *codec) { |
182 | return findSWFormatScores(codec, sourceSWFormat); |
183 | }); |
184 | } |
185 | |
186 | AVRational adjustFrameRate(const AVRational *supportedRates, qreal requestedRate) |
187 | { |
188 | auto calcScore = [requestedRate](const AVRational &rate) { |
189 | // relative comparison |
190 | return qMin(a: requestedRate * rate.den, b: qreal(rate.num)) |
191 | / qMax(a: requestedRate * rate.den, b: qreal(rate.num)); |
192 | }; |
193 | |
194 | const auto result = findBestAVValue(values: supportedRates, calculateScore: calcScore).first; |
195 | if (result.num && result.den) |
196 | return result; |
197 | |
198 | const auto [num, den] = qRealToFraction(value: requestedRate); |
199 | return { .num: num, .den: den }; |
200 | } |
201 | |
202 | AVRational adjustFrameTimeBase(const AVRational *supportedRates, AVRational frameRate) |
203 | { |
204 | // TODO: user-specified frame rate might be required. |
205 | if (supportedRates) { |
206 | auto hasFrameRate = [&]() { |
207 | for (auto rate = supportedRates; rate->num && rate->den; ++rate) |
208 | if (rate->den == frameRate.den && rate->num == frameRate.num) |
209 | return true; |
210 | |
211 | return false; |
212 | }; |
213 | |
214 | Q_ASSERT(hasFrameRate()); |
215 | |
216 | return { .num: frameRate.den, .den: frameRate.num }; |
217 | } |
218 | |
219 | constexpr int TimeScaleFactor = 1000; // Allows not to follow fixed rate |
220 | return { .num: frameRate.den, .den: frameRate.num * TimeScaleFactor }; |
221 | } |
222 | |
223 | QSize adjustVideoResolution(const AVCodec *codec, QSize requestedResolution) |
224 | { |
225 | #ifdef Q_OS_WINDOWS |
226 | // TODO: investigate, there might be more encoders not supporting odd resolution |
227 | if (strcmp(codec->name, "h264_mf" ) == 0) { |
228 | auto makeEven = [](int size) { return size & ~1; }; |
229 | return QSize(makeEven(requestedResolution.width()), makeEven(requestedResolution.height())); |
230 | } |
231 | #else |
232 | Q_UNUSED(codec); |
233 | #endif |
234 | return requestedResolution; |
235 | } |
236 | |
237 | } // namespace QFFmpeg |
238 | |
239 | QT_END_NAMESPACE |
240 | |