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