summaryrefslogtreecommitdiffstats
path: root/src/plugins/multimedia/ffmpeg/qffmpegvideoencoderutils.cpp
blob: f22b4865d4a455945700aaf4910a0c93f9fb400d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only

#include "qffmpegvideoencoderutils_p.h"
#include "private/qmultimediautils_p.h"

extern "C" {
#include <libavutil/pixdesc.h>
}

QT_BEGIN_NAMESPACE

namespace QFFmpeg {

static AVScore calculateTargetSwFormatScore(const AVPixFmtDescriptor *sourceSwFormatDesc,
                                            AVPixelFormat fmt)
{
    // determine the format used by the encoder.
    // We prefer YUV422 based formats such as NV12 or P010. Selection trues to find the best
    // matching format for the encoder depending on the bit depth of the source format

    const auto *desc = av_pix_fmt_desc_get(fmt);
    if (!desc)
        return NotSuitableAVScore;

    const int sourceDepth = sourceSwFormatDesc ? sourceSwFormatDesc->comp[0].depth : 0;

    if (desc->flags & AV_PIX_FMT_FLAG_HWACCEL)
        // we really don't want HW accelerated formats here
        return NotSuitableAVScore;

    auto score = DefaultAVScore;

    if (desc == sourceSwFormatDesc)
        // prefer exact matches
        score += 10;
    if (desc->comp[0].depth == sourceDepth)
        score += 100;
    else if (desc->comp[0].depth < sourceDepth)
        score -= 100 + (sourceDepth - desc->comp[0].depth);
    if (desc->log2_chroma_h == 1)
        score += 1;
    if (desc->log2_chroma_w == 1)
        score += 1;
    if (desc->flags & AV_PIX_FMT_FLAG_BE)
        score -= 10;
    if (desc->flags & AV_PIX_FMT_FLAG_PAL)
        // we don't want paletted formats
        score -= 10000;
    if (desc->flags & AV_PIX_FMT_FLAG_RGB)
        // we don't want RGB formats
        score -= 1000;

    // qCDebug(qLcVideoFrameEncoder)
    //        << "checking format" << fmt << Qt::hex << desc->flags << desc->comp[0].depth
    //        << desc->log2_chroma_h << desc->log2_chroma_w << "score:" << score;

    return score;
}

static auto targetSwFormatScoreCalculator(AVPixelFormat sourceFormat)
{
    const auto sourceSwFormatDesc = av_pix_fmt_desc_get(sourceFormat);
    return [=](AVPixelFormat fmt) { return calculateTargetSwFormatScore(sourceSwFormatDesc, fmt); };
}

static bool isHwFormatAcceptedByCodec(AVPixelFormat pixFormat)
{
    switch (pixFormat) {
    case AV_PIX_FMT_MEDIACODEC:
        // Mediacodec doesn't accept AV_PIX_FMT_MEDIACODEC (QTBUG-116836)
        return false;
    default:
        return true;
    }
}

AVPixelFormat findTargetSWFormat(AVPixelFormat sourceSWFormat, const AVCodec *codec,
                                 const HWAccel &accel)
{
    auto scoreCalculator = targetSwFormatScoreCalculator(sourceSWFormat);

    const auto constraints = accel.constraints();
    if (constraints && constraints->valid_sw_formats)
        return findBestAVFormat(constraints->valid_sw_formats, scoreCalculator).first;

    // Some codecs, e.g. mediacodec, don't expose constraints, let's find the format in
    // codec->pix_fmts
    if (codec->pix_fmts)
        return findBestAVFormat(codec->pix_fmts, scoreCalculator).first;

    return AV_PIX_FMT_NONE;
}

AVPixelFormat findTargetFormat(AVPixelFormat sourceFormat, AVPixelFormat sourceSWFormat,
                               const AVCodec *codec, const HWAccel *accel)
{
    Q_UNUSED(sourceFormat);

    if (accel) {
        const auto hwFormat = accel->hwFormat();

        // TODO: handle codec->capabilities & AV_CODEC_CAP_HARDWARE here
        if (!isHwFormatAcceptedByCodec(hwFormat))
            return findTargetSWFormat(sourceSWFormat, codec, *accel);

        const auto constraints = accel->constraints();
        if (constraints && hasAVFormat(constraints->valid_hw_formats, hwFormat))
            return hwFormat;

        // Some codecs, don't expose constraints, let's find the format in codec->pix_fmts
        if (hasAVFormat(codec->pix_fmts, hwFormat))
            return hwFormat;
    }

    if (!codec->pix_fmts) {
        qWarning() << "Codec pix formats are undefined, it's likely to behave incorrectly";

        return sourceSWFormat;
    }

    auto swScoreCalculator = targetSwFormatScoreCalculator(sourceSWFormat);
    return findBestAVFormat(codec->pix_fmts, swScoreCalculator).first;
}

std::pair<const AVCodec *, std::unique_ptr<HWAccel>> findHwEncoder(AVCodecID codecID,
                                                                   const QSize &resolution)
{
    auto matchesSizeConstraints = [&resolution](const HWAccel &accel) {
        const auto constraints = accel.constraints();
        if (!constraints)
            return true;

        return resolution.width() >= constraints->min_width
                && resolution.height() >= constraints->min_height
                && resolution.width() <= constraints->max_width
                && resolution.height() <= constraints->max_height;
    };

    // 1st - attempt to find hw accelerated encoder
    auto result = HWAccel::findEncoderWithHwAccel(codecID, matchesSizeConstraints);
    Q_ASSERT(!!result.first == !!result.second);

    return result;
}

const AVCodec *findSwEncoder(AVCodecID codecID, AVPixelFormat sourceSWFormat)
{
    auto formatScoreCalculator = targetSwFormatScoreCalculator(sourceSWFormat);

    return findAVEncoder(codecID, [&formatScoreCalculator](const AVCodec *codec) {
        if (!codec->pix_fmts)
            // codecs without pix_fmts are suspicious
            return MinAVScore;

        return findBestAVFormat(codec->pix_fmts, formatScoreCalculator).second;
    });
}

AVRational adjustFrameRate(const AVRational *supportedRates, qreal requestedRate)
{
    qreal diff = std::numeric_limits<qreal>::max();

    auto getDiff = [requestedRate](qreal currentRate) {
        return qMax(requestedRate, currentRate) / qMin(requestedRate, currentRate);

        // Using just a liniar delta is also possible, but
        // relative comparison should work better
        // return qAbs(currentRate - requestedRate);
    };

    if (supportedRates) {
        const AVRational *result = nullptr;
        for (auto rate = supportedRates; rate->num && rate->den; ++rate) {
            const qreal currentDiff = getDiff(qreal(rate->num) / rate->den);

            if (currentDiff < diff) {
                diff = currentDiff;
                result = supportedRates;
            }
        }

        if (result)
            return *result;
    }

    const auto [num, den] = qRealToFraction(requestedRate);
    return { num, den };
}

AVRational adjustFrameTimeBase(const AVRational *supportedRates, AVRational frameRate)
{
    // TODO: user-specified frame rate might be required.
    if (supportedRates) {
        auto hasFrameRate = [&]() {
            for (auto rate = supportedRates; rate->num && rate->den; ++rate)
                if (rate->den == frameRate.den && rate->num == frameRate.num)
                    return true;

            return false;
        };

        Q_ASSERT(hasFrameRate());

        return { frameRate.den, frameRate.num };
    }

    constexpr int TimeScaleFactor = 1000; // Allows not to follow fixed rate
    return { frameRate.den, frameRate.num * TimeScaleFactor };
}

QSize adjustVideoResolution(const AVCodec *codec, QSize requestedResolution)
{
#ifdef Q_OS_WINDOWS
    // TODO: investigate, there might be more encoders not supporting odd resolution
    if (strcmp(codec->name, "h264_mf") == 0) {
        auto makeEven = [](int size) { return size & ~1; };
        return QSize(makeEven(requestedResolution.width()), makeEven(requestedResolution.height()));
    }
#else
    Q_UNUSED(codec);
#endif
    return requestedResolution;
}

} // namespace QFFmpeg

QT_END_NAMESPACE