Qt 6.x
The Qt SDK
Loading...
Searching...
No Matches
qffmpeg.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 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
4#include "qffmpeg_p.h"
5
6#include <qdebug.h>
7#include <qloggingcategory.h>
8#include <qffmpeghwaccel_p.h> // TODO: probably decompose HWAccel and get rid of the header in the base utils
9
10#include <algorithm>
11#include <vector>
12#include <array>
13#include <optional>
14
15extern "C" {
16#include <libavutil/pixdesc.h>
17#include <libavutil/samplefmt.h>
18
19#ifdef Q_OS_DARWIN
20#include <libavutil/hwcontext_videotoolbox.h>
21#endif
22}
23
25
26static Q_LOGGING_CATEGORY(qLcFFmpegUtils, "qt.multimedia.ffmpeg.utils");
27
28namespace QFFmpeg {
29
30namespace {
31
32enum CodecStorageType {
33 ENCODERS,
34 DECODERS,
35
36 // TODO: maybe split sw/hw codecs
37
38 CODEC_STORAGE_TYPE_COUNT
39};
40
41using CodecsStorage = std::vector<const AVCodec *>;
42
43struct CodecsComparator
44{
45 bool operator()(const AVCodec *a, const AVCodec *b) const { return a->id < b->id; }
46
47 bool operator()(const AVCodec *a, AVCodecID id) const { return a->id < id; }
48};
49
50static void dumpCodecInfo(const AVCodec *codec)
51{
52 const auto mediaType = codec->type == AVMEDIA_TYPE_VIDEO ? "video"
53 : codec->type == AVMEDIA_TYPE_AUDIO ? "audio"
54 : codec->type == AVMEDIA_TYPE_SUBTITLE ? "subtitle"
55 : "other_type";
56 qCDebug(qLcFFmpegUtils) << mediaType << (av_codec_is_encoder(codec) ? "encoder:" : "decoder:")
57 << codec->name << "id:" << codec->id
58 << "capabilities:" << codec->capabilities;
59 if (codec->pix_fmts) {
60 qCDebug(qLcFFmpegUtils) << " pix_fmts:";
61 for (auto f = codec->pix_fmts; *f != AV_PIX_FMT_NONE; ++f) {
62 auto desc = av_pix_fmt_desc_get(*f);
63 qCDebug(qLcFFmpegUtils) << " id:" << *f << desc->name
64 << ((desc->flags & AV_PIX_FMT_FLAG_HWACCEL) ? "hw" : "sw")
65 << "depth:" << desc->comp[0].depth << "flags:" << desc->flags;
66 }
67 }
68
69 if (codec->sample_fmts) {
70 qCDebug(qLcFFmpegUtils) << " sample_fmts:";
71 for (auto f = codec->sample_fmts; *f != AV_SAMPLE_FMT_NONE; ++f) {
72 const auto name = av_get_sample_fmt_name(*f);
73 qCDebug(qLcFFmpegUtils) << " id:" << *f << (name ? name : "unknown")
74 << "bytes_per_sample:" << av_get_bytes_per_sample(*f)
75 << "is_planar:" << av_sample_fmt_is_planar(*f);
76 }
77 }
78
79 if (avcodec_get_hw_config(codec, 0)) {
80 qCDebug(qLcFFmpegUtils) << " hw config:";
81 for (int index = 0; auto config = avcodec_get_hw_config(codec, index); ++index) {
82 const auto pixFmtForDevice = pixelFormatForHwDevice(config->device_type);
83 auto pixFmtDesc = av_pix_fmt_desc_get(config->pix_fmt);
84 auto pixFmtForDeviceDesc = av_pix_fmt_desc_get(pixFmtForDevice);
85 qCDebug(qLcFFmpegUtils)
86 << " device_type:" << config->device_type << "pix_fmt:" << config->pix_fmt
87 << (pixFmtDesc ? pixFmtDesc->name : "unknown")
88 << "pixelFormatForHwDevice:" << pixelFormatForHwDevice(config->device_type)
89 << (pixFmtForDeviceDesc ? pixFmtForDeviceDesc->name : "unknown");
90 }
91 }
92}
93
94static bool isCodecValid(const AVCodec *encoder,
95 const std::vector<AVHWDeviceType> &availableHwDeviceTypes)
96{
97 if (encoder->type != AVMEDIA_TYPE_VIDEO)
98 return true;
99
100 if (!encoder->pix_fmts)
101 return true; // To be investigated. This happens for RAW_VIDEO, that is supposed to be OK,
102 // and with v4l2m2m codec, that is suspicious.
103
104 auto checkFormat = [&](AVPixelFormat pixelFormat) {
105 if (isSwPixelFormat(pixelFormat))
106 return true; // If a codec supports sw pixel formats, it can be used without hw accel
107
108 return std::any_of(availableHwDeviceTypes.begin(), availableHwDeviceTypes.end(),
109 [&pixelFormat](AVHWDeviceType type) {
110 return pixelFormatForHwDevice(type) == pixelFormat;
111 });
112 };
113
114 return findAVFormat(encoder->pix_fmts, checkFormat) != AV_PIX_FMT_NONE;
115}
116
117const CodecsStorage &codecsStorage(CodecStorageType codecsType)
118{
119 static const auto &storages = []() {
120 std::array<CodecsStorage, CODEC_STORAGE_TYPE_COUNT> result;
121 void *opaque = nullptr;
122
123 while (auto codec = av_codec_iterate(&opaque)) {
124 // TODO: to be investigated
125 // FFmpeg functions avcodec_find_decoder/avcodec_find_encoder
126 // find experimental codecs in the last order,
127 // now we don't consider them at all since they are supposed to
128 // be not stable, maybe we shouldn't.
129 if (codec->capabilities & AV_CODEC_CAP_EXPERIMENTAL) {
130 qCDebug(qLcFFmpegUtils) << "Skip experimental codec" << codec->name;
131 continue;
132 }
133
134 if (av_codec_is_decoder(codec)) {
135 if (isCodecValid(codec, HWAccel::decodingDeviceTypes()))
136 result[DECODERS].emplace_back(codec);
137 else
138 qCDebug(qLcFFmpegUtils) << "Skip decoder" << codec->name
139 << "due to disabled matching hw acceleration";
140 }
141
142 if (av_codec_is_encoder(codec)) {
143 if (isCodecValid(codec, HWAccel::encodingDeviceTypes()))
144 result[ENCODERS].emplace_back(codec);
145 else
146 qCDebug(qLcFFmpegUtils) << "Skip encoder" << codec->name
147 << "due to disabled matching hw acceleration";
148 }
149 }
150
151 for (auto &storage : result) {
152 storage.shrink_to_fit();
153
154 // we should ensure the original order
155 std::stable_sort(storage.begin(), storage.end(), CodecsComparator{});
156 }
157
158 // It print pretty much logs, so let's print it only for special case
159 const bool shouldDumpCodecsInfo = qLcFFmpegUtils().isEnabled(QtDebugMsg)
160 && qEnvironmentVariableIsSet("QT_FFMPEG_DEBUG");
161
162 if (shouldDumpCodecsInfo) {
163 qCDebug(qLcFFmpegUtils) << "Advanced ffmpeg codecs info:";
164 for (auto &storage : result) {
165 std::for_each(storage.begin(), storage.end(), &dumpCodecInfo);
166 qCDebug(qLcFFmpegUtils) << "---------------------------";
167 }
168 }
169
170 return result;
171 }();
172
173 return storages[codecsType];
174}
175
176static const char *preferredHwCodecNameSuffix(bool isEncoder, AVHWDeviceType deviceType)
177{
178 switch (deviceType) {
179 case AV_HWDEVICE_TYPE_VAAPI:
180 return "_vaapi";
181 case AV_HWDEVICE_TYPE_MEDIACODEC:
182 return "_mediacodec";
183 case AV_HWDEVICE_TYPE_VIDEOTOOLBOX:
184 return "_videotoolbox";
185 case AV_HWDEVICE_TYPE_D3D11VA:
186 case AV_HWDEVICE_TYPE_DXVA2:
187 return "_mf";
188 case AV_HWDEVICE_TYPE_CUDA:
189 case AV_HWDEVICE_TYPE_VDPAU:
190 return isEncoder ? "_nvenc" : "_cuvid";
191 default:
192 return nullptr;
193 }
194}
195
196template<typename CodecScoreGetter>
197const AVCodec *findAVCodec(CodecStorageType codecsType, AVCodecID codecId,
198 const CodecScoreGetter &scoreGetter)
199{
200 const auto &storage = codecsStorage(codecsType);
201 auto it = std::lower_bound(storage.begin(), storage.end(), codecId, CodecsComparator{});
202
203 const AVCodec *result = nullptr;
204 AVScore resultScore = NotSuitableAVScore;
205
206 for (; it != storage.end() && (*it)->id == codecId && resultScore != BestAVScore; ++it) {
207 const auto score = scoreGetter(*it);
208
209 if (score > resultScore) {
210 resultScore = score;
211 result = *it;
212 }
213 }
214
215 return result;
216}
217
218AVScore hwCodecNameScores(const AVCodec *codec, AVHWDeviceType deviceType)
219{
220 if (auto suffix = preferredHwCodecNameSuffix(av_codec_is_encoder(codec), deviceType)) {
221 const auto substr = strstr(codec->name, suffix);
222 if (substr && !substr[strlen(suffix)])
223 return BestAVScore;
224
225 return DefaultAVScore;
226 }
227
228 return BestAVScore;
229}
230
231const AVCodec *findAVCodec(CodecStorageType codecsType, AVCodecID codecId,
232 const std::optional<AVHWDeviceType> &deviceType,
233 const std::optional<PixelOrSampleFormat> &format)
234{
235 return findAVCodec(codecsType, codecId, [&](const AVCodec *codec) {
237 return NotSuitableAVScore;
238
239 if (!deviceType)
240 return BestAVScore; // find any codec with the id
241
242 if (*deviceType == AV_HWDEVICE_TYPE_NONE
243 && findAVFormat(codec->pix_fmts, &isSwPixelFormat) != AV_PIX_FMT_NONE)
244 return BestAVScore;
245
246 if (*deviceType != AV_HWDEVICE_TYPE_NONE) {
247 for (int index = 0; auto config = avcodec_get_hw_config(codec, index); ++index) {
248 if (config->device_type != deviceType)
249 continue;
250
251 if (format && config->pix_fmt != AV_PIX_FMT_NONE && config->pix_fmt != *format)
252 continue;
253
254 return hwCodecNameScores(codec, *deviceType);
255 }
256
257 // The situation happens mostly with encoders
258 // Probably, it's ffmpeg bug: avcodec_get_hw_config returns null even though
259 // hw acceleration is supported
261 return hwCodecNameScores(codec, *deviceType);
262 }
263
264 return NotSuitableAVScore;
265 });
266}
267
268} // namespace
269
270const AVCodec *findAVDecoder(AVCodecID codecId, const std::optional<AVHWDeviceType> &deviceType,
271 const std::optional<PixelOrSampleFormat> &format)
272{
273 return findAVCodec(DECODERS, codecId, deviceType, format);
274}
275
276const AVCodec *findAVEncoder(AVCodecID codecId, const std::optional<AVHWDeviceType> &deviceType,
277 const std::optional<PixelOrSampleFormat> &format)
278{
279 return findAVCodec(ENCODERS, codecId, deviceType, format);
280}
281
282const AVCodec *findAVEncoder(AVCodecID codecId,
283 const std::function<AVScore(const AVCodec *)> &scoresGetter)
284{
285 return findAVCodec(ENCODERS, codecId, scoresGetter);
286}
287
289{
290 if (codec->type == AVMEDIA_TYPE_VIDEO)
291 return hasAVFormat(codec->pix_fmts, AVPixelFormat(format));
292
293 if (codec->type == AVMEDIA_TYPE_AUDIO)
294 return hasAVFormat(codec->sample_fmts, AVSampleFormat(format));
295
296 return false;
297}
298
299bool isHwPixelFormat(AVPixelFormat format)
300{
301 const auto desc = av_pix_fmt_desc_get(format);
302 return desc && (desc->flags & AV_PIX_FMT_FLAG_HWACCEL) != 0;
303}
304
305AVPixelFormat pixelFormatForHwDevice(AVHWDeviceType deviceType)
306{
307 switch (deviceType) {
308 case AV_HWDEVICE_TYPE_VIDEOTOOLBOX:
309 return AV_PIX_FMT_VIDEOTOOLBOX;
310 case AV_HWDEVICE_TYPE_VAAPI:
311 return AV_PIX_FMT_VAAPI;
312 case AV_HWDEVICE_TYPE_MEDIACODEC:
313 return AV_PIX_FMT_MEDIACODEC;
314 case AV_HWDEVICE_TYPE_CUDA:
315 return AV_PIX_FMT_CUDA;
316 case AV_HWDEVICE_TYPE_VDPAU:
317 return AV_PIX_FMT_VDPAU;
318 case AV_HWDEVICE_TYPE_OPENCL:
319 return AV_PIX_FMT_OPENCL;
320 case AV_HWDEVICE_TYPE_QSV:
321 return AV_PIX_FMT_QSV;
322 case AV_HWDEVICE_TYPE_D3D11VA:
323 return AV_PIX_FMT_D3D11;
324 case AV_HWDEVICE_TYPE_DXVA2:
325 return AV_PIX_FMT_DXVA2_VLD;
326 case AV_HWDEVICE_TYPE_DRM:
327 return AV_PIX_FMT_DRM_PRIME;
328#if QT_FFMPEG_HAS_VULKAN
329 case AV_HWDEVICE_TYPE_VULKAN:
330 return AV_PIX_FMT_VULKAN;
331#endif
332 default:
333 return AV_PIX_FMT_NONE;
334 }
335}
336
337#ifdef Q_OS_DARWIN
338bool isCVFormatSupported(uint32_t cvFormat)
339{
340 return av_map_videotoolbox_format_to_pixfmt(cvFormat) != AV_PIX_FMT_NONE;
341}
342
343std::string cvFormatToString(uint32_t cvFormat)
344{
345 auto formatDescIt = std::make_reverse_iterator(reinterpret_cast<const char *>(&cvFormat));
346 return std::string(formatDescIt - 4, formatDescIt);
347}
348
349#endif
350
351} // namespace QFFmpeg
352
static const std::vector< AVHWDeviceType > & decodingDeviceTypes()
static const std::vector< AVHWDeviceType > & encodingDeviceTypes()
QSet< QString >::iterator it
constexpr AVScore BestAVScore
Definition qffmpeg_p.h:140
const AVCodec * findAVEncoder(AVCodecID codecId, const std::optional< AVHWDeviceType > &deviceType, const std::optional< PixelOrSampleFormat > &format)
Definition qffmpeg.cpp:276
const char * name
bool isHwPixelFormat(AVPixelFormat format)
Definition qffmpeg.cpp:299
bool isAVFormatSupported(const AVCodec *codec, PixelOrSampleFormat format)
Definition qffmpeg.cpp:288
const AVCodec * findAVDecoder(AVCodecID codecId, const std::optional< AVHWDeviceType > &deviceType, const std::optional< PixelOrSampleFormat > &format)
Definition qffmpeg.cpp:270
bool isSwPixelFormat(AVPixelFormat format)
Definition qffmpeg_p.h:191
constexpr AVScore DefaultAVScore
Definition qffmpeg_p.h:141
bool hasAVFormat(const Format *fmts, Format format)
Definition qffmpeg_p.h:159
int PixelOrSampleFormat
Definition qffmpeg_p.h:138
Format findAVFormat(const Format *fmts, const Predicate &predicate)
Definition qffmpeg_p.h:165
int AVScore
Definition qffmpeg_p.h:139
constexpr AVScore NotSuitableAVScore
Definition qffmpeg_p.h:142
AVPixelFormat pixelFormatForHwDevice(AVHWDeviceType deviceType)
Definition qffmpeg.cpp:305
Combined button and popup list for selecting options.
EGLConfig config
QMediaFormat::AudioCodec codec
static AVCodecID codecId(QMediaFormat::VideoCodec codec)
@ QtDebugMsg
Definition qlogging.h:30
#define Q_LOGGING_CATEGORY(name,...)
#define qCDebug(category,...)
GLboolean GLboolean GLboolean b
GLint GLenum GLsizei GLsizei GLsizei depth
GLboolean GLboolean GLboolean GLboolean a
[7]
GLuint index
[2]
GLenum GLuint id
[7]
GLfloat GLfloat f
GLenum type
GLbitfield flags
GLuint name
GLint GLsizei GLsizei GLenum format
GLuint64EXT * result
[6]
Q_CORE_EXPORT bool qEnvironmentVariableIsSet(const char *varName) noexcept
@ desc
static QInputDevice::DeviceType deviceType(const UINT cursorType)
QStorageInfo storage
[1]