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

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