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
qffmpeghwaccel.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
5
6#ifdef Q_OS_WINDOWS
7# include "qffmpeghwaccel_d3d11_p.h"
8# include <QtCore/private/qsystemlibrary_p.h>
9#endif
10
11#include "qffmpeg_p.h"
16
17#include <QtCore/QElapsedTimer>
18
19#ifdef Q_OS_LINUX
20# include "QtCore/qfile.h"
21# include <QLibrary>
22#endif
23
24#include <rhi/qrhi.h>
25#include <qloggingcategory.h>
26#include <unordered_set>
27
28/* Infrastructure for HW acceleration goes into this file. */
29
30QT_BEGIN_NAMESPACE
31
32using namespace Qt::StringLiterals;
33
34Q_STATIC_LOGGING_CATEGORY(qLHWAccel, "qt.multimedia.ffmpeg.hwaccel");
35
36namespace QFFmpeg {
37
39#if defined(Q_OS_ANDROID)
40 AV_HWDEVICE_TYPE_MEDIACODEC,
41#elif defined(Q_OS_LINUX)
42 AV_HWDEVICE_TYPE_CUDA,
43 AV_HWDEVICE_TYPE_VAAPI,
44
45 // TODO: investigate VDPAU advantages.
46 // nvenc/nvdec codecs use AV_HWDEVICE_TYPE_CUDA by default, but they can also use VDPAU
47 // if it's included into the ffmpeg build and vdpau drivers are installed.
48 // AV_HWDEVICE_TYPE_VDPAU
49#elif defined (Q_OS_WIN)
50 AV_HWDEVICE_TYPE_D3D11VA,
51#elif defined (Q_OS_DARWIN)
52 AV_HWDEVICE_TYPE_VIDEOTOOLBOX,
53#endif
54};
55
56static AVBufferUPtr loadHWContext(AVHWDeviceType type)
57{
58 AVBufferRef *hwContext = nullptr;
59 qCDebug(qLHWAccel) << " Checking HW context:" << av_hwdevice_get_type_name(type);
60 int ret = av_hwdevice_ctx_create(&hwContext, type, nullptr, nullptr, 0);
61
62 if (ret == 0) {
63 qCDebug(qLHWAccel) << " Using above hw context.";
64 return AVBufferUPtr(hwContext);
65 }
66 qCDebug(qLHWAccel) << " Could not create hw context:" << ret << strerror(-ret);
67 return nullptr;
68}
69
70// FFmpeg might crash on loading non-existing hw devices.
71// Let's roughly precheck drivers/libraries.
72static bool precheckDriver(AVHWDeviceType type)
73{
74 // precheckings might need some improvements
75#if defined(Q_OS_LINUX)
76 if (type == AV_HWDEVICE_TYPE_CUDA) {
77 if (!QFile::exists(QLatin1String("/proc/driver/nvidia/version")))
78 return false;
79
80 // QTBUG-122199
81 // CUDA backend requires libnvcuvid in libavcodec
82 QLibrary lib(u"libnvcuvid.so"_s);
83 if (!lib.load())
84 return false;
85 lib.unload();
86 return true;
87 }
88#elif defined(Q_OS_WINDOWS)
89 if (type == AV_HWDEVICE_TYPE_D3D11VA)
90 return QSystemLibrary(QLatin1String("d3d11.dll")).load();
91
92#if QT_FFMPEG_HAS_D3D12VA
93 if (type == AV_HWDEVICE_TYPE_D3D12VA)
94 return QSystemLibrary(QLatin1String("d3d12.dll")).load();
95#endif
96
97 if (type == AV_HWDEVICE_TYPE_DXVA2)
98 return QSystemLibrary(QLatin1String("d3d9.dll")).load();
99
100 // TODO: check nvenc/nvdec and revisit the checking
101 if (type == AV_HWDEVICE_TYPE_CUDA)
102 return QSystemLibrary(QLatin1String("nvml.dll")).load();
103#else
104 Q_UNUSED(type);
105#endif
106
107 return true;
108}
109
110static bool checkHwType(AVHWDeviceType type)
111{
112 const auto deviceName = av_hwdevice_get_type_name(type);
113 if (!deviceName) {
114 qWarning() << "Internal FFmpeg error, unknow hw type:" << type;
115 return false;
116 }
117
118 if (!precheckDriver(type)) {
119 qCDebug(qLHWAccel) << "Drivers for hw device" << deviceName << "is not installed";
120 return false;
121 }
122
123#if QT_FFMPEG_HAS_D3D12VA
124 if (type == AV_HWDEVICE_TYPE_D3D12VA)
125 return false; // QTBUG-146635: opening d3d12va codecs fails
126#endif
127
128 if (type == AV_HWDEVICE_TYPE_MEDIACODEC ||
129 type == AV_HWDEVICE_TYPE_VIDEOTOOLBOX ||
130 type == AV_HWDEVICE_TYPE_D3D11VA ||
131#if QT_FFMPEG_HAS_D3D12VA
132 type == AV_HWDEVICE_TYPE_D3D12VA ||
133#endif
134 type == AV_HWDEVICE_TYPE_DXVA2)
135 return true; // Don't waste time; it's expected to work fine if the precheck is OK
136
137 QScopedValueRollback rollback(FFmpegLogsEnabledInThread);
139
140 return loadHWContext(type) != nullptr;
141}
142
144{
145 static const auto types = []() {
146 qCDebug(qLHWAccel) << "Check device types";
147 QElapsedTimer timer;
148 timer.start();
149
150 // gather hw pix formats
151 std::unordered_set<AVPixelFormat> hwPixFormats;
152 for (const Codec codec : CodecEnumerator()) {
153 forEachAVPixelFormat(codec, [&](AVPixelFormat format) {
154 if (isHwPixelFormat(format))
155 hwPixFormats.insert(format);
156 });
157 }
158
159 // create a device types list
160 std::vector<AVHWDeviceType> result;
161 AVHWDeviceType type = AV_HWDEVICE_TYPE_NONE;
162 while ((type = av_hwdevice_iterate_types(type)) != AV_HWDEVICE_TYPE_NONE)
163 if (hwPixFormats.count(pixelFormatForHwDevice(type)) && checkHwType(type))
164 result.push_back(type);
165 result.shrink_to_fit();
166
167 // reorder the list accordingly preferredHardwareAccelerators
168 auto it = result.begin();
169 for (const auto preffered : preferredHardwareAccelerators) {
170 auto found = std::find(it, result.end(), preffered);
171 if (found != result.end())
172 std::rotate(it++, found, std::next(found));
173 }
174
175 using namespace std::chrono;
176 qCDebug(qLHWAccel) << "Device types checked. Spent time:" << duration_cast<microseconds>(timer.durationElapsed());
177
178 return result;
179 }();
180
181 return types;
182}
183
184static std::vector<AVHWDeviceType> deviceTypes(const char *envVarName)
185{
186 const auto definedDeviceTypes = qgetenv(envVarName);
187
188 if (definedDeviceTypes.isNull())
189 return deviceTypes();
190
191 std::vector<AVHWDeviceType> result;
192 const auto definedDeviceTypesString = QString::fromUtf8(definedDeviceTypes).toLower();
193 for (const auto &deviceType : definedDeviceTypesString.split(u',')) {
194 if (!deviceType.isEmpty()) {
195 const auto foundType = av_hwdevice_find_type_by_name(deviceType.toUtf8().data());
196 if (foundType == AV_HWDEVICE_TYPE_NONE)
197 qWarning() << "Unknown hw device type" << deviceType;
198 else
199 result.emplace_back(foundType);
200 }
201 }
202
203 result.shrink_to_fit();
204 return result;
205}
206
207std::pair<std::optional<Codec>, HWAccelUPtr> HWAccel::findDecoderWithHwAccel(AVCodecID id)
208{
209 for (auto type : decodingDeviceTypes()) {
210 const std::optional<Codec> codec = findAVDecoder(id, pixelFormatForHwDevice(type));
211
212 if (!codec)
213 continue;
214
215 qCDebug(qLHWAccel) << "Found potential codec" << codec->name() << "for hw accel" << type
216 << "; Checking the hw device...";
217
218 HWAccelUPtr hwAccel = create(type);
219
220 if (!hwAccel)
221 continue;
222
223 qCDebug(qLHWAccel) << "HW device is OK";
224
225 return { codec, std::move(hwAccel) };
226 }
227
228 qCDebug(qLHWAccel) << "No hw acceleration found for codec id" << id;
229
230 return { std::nullopt, nullptr };
231}
232
233static bool isNoConversionFormat(AVPixelFormat f)
234{
235 bool needsConversion = true;
236 QFFmpegVideoBuffer::toQtPixelFormat(f, &needsConversion);
237 return !needsConversion;
238};
239
240// Used for the AVCodecContext::get_format callback
241AVPixelFormat getFormat(AVCodecContext *codecContext, const AVPixelFormat *fmt)
242{
243 QSpan<const AVPixelFormat> suggestedFormats = makeSpan(fmt);
244 // First check HW accelerated codecs, the HW device context must be set
245 if (codecContext->hw_device_ctx) {
246 auto *device_ctx = (AVHWDeviceContext *)codecContext->hw_device_ctx->data;
247 ValueAndScore<AVPixelFormat> formatAndScore;
248
249 // to be rewritten via findBestAVFormat
250 const Codec codec{ codecContext->codec };
251 for (const AVCodecHWConfig *config : codec.hwConfigs()) {
252 if (!(config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX))
253 continue;
254
255 if (device_ctx->type != config->device_type)
256 continue;
257
258 const bool isDeprecated = (config->methods & AV_CODEC_HW_CONFIG_METHOD_AD_HOC) != 0;
259 const bool shouldCheckCodecFormats = config->pix_fmt == AV_PIX_FMT_NONE;
260
261 auto scoresGettor = [&](AVPixelFormat format) {
262 // check in supported codec->pix_fmts (avcodec_get_supported_config with
263 // AV_CODEC_CONFIG_PIX_FORMAT since n7.1); no reason to use findAVPixelFormat as
264 // we're already in the hw_config loop
265 const auto pixelFormats = codec.pixelFormats();
266 if (shouldCheckCodecFormats && !hasValue(pixelFormats, format))
267 return NotSuitableAVScore;
268
269 if (!shouldCheckCodecFormats && config->pix_fmt != format)
270 return NotSuitableAVScore;
271
272 auto result = DefaultAVScore;
273
274 if (isDeprecated)
275 result -= 10000;
276 if (isHwPixelFormat(format))
277 result += 10;
278
279 return result;
280 };
281
282 const auto found = findBestAVValueWithScore(suggestedFormats, scoresGettor);
283
284 if (found.score > formatAndScore.score)
285 formatAndScore = found;
286 }
287
288 const auto format = formatAndScore.value;
289 if (format) {
290 TextureConverter::applyDecoderPreset(*format, *codecContext);
291 qCDebug(qLHWAccel) << "Selected format" << *format << "for hw" << device_ctx->type;
292 return *format;
293 }
294 }
295
296 // prefer video formats we can handle directly
297 const auto noConversionFormat = findIf(suggestedFormats, &isNoConversionFormat);
298 if (noConversionFormat) {
299 qCDebug(qLHWAccel) << "Selected format with no conversion" << *noConversionFormat;
300 return *noConversionFormat;
301 }
302
303 const AVPixelFormat format = !suggestedFormats.empty() ? suggestedFormats[0] : AV_PIX_FMT_NONE;
304 qCDebug(qLHWAccel) << "Selected format with conversion" << format;
305
306 // take the native format, this will involve one additional format conversion on the CPU side
307 return format;
308}
309
310HWAccel::~HWAccel() = default;
311
312HWAccelUPtr HWAccel::create(AVHWDeviceType deviceType)
313{
314 if (auto ctx = loadHWContext(deviceType))
315 return HWAccelUPtr(new HWAccel(std::move(ctx)));
316 else
317 return {};
318}
319
320AVPixelFormat HWAccel::format(AVFrame *frame)
321{
322 if (!frame->hw_frames_ctx)
323 return AVPixelFormat(frame->format);
324
325 auto *hwFramesContext = (AVHWFramesContext *)frame->hw_frames_ctx->data;
326 Q_ASSERT(hwFramesContext);
327 return AVPixelFormat(hwFramesContext->sw_format);
328}
329
330const std::vector<AVHWDeviceType> &HWAccel::encodingDeviceTypes()
331{
332 static const auto &result = deviceTypes("QT_FFMPEG_ENCODING_HW_DEVICE_TYPES");
333 return result;
334}
335
336const std::vector<AVHWDeviceType> &HWAccel::decodingDeviceTypes()
337{
338 static const auto &result = deviceTypes("QT_FFMPEG_DECODING_HW_DEVICE_TYPES");
339 return result;
340}
341
343{
344 return m_hwDeviceContext ? (AVHWDeviceContext *)m_hwDeviceContext->data : nullptr;
345}
346
348{
349 return pixelFormatForHwDevice(deviceType());
350}
351
353{
354 std::call_once(m_constraintsOnceFlag, [this]() {
355 if (auto context = hwDeviceContextAsBuffer())
356 m_constraints.reset(av_hwdevice_get_hwframe_constraints(context, nullptr));
357 });
358
359 return m_constraints.get();
360}
361
362bool HWAccel::matchesSizeContraints(QSize size) const
363{
364 const auto constraints = this->constraints();
365 if (!constraints)
366 return true;
367
368 return size.width() >= constraints->min_width
369 && size.height() >= constraints->min_height
370 && size.width() <= constraints->max_width
371 && size.height() <= constraints->max_height;
372}
373
375{
376 return m_hwDeviceContext ? hwDeviceContext()->type : AV_HWDEVICE_TYPE_NONE;
377}
378
379void HWAccel::createFramesContext(AVPixelFormat swFormat, const QSize &size)
380{
381 if (m_hwFramesContext) {
382 qWarning() << "Frames context has been already created!";
383 return;
384 }
385
386 if (!m_hwDeviceContext)
387 return;
388
389 m_hwFramesContext.reset(av_hwframe_ctx_alloc(m_hwDeviceContext.get()));
390 auto *c = (AVHWFramesContext *)m_hwFramesContext->data;
391 c->format = hwFormat();
392 c->sw_format = swFormat;
393 c->width = size.width();
394 c->height = size.height();
395 qCDebug(qLHWAccel) << "init frames context";
396 int err = av_hwframe_ctx_init(m_hwFramesContext.get());
397 if (err < 0)
398 qWarning() << "failed to init HW frame context" << err << AVError(err);
399 else
400 qCDebug(qLHWAccel) << "Initialized frames context" << size << c->format << c->sw_format;
401}
402
403void HWAccel::updateFramesContext(AVPixelFormat swFormat, const QSize &size)
404{
405 if (m_hwFramesContext) {
406 auto *c = (AVHWFramesContext *)m_hwFramesContext->data;
407 if (c->sw_format != swFormat || QSize(c->width, c->height) != size)
408 m_hwFramesContext.reset();
409 else
410 return;
411 }
412
413 createFramesContext(swFormat, size);
414}
415
416AVHWFramesContext *HWAccel::hwFramesContext() const
417{
418 return m_hwFramesContext ? (AVHWFramesContext *)m_hwFramesContext->data : nullptr;
419}
420
421static void deleteHwFrameContextData(AVHWFramesContext *context)
422{
423 std::unique_ptr<HwFrameContextData> contextData(
424 static_cast<HwFrameContextData *>(context->user_opaque));
425 Q_ASSERT(contextData);
426
427 if (contextData->avDeleter) {
428 context->user_opaque = contextData->avUserOpaque;
429 context->free = contextData->avDeleter;
430
431 context->free(context);
432 }
433}
434
435HwFrameContextData &HwFrameContextData::ensure(AVFrame &hwFrame)
436{
437 Q_ASSERT(hwFrame.hw_frames_ctx && hwFrame.hw_frames_ctx->data);
438
439 auto context = reinterpret_cast<AVHWFramesContext *>(hwFrame.hw_frames_ctx->data);
440
441 if (context->free != deleteHwFrameContextData) {
442 // In most cases, we expect null context->free and context->user_opaque.
443 // However, FFmpeg decoding implementations for specific graphic card backends
444 // may set a custom deleter. Let's save the deleter and its data (user_opaque)
445 // to invoke it in deleteHwFrameContextData.
446 context->user_opaque = new HwFrameContextData{ context->free, context->user_opaque };
447 context->free = deleteHwFrameContextData;
448 } else {
449 Q_ASSERT(context->user_opaque);
450 }
451
452 return *static_cast<HwFrameContextData *>(context->user_opaque);
453}
454
455AVFrameUPtr copyFromHwPool(AVFrameUPtr frame)
456{
457#ifdef Q_OS_WINDOWS
458 return copyFromHwPoolD3D11(std::move(frame));
459#else
460 return frame;
461#endif
462}
463
464} // namespace QFFmpeg
465
466QT_END_NAMESPACE
AVHWFramesContext * hwFramesContext() const
bool matchesSizeContraints(QSize size) const
void createFramesContext(AVPixelFormat swFormat, const QSize &size)
const AVHWFramesConstraints * constraints() const
void updateFramesContext(AVPixelFormat swFormat, const QSize &size)
AVHWDeviceContext * hwDeviceContext() const
AVPixelFormat hwFormat() const
AVHWDeviceType deviceType() const
AVPixelFormat getFormat(AVCodecContext *s, const AVPixelFormat *fmt)
static bool isNoConversionFormat(AVPixelFormat f)
AVFrameUPtr copyFromHwPool(AVFrameUPtr frame)
static const std::vector< AVHWDeviceType > & deviceTypes()
std::conditional_t< QT_FFMPEG_AVIO_WRITE_CONST, const uint8_t *, uint8_t * > AvioWriteBufferType
static AVBufferUPtr loadHWContext(AVHWDeviceType type)
static bool checkHwType(AVHWDeviceType type)
static void deleteHwFrameContextData(AVHWFramesContext *context)
static bool precheckDriver(AVHWDeviceType type)
static std::vector< AVHWDeviceType > deviceTypes(const char *envVarName)
static const std::initializer_list< AVHWDeviceType > preferredHardwareAccelerators
bool FFmpegLogsEnabledInThread
#define qCDebug(category,...)
#define Q_STATIC_LOGGING_CATEGORY(name,...)
The HwFrameContextData class contains custom belongings of hw frames context.