summaryrefslogtreecommitdiffstats
path: root/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegencoderoptions.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/multimedia/ffmpeg/recordingengine/qffmpegencoderoptions.cpp')
-rw-r--r--src/plugins/multimedia/ffmpeg/recordingengine/qffmpegencoderoptions.cpp362
1 files changed, 362 insertions, 0 deletions
diff --git a/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegencoderoptions.cpp b/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegencoderoptions.cpp
new file mode 100644
index 000000000..bd6a8e09b
--- /dev/null
+++ b/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegencoderoptions.cpp
@@ -0,0 +1,362 @@
+// 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 "qffmpegencoderoptions_p.h"
+
+#if QT_CONFIG(vaapi)
+#include <va/va.h>
+#endif
+
+QT_BEGIN_NAMESPACE
+
+// unfortunately there is no common way to specify options for the encoders. The code here tries to map our settings sensibly
+// to options available in different encoders
+
+// For constant quality options, we're trying to map things to approx those bit rates for 1080p@30fps (in Mbps):
+// VeryLow Low Normal High VeryHigh
+// H264: 0.8M 1.5M 3.5M 6M 10M
+// H265: 0.5M 1.0M 2.5M 4M 7M
+
+[[maybe_unused]]
+static int bitrateForSettings(const QMediaEncoderSettings &settings, bool hdr = false)
+{
+ // calculate an acceptable bitrate depending on video codec, resolution, framerate and requested quality
+ // The calculations are rather heuristic here, trying to take into account how well codecs compress using
+ // the tables above.
+
+ // The table here is for 30FPS
+ const double bitsPerPixel[int(QMediaFormat::VideoCodec::LastVideoCodec)+1][QMediaRecorder::VeryHighQuality+1] = {
+ { 1.2, 2.25, 5, 9, 15 }, // MPEG1,
+ { 0.8, 1.5, 3.5, 6, 10 }, // MPEG2
+ { 0.4, 0.75, 1.75, 3, 5 }, // MPEG4
+ { 0.4, 0.75, 1.75, 3, 5 }, // H264
+ { 0.3, 0.5, 0.2, 2, 3 }, // H265
+ { 0.4, 0.75, 1.75, 3, 5 }, // VP8
+ { 0.3, 0.5, 0.2, 2, 3 }, // VP9
+ { 0.2, 0.4, 0.9, 1.5, 2.5 }, // AV1
+ { 0.4, 0.75, 1.75, 3, 5 }, // Theora
+ { 0.8, 1.5, 3.5, 6, 10 }, // WMV
+ { 16, 24, 32, 40, 48 }, // MotionJPEG
+ };
+
+ QSize s = settings.videoResolution();
+ double bitrate = bitsPerPixel[int(settings.videoCodec())][settings.quality()]*s.width()*s.height();
+
+ if (settings.videoCodec() != QMediaFormat::VideoCodec::MotionJPEG) {
+ // We assume that doubling the framerate requires 1.5 times the amount of data (not twice, as intraframe
+ // differences will be smaller). 4 times the frame rate uses thus 2.25 times the data, etc.
+ float rateMultiplier = log2(settings.videoFrameRate()/30.);
+ bitrate *= pow(1.5, rateMultiplier);
+ } else {
+ // MotionJPEG doesn't optimize between frames, so we have a linear dependency on framerate
+ bitrate *= settings.videoFrameRate()/30.;
+ }
+
+ // HDR requires 10bits per pixel instead of 8, so apply a factor of 1.25.
+ if (hdr)
+ bitrate *= 1.25;
+ return bitrate;
+}
+
+static void apply_openh264(const QMediaEncoderSettings &settings, AVCodecContext *codec,
+ AVDictionary **opts)
+{
+ if (settings.encodingMode() == QMediaRecorder::ConstantBitRateEncoding
+ || settings.encodingMode() == QMediaRecorder::AverageBitRateEncoding) {
+ codec->bit_rate = settings.videoBitRate();
+ av_dict_set(opts, "rc_mode", "bitrate", 0);
+ } else {
+ av_dict_set(opts, "rc_mode", "quality", 0);
+ static const int q[] = { 51, 48, 38, 25, 5 };
+ codec->qmax = codec->qmin = q[settings.quality()];
+ }
+}
+
+static void apply_x264(const QMediaEncoderSettings &settings, AVCodecContext *codec, AVDictionary **opts)
+{
+ if (settings.encodingMode() == QMediaRecorder::ConstantBitRateEncoding || settings.encodingMode() == QMediaRecorder::AverageBitRateEncoding) {
+ codec->bit_rate = settings.videoBitRate();
+ } else {
+ const char *scales[] = {
+ "29", "26", "23", "21", "19"
+ };
+ av_dict_set(opts, "crf", scales[settings.quality()], 0);
+ }
+}
+
+static void apply_x265(const QMediaEncoderSettings &settings, AVCodecContext *codec, AVDictionary **opts)
+{
+ if (settings.encodingMode() == QMediaRecorder::ConstantBitRateEncoding || settings.encodingMode() == QMediaRecorder::AverageBitRateEncoding) {
+ codec->bit_rate = settings.videoBitRate();
+ } else {
+ const char *scales[QMediaRecorder::VeryHighQuality+1] = {
+ "40", "34", "28", "26", "24",
+ };
+ av_dict_set(opts, "crf", scales[settings.quality()], 0);
+ }
+}
+
+static void apply_libvpx(const QMediaEncoderSettings &settings, AVCodecContext *codec, AVDictionary **opts)
+{
+ if (settings.encodingMode() == QMediaRecorder::ConstantBitRateEncoding || settings.encodingMode() == QMediaRecorder::AverageBitRateEncoding) {
+ codec->bit_rate = settings.videoBitRate();
+ } else {
+ const char *scales[QMediaRecorder::VeryHighQuality+1] = {
+ "38", "34", "31", "28", "25",
+ };
+ av_dict_set(opts, "crf", scales[settings.quality()], 0);
+ av_dict_set(opts, "b", 0, 0);
+ }
+ av_dict_set(opts, "row-mt", "1", 0); // better multithreading
+}
+
+#ifdef Q_OS_DARWIN
+static void apply_videotoolbox(const QMediaEncoderSettings &settings, AVCodecContext *codec, AVDictionary **opts)
+{
+ if (settings.encodingMode() == QMediaRecorder::ConstantBitRateEncoding || settings.encodingMode() == QMediaRecorder::AverageBitRateEncoding) {
+ codec->bit_rate = settings.videoBitRate();
+ } else {
+ // only use quality on macOS/ARM, as FFmpeg doesn't support it on the other platforms and would throw
+ // an error when initializing the codec
+#if defined(Q_OS_MACOS) && defined(Q_PROCESSOR_ARM_64)
+ // Videotoolbox describes quality as a number from 0 to 1, with low == 0.25, normal 0.5, high 0.75 and lossless = 1
+ // ffmpeg uses a different scale going from 0 to 11800.
+ // Values here are adjusted to agree approximately with the target bit rates listed above
+ const int scales[] = {
+ 3000, 4800, 5900, 6900, 7700,
+ };
+ codec->global_quality = scales[settings.quality()];
+ codec->flags |= AV_CODEC_FLAG_QSCALE;
+#else
+ codec->bit_rate = bitrateForSettings(settings);
+#endif
+ }
+
+ // Videotooldox hw acceleration fails of some hardwares,
+ // allow_sw makes sw encoding available if hw encoding failed.
+ // Under the hood, ffmpeg sets
+ // kVTVideoEncoderSpecification_EnableHardwareAcceleratedVideoEncoder instead of
+ // kVTVideoEncoderSpecification_RequireHardwareAcceleratedVideoEncoder
+ av_dict_set(opts, "allow_sw", "1", 0);
+}
+#endif
+
+#if QT_CONFIG(vaapi)
+static void apply_vaapi(const QMediaEncoderSettings &settings, AVCodecContext *codec, AVDictionary **/*opts*/)
+{
+ // See also vaapi_encode_init_rate_control() in libavcodec
+ if (settings.encodingMode() == QMediaRecorder::ConstantBitRateEncoding) {
+ codec->bit_rate = settings.videoBitRate();
+ codec->rc_max_rate = settings.videoBitRate();
+ } else if (settings.encodingMode() == QMediaRecorder::AverageBitRateEncoding) {
+ codec->bit_rate = settings.videoBitRate();
+ } else {
+ const int *quality = nullptr;
+ // unfortunately, all VA codecs use different quality scales :/
+ switch (settings.videoCodec()) {
+ case QMediaFormat::VideoCodec::MPEG2: {
+ static const int q[] = { 20, 15, 10, 8, 6 };
+ quality = q;
+ break;
+ }
+ case QMediaFormat::VideoCodec::MPEG4:
+ case QMediaFormat::VideoCodec::H264: {
+ static const int q[] = { 29, 26, 23, 21, 19 };
+ quality = q;
+ break;
+ }
+ case QMediaFormat::VideoCodec::H265: {
+ static const int q[] = { 40, 34, 28, 26, 24 };
+ quality = q;
+ break;
+ }
+ case QMediaFormat::VideoCodec::VP8: {
+ static const int q[] = { 56, 48, 40, 34, 28 };
+ quality = q;
+ break;
+ }
+ case QMediaFormat::VideoCodec::VP9: {
+ static const int q[] = { 124, 112, 100, 88, 76 };
+ quality = q;
+ break;
+ }
+ case QMediaFormat::VideoCodec::MotionJPEG: {
+ static const int q[] = { 40, 60, 80, 90, 95 };
+ quality = q;
+ break;
+ }
+ case QMediaFormat::VideoCodec::AV1:
+ case QMediaFormat::VideoCodec::Theora:
+ case QMediaFormat::VideoCodec::WMV:
+ default:
+ break;
+ }
+
+ if (quality)
+ codec->global_quality = quality[settings.quality()];
+ }
+}
+#endif
+
+static void apply_nvenc(const QMediaEncoderSettings &settings, AVCodecContext *codec,
+ AVDictionary **opts)
+{
+ switch (settings.encodingMode()) {
+ case QMediaRecorder::EncodingMode::AverageBitRateEncoding:
+ av_dict_set(opts, "vbr", "1", 0);
+ codec->bit_rate = settings.videoBitRate();
+ break;
+ case QMediaRecorder::EncodingMode::ConstantBitRateEncoding:
+ av_dict_set(opts, "cbr", "1", 0);
+ codec->bit_rate = settings.videoBitRate();
+ codec->rc_max_rate = codec->rc_min_rate = codec->bit_rate;
+ break;
+ case QMediaRecorder::EncodingMode::ConstantQualityEncoding: {
+ static const char *q[] = { "51", "48", "35", "15", "1" };
+ av_dict_set(opts, "cq", q[settings.quality()], 0);
+ } break;
+ default:
+ break;
+ }
+}
+
+#ifdef Q_OS_WINDOWS
+static void apply_mf(const QMediaEncoderSettings &settings, AVCodecContext *codec, AVDictionary **opts)
+{
+ if (settings.encodingMode() == QMediaRecorder::ConstantBitRateEncoding || settings.encodingMode() == QMediaRecorder::AverageBitRateEncoding) {
+ codec->bit_rate = settings.videoBitRate();
+ av_dict_set(opts, "rate_control", "cbr", 0);
+ } else {
+ av_dict_set(opts, "rate_control", "quality", 0);
+ const char *scales[] = {
+ "25", "50", "75", "90", "100"
+ };
+ av_dict_set(opts, "quality", scales[settings.quality()], 0);
+ }
+}
+#endif
+
+#ifdef Q_OS_ANDROID
+static void apply_mediacodec(const QMediaEncoderSettings &settings, AVCodecContext *codec,
+ AVDictionary **opts)
+{
+ codec->bit_rate = settings.videoBitRate();
+
+ const int quality[] = { 25, 50, 75, 90, 100 };
+ codec->global_quality = quality[settings.quality()];
+
+ switch (settings.encodingMode()) {
+ case QMediaRecorder::EncodingMode::AverageBitRateEncoding:
+ av_dict_set(opts, "bitrate_mode", "vbr", 1);
+ break;
+ case QMediaRecorder::EncodingMode::ConstantBitRateEncoding:
+ av_dict_set(opts, "bitrate_mode", "cbr", 1);
+ break;
+ case QMediaRecorder::EncodingMode::ConstantQualityEncoding:
+ // av_dict_set(opts, "bitrate_mode", "cq", 1);
+ av_dict_set(opts, "bitrate_mode", "cbr", 1);
+ break;
+ default:
+ break;
+ }
+
+ switch (settings.videoCodec()) {
+ case QMediaFormat::VideoCodec::H264: {
+ const char *levels[] = { "2.2", "3.2", "4.2", "5.2", "6.2" };
+ av_dict_set(opts, "level", levels[settings.quality()], 1);
+ codec->profile = FF_PROFILE_H264_HIGH;
+ break;
+ }
+ case QMediaFormat::VideoCodec::H265: {
+ const char *levels[] = { "h2.1", "h3.1", "h4.1", "h5.1", "h6.1" };
+ av_dict_set(opts, "level", levels[settings.quality()], 1);
+ codec->profile = FF_PROFILE_HEVC_MAIN;
+ break;
+ }
+ default:
+ break;
+ }
+}
+#endif
+
+namespace QFFmpeg {
+
+using ApplyOptions = void (*)(const QMediaEncoderSettings &settings, AVCodecContext *codec, AVDictionary **opts);
+
+const struct {
+ const char *name;
+ ApplyOptions apply;
+} videoCodecOptionTable[] = { { "libx264", apply_x264 },
+ { "libx265xx", apply_x265 },
+ { "libvpx", apply_libvpx },
+ { "libvpx_vp9", apply_libvpx },
+ { "libopenh264", apply_openh264 },
+ { "h264_nvenc", apply_nvenc },
+ { "hevc_nvenc", apply_nvenc },
+ { "av1_nvenc", apply_nvenc },
+#ifdef Q_OS_DARWIN
+ { "h264_videotoolbox", apply_videotoolbox },
+ { "hevc_videotoolbox", apply_videotoolbox },
+ { "prores_videotoolbox", apply_videotoolbox },
+ { "vp9_videotoolbox", apply_videotoolbox },
+#endif
+#if QT_CONFIG(vaapi)
+ { "mpeg2_vaapi", apply_vaapi },
+ { "mjpeg_vaapi", apply_vaapi },
+ { "h264_vaapi", apply_vaapi },
+ { "hevc_vaapi", apply_vaapi },
+ { "vp8_vaapi", apply_vaapi },
+ { "vp9_vaapi", apply_vaapi },
+#endif
+#ifdef Q_OS_WINDOWS
+ { "hevc_mf", apply_mf },
+ { "h264_mf", apply_mf },
+#endif
+#ifdef Q_OS_ANDROID
+ { "hevc_mediacodec", apply_mediacodec },
+ { "h264_mediacodec", apply_mediacodec },
+#endif
+ { nullptr, nullptr } };
+
+const struct {
+ const char *name;
+ ApplyOptions apply;
+} audioCodecOptionTable[] = {
+ { nullptr, nullptr }
+};
+
+void applyVideoEncoderOptions(const QMediaEncoderSettings &settings, const QByteArray &codecName, AVCodecContext *codec, AVDictionary **opts)
+{
+ av_dict_set(opts, "threads", "auto", 0); // we always want automatic threading
+
+ auto *table = videoCodecOptionTable;
+ while (table->name) {
+ if (codecName == table->name) {
+ table->apply(settings, codec, opts);
+ return;
+ }
+
+ ++table;
+ }
+}
+
+void applyAudioEncoderOptions(const QMediaEncoderSettings &settings, const QByteArray &codecName, AVCodecContext *codec, AVDictionary **opts)
+{
+ codec->thread_count = -1; // we always want automatic threading
+ if (settings.encodingMode() == QMediaRecorder::ConstantBitRateEncoding || settings.encodingMode() == QMediaRecorder::AverageBitRateEncoding)
+ codec->bit_rate = settings.audioBitRate();
+
+ auto *table = audioCodecOptionTable;
+ while (table->name) {
+ if (codecName == table->name) {
+ table->apply(settings, codec, opts);
+ return;
+ }
+
+ ++table;
+ }
+
+}
+
+}
+
+QT_END_NAMESPACE