6#include <QtFFmpegMediaPluginImpl/private/qffmpeg_p.h>
8#include <QtMultimedia/private/qmultimediautils_p.h>
9#include <QtCore/qglobalstatic.h>
10#include <QtCore/qloggingcategory.h>
11#include <QtCore/qoperatingsystemversion.h>
12#include <QtCore/qreadwritelock.h>
13#include <QtCore/private/qminimalflatset_p.h>
14#include <QtFFmpegMediaPluginImpl/private/qffmpegrecordingengineutils_p.h>
18#include <shared_mutex>
21#include <libavutil/hwcontext.h>
22#include <libavutil/pixdesc.h>
35bool is16BitFormat(
const AVPixFmtDescriptor *desc)
37 return desc->comp[0].depth == 16;
40bool is10BitFormat(
const AVPixFmtDescriptor *desc)
42 return desc->comp[0].depth == 10;
45bool is8BitFormat(
const AVPixFmtDescriptor *desc)
47 return desc->comp[0].depth == 8;
50bool is444Format(
const AVPixFmtDescriptor *desc)
52 return desc->log2_chroma_h == 0 && desc->log2_chroma_w == 0;
55bool is422Format(
const AVPixFmtDescriptor *desc)
57 return desc->log2_chroma_h == 1 && desc->log2_chroma_w == 0;
60bool is420Format(
const AVPixFmtDescriptor *desc)
62 return desc->log2_chroma_h == 1 && desc->log2_chroma_w == 1;
65bool isGreyFormat(
const AVPixFmtDescriptor *desc)
67 return desc->nb_components == 1;
70AVScore scoreTargetSwFormat(
const AVPixFmtDescriptor *sourceSwFormatDesc, AVPixelFormat fmt)
72 constexpr int AlphaMismatchPenalty = 50;
78 const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(fmt);
80 return NotSuitableAVScore;
82 if (desc->flags & AV_PIX_FMT_FLAG_HWACCEL)
84 return NotSuitableAVScore;
86 AVScore score = DefaultAVScore;
88 if (desc == sourceSwFormatDesc)
92 const int sourceBpp = av_get_bits_per_pixel(sourceSwFormatDesc);
93 int bpp = av_get_bits_per_pixel(desc);
95 const bool sourceHasAlpha = sourceSwFormatDesc->flags & AV_PIX_FMT_FLAG_ALPHA;
96 if ((desc->flags & AV_PIX_FMT_FLAG_ALPHA) && !sourceHasAlpha) {
100 bpp -= desc->comp[desc->nb_components - 1].depth;
101 score -= AlphaMismatchPenalty;
105 if (bpp == sourceBpp)
107 else if (bpp < sourceBpp)
108 score -= 100 + (sourceBpp - bpp);
111 if (is8BitFormat(sourceSwFormatDesc)) {
112 if (is10BitFormat(desc))
114 else if (is16BitFormat(desc))
119 if (is420Format(desc))
121 else if (is422Format(desc))
123 else if (is444Format(desc))
126 if constexpr (QOperatingSystemVersion::currentType() == QOperatingSystemVersion::Android) {
129 if (fmt == AV_PIX_FMT_NV12)
133 if (isGreyFormat(desc) && !isGreyFormat(sourceSwFormatDesc))
134 return AVScore::NotSuitableAVScore;
136 if (desc->flags & AV_PIX_FMT_FLAG_BE)
138 if (desc->flags & AV_PIX_FMT_FLAG_PAL)
140 if (desc->flags & AV_PIX_FMT_FLAG_RGB)
146auto targetSwFormatScoreCalculator(AVPixelFormat sourceFormat)
148 const auto sourceSwFormatDesc = av_pix_fmt_desc_get(sourceFormat);
149 return [=](AVPixelFormat fmt) {
150 return scoreTargetSwFormat(sourceSwFormatDesc, fmt);
154bool isHwFormatAcceptedByCodec(AVPixelFormat pixFormat)
157 case AV_PIX_FMT_MEDIACODEC:
168static constexpr AVPixelFormat probeCandidates[] = {
169 AV_PIX_FMT_YUV420P, AV_PIX_FMT_NV12, AV_PIX_FMT_NV21, AV_PIX_FMT_YUVJ420P,
170 AV_PIX_FMT_YUV422P, AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUVA420P, AV_PIX_FMT_P010LE,
171 AV_PIX_FMT_YUV420P10LE, AV_PIX_FMT_YUV422P10LE, AV_PIX_FMT_YUV444P10LE, AV_PIX_FMT_GRAY8,
174class ProbedPixelFormatCache
177 std::vector<AVPixelFormat> pixelFormats(
const Codec &codec, QSize resolution,
178 const HWAccel *accel);
181 std::optional<std::vector<AVPixelFormat>> lookup(
const AVCodec *codec, QSize resolution,
182 AVPixelFormat hwFormat);
187 AVPixelFormat hwFormat = AV_PIX_FMT_NONE;
188 AVPixelFormat format = AV_PIX_FMT_NONE;
189 bool supported =
false;
192 QReadWriteLock m_lock;
193 std::multimap<
const AVCodec *, ProbeRecord> m_cache;
196std::vector<AVPixelFormat>
197ProbedPixelFormatCache::pixelFormats(
const Codec &codec, QSize resolution,
const HWAccel *accel)
199 const AVPixelFormat hwFormat = accel ? accel->hwFormat() : AV_PIX_FMT_NONE;
202 std::shared_lock locker(m_lock);
203 if (std::optional cached = lookup(codec.get(), resolution, hwFormat))
207 std::unique_lock locker(m_lock);
208 if (std::optional cached = lookup(codec.get(), resolution, hwFormat))
211 const std::vector<AVPixelFormat> supported = probePixelFormats(codec, resolution, accel);
212 qCDebug(qLcVideoEncoderUtils) <<
"Probed pixel formats for" << codec.name() <<
"at"
213 << resolution <<
"hwFormat" << hwFormat <<
":" << supported;
215 using namespace QtMultimediaPrivate;
216 for (AVPixelFormat format : probeCandidates)
217 m_cache.emplace(codec.get(),
222 ranges::contains(supported, format),
228std::optional<std::vector<AVPixelFormat>>
229ProbedPixelFormatCache::lookup(
const AVCodec *codec, QSize resolution, AVPixelFormat hwFormat)
231 using namespace QtMultimediaPrivate;
232 namespace ranges = QtMultimediaPrivate::ranges;
234 auto range_pair = m_cache.equal_range(codec);
235 ranges::subrange<std::multimap<
const AVCodec *, ProbeRecord>::iterator> range{
240 auto matches = [&](
const auto &entry) {
241 return entry.second.resolution == resolution && entry.second.hwFormat == hwFormat;
244 if (!ranges::any_of(range, matches))
247 return views::filter(range, [&](
const auto &entry) {
248 return matches(entry) && entry.second.supported;
249 }) | views::transform([](
const auto &entry) {
250 return entry.second.format;
251 }) | ranges::to<std::vector>();
254Q_GLOBAL_STATIC(ProbedPixelFormatCache, probedPixelFormatCache)
256bool isProbingDisabled()
258 static const bool disabled =
259 qEnvironmentVariableIntValue(
"QT_FFMPEG_DISABLE_ENCODER_PROBE") != 0;
263AVBufferUPtr createProbeFramesContext(
const HWAccel &accel, AVPixelFormat swFormat,
266 AVBufferUPtr framesContext{ av_hwframe_ctx_alloc(accel.hwDeviceContextAsBuffer()) };
270 auto *context =
reinterpret_cast<AVHWFramesContext *>(framesContext->data);
271 context->format = accel.hwFormat();
272 context->sw_format = swFormat;
273 context->width = resolution.width();
274 context->height = resolution.height();
276 if (av_hwframe_ctx_init(framesContext.get()) < 0)
279 return framesContext;
286bool codecDeclaresSwFormats(
const Codec &codec, QSize resolution,
const HWAccel *accel)
288 const std::vector<AVPixelFormat> formats = encoderPixelFormats(codec, resolution, accel);
289 return QtMultimediaPrivate::ranges::any_of(formats, [](AVPixelFormat fmt) {
290 return !isHwPixelFormat(fmt);
294inline constexpr auto filterSuitablePixelFormats =
295 QtMultimediaPrivate::views::filter([](
const ScoredPixelFormat &scoredFmt) {
296 return scoredFmt.score > AVScore::NotSuitableAVScore;
302 const HWAccel *accel)
304 std::vector<AVPixelFormat> result;
306 for (AVPixelFormat format : probeCandidates) {
307 AVCodecContextUPtr context{ avcodec_alloc_context3(codec.get()) };
309 qCWarning(qLcVideoEncoderUtils)
310 <<
"Could not allocate codec context to probe" << codec.name();
314 QSpan<
const AVRational> supportedFrameRates = codec.frameRates();
315 const AVRational frameRate =
316 supportedFrameRates.empty() ? AVRational{ 25, 1 } : supportedFrameRates.front();
318 context->width = resolution.width();
319 context->height = resolution.height();
320 context->time_base = { frameRate.den, frameRate.num };
321 context->framerate = frameRate;
323 AVBufferUPtr framesContext;
325 framesContext = createProbeFramesContext(*accel, format, resolution);
328 context->hw_frames_ctx = av_buffer_ref(framesContext.get());
329 context->pix_fmt = accel->hwFormat();
331 context->pix_fmt = format;
334 AVDictionaryHolder opts;
335 applyExperimentalCodecOptions(codec, opts);
337 if (avcodec_open2(context.get(), codec.get(), opts) >= 0)
338 result.push_back(format);
345 const HWAccel *accel)
349 const QSpan<
const AVPixelFormat> declared = codec.pixelFormats();
350 if (!declared.empty() || isProbingDisabled())
351 return std::vector<AVPixelFormat>(declared.begin(), declared.end());
353 return probedPixelFormatCache->pixelFormats(codec, resolution, accel);
358 const auto *desc = av_pix_fmt_desc_get(source);
359 return desc ? scoreTargetSwFormat(desc, target) : AVScore::NotSuitableAVScore;
363 const HWAccel &accel, QSize resolution)
365 using namespace QtMultimediaPrivate;
367 auto scoreTargetSwFormat = targetSwFormatScoreCalculator(sourceSWFormat);
369 const auto constraints = accel.constraints();
370 if (constraints && constraints->valid_sw_formats) {
372 const auto validSWFormatsForHWAccel =
373 makeSpan(constraints->valid_sw_formats) | ranges::to<QMinimalFlatSet>();
375 auto bestPixelFormat = [&]() -> std::optional<AVPixelFormat> {
376 if (codecDeclaresSwFormats(codec, resolution, &accel)) {
379 const std::vector<AVPixelFormat> codecPixelFormats =
380 encoderPixelFormats(codec, resolution, &accel);
381 auto formats = views::filter(codecPixelFormats, [&](AVPixelFormat fmt) {
382 return validSWFormatsForHWAccel.contains(fmt);
385 return findBestAVValue(formats, scoreTargetSwFormat);
387 return findBestAVValue(validSWFormatsForHWAccel, scoreTargetSwFormat);
392 return bestPixelFormat;
398 const std::vector<AVPixelFormat> codecPixelFormats =
399 encoderPixelFormats(codec, resolution, &accel);
400 return findBestAVValue(codecPixelFormats, scoreTargetSwFormat);
405 const HWAccel *accel, QSize resolution)
407 if constexpr (
false) {
408 qDebug() <<
"findAndScoreTargetSWFormats" << codec.name() << codec.id();
411 using namespace QtMultimediaPrivate;
413 auto scoreTargetSwFormat = targetSwFormatScoreCalculator(sourceSWFormat);
415 auto score = views::transform([&](AVPixelFormat arg) {
416 return ScoredPixelFormat{ arg, scoreTargetSwFormat(arg) };
419 std::vector<ScoredPixelFormat> scoredPixelFormats = [&] {
420 const AVHWFramesConstraints *constraints = accel ? accel->constraints() :
nullptr;
421 if (constraints && constraints->valid_sw_formats) {
422 const auto validSWFormatsForHWAccel =
423 makeSpan(constraints->valid_sw_formats) | ranges::to<QMinimalFlatSet>();
425 if (codecDeclaresSwFormats(codec, resolution, accel)) {
428 const std::vector<AVPixelFormat> codecPixelFormats =
429 encoderPixelFormats(codec, resolution, accel);
430 auto validCodecPixelFormats =
431 views::filter(codecPixelFormats, [&](AVPixelFormat fmt) {
432 return validSWFormatsForHWAccel.contains(fmt);
434 return validCodecPixelFormats | score | filterSuitablePixelFormats
435 | ranges::to<std::vector>();
439 return validSWFormatsForHWAccel | score | filterSuitablePixelFormats
440 | ranges::to<std::vector>();
449 const std::vector<AVPixelFormat> codecPixelFormats =
450 encoderPixelFormats(codec, resolution, accel);
451 return codecPixelFormats | score | filterSuitablePixelFormats
452 | ranges::to<std::vector>();
456 ranges::sort(scoredPixelFormats, [](
const ScoredPixelFormat &a,
const ScoredPixelFormat &b) {
457 return a.score > b.score;
460 return scoredPixelFormats;
464 const HWAccel *accel, QSize resolution)
466 using namespace QtMultimediaPrivate;
469 const auto hwFormat = accel->hwFormat();
472 if (!isHwFormatAcceptedByCodec(hwFormat))
473 return findTargetSWFormat(sourceSWFormat, codec, *accel, resolution);
475 const auto constraints = accel->constraints();
476 if (constraints && ranges::contains(makeSpan(constraints->valid_hw_formats), hwFormat))
482 if (isAVFormatSupported(codec, hwFormat))
486 const auto pixelFormats = encoderPixelFormats(codec, resolution,
nullptr);
487 if (pixelFormats.empty()) {
488 qCWarning(qLcVideoEncoderUtils)
489 <<
"Codec pix formats are undefined and probing" << codec.name()
490 <<
"found none either, it's likely to behave incorrectly";
492 return sourceSWFormat;
495 auto swScoreCalculator = targetSwFormatScoreCalculator(sourceSWFormat);
496 return findBestAVValue(pixelFormats, swScoreCalculator);
501 const auto pixelFormats = encoderPixelFormats(codec, resolution,
nullptr);
502 if (pixelFormats.empty())
507 auto formatScoreCalculator = targetSwFormatScoreCalculator(sourceSWFormat);
508 std::optional bestFormatWithScore =
509 findBestAVValueWithScore(pixelFormats, formatScoreCalculator);
510 if (bestFormatWithScore)
511 return bestFormatWithScore->score;
519 qreal preferredRate = 0.;
520 if (settingsRate > 0)
521 preferredRate = settingsRate;
522 else if (sourceRate > 0)
523 preferredRate = sourceRate;
524 else if (supportedRates.empty())
527 preferredRate = qreal(DefaultVideoFrameRate);
529 auto calcScore = [preferredRate](
const AVRational &rate) {
531 return qMin(preferredRate * rate.den, qreal(rate.num))
532 / qMax(preferredRate * rate.den, qreal(rate.num));
535 const auto result = findBestAVValue(supportedRates, calcScore);
536 if (result && result->num && result->den)
539 const auto [num, den] = qRealToFraction(preferredRate);
547 if (!supportedRates.empty()) {
548 auto hasFrameRate = [&]() {
549 for (AVRational rate : supportedRates)
550 if (rate.den == frameRate.den && rate.num == frameRate.num)
556 Q_ASSERT(hasFrameRate());
558 return { frameRate.den, frameRate.num };
562 return { frameRate.den, frameRate.num };
564 constexpr int TimeScaleFactor = 1000;
565 return { frameRate.den, frameRate.num * TimeScaleFactor };
570 if constexpr (QOperatingSystemVersion::currentType() == QOperatingSystemVersion::Windows) {
572 if (codec.name() ==
"h264_mf"_L1) {
573 auto makeEven = [](
int size) {
return size & ~1; };
574 return QSize(makeEven(requestedResolution.width()), makeEven(requestedResolution.height()));
577 return requestedResolution;
582 SwsFlags conversionType = SWS_FAST_BILINEAR;
584 if constexpr (QOperatingSystemVersion::currentType() == QOperatingSystemVersion::Android) {
587 if (targetSize.width() > sourceSize.width() || targetSize.height() > sourceSize.height())
588 conversionType = SWS_BICUBIC;
591 return conversionType;
std::vector< ScoredPixelFormat > findAndScoreTargetSWFormats(AVPixelFormat sourceSWFormat, const Codec &codec, const HWAccel *accel, QSize resolution)
SwsFlags getScaleConversionType(const QSize &sourceSize, const QSize &targetSize)
QSize adjustVideoResolution(const Codec &codec, QSize requestedResolution)
std::optional< AVPixelFormat > findTargetFormat(AVPixelFormat sourceSWFormat, const Codec &codec, const HWAccel *accel, QSize resolution)
std::vector< AVPixelFormat > encoderPixelFormats(const Codec &codec, QSize resolution, const HWAccel *accel)
AVRational adjustFrameRate(QSpan< const AVRational > supportedRates, qreal settingsRate, qreal sourceRate)
adjustFrameRate resolves the effective frame rate to negotiate with a codec.
AVScore scoreTargetSwFormat(AVPixelFormat source, AVPixelFormat target)
std::optional< AVPixelFormat > findTargetSWFormat(AVPixelFormat sourceSWFormat, const Codec &codec, const HWAccel &accel, QSize resolution)
AVRational adjustFrameTimeBase(QSpan< const AVRational > supportedRates, AVRational frameRate, bool isFixedRate)
adjustFrameTimeBase gets adjusted timebase by a list of supported frame rates and an already adjusted...
AVScore findSWFormatScores(const Codec &codec, AVPixelFormat sourceSWFormat, QSize resolution)
std::vector< AVPixelFormat > probePixelFormats(const Codec &codec, QSize resolution, const HWAccel *accel)
QT_BEGIN_NAMESPACE Q_STATIC_LOGGING_CATEGORY(lcSynthesizedIterableAccess, "qt.iterable.synthesized", QtWarningMsg)