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