Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qffmpegencoderoptions.cpp
Go to the documentation of this file.
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
4
5#include <QtFFmpegMediaPluginImpl/private/qffmpegmediaformatinfo_p.h>
6#include <QtFFmpegMediaPluginImpl/private/qffmpegrecordingengineutils_p.h>
7
8#include <QtMultimedia/qaudioformat.h>
9#include <QtCore/private/qflatmap_p.h>
10
11#if QT_CONFIG(vaapi)
12#include <va/va.h>
13#endif
14
15#ifdef Q_OS_ANDROID
16extern "C" {
17#include <libavcodec/avcodec.h>
18}
19#endif
20
21#include <libavutil/channel_layout.h>
22
24
25// unfortunately there is no common way to specify options for the encoders. The code here tries to map our settings sensibly
26// to options available in different encoders
27
28// For constant quality options, we're trying to map things to approx those bit rates for 1080p@30fps (in Mbps):
29// VeryLow Low Normal High VeryHigh
30// H264: 0.8M 1.5M 3.5M 6M 10M
31// H265: 0.5M 1.0M 2.5M 4M 7M
32
33[[maybe_unused]]
34static int bitrateForSettings(const QMediaEncoderSettings &settings, bool hdr = false)
35{
36 // calculate an acceptable bitrate depending on video codec, resolution, framerate and requested quality
37 // The calculations are rather heuristic here, trying to take into account how well codecs compress using
38 // the tables above.
39
40 // The table here is for 30FPS
41 const double bitsPerPixel[int(QMediaFormat::VideoCodec::LastVideoCodec)+1][QMediaRecorder::VeryHighQuality+1] = {
42 { 1.2, 2.25, 5, 9, 15 }, // MPEG1,
43 { 0.8, 1.5, 3.5, 6, 10 }, // MPEG2
44 { 0.4, 0.75, 1.75, 3, 5 }, // MPEG4
45 { 0.4, 0.75, 1.75, 3, 5 }, // H264
46 { 0.3, 0.5, 0.2, 2, 3 }, // H265
47 { 0.4, 0.75, 1.75, 3, 5 }, // VP8
48 { 0.3, 0.5, 0.2, 2, 3 }, // VP9
49 { 0.2, 0.4, 0.9, 1.5, 2.5 }, // AV1
50 { 0.4, 0.75, 1.75, 3, 5 }, // Theora
51 { 0.8, 1.5, 3.5, 6, 10 }, // WMV
52 { 16, 24, 32, 40, 48 }, // MotionJPEG
53 };
54
55 QSize s = settings.videoResolution();
56 double bitrate = bitsPerPixel[int(settings.videoCodec())][settings.quality()]*s.width()*s.height();
57
58 // Frame rate can be 0 with variable-rate sources
59 const double frameRate = settings.videoFrameRate() > 0.
60 ? settings.videoFrameRate()
61 : static_cast<double>(QFFmpeg::DefaultVideoFrameRate);
62
63 if (settings.videoCodec() != QMediaFormat::VideoCodec::MotionJPEG) {
64 // We assume that doubling the framerate requires 1.5 times the amount of data (not twice, as intraframe
65 // differences will be smaller). 4 times the frame rate uses thus 2.25 times the data, etc.
66 float rateMultiplier = log2(frameRate / 30.);
67 bitrate *= pow(1.5, rateMultiplier);
68 } else {
69 // MotionJPEG doesn't optimize between frames, so we have a linear dependency on framerate
70 bitrate *= frameRate / 30.;
71 }
72
73 // HDR requires 10bits per pixel instead of 8, so apply a factor of 1.25.
74 if (hdr)
75 bitrate *= 1.25;
76 return bitrate;
77}
78
79static void apply_openh264(const QMediaEncoderSettings &settings, AVCodecContext *codec,
80 AVDictionary **opts)
81{
82 if (settings.encodingMode() == QMediaRecorder::ConstantBitRateEncoding
83 || settings.encodingMode() == QMediaRecorder::AverageBitRateEncoding) {
84 codec->bit_rate = settings.videoBitRate();
85 av_dict_set(opts, "rc_mode", "bitrate", 0);
86 } else {
87 av_dict_set(opts, "rc_mode", "quality", 0);
88 static const int q[] = { 51, 48, 38, 25, 5 };
89 codec->qmax = codec->qmin = q[settings.quality()];
90 }
91}
92
93static void apply_x264(const QMediaEncoderSettings &settings, AVCodecContext *codec, AVDictionary **opts)
94{
95 if (settings.encodingMode() == QMediaRecorder::ConstantBitRateEncoding || settings.encodingMode() == QMediaRecorder::AverageBitRateEncoding) {
96 codec->bit_rate = settings.videoBitRate();
97 } else {
98 const char *scales[] = {
99 "29", "26", "23", "21", "19"
100 };
101 av_dict_set(opts, "crf", scales[settings.quality()], 0);
102 }
103}
104
105static void apply_x265(const QMediaEncoderSettings &settings, AVCodecContext *codec, AVDictionary **opts)
106{
107 if (settings.encodingMode() == QMediaRecorder::ConstantBitRateEncoding || settings.encodingMode() == QMediaRecorder::AverageBitRateEncoding) {
108 codec->bit_rate = settings.videoBitRate();
109 } else {
110 const char *scales[QMediaRecorder::VeryHighQuality+1] = {
111 "40", "34", "28", "26", "24",
112 };
113 av_dict_set(opts, "crf", scales[settings.quality()], 0);
114 }
115}
116
117static void apply_libvpx(const QMediaEncoderSettings &settings, AVCodecContext *codec, AVDictionary **opts)
118{
119 if (settings.encodingMode() == QMediaRecorder::ConstantBitRateEncoding || settings.encodingMode() == QMediaRecorder::AverageBitRateEncoding) {
120 codec->bit_rate = settings.videoBitRate();
121 } else {
122 const char *scales[QMediaRecorder::VeryHighQuality+1] = {
123 "38", "34", "31", "28", "25",
124 };
125 av_dict_set(opts, "crf", scales[settings.quality()], 0);
126 av_dict_set(opts, "b", nullptr, 0);
127 }
128 av_dict_set(opts, "row-mt", "1", 0); // better multithreading
129}
130
131static void apply_mpeg4(const QMediaEncoderSettings &settings, AVCodecContext *codec,
132 AVDictionary **opts)
133{
134 // compare https://trac.ffmpeg.org/wiki/Encode/MPEG-4
135
136 QMediaRecorder::EncodingMode encodingMode = settings.encodingMode();
137 switch (encodingMode) {
138 case QMediaRecorder::ConstantBitRateEncoding:
139 case QMediaRecorder::QMediaRecorder::AverageBitRateEncoding: {
140 codec->bit_rate = settings.videoBitRate();
141 if (encodingMode == QMediaRecorder::ConstantBitRateEncoding)
142 codec->rc_min_rate = codec->rc_max_rate = settings.videoBitRate();
143
144 break;
145 }
146 case QMediaRecorder::ConstantQualityEncoding: {
147 constexpr auto scales = std::array{
148 31, // VeryLowQuality
149 23, // LowQuality
150 16, // NormalQuality
151 9, // HighQuality
152 1, // VeryHighQuality
153 };
154 av_dict_set_int(opts, "qscale", scales[settings.quality()], 0);
155 codec->global_quality = scales[settings.quality()] * FF_QP2LAMBDA;
156 codec->flags |= AV_CODEC_FLAG_QSCALE;
157 break;
158 }
159 case QMediaRecorder::TwoPassEncoding: {
160 qWarning("Two pass encoding is not supported for MPEG4");
161 break;
162 }
163 default: {
164 Q_UNREACHABLE_RETURN();
165 }
166 }
167}
168
169#if defined(Q_OS_LINUX) || defined(Q_OS_ANDROID)
170static void apply_v4l2m2m(const QMediaEncoderSettings &settings, AVCodecContext *codec,
171 AVDictionary ** /*opts*/)
172{
173 if (settings.encodingMode() == QMediaRecorder::ConstantBitRateEncoding
174 || settings.encodingMode() == QMediaRecorder::AverageBitRateEncoding) {
175 codec->bit_rate = settings.videoBitRate();
176 return;
177 }
178
179 // Constant quality: v4l2m2m encoders don't read global_quality/AV_CODEC_FLAG_QSCALE,
180 // so approximate a fixed QP via qmin==qmax over the codec-specific V4L2 QP range.
181 // Bitrate is still required by the driver's frame-level rate controller, so keep
182 // supplying a heuristic ceiling alongside the QP pin.
183 codec->bit_rate = bitrateForSettings(settings);
184
185 const int quality = settings.quality();
186 switch (settings.videoCodec()) {
187 case QMediaFormat::VideoCodec::H264: {
188 static constexpr int q[] = { 45, 38, 28, 20, 10 }; // 0..51
189 codec->qmin = codec->qmax = q[quality];
190 break;
191 }
192 case QMediaFormat::VideoCodec::MPEG4: {
193 static constexpr int q[] = { 28, 22, 16, 10, 4 }; // 1..31
194 codec->qmin = codec->qmax = q[quality];
195 break;
196 }
197 case QMediaFormat::VideoCodec::VP8: {
198 static constexpr int q[] = { 108, 88, 64, 40, 16 }; // 0..127
199 codec->qmin = codec->qmax = q[quality];
200 break;
201 }
202 case QMediaFormat::VideoCodec::VP9: {
203 static constexpr int q[] = { 216, 176, 128, 80, 32 }; // 0..255
204 codec->qmin = codec->qmax = q[quality];
205 break;
206 }
207 default:
208 // e.g. HEVC/H263: driver exposes no QP control, bit_rate heuristic above is all we can do
209 break;
210 }
211}
212#endif
213
214#ifdef Q_OS_DARWIN
215static void apply_videotoolbox(const QMediaEncoderSettings &settings, AVCodecContext *codec, AVDictionary **opts)
216{
217 if (settings.encodingMode() == QMediaRecorder::ConstantBitRateEncoding || settings.encodingMode() == QMediaRecorder::AverageBitRateEncoding) {
218 codec->bit_rate = settings.videoBitRate();
219 } else {
220 // only use quality on macOS/ARM, as FFmpeg doesn't support it on the other platforms and would throw
221 // an error when initializing the codec
222#if defined(Q_OS_MACOS) && defined(Q_PROCESSOR_ARM_64)
223 // Videotoolbox describes quality as a number from 0 to 1, with low == 0.25, normal 0.5, high 0.75 and lossless = 1
224 // ffmpeg uses a different scale going from 0 to 11800.
225 // Values here are adjusted to agree approximately with the target bit rates listed above
226 const int scales[] = {
227 3000, 4800, 5900, 6900, 7700,
228 };
229 codec->global_quality = scales[settings.quality()];
230 codec->flags |= AV_CODEC_FLAG_QSCALE;
231#else
232 codec->bit_rate = bitrateForSettings(settings);
233#endif
234 }
235
236 // Videotooldox hw acceleration fails of some hardwares,
237 // allow_sw makes sw encoding available if hw encoding failed.
238 // Under the hood, ffmpeg sets
239 // kVTVideoEncoderSpecification_EnableHardwareAcceleratedVideoEncoder instead of
240 // kVTVideoEncoderSpecification_RequireHardwareAcceleratedVideoEncoder
241 av_dict_set(opts, "allow_sw", "1", 0);
242}
243#endif
244
245#if QT_CONFIG(vaapi)
246static void apply_vaapi(const QMediaEncoderSettings &settings, AVCodecContext *codec, AVDictionary **/*opts*/)
247{
248 // See also vaapi_encode_init_rate_control() in libavcodec
249 if (settings.encodingMode() == QMediaRecorder::ConstantBitRateEncoding) {
250 codec->bit_rate = settings.videoBitRate();
251 codec->rc_max_rate = settings.videoBitRate();
252 } else if (settings.encodingMode() == QMediaRecorder::AverageBitRateEncoding) {
253 codec->bit_rate = settings.videoBitRate();
254 } else {
255 const int *quality = nullptr;
256 // unfortunately, all VA codecs use different quality scales :/
257 switch (settings.videoCodec()) {
258 case QMediaFormat::VideoCodec::MPEG2: {
259 static const int q[] = { 20, 15, 10, 8, 6 };
260 quality = q;
261 break;
262 }
263 case QMediaFormat::VideoCodec::MPEG4:
264 case QMediaFormat::VideoCodec::H264: {
265 static const int q[] = { 29, 26, 23, 21, 19 };
266 quality = q;
267 break;
268 }
269 case QMediaFormat::VideoCodec::H265: {
270 static const int q[] = { 40, 34, 28, 26, 24 };
271 quality = q;
272 break;
273 }
274 case QMediaFormat::VideoCodec::VP8: {
275 static const int q[] = { 56, 48, 40, 34, 28 };
276 quality = q;
277 break;
278 }
279 case QMediaFormat::VideoCodec::VP9: {
280 static const int q[] = { 124, 112, 100, 88, 76 };
281 quality = q;
282 break;
283 }
284 case QMediaFormat::VideoCodec::MotionJPEG: {
285 static const int q[] = { 40, 60, 80, 90, 95 };
286 quality = q;
287 break;
288 }
289 case QMediaFormat::VideoCodec::AV1:
290 case QMediaFormat::VideoCodec::Theora:
291 case QMediaFormat::VideoCodec::WMV:
292 default:
293 break;
294 }
295
296 if (quality)
297 codec->global_quality = quality[settings.quality()];
298 }
299}
300#endif
301
302static void apply_nvenc(const QMediaEncoderSettings &settings, AVCodecContext *codec,
303 AVDictionary **opts)
304{
305 switch (settings.encodingMode()) {
306 case QMediaRecorder::EncodingMode::AverageBitRateEncoding:
307 av_dict_set(opts, "vbr", "1", 0);
308 codec->bit_rate = settings.videoBitRate();
309 break;
310 case QMediaRecorder::EncodingMode::ConstantBitRateEncoding:
311 av_dict_set(opts, "cbr", "1", 0);
312 codec->bit_rate = settings.videoBitRate();
313 codec->rc_max_rate = codec->rc_min_rate = codec->bit_rate;
314 break;
315 case QMediaRecorder::EncodingMode::ConstantQualityEncoding: {
316 static const char *q[] = { "51", "48", "35", "15", "1" };
317 av_dict_set(opts, "cq", q[settings.quality()], 0);
318 } break;
319 default:
320 break;
321 }
322}
323
324#ifdef Q_OS_WINDOWS
325static void apply_mf(const QMediaEncoderSettings &settings, AVCodecContext *codec, AVDictionary **opts)
326{
327 if (settings.encodingMode() == QMediaRecorder::ConstantBitRateEncoding || settings.encodingMode() == QMediaRecorder::AverageBitRateEncoding) {
328 codec->bit_rate = settings.videoBitRate();
329 av_dict_set(opts, "rate_control", "cbr", 0);
330 } else {
331 av_dict_set(opts, "rate_control", "quality", 0);
332 const char *scales[] = {
333 "25", "50", "75", "90", "100"
334 };
335 av_dict_set(opts, "quality", scales[settings.quality()], 0);
336 }
337}
338#endif
339
340#ifdef Q_OS_ANDROID
341static void apply_mediacodec(const QMediaEncoderSettings &settings, AVCodecContext *codec,
342 AVDictionary **opts)
343{
344 if (settings.videoBitRate() != -1)
345 codec->bit_rate = settings.videoBitRate();
346 else
347 codec->bit_rate = bitrateForSettings(settings);
348
349 const int quality[] = { 25, 50, 75, 90, 100 };
350 codec->global_quality = quality[settings.quality()];
351
352 switch (settings.encodingMode()) {
353 case QMediaRecorder::EncodingMode::AverageBitRateEncoding:
354 av_dict_set(opts, "bitrate_mode", "vbr", 1);
355 break;
356 case QMediaRecorder::EncodingMode::ConstantBitRateEncoding:
357 av_dict_set(opts, "bitrate_mode", "cbr", 1);
358 break;
359 case QMediaRecorder::EncodingMode::ConstantQualityEncoding:
360 // av_dict_set(opts, "bitrate_mode", "cq", 1);
361 av_dict_set(opts, "bitrate_mode", "cbr", 1);
362 break;
363 default:
364 break;
365 }
366
367 switch (settings.videoCodec()) {
368 case QMediaFormat::VideoCodec::H264: {
369 const char *levels[] = { "2.2", "3.2", "4.2", "5.2", "6.2" };
370 av_dict_set(opts, "level", levels[settings.quality()], 1);
371 codec->profile = AV_PROFILE_H264_HIGH;
372 break;
373 }
374 case QMediaFormat::VideoCodec::H265: {
375 // Set the level only for FFmpeg versions that correctly recognize level values.
376 // Affected revisions: from n7.1 https://github.com/FFmpeg/FFmpeg/commit/7753a9d62725d5bd8313e2d249acbe1c8af79ab1
377 // up to https://github.com/FFmpeg/FFmpeg/commit/020d9f2b4886aa620252da4db7a4936378d6eb3a
378 if (avcodec_version() < 4000612 || avcodec_version() > 4002660) {
379 const char *levels[] = { "h2.1", "h3.1", "h4.1", "h5.1", "h6.1" };
380 av_dict_set(opts, "level", levels[settings.quality()], 1);
381 }
382
383 codec->profile = AV_PROFILE_HEVC_MAIN;
384 break;
385 }
386 default:
387 break;
388 }
389}
390#endif
391
392namespace QFFmpeg {
393
394using ApplyVideoCodecOptions = void (*)(const QMediaEncoderSettings &settings,
395 AVCodecContext *codec, AVDictionary **opts);
396
397using namespace Qt::StringLiterals;
398
402
404 { "libx264"_L1, apply_x264 },
405 { "libx265"_L1, apply_x265 },
406 { "libvpx"_L1, apply_libvpx },
407 { "libvpx_vp9"_L1, apply_libvpx },
408 { "libopenh264"_L1, apply_openh264 },
409 { "h264_nvenc"_L1, apply_nvenc },
410 { "hevc_nvenc"_L1, apply_nvenc },
411 { "av1_nvenc"_L1, apply_nvenc },
412 { "mpeg4"_L1, apply_mpeg4 },
413#if defined(Q_OS_LINUX) || defined(Q_OS_ANDROID)
414 { "h264_v4l2m2m"_L1, apply_v4l2m2m },
415 { "hevc_v4l2m2m"_L1, apply_v4l2m2m },
416 { "mpeg4_v4l2m2m"_L1, apply_v4l2m2m },
417 { "vp8_v4l2m2m"_L1, apply_v4l2m2m },
418#endif
419#ifdef Q_OS_DARWIN
420 { "h264_videotoolbox"_L1, apply_videotoolbox },
421 { "hevc_videotoolbox"_L1, apply_videotoolbox },
422 { "prores_videotoolbox"_L1, apply_videotoolbox },
423 { "vp9_videotoolbox"_L1, apply_videotoolbox },
424#endif
425#if QT_CONFIG(vaapi)
426 { "mpeg2_vaapi"_L1, apply_vaapi },
427 { "mjpeg_vaapi"_L1, apply_vaapi },
428 { "h264_vaapi"_L1, apply_vaapi },
429 { "hevc_vaapi"_L1, apply_vaapi },
430 { "vp8_vaapi"_L1, apply_vaapi },
431 { "vp9_vaapi"_L1, apply_vaapi },
432#endif
433#ifdef Q_OS_WINDOWS
434 { "hevc_mf"_L1, apply_mf },
435 { "h264_mf"_L1, apply_mf },
436#endif
437#ifdef Q_OS_ANDROID
438 { "hevc_mediacodec"_L1, apply_mediacodec },
439 { "h264_mediacodec"_L1, apply_mediacodec },
440#endif
441
442};
443
444void applyVideoEncoderOptions(const QMediaEncoderSettings &settings, QLatin1StringView codecName,
445 AVCodecContext *codec, AVDictionary **opts)
446{
447 av_dict_set(opts, "threads", "auto", 0); // we always want automatic threading
448
449 auto entry = videoCodecOptionTable.find(codecName);
450
451 if (entry != videoCodecOptionTable.end())
452 entry->second(settings, codec, opts);
453}
454
455void applyAudioEncoderOptions(const QMediaEncoderSettings &settings,
456 QLatin1StringView /*codecName*/, AVCodecContext *codec,
457 AVDictionary ** /*opts*/)
458{
459 codec->thread_count = -1; // we always want automatic threading
460 if (settings.encodingMode() == QMediaRecorder::ConstantBitRateEncoding
461 || settings.encodingMode() == QMediaRecorder::AverageBitRateEncoding)
462 codec->bit_rate = settings.audioBitRate();
463
464 if (settings.audioSampleRate() != -1)
465 codec->sample_rate = settings.audioSampleRate();
466
467 if (settings.audioChannelCount() != -1) {
468 auto mask = QFFmpegMediaFormatInfo::avChannelLayout(
469 QAudioFormat::defaultChannelConfigForChannelCount(settings.audioChannelCount()));
470
471#if QT_FFMPEG_HAS_AV_CHANNEL_LAYOUT
472 av_channel_layout_from_mask(&codec->ch_layout, mask);
473#else
474 codec->channel_layout = mask;
475 codec->channels = qPopulationCount(codec->channel_layout);
476#endif
477 }
478}
479
480} // namespace QFFmpeg
481
482QT_END_NAMESPACE
void applyAudioEncoderOptions(const QMediaEncoderSettings &settings, QLatin1StringView, AVCodecContext *codec, AVDictionary **)
void applyVideoEncoderOptions(const QMediaEncoderSettings &settings, QLatin1StringView codecName, AVCodecContext *codec, AVDictionary **opts)
void(*)(const QMediaEncoderSettings &settings, AVCodecContext *codec, AVDictionary **opts) ApplyVideoCodecOptions
const VideoCodecOptionsTableType videoCodecOptionTable
Combined button and popup list for selecting options.
static void apply_libvpx(const QMediaEncoderSettings &settings, AVCodecContext *codec, AVDictionary **opts)
static QT_BEGIN_NAMESPACE int bitrateForSettings(const QMediaEncoderSettings &settings, bool hdr=false)
static void apply_mpeg4(const QMediaEncoderSettings &settings, AVCodecContext *codec, AVDictionary **opts)
static void apply_x265(const QMediaEncoderSettings &settings, AVCodecContext *codec, AVDictionary **opts)
static void apply_nvenc(const QMediaEncoderSettings &settings, AVCodecContext *codec, AVDictionary **opts)
static void apply_x264(const QMediaEncoderSettings &settings, AVCodecContext *codec, AVDictionary **opts)
static void apply_openh264(const QMediaEncoderSettings &settings, AVCodecContext *codec, AVDictionary **opts)