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