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
101AVPixelFormat 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
119AVPixelFormat 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
154std::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
167AVScore 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
179const 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
186AVRational 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
202AVRational 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
223QSize 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
239QT_END_NAMESPACE
240

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