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#include "qffmpegencoderoptions_p.h"
4
5#if QT_CONFIG(vaapi)
6#include <va/va.h>
7#endif
8
9QT_BEGIN_NAMESPACE
10
11// unfortunately there is no common way to specify options for the encoders. The code here tries to map our settings sensibly
12// to options available in different encoders
13
14// For constant quality options, we're trying to map things to approx those bit rates for 1080p@30fps (in Mbps):
15// VeryLow Low Normal High VeryHigh
16// H264: 0.8M 1.5M 3.5M 6M 10M
17// H265: 0.5M 1.0M 2.5M 4M 7M
18
19[[maybe_unused]]
20static int bitrateForSettings(const QMediaEncoderSettings &settings, bool hdr = false)
21{
22 // calculate an acceptable bitrate depending on video codec, resolution, framerate and requested quality
23 // The calculations are rather heuristic here, trying to take into account how well codecs compress using
24 // the tables above.
25
26 // The table here is for 30FPS
27 const double bitsPerPixel[int(QMediaFormat::VideoCodec::LastVideoCodec)+1][QMediaRecorder::VeryHighQuality+1] = {
28 { 1.2, 2.25, 5, 9, 15 }, // MPEG1,
29 { 0.8, 1.5, 3.5, 6, 10 }, // MPEG2
30 { 0.4, 0.75, 1.75, 3, 5 }, // MPEG4
31 { 0.4, 0.75, 1.75, 3, 5 }, // H264
32 { 0.3, 0.5, 0.2, 2, 3 }, // H265
33 { 0.4, 0.75, 1.75, 3, 5 }, // VP8
34 { 0.3, 0.5, 0.2, 2, 3 }, // VP9
35 { 0.2, 0.4, 0.9, 1.5, 2.5 }, // AV1
36 { 0.4, 0.75, 1.75, 3, 5 }, // Theora
37 { 0.8, 1.5, 3.5, 6, 10 }, // WMV
38 { 16, 24, 32, 40, 48 }, // MotionJPEG
39 };
40
41 QSize s = settings.videoResolution();
42 double bitrate = bitsPerPixel[int(settings.videoCodec())][settings.quality()]*s.width()*s.height();
43
44 if (settings.videoCodec() != QMediaFormat::VideoCodec::MotionJPEG) {
45 // We assume that doubling the framerate requires 1.5 times the amount of data (not twice, as intraframe
46 // differences will be smaller). 4 times the frame rate uses thus 2.25 times the data, etc.
47 float rateMultiplier = log2(x: settings.videoFrameRate()/30.);
48 bitrate *= pow(x: 1.5, y: rateMultiplier);
49 } else {
50 // MotionJPEG doesn't optimize between frames, so we have a linear dependency on framerate
51 bitrate *= settings.videoFrameRate()/30.;
52 }
53
54 // HDR requires 10bits per pixel instead of 8, so apply a factor of 1.25.
55 if (hdr)
56 bitrate *= 1.25;
57 return bitrate;
58}
59
60static void apply_openh264(const QMediaEncoderSettings &settings, AVCodecContext *codec,
61 AVDictionary **opts)
62{
63 if (settings.encodingMode() == QMediaRecorder::ConstantBitRateEncoding
64 || settings.encodingMode() == QMediaRecorder::AverageBitRateEncoding) {
65 codec->bit_rate = settings.videoBitRate();
66 av_dict_set(pm: opts, key: "rc_mode", value: "bitrate", flags: 0);
67 } else {
68 av_dict_set(pm: opts, key: "rc_mode", value: "quality", flags: 0);
69 static const int q[] = { 51, 48, 38, 25, 5 };
70 codec->qmax = codec->qmin = q[settings.quality()];
71 }
72}
73
74static void apply_x264(const QMediaEncoderSettings &settings, AVCodecContext *codec, AVDictionary **opts)
75{
76 if (settings.encodingMode() == QMediaRecorder::ConstantBitRateEncoding || settings.encodingMode() == QMediaRecorder::AverageBitRateEncoding) {
77 codec->bit_rate = settings.videoBitRate();
78 } else {
79 const char *scales[] = {
80 "29", "26", "23", "21", "19"
81 };
82 av_dict_set(pm: opts, key: "crf", value: scales[settings.quality()], flags: 0);
83 }
84}
85
86static void apply_x265(const QMediaEncoderSettings &settings, AVCodecContext *codec, AVDictionary **opts)
87{
88 if (settings.encodingMode() == QMediaRecorder::ConstantBitRateEncoding || settings.encodingMode() == QMediaRecorder::AverageBitRateEncoding) {
89 codec->bit_rate = settings.videoBitRate();
90 } else {
91 const char *scales[QMediaRecorder::VeryHighQuality+1] = {
92 "40", "34", "28", "26", "24",
93 };
94 av_dict_set(pm: opts, key: "crf", value: scales[settings.quality()], flags: 0);
95 }
96}
97
98static void apply_libvpx(const QMediaEncoderSettings &settings, AVCodecContext *codec, AVDictionary **opts)
99{
100 if (settings.encodingMode() == QMediaRecorder::ConstantBitRateEncoding || settings.encodingMode() == QMediaRecorder::AverageBitRateEncoding) {
101 codec->bit_rate = settings.videoBitRate();
102 } else {
103 const char *scales[QMediaRecorder::VeryHighQuality+1] = {
104 "38", "34", "31", "28", "25",
105 };
106 av_dict_set(pm: opts, key: "crf", value: scales[settings.quality()], flags: 0);
107 av_dict_set(pm: opts, key: "b", value: 0, flags: 0);
108 }
109 av_dict_set(pm: opts, key: "row-mt", value: "1", flags: 0); // better multithreading
110}
111
112#ifdef Q_OS_DARWIN
113static void apply_videotoolbox(const QMediaEncoderSettings &settings, AVCodecContext *codec, AVDictionary **opts)
114{
115 if (settings.encodingMode() == QMediaRecorder::ConstantBitRateEncoding || settings.encodingMode() == QMediaRecorder::AverageBitRateEncoding) {
116 codec->bit_rate = settings.videoBitRate();
117 } else {
118 // only use quality on macOS/ARM, as FFmpeg doesn't support it on the other platforms and would throw
119 // an error when initializing the codec
120#if defined(Q_OS_MACOS) && defined(Q_PROCESSOR_ARM_64)
121 // Videotoolbox describes quality as a number from 0 to 1, with low == 0.25, normal 0.5, high 0.75 and lossless = 1
122 // ffmpeg uses a different scale going from 0 to 11800.
123 // Values here are adjusted to agree approximately with the target bit rates listed above
124 const int scales[] = {
125 3000, 4800, 5900, 6900, 7700,
126 };
127 codec->global_quality = scales[settings.quality()];
128 codec->flags |= AV_CODEC_FLAG_QSCALE;
129#else
130 codec->bit_rate = bitrateForSettings(settings);
131#endif
132 }
133
134 // Videotooldox hw acceleration fails of some hardwares,
135 // allow_sw makes sw encoding available if hw encoding failed.
136 // Under the hood, ffmpeg sets
137 // kVTVideoEncoderSpecification_EnableHardwareAcceleratedVideoEncoder instead of
138 // kVTVideoEncoderSpecification_RequireHardwareAcceleratedVideoEncoder
139 av_dict_set(opts, "allow_sw", "1", 0);
140}
141#endif
142
143#if QT_CONFIG(vaapi)
144static void apply_vaapi(const QMediaEncoderSettings &settings, AVCodecContext *codec, AVDictionary **/*opts*/)
145{
146 // See also vaapi_encode_init_rate_control() in libavcodec
147 if (settings.encodingMode() == QMediaRecorder::ConstantBitRateEncoding) {
148 codec->bit_rate = settings.videoBitRate();
149 codec->rc_max_rate = settings.videoBitRate();
150 } else if (settings.encodingMode() == QMediaRecorder::AverageBitRateEncoding) {
151 codec->bit_rate = settings.videoBitRate();
152 } else {
153 const int *quality = nullptr;
154 // unfortunately, all VA codecs use different quality scales :/
155 switch (settings.videoCodec()) {
156 case QMediaFormat::VideoCodec::MPEG2: {
157 static const int q[] = { 20, 15, 10, 8, 6 };
158 quality = q;
159 break;
160 }
161 case QMediaFormat::VideoCodec::MPEG4:
162 case QMediaFormat::VideoCodec::H264: {
163 static const int q[] = { 29, 26, 23, 21, 19 };
164 quality = q;
165 break;
166 }
167 case QMediaFormat::VideoCodec::H265: {
168 static const int q[] = { 40, 34, 28, 26, 24 };
169 quality = q;
170 break;
171 }
172 case QMediaFormat::VideoCodec::VP8: {
173 static const int q[] = { 56, 48, 40, 34, 28 };
174 quality = q;
175 break;
176 }
177 case QMediaFormat::VideoCodec::VP9: {
178 static const int q[] = { 124, 112, 100, 88, 76 };
179 quality = q;
180 break;
181 }
182 case QMediaFormat::VideoCodec::MotionJPEG: {
183 static const int q[] = { 40, 60, 80, 90, 95 };
184 quality = q;
185 break;
186 }
187 case QMediaFormat::VideoCodec::AV1:
188 case QMediaFormat::VideoCodec::Theora:
189 case QMediaFormat::VideoCodec::WMV:
190 default:
191 break;
192 }
193
194 if (quality)
195 codec->global_quality = quality[settings.quality()];
196 }
197}
198#endif
199
200static void apply_nvenc(const QMediaEncoderSettings &settings, AVCodecContext *codec,
201 AVDictionary **opts)
202{
203 switch (settings.encodingMode()) {
204 case QMediaRecorder::EncodingMode::AverageBitRateEncoding:
205 av_dict_set(pm: opts, key: "vbr", value: "1", flags: 0);
206 codec->bit_rate = settings.videoBitRate();
207 break;
208 case QMediaRecorder::EncodingMode::ConstantBitRateEncoding:
209 av_dict_set(pm: opts, key: "cbr", value: "1", flags: 0);
210 codec->bit_rate = settings.videoBitRate();
211 codec->rc_max_rate = codec->rc_min_rate = codec->bit_rate;
212 break;
213 case QMediaRecorder::EncodingMode::ConstantQualityEncoding: {
214 static const char *q[] = { "51", "48", "35", "15", "1" };
215 av_dict_set(pm: opts, key: "cq", value: q[settings.quality()], flags: 0);
216 } break;
217 default:
218 break;
219 }
220}
221
222#ifdef Q_OS_WINDOWS
223static void apply_mf(const QMediaEncoderSettings &settings, AVCodecContext *codec, AVDictionary **opts)
224{
225 if (settings.encodingMode() == QMediaRecorder::ConstantBitRateEncoding || settings.encodingMode() == QMediaRecorder::AverageBitRateEncoding) {
226 codec->bit_rate = settings.videoBitRate();
227 av_dict_set(opts, "rate_control", "cbr", 0);
228 } else {
229 av_dict_set(opts, "rate_control", "quality", 0);
230 const char *scales[] = {
231 "25", "50", "75", "90", "100"
232 };
233 av_dict_set(opts, "quality", scales[settings.quality()], 0);
234 }
235}
236#endif
237
238#ifdef Q_OS_ANDROID
239static void apply_mediacodec(const QMediaEncoderSettings &settings, AVCodecContext *codec,
240 AVDictionary **opts)
241{
242 codec->bit_rate = settings.videoBitRate();
243
244 const int quality[] = { 25, 50, 75, 90, 100 };
245 codec->global_quality = quality[settings.quality()];
246
247 switch (settings.encodingMode()) {
248 case QMediaRecorder::EncodingMode::AverageBitRateEncoding:
249 av_dict_set(opts, "bitrate_mode", "vbr", 1);
250 break;
251 case QMediaRecorder::EncodingMode::ConstantBitRateEncoding:
252 av_dict_set(opts, "bitrate_mode", "cbr", 1);
253 break;
254 case QMediaRecorder::EncodingMode::ConstantQualityEncoding:
255 // av_dict_set(opts, "bitrate_mode", "cq", 1);
256 av_dict_set(opts, "bitrate_mode", "cbr", 1);
257 break;
258 default:
259 break;
260 }
261
262 switch (settings.videoCodec()) {
263 case QMediaFormat::VideoCodec::H264: {
264 const char *levels[] = { "2.2", "3.2", "4.2", "5.2", "6.2" };
265 av_dict_set(opts, "level", levels[settings.quality()], 1);
266 codec->profile = FF_PROFILE_H264_HIGH;
267 break;
268 }
269 case QMediaFormat::VideoCodec::H265: {
270 const char *levels[] = { "h2.1", "h3.1", "h4.1", "h5.1", "h6.1" };
271 av_dict_set(opts, "level", levels[settings.quality()], 1);
272 codec->profile = FF_PROFILE_HEVC_MAIN;
273 break;
274 }
275 default:
276 break;
277 }
278}
279#endif
280
281namespace QFFmpeg {
282
283using ApplyOptions = void (*)(const QMediaEncoderSettings &settings, AVCodecContext *codec, AVDictionary **opts);
284
285const struct {
286 const char *name;
287 ApplyOptions apply;
288} videoCodecOptionTable[] = { { .name: "libx264", .apply: apply_x264 },
289 { .name: "libx265xx", .apply: apply_x265 },
290 { .name: "libvpx", .apply: apply_libvpx },
291 { .name: "libvpx_vp9", .apply: apply_libvpx },
292 { .name: "libopenh264", .apply: apply_openh264 },
293 { .name: "h264_nvenc", .apply: apply_nvenc },
294 { .name: "hevc_nvenc", .apply: apply_nvenc },
295 { .name: "av1_nvenc", .apply: apply_nvenc },
296#ifdef Q_OS_DARWIN
297 { "h264_videotoolbox", apply_videotoolbox },
298 { "hevc_videotoolbox", apply_videotoolbox },
299 { "prores_videotoolbox", apply_videotoolbox },
300 { "vp9_videotoolbox", apply_videotoolbox },
301#endif
302#if QT_CONFIG(vaapi)
303 { .name: "mpeg2_vaapi", .apply: apply_vaapi },
304 { .name: "mjpeg_vaapi", .apply: apply_vaapi },
305 { .name: "h264_vaapi", .apply: apply_vaapi },
306 { .name: "hevc_vaapi", .apply: apply_vaapi },
307 { .name: "vp8_vaapi", .apply: apply_vaapi },
308 { .name: "vp9_vaapi", .apply: apply_vaapi },
309#endif
310#ifdef Q_OS_WINDOWS
311 { "hevc_mf", apply_mf },
312 { "h264_mf", apply_mf },
313#endif
314#ifdef Q_OS_ANDROID
315 { "hevc_mediacodec", apply_mediacodec },
316 { "h264_mediacodec", apply_mediacodec },
317#endif
318 { .name: nullptr, .apply: nullptr } };
319
320const struct {
321 const char *name;
322 ApplyOptions apply;
323} audioCodecOptionTable[] = {
324 { .name: nullptr, .apply: nullptr }
325};
326
327void applyVideoEncoderOptions(const QMediaEncoderSettings &settings, const QByteArray &codecName, AVCodecContext *codec, AVDictionary **opts)
328{
329 av_dict_set(pm: opts, key: "threads", value: "auto", flags: 0); // we always want automatic threading
330
331 auto *table = videoCodecOptionTable;
332 while (table->name) {
333 if (codecName == table->name) {
334 table->apply(settings, codec, opts);
335 return;
336 }
337
338 ++table;
339 }
340}
341
342void applyAudioEncoderOptions(const QMediaEncoderSettings &settings, const QByteArray &codecName, AVCodecContext *codec, AVDictionary **opts)
343{
344 codec->thread_count = -1; // we always want automatic threading
345 if (settings.encodingMode() == QMediaRecorder::ConstantBitRateEncoding || settings.encodingMode() == QMediaRecorder::AverageBitRateEncoding)
346 codec->bit_rate = settings.audioBitRate();
347
348 auto *table = audioCodecOptionTable;
349 while (table->name) {
350 if (codecName == table->name) {
351 table->apply(settings, codec, opts);
352 return;
353 }
354
355 ++table;
356 }
357
358}
359
360}
361
362QT_END_NAMESPACE
363

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