| 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 | |