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 |
14 | extern "C" { |
15 | #include <libavcodec/avcodec.h> |
16 | } |
17 | #endif |
18 | |
19 | #include <libavutil/channel_layout.h> |
20 | |
21 | QT_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]] |
32 | static 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 | |
72 | static 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 | |
86 | static 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 | |
98 | static 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 | |
110 | static 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 |
125 | static 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) |
156 | static 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 | |
212 | static 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 |
235 | static 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 |
251 | static 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 | |
302 | namespace QFFmpeg { |
303 | |
304 | using ApplyOptions = void (*)(const QMediaEncoderSettings &settings, AVCodecContext *codec, AVDictionary **opts); |
305 | |
306 | const 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 | |
341 | const struct { |
342 | const char *name; |
343 | ApplyOptions apply; |
344 | } audioCodecOptionTable[] = { |
345 | { .name: nullptr, .apply: nullptr } |
346 | }; |
347 | |
348 | void 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 | |
363 | void 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 | |
398 | QT_END_NAMESPACE |
399 | |