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
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
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(settings.videoFrameRate()/30.);
60 bitrate *= pow(1.5, 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(opts, "rc_mode", "bitrate", 0);
79 } else {
80 av_dict_set(opts, "rc_mode", "quality", 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(opts, "crf", scales[settings.quality()], 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(opts, "crf", scales[settings.quality()], 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(opts, "crf", scales[settings.quality()], 0);
119 av_dict_set(opts, "b", nullptr, 0);
120 }
121 av_dict_set(opts, "row-mt", "1", 0); // better multithreading
122}
123
124static void apply_mpeg4(const QMediaEncoderSettings &settings, AVCodecContext *codec,
125 AVDictionary **opts)
126{
127 // compare https://trac.ffmpeg.org/wiki/Encode/MPEG-4
128
129 QMediaRecorder::EncodingMode encodingMode = settings.encodingMode();
130 switch (encodingMode) {
131 case QMediaRecorder::ConstantBitRateEncoding:
132 case QMediaRecorder::QMediaRecorder::AverageBitRateEncoding: {
133 codec->bit_rate = settings.videoBitRate();
134 if (encodingMode == QMediaRecorder::ConstantBitRateEncoding)
135 codec->rc_min_rate = codec->rc_max_rate = settings.videoBitRate();
136
137 break;
138 }
139 case QMediaRecorder::ConstantQualityEncoding: {
140 constexpr auto scales = std::array{
141 31, // VeryLowQuality
142 23, // LowQuality
143 16, // NormalQuality
144 9, // HighQuality
145 1, // VeryHighQuality
146 };
147 av_dict_set_int(opts, "qscale", scales[settings.quality()], 0);
148 break;
149 }
150 case QMediaRecorder::TwoPassEncoding: {
151 qWarning("Two pass encoding is not supported for MPEG4");
152 break;
153 }
154 default: {
155 Q_UNREACHABLE_RETURN();
156 }
157 }
158}
159
160#ifdef Q_OS_DARWIN
161static void apply_videotoolbox(const QMediaEncoderSettings &settings, AVCodecContext *codec, AVDictionary **opts)
162{
163 if (settings.encodingMode() == QMediaRecorder::ConstantBitRateEncoding || settings.encodingMode() == QMediaRecorder::AverageBitRateEncoding) {
164 codec->bit_rate = settings.videoBitRate();
165 } else {
166 // only use quality on macOS/ARM, as FFmpeg doesn't support it on the other platforms and would throw
167 // an error when initializing the codec
168#if defined(Q_OS_MACOS) && defined(Q_PROCESSOR_ARM_64)
169 // Videotoolbox describes quality as a number from 0 to 1, with low == 0.25, normal 0.5, high 0.75 and lossless = 1
170 // ffmpeg uses a different scale going from 0 to 11800.
171 // Values here are adjusted to agree approximately with the target bit rates listed above
172 const int scales[] = {
173 3000, 4800, 5900, 6900, 7700,
174 };
175 codec->global_quality = scales[settings.quality()];
176 codec->flags |= AV_CODEC_FLAG_QSCALE;
177#else
178 codec->bit_rate = bitrateForSettings(settings);
179#endif
180 }
181
182 // Videotooldox hw acceleration fails of some hardwares,
183 // allow_sw makes sw encoding available if hw encoding failed.
184 // Under the hood, ffmpeg sets
185 // kVTVideoEncoderSpecification_EnableHardwareAcceleratedVideoEncoder instead of
186 // kVTVideoEncoderSpecification_RequireHardwareAcceleratedVideoEncoder
187 av_dict_set(opts, "allow_sw", "1", 0);
188}
189#endif
190
191#if QT_CONFIG(vaapi)
192static void apply_vaapi(const QMediaEncoderSettings &settings, AVCodecContext *codec, AVDictionary **/*opts*/)
193{
194 // See also vaapi_encode_init_rate_control() in libavcodec
195 if (settings.encodingMode() == QMediaRecorder::ConstantBitRateEncoding) {
196 codec->bit_rate = settings.videoBitRate();
197 codec->rc_max_rate = settings.videoBitRate();
198 } else if (settings.encodingMode() == QMediaRecorder::AverageBitRateEncoding) {
199 codec->bit_rate = settings.videoBitRate();
200 } else {
201 const int *quality = nullptr;
202 // unfortunately, all VA codecs use different quality scales :/
203 switch (settings.videoCodec()) {
204 case QMediaFormat::VideoCodec::MPEG2: {
205 static const int q[] = { 20, 15, 10, 8, 6 };
206 quality = q;
207 break;
208 }
209 case QMediaFormat::VideoCodec::MPEG4:
210 case QMediaFormat::VideoCodec::H264: {
211 static const int q[] = { 29, 26, 23, 21, 19 };
212 quality = q;
213 break;
214 }
215 case QMediaFormat::VideoCodec::H265: {
216 static const int q[] = { 40, 34, 28, 26, 24 };
217 quality = q;
218 break;
219 }
220 case QMediaFormat::VideoCodec::VP8: {
221 static const int q[] = { 56, 48, 40, 34, 28 };
222 quality = q;
223 break;
224 }
225 case QMediaFormat::VideoCodec::VP9: {
226 static const int q[] = { 124, 112, 100, 88, 76 };
227 quality = q;
228 break;
229 }
230 case QMediaFormat::VideoCodec::MotionJPEG: {
231 static const int q[] = { 40, 60, 80, 90, 95 };
232 quality = q;
233 break;
234 }
235 case QMediaFormat::VideoCodec::AV1:
236 case QMediaFormat::VideoCodec::Theora:
237 case QMediaFormat::VideoCodec::WMV:
238 default:
239 break;
240 }
241
242 if (quality)
243 codec->global_quality = quality[settings.quality()];
244 }
245}
246#endif
247
248static void apply_nvenc(const QMediaEncoderSettings &settings, AVCodecContext *codec,
249 AVDictionary **opts)
250{
251 switch (settings.encodingMode()) {
252 case QMediaRecorder::EncodingMode::AverageBitRateEncoding:
253 av_dict_set(opts, "vbr", "1", 0);
254 codec->bit_rate = settings.videoBitRate();
255 break;
256 case QMediaRecorder::EncodingMode::ConstantBitRateEncoding:
257 av_dict_set(opts, "cbr", "1", 0);
258 codec->bit_rate = settings.videoBitRate();
259 codec->rc_max_rate = codec->rc_min_rate = codec->bit_rate;
260 break;
261 case QMediaRecorder::EncodingMode::ConstantQualityEncoding: {
262 static const char *q[] = { "51", "48", "35", "15", "1" };
263 av_dict_set(opts, "cq", q[settings.quality()], 0);
264 } break;
265 default:
266 break;
267 }
268}
269
270#ifdef Q_OS_WINDOWS
271static void apply_mf(const QMediaEncoderSettings &settings, AVCodecContext *codec, AVDictionary **opts)
272{
273 if (settings.encodingMode() == QMediaRecorder::ConstantBitRateEncoding || settings.encodingMode() == QMediaRecorder::AverageBitRateEncoding) {
274 codec->bit_rate = settings.videoBitRate();
275 av_dict_set(opts, "rate_control", "cbr", 0);
276 } else {
277 av_dict_set(opts, "rate_control", "quality", 0);
278 const char *scales[] = {
279 "25", "50", "75", "90", "100"
280 };
281 av_dict_set(opts, "quality", scales[settings.quality()], 0);
282 }
283}
284#endif
285
286#ifdef Q_OS_ANDROID
287static void apply_mediacodec(const QMediaEncoderSettings &settings, AVCodecContext *codec,
288 AVDictionary **opts)
289{
290 if (settings.videoBitRate() != -1)
291 codec->bit_rate = settings.videoBitRate();
292 else
293 codec->bit_rate = bitrateForSettings(settings);
294
295 const int quality[] = { 25, 50, 75, 90, 100 };
296 codec->global_quality = quality[settings.quality()];
297
298 switch (settings.encodingMode()) {
299 case QMediaRecorder::EncodingMode::AverageBitRateEncoding:
300 av_dict_set(opts, "bitrate_mode", "vbr", 1);
301 break;
302 case QMediaRecorder::EncodingMode::ConstantBitRateEncoding:
303 av_dict_set(opts, "bitrate_mode", "cbr", 1);
304 break;
305 case QMediaRecorder::EncodingMode::ConstantQualityEncoding:
306 // av_dict_set(opts, "bitrate_mode", "cq", 1);
307 av_dict_set(opts, "bitrate_mode", "cbr", 1);
308 break;
309 default:
310 break;
311 }
312
313 switch (settings.videoCodec()) {
314 case QMediaFormat::VideoCodec::H264: {
315 const char *levels[] = { "2.2", "3.2", "4.2", "5.2", "6.2" };
316 av_dict_set(opts, "level", levels[settings.quality()], 1);
317 codec->profile = FF_PROFILE_H264_HIGH;
318 break;
319 }
320 case QMediaFormat::VideoCodec::H265: {
321 // Set the level only for FFmpeg versions that correctly recognize level values.
322 // Affected revisions: from n7.1 https://github.com/FFmpeg/FFmpeg/commit/7753a9d62725d5bd8313e2d249acbe1c8af79ab1
323 // up to https://github.com/FFmpeg/FFmpeg/commit/020d9f2b4886aa620252da4db7a4936378d6eb3a
324 if (avcodec_version() < 4000612 || avcodec_version() > 4002660) {
325 const char *levels[] = { "h2.1", "h3.1", "h4.1", "h5.1", "h6.1" };
326 av_dict_set(opts, "level", levels[settings.quality()], 1);
327 }
328
329 codec->profile = FF_PROFILE_HEVC_MAIN;
330 break;
331 }
332 default:
333 break;
334 }
335}
336#endif
337
338namespace QFFmpeg {
339
340using ApplyOptions = void (*)(const QMediaEncoderSettings &settings, AVCodecContext *codec, AVDictionary **opts);
341
342const struct {
343 const char *name;
345} videoCodecOptionTable[] = { { "libx264", apply_x264 },
346 { "libx265xx", apply_x265 },
347 { "libvpx", apply_libvpx },
348 { "libvpx_vp9", apply_libvpx },
349 { "libopenh264", apply_openh264 },
350 { "h264_nvenc", apply_nvenc },
351 { "hevc_nvenc", apply_nvenc },
352 { "av1_nvenc", apply_nvenc },
353 { "mpeg4", apply_mpeg4 },
354#ifdef Q_OS_DARWIN
355 { "h264_videotoolbox", apply_videotoolbox },
356 { "hevc_videotoolbox", apply_videotoolbox },
357 { "prores_videotoolbox", apply_videotoolbox },
358 { "vp9_videotoolbox", apply_videotoolbox },
359#endif
360#if QT_CONFIG(vaapi)
361 { "mpeg2_vaapi", apply_vaapi },
362 { "mjpeg_vaapi", apply_vaapi },
363 { "h264_vaapi", apply_vaapi },
364 { "hevc_vaapi", apply_vaapi },
365 { "vp8_vaapi", apply_vaapi },
366 { "vp9_vaapi", apply_vaapi },
367#endif
368#ifdef Q_OS_WINDOWS
369 { "hevc_mf", apply_mf },
370 { "h264_mf", apply_mf },
371#endif
372#ifdef Q_OS_ANDROID
373 { "hevc_mediacodec", apply_mediacodec },
374 { "h264_mediacodec", apply_mediacodec },
375#endif
376 { nullptr, nullptr } };
377
378const struct {
379 const char *name;
380 ApplyOptions apply;
381} audioCodecOptionTable[] = {
382 { nullptr, nullptr }
384
385void applyVideoEncoderOptions(const QMediaEncoderSettings &settings, const QByteArray &codecName, AVCodecContext *codec, AVDictionary **opts)
386{
387 av_dict_set(opts, "threads", "auto", 0); // we always want automatic threading
388
389 auto *table = videoCodecOptionTable;
390 while (table->name) {
391 if (codecName == table->name) {
392 table->apply(settings, codec, opts);
393 return;
394 }
395
396 ++table;
397 }
398}
399
400void applyAudioEncoderOptions(const QMediaEncoderSettings &settings, const QByteArray &codecName, AVCodecContext *codec, AVDictionary **opts)
401{
402 codec->thread_count = -1; // we always want automatic threading
403 if (settings.encodingMode() == QMediaRecorder::ConstantBitRateEncoding || settings.encodingMode() == QMediaRecorder::AverageBitRateEncoding)
404 codec->bit_rate = settings.audioBitRate();
405
406 if (settings.audioSampleRate() != -1)
407 codec->sample_rate = settings.audioSampleRate();
408
409 if (settings.audioChannelCount() != -1) {
410 auto mask = QFFmpegMediaFormatInfo::avChannelLayout(
411 QAudioFormat::defaultChannelConfigForChannelCount(settings.audioChannelCount()));
412
413#if QT_FFMPEG_HAS_AV_CHANNEL_LAYOUT
414 av_channel_layout_from_mask(&codec->ch_layout, mask);
415#else
416 codec->channel_layout = mask;
417 codec->channels = qPopulationCount(codec->channel_layout);
418#endif
419 }
420
421 auto *table = audioCodecOptionTable;
422 while (table->name) {
423 if (codecName == table->name) {
424 table->apply(settings, codec, opts);
425 return;
426 }
427
428 ++table;
429 }
430
431}
432
433} // namespace QFFmpeg
434
435QT_END_NAMESPACE
void(*)(const QMediaEncoderSettings &settings, AVCodecContext *codec, AVDictionary **opts) ApplyOptions
const char * name
ApplyOptions apply
std::conditional_t< QT_FFMPEG_AVIO_WRITE_CONST, const uint8_t *, uint8_t * > AvioWriteBufferType
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)