Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
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
4#include "libavutil/version.h"
5
6#include "qffmpeghwaccel_p.h"
7#if QT_CONFIG(vaapi)
9#endif
10#ifdef Q_OS_DARWIN
12#endif
13#if QT_CONFIG(wmf)
15#include <QtCore/private/qsystemlibrary_p.h>
16
17#endif
18#ifdef Q_OS_ANDROID
20#endif
21#include "qffmpeg_p.h"
24#include "QtCore/qfile.h"
25
26#include <rhi/qrhi.h>
27#include <qloggingcategory.h>
28#include <unordered_set>
29#ifdef Q_OS_LINUX
30#include <QLibrary>
31#endif
32
33/* Infrastructure for HW acceleration goes into this file. */
34
36
37Q_STATIC_LOGGING_CATEGORY(qLHWAccel, "qt.multimedia.ffmpeg.hwaccel");
38extern bool thread_local FFmpegLogsEnabledInThread;
39
40namespace QFFmpeg {
41
42static const std::initializer_list<AVHWDeviceType> preferredHardwareAccelerators = {
43#if defined(Q_OS_ANDROID)
44 AV_HWDEVICE_TYPE_MEDIACODEC,
45#elif defined(Q_OS_LINUX)
46 AV_HWDEVICE_TYPE_CUDA,
47 AV_HWDEVICE_TYPE_VAAPI,
48
49 // TODO: investigate VDPAU advantages.
50 // nvenc/nvdec codecs use AV_HWDEVICE_TYPE_CUDA by default, but they can also use VDPAU
51 // if it's included into the ffmpeg build and vdpau drivers are installed.
52 // AV_HWDEVICE_TYPE_VDPAU
53#elif defined (Q_OS_WIN)
54 AV_HWDEVICE_TYPE_D3D11VA,
55#elif defined (Q_OS_DARWIN)
56 AV_HWDEVICE_TYPE_VIDEOTOOLBOX,
57#endif
58};
59
60static AVBufferUPtr loadHWContext(AVHWDeviceType type)
61{
62 AVBufferRef *hwContext = nullptr;
63 qCDebug(qLHWAccel) << " Checking HW context:" << av_hwdevice_get_type_name(type);
64 int ret = av_hwdevice_ctx_create(&hwContext, type, nullptr, nullptr, 0);
65
66 if (ret == 0) {
67 qCDebug(qLHWAccel) << " Using above hw context.";
68 return AVBufferUPtr(hwContext);
69 }
70 qCDebug(qLHWAccel) << " Could not create hw context:" << ret << strerror(-ret);
71 return nullptr;
72}
73
74// FFmpeg might crash on loading non-existing hw devices.
75// Let's roughly precheck drivers/libraries.
76static bool precheckDriver(AVHWDeviceType type)
77{
78 // precheckings might need some improvements
79#if defined(Q_OS_LINUX)
80 if (type == AV_HWDEVICE_TYPE_CUDA) {
81 if (!QFile::exists(QLatin1String("/proc/driver/nvidia/version")))
82 return false;
83
84 // QTBUG-122199
85 // CUDA backend requires libnvcuvid in libavcodec
86 QLibrary lib("libnvcuvid.so");
87 if (!lib.load())
88 return false;
89 lib.unload();
90 return true;
91 }
92#elif defined(Q_OS_WINDOWS)
93 if (type == AV_HWDEVICE_TYPE_D3D11VA)
94 return QSystemLibrary(QLatin1String("d3d11.dll")).load();
95
96#if QT_FFMPEG_HAS_D3D12VA
97 if (type == AV_HWDEVICE_TYPE_D3D12VA)
98 return QSystemLibrary(QLatin1String("d3d12.dll")).load();
99#endif
100
101 if (type == AV_HWDEVICE_TYPE_DXVA2)
102 return QSystemLibrary(QLatin1String("d3d9.dll")).load();
103
104 // TODO: check nvenc/nvdec and revisit the checking
105 if (type == AV_HWDEVICE_TYPE_CUDA)
106 return QSystemLibrary(QLatin1String("nvml.dll")).load();
107#else
108 Q_UNUSED(type);
109#endif
110
111 return true;
112}
113
114static bool checkHwType(AVHWDeviceType type)
115{
116 const auto deviceName = av_hwdevice_get_type_name(type);
117 if (!deviceName) {
118 qWarning() << "Internal FFmpeg error, unknow hw type:" << type;
119 return false;
120 }
121
122 if (!precheckDriver(type)) {
123 qCDebug(qLHWAccel) << "Drivers for hw device" << deviceName << "is not installed";
124 return false;
125 }
126
127 if (type == AV_HWDEVICE_TYPE_MEDIACODEC ||
128 type == AV_HWDEVICE_TYPE_VIDEOTOOLBOX ||
129 type == AV_HWDEVICE_TYPE_D3D11VA ||
131 type == AV_HWDEVICE_TYPE_D3D12VA ||
132#endif
133 type == AV_HWDEVICE_TYPE_DXVA2)
134 return true; // Don't waste time; it's expected to work fine of the precheck is OK
135
136
139
140 return loadHWContext(type) != nullptr;
141}
142
143static const std::vector<AVHWDeviceType> &deviceTypes()
144{
145 static const auto types = []() {
146 qCDebug(qLHWAccel) << "Check device types";
148 timer.start();
149
150 // gather hw pix formats
151 std::unordered_set<AVPixelFormat> hwPixFormats;
152 void *opaque = nullptr;
153 while (auto codec = av_codec_iterate(&opaque)) {
154 findAVPixelFormat(codec, [&](AVPixelFormat format) {
156 hwPixFormats.insert(format);
157 return false;
158 });
159 }
160
161 // create a device types list
162 std::vector<AVHWDeviceType> result;
163 AVHWDeviceType type = AV_HWDEVICE_TYPE_NONE;
164 while ((type = av_hwdevice_iterate_types(type)) != AV_HWDEVICE_TYPE_NONE)
165 if (hwPixFormats.count(pixelFormatForHwDevice(type)) && checkHwType(type))
166 result.push_back(type);
167 result.shrink_to_fit();
168
169 // reorder the list accordingly preferredHardwareAccelerators
170 auto it = result.begin();
171 for (const auto preffered : preferredHardwareAccelerators) {
172 auto found = std::find(it, result.end(), preffered);
173 if (found != result.end())
174 std::rotate(it++, found, std::next(found));
175 }
176
177 using namespace std::chrono;
178 qCDebug(qLHWAccel) << "Device types checked. Spent time:" << duration_cast<microseconds>(timer.durationElapsed());
179
180 return result;
181 }();
182
183 return types;
184}
185
186static std::vector<AVHWDeviceType> deviceTypes(const char *envVarName)
187{
188 const auto definedDeviceTypes = qgetenv(envVarName);
189
190 if (definedDeviceTypes.isNull())
191 return deviceTypes();
192
193 std::vector<AVHWDeviceType> result;
194 const auto definedDeviceTypesString = QString::fromUtf8(definedDeviceTypes).toLower();
195 for (const auto &deviceType : definedDeviceTypesString.split(',')) {
196 if (!deviceType.isEmpty()) {
197 const auto foundType = av_hwdevice_find_type_by_name(deviceType.toUtf8().data());
198 if (foundType == AV_HWDEVICE_TYPE_NONE)
199 qWarning() << "Unknown hw device type" << deviceType;
200 else
201 result.emplace_back(foundType);
202 }
203 }
204
205 result.shrink_to_fit();
206 return result;
207}
208
209template<typename CodecFinder>
210std::pair<const AVCodec *, std::unique_ptr<HWAccel>>
211findCodecWithHwAccel(AVCodecID id, const std::vector<AVHWDeviceType> &deviceTypes,
212 CodecFinder codecFinder,
213 const std::function<bool(const HWAccel &)> &hwAccelPredicate)
214{
215 for (auto type : deviceTypes) {
216 const auto codec = codecFinder(id, type, {});
217
218 if (!codec)
219 continue;
220
221 qCDebug(qLHWAccel) << "Found potential codec" << codec->name << "for hw accel" << type
222 << "; Checking the hw device...";
223
224 auto hwAccel = QFFmpeg::HWAccel::create(type);
225
226 if (!hwAccel)
227 continue;
228
229 if (hwAccelPredicate && !hwAccelPredicate(*hwAccel)) {
230 qCDebug(qLHWAccel) << "HW device is available but doesn't suit due to restrictions";
231 continue;
232 }
233
234 qCDebug(qLHWAccel) << "HW device is OK";
235
236 return { codec, std::move(hwAccel) };
237 }
238
239 qCDebug(qLHWAccel) << "No hw acceleration found for codec id" << id;
240
241 return { nullptr, nullptr };
242}
243
244static bool isNoConversionFormat(AVPixelFormat f)
245{
246 bool needsConversion = true;
247 QFFmpegVideoBuffer::toQtPixelFormat(f, &needsConversion);
248 return !needsConversion;
249};
250
251namespace {
252
253bool hwTextureConversionEnabled()
254{
255
256 // HW textures conversions are not stable in specific cases, dependent on the hardware and OS.
257 // We need the env var for testing with no textures conversion on the user's side.
258 static const int disableHwConversion =
259 qEnvironmentVariableIntValue("QT_DISABLE_HW_TEXTURES_CONVERSION");
260
261 return !disableHwConversion;
262}
263
264void setupDecoder(const AVPixelFormat format, AVCodecContext *const codecContext)
265{
266 if (!hwTextureConversionEnabled())
267 return;
268
269#if QT_CONFIG(wmf)
270 if (format == AV_PIX_FMT_D3D11)
271 QFFmpeg::D3D11TextureConverter::SetupDecoderTextures(codecContext);
272#elif defined Q_OS_ANDROID
273 if (format == AV_PIX_FMT_MEDIACODEC)
275#else
276 Q_UNUSED(codecContext);
278#endif
279}
280
281} // namespace
282
283// Used for the AVCodecContext::get_format callback
284AVPixelFormat getFormat(AVCodecContext *codecContext, const AVPixelFormat *suggestedFormats)
285{
286 // First check HW accelerated codecs, the HW device context must be set
287 if (codecContext->hw_device_ctx) {
288 auto *device_ctx = (AVHWDeviceContext *)codecContext->hw_device_ctx->data;
289 std::pair formatAndScore(AV_PIX_FMT_NONE, NotSuitableAVScore);
290
291 // to be rewritten via findBestAVFormat
292 for (int i = 0;
293 const AVCodecHWConfig *config = avcodec_get_hw_config(codecContext->codec, i); i++) {
294 if (!(config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX))
295 continue;
296
297 if (device_ctx->type != config->device_type)
298 continue;
299
300 const bool isDeprecated = (config->methods & AV_CODEC_HW_CONFIG_METHOD_AD_HOC) != 0;
301 const bool shouldCheckCodecFormats = config->pix_fmt == AV_PIX_FMT_NONE;
302
303 auto scoresGettor = [&](AVPixelFormat format) {
304 // check in supported codec->pix_fmts;
305 // no reason to use findAVPixelFormat as we're already in the hw_config loop
306 if (shouldCheckCodecFormats && !hasAVFormat(codecContext->codec->pix_fmts, format))
307 return NotSuitableAVScore;
308
309 if (!shouldCheckCodecFormats && config->pix_fmt != format)
310 return NotSuitableAVScore;
311
312 auto result = DefaultAVScore;
313
314 if (isDeprecated)
315 result -= 10000;
317 result += 10;
318
319 return result;
320 };
321
322 const auto found = findBestAVValue(suggestedFormats, scoresGettor);
323
324 if (found.second > formatAndScore.second)
325 formatAndScore = found;
326 }
327
328 const auto &format = formatAndScore.first;
329 if (format != AV_PIX_FMT_NONE) {
330 setupDecoder(format, codecContext);
331 qCDebug(qLHWAccel) << "Selected format" << format << "for hw" << device_ctx->type;
332 return format;
333 }
334 }
335
336 // prefer video formats we can handle directly
337 const auto noConversionFormat = findAVFormat(suggestedFormats, &isNoConversionFormat);
338 if (noConversionFormat != AV_PIX_FMT_NONE) {
339 qCDebug(qLHWAccel) << "Selected format with no conversion" << noConversionFormat;
340 return noConversionFormat;
341 }
342
343 qCDebug(qLHWAccel) << "Selected format with conversion" << *suggestedFormats;
344
345 // take the native format, this will involve one additional format conversion on the CPU side
346 return *suggestedFormats;
347}
348
349HWAccel::~HWAccel() = default;
350
351std::unique_ptr<HWAccel> HWAccel::create(AVHWDeviceType deviceType)
352{
353 if (auto ctx = loadHWContext(deviceType))
354 return std::unique_ptr<HWAccel>(new HWAccel(std::move(ctx)));
355 else
356 return {};
357}
358
359AVPixelFormat HWAccel::format(AVFrame *frame)
360{
361 if (!frame->hw_frames_ctx)
362 return AVPixelFormat(frame->format);
363
364 auto *hwFramesContext = (AVHWFramesContext *)frame->hw_frames_ctx->data;
366 return AVPixelFormat(hwFramesContext->sw_format);
367}
368
369const std::vector<AVHWDeviceType> &HWAccel::encodingDeviceTypes()
370{
371 static const auto &result = deviceTypes("QT_FFMPEG_ENCODING_HW_DEVICE_TYPES");
372 return result;
373}
374
375const std::vector<AVHWDeviceType> &HWAccel::decodingDeviceTypes()
376{
377 static const auto &result = deviceTypes("QT_FFMPEG_DECODING_HW_DEVICE_TYPES");
378 return result;
379}
380
381AVHWDeviceContext *HWAccel::hwDeviceContext() const
382{
383 return m_hwDeviceContext ? (AVHWDeviceContext *)m_hwDeviceContext->data : nullptr;
384}
385
386AVPixelFormat HWAccel::hwFormat() const
387{
389}
390
391const AVHWFramesConstraints *HWAccel::constraints() const
392{
393 std::call_once(m_constraintsOnceFlag, [this]() {
394 if (auto context = hwDeviceContextAsBuffer())
395 m_constraints.reset(av_hwdevice_get_hwframe_constraints(context, nullptr));
396 });
397
398 return m_constraints.get();
399}
400
401std::pair<const AVCodec *, std::unique_ptr<HWAccel>>
402HWAccel::findEncoderWithHwAccel(AVCodecID id, const std::function<bool(const HWAccel &)>& hwAccelPredicate)
403{
404 auto finder = qOverload<AVCodecID, const std::optional<AVHWDeviceType> &,
405 const std::optional<PixelOrSampleFormat> &>(&QFFmpeg::findAVEncoder);
406 return findCodecWithHwAccel(id, encodingDeviceTypes(), finder, hwAccelPredicate);
407}
408
409std::pair<const AVCodec *, std::unique_ptr<HWAccel>>
410HWAccel::findDecoderWithHwAccel(AVCodecID id, const std::function<bool(const HWAccel &)>& hwAccelPredicate)
411{
413 hwAccelPredicate);
414}
415
416AVHWDeviceType HWAccel::deviceType() const
417{
418 return m_hwDeviceContext ? hwDeviceContext()->type : AV_HWDEVICE_TYPE_NONE;
419}
420
421void HWAccel::createFramesContext(AVPixelFormat swFormat, const QSize &size)
422{
423 if (m_hwFramesContext) {
424 qWarning() << "Frames context has been already created!";
425 return;
426 }
427
428 if (!m_hwDeviceContext)
429 return;
430
431 m_hwFramesContext.reset(av_hwframe_ctx_alloc(m_hwDeviceContext.get()));
432 auto *c = (AVHWFramesContext *)m_hwFramesContext->data;
433 c->format = hwFormat();
434 c->sw_format = swFormat;
435 c->width = size.width();
436 c->height = size.height();
437 qCDebug(qLHWAccel) << "init frames context";
438 int err = av_hwframe_ctx_init(m_hwFramesContext.get());
439 if (err < 0)
440 qWarning() << "failed to init HW frame context" << err << err2str(err);
441 else
442 qCDebug(qLHWAccel) << "Initialized frames context" << size << c->format << c->sw_format;
443}
444
445AVHWFramesContext *HWAccel::hwFramesContext() const
446{
447 return m_hwFramesContext ? (AVHWFramesContext *)m_hwFramesContext->data : nullptr;
448}
449
450
452 : d(new Data)
453{
454 d->rhi = rhi;
455}
456
458{
459 if (!frame || isNull())
460 return nullptr;
461
462 Q_ASSERT(frame->format == d->format);
463 return d->backend->getTextures(frame);
464}
465
466void TextureConverter::updateBackend(AVPixelFormat fmt)
467{
468 d->backend = nullptr;
469 if (!d->rhi)
470 return;
471
472 if (!hwTextureConversionEnabled())
473 return;
474
475 switch (fmt) {
476#if QT_CONFIG(vaapi)
477 case AV_PIX_FMT_VAAPI:
478 d->backend = std::make_unique<VAAPITextureConverter>(d->rhi);
479 break;
480#endif
481#ifdef Q_OS_DARWIN
482 case AV_PIX_FMT_VIDEOTOOLBOX:
483 d->backend = std::make_unique<VideoToolBoxTextureConverter>(d->rhi);
484 break;
485#endif
486#if QT_CONFIG(wmf)
487 case AV_PIX_FMT_D3D11:
488 d->backend = std::make_unique<D3D11TextureConverter>(d->rhi);
489 break;
490#endif
491#ifdef Q_OS_ANDROID
492 case AV_PIX_FMT_MEDIACODEC:
493 d->backend = std::make_unique<MediaCodecTextureConverter>(d->rhi);
494 break;
495#endif
496 default:
497 break;
498 }
499 d->format = fmt;
500}
501
503{
504#if QT_CONFIG(wmf)
505 return copyFromHwPoolD3D11(std::move(frame));
506#else
507 return frame;
508#endif
509}
510
511} // namespace QFFmpeg
512
\inmodule QtCore
static QVideoFrameFormat::PixelFormat toQtPixelFormat(AVPixelFormat avPixelFormat, bool *needsConversion=nullptr)
static std::pair< const AVCodec *, std::unique_ptr< HWAccel > > findDecoderWithHwAccel(AVCodecID id, const std::function< bool(const HWAccel &)> &hwAccelPredicate=nullptr)
AVHWFramesContext * hwFramesContext() const
static const std::vector< AVHWDeviceType > & decodingDeviceTypes()
static const std::vector< AVHWDeviceType > & encodingDeviceTypes()
void createFramesContext(AVPixelFormat swFormat, const QSize &size)
static AVPixelFormat format(AVFrame *frame)
static std::pair< const AVCodec *, std::unique_ptr< HWAccel > > findEncoderWithHwAccel(AVCodecID id, const std::function< bool(const HWAccel &)> &hwAccelPredicate=nullptr)
AVBufferRef * hwDeviceContextAsBuffer() const
const AVHWFramesConstraints * constraints() const
AVHWDeviceContext * hwDeviceContext() const
AVPixelFormat hwFormat() const
static std::unique_ptr< HWAccel > create(AVHWDeviceType deviceType)
AVHWDeviceType deviceType() const
static void setupDecoderSurface(AVCodecContext *s)
TextureSet * getTextures(AVFrame *frame)
TextureConverter(QRhi *rhi=nullptr)
bool exists() const
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qfile.cpp:351
\inmodule QtCore \reentrant
Definition qlibrary.h:17
bool load()
Loads the library and returns true if the library was loaded successfully; otherwise returns false.
Definition qlibrary.cpp:813
bool unload()
Unloads the library and returns true if the library could be unloaded; otherwise returns false.
Definition qlibrary.cpp:843
\inmodule QtGuiPrivate \inheaderfile rhi/qrhi.h
Definition qrhi.h:1805
iterator begin()
Definition qset.h:137
iterator end()
Definition qset.h:141
\inmodule QtCore
Definition qsize.h:25
static QString fromUtf8(QByteArrayView utf8)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:6028
void start(int msec)
Starts or restarts the timer with a timeout interval of msec milliseconds.
Definition qtimer.cpp:241
EGLContext ctx
QSet< QString >::iterator it
const AVCodec * findAVEncoder(AVCodecID codecId, const std::optional< AVHWDeviceType > &deviceType, const std::optional< PixelOrSampleFormat > &format)
Definition qffmpeg.cpp:435
AVFrameUPtr copyFromHwPoolD3D11(AVFrameUPtr src)
AVPixelFormat getFormat(AVCodecContext *codecContext, const AVPixelFormat *suggestedFormats)
bool isHwPixelFormat(AVPixelFormat format)
Definition qffmpeg.cpp:460
static const std::initializer_list< AVHWDeviceType > preferredHardwareAccelerators
QString err2str(int errnum)
Definition qffmpeg_p.h:64
static bool isNoConversionFormat(AVPixelFormat f)
const AVCodec * findAVDecoder(AVCodecID codecId, const std::optional< AVHWDeviceType > &deviceType, const std::optional< PixelOrSampleFormat > &format)
Definition qffmpeg.cpp:429
AVFrameUPtr copyFromHwPool(AVFrameUPtr frame)
constexpr AVScore DefaultAVScore
Definition qffmpeg_p.h:160
bool hasAVFormat(const Format *fmts, Format format)
Definition qffmpeg_p.h:187
std::unique_ptr< AVFrame, AVDeleter< decltype(&av_frame_free), &av_frame_free > > AVFrameUPtr
Definition qffmpeg_p.h:134
static const std::vector< AVHWDeviceType > & deviceTypes()
std::unique_ptr< AVBufferRef, AVDeleter< decltype(&av_buffer_unref), &av_buffer_unref > > AVBufferUPtr
Definition qffmpeg_p.h:148
static AVBufferUPtr loadHWContext(AVHWDeviceType type)
static bool checkHwType(AVHWDeviceType type)
Format findAVFormat(const Format *fmts, const Predicate &predicate)
Definition qffmpeg_p.h:193
std::pair< const AVCodec *, std::unique_ptr< HWAccel > > findCodecWithHwAccel(AVCodecID id, const std::vector< AVHWDeviceType > &deviceTypes, CodecFinder codecFinder, const std::function< bool(const HWAccel &)> &hwAccelPredicate)
constexpr AVScore NotSuitableAVScore
Definition qffmpeg_p.h:161
AVPixelFormat findAVPixelFormat(const AVCodec *codec, const Predicate &predicate)
Definition qffmpeg_p.h:213
AVPixelFormat pixelFormatForHwDevice(AVHWDeviceType deviceType)
Definition qffmpeg.cpp:480
auto findBestAVValue(const Value *values, const CalculateScore &calculateScore)
Definition qffmpeg_p.h:230
static bool precheckDriver(AVHWDeviceType type)
Combined button and popup list for selecting options.
EGLConfig config
#define QT_FFMPEG_HAS_D3D12VA
bool thread_local FFmpegLogsEnabledInThread
QMediaFormat::VideoCodec codec
#define qWarning
Definition qlogging.h:167
#define qCDebug(category,...)
#define Q_STATIC_LOGGING_CATEGORY(name,...)
return ret
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLsizei GLenum GLenum * types
GLenum GLuint id
[7]
GLfloat GLfloat f
GLenum type
GLint GLsizei GLsizei GLenum format
const GLubyte * c
GLuint64EXT * result
[6]
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
QLatin1StringView QLatin1String
Definition qstringfwd.h:31
Q_CORE_EXPORT QByteArray qgetenv(const char *varName)
Q_CORE_EXPORT int qEnvironmentVariableIntValue(const char *varName, bool *ok=nullptr) noexcept
#define Q_UNUSED(x)
QVideoFrameFormat::PixelFormat fmt
static QInputDevice::DeviceType deviceType(const UINT cursorType)
QTimer * timer
[3]
QFrame frame
[0]