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