Qt 6.x
The Qt SDK
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#endif
16#ifdef Q_OS_ANDROID
18#endif
19#include "qffmpeg_p.h"
21
22#include <rhi/qrhi.h>
23#include <qloggingcategory.h>
24#include <set>
25
26/* Infrastructure for HW acceleration goes into this file. */
27
29
30static Q_LOGGING_CATEGORY(qLHWAccel, "qt.multimedia.ffmpeg.hwaccel");
31
32namespace QFFmpeg {
33
34static const std::initializer_list<AVHWDeviceType> preferredHardwareAccelerators = {
35#if defined(Q_OS_ANDROID)
36 AV_HWDEVICE_TYPE_MEDIACODEC,
37#elif defined(Q_OS_LINUX)
38 AV_HWDEVICE_TYPE_VAAPI,
39 AV_HWDEVICE_TYPE_VDPAU,
40 AV_HWDEVICE_TYPE_CUDA,
41#elif defined (Q_OS_WIN)
42 AV_HWDEVICE_TYPE_D3D11VA,
43#elif defined (Q_OS_DARWIN)
44 AV_HWDEVICE_TYPE_VIDEOTOOLBOX,
45#endif
46};
47
48static std::vector<AVHWDeviceType> deviceTypes(const char *envVarName)
49{
50 std::vector<AVHWDeviceType> result;
51
52 const auto definedDeviceTypes = qgetenv(envVarName);
53 if (!definedDeviceTypes.isNull()) {
54 const auto definedDeviceTypesString = QString::fromUtf8(definedDeviceTypes).toLower();
55 for (const auto &deviceType : definedDeviceTypesString.split(',')) {
56 if (!deviceType.isEmpty()) {
57 const auto foundType = av_hwdevice_find_type_by_name(deviceType.toUtf8().data());
58 if (foundType == AV_HWDEVICE_TYPE_NONE)
59 qWarning() << "Unknown hw device type" << deviceType;
60 else
61 result.emplace_back(foundType);
62 }
63 }
64
65 return result;
66 } else {
67 std::set<AVHWDeviceType> deviceTypesSet;
68 AVHWDeviceType type = AV_HWDEVICE_TYPE_NONE;
69 while ((type = av_hwdevice_iterate_types(type)) != AV_HWDEVICE_TYPE_NONE)
70 deviceTypesSet.insert(type);
71
72 for (const auto preffered : preferredHardwareAccelerators)
73 if (deviceTypesSet.erase(preffered))
74 result.push_back(preffered);
75
76 result.insert(result.end(), deviceTypesSet.begin(), deviceTypesSet.end());
77 }
78
79 result.shrink_to_fit();
80 return result;
81}
82
83static AVBufferRef *loadHWContext(const AVHWDeviceType type)
84{
85 AVBufferRef *hwContext = nullptr;
86 int ret = av_hwdevice_ctx_create(&hwContext, type, nullptr, nullptr, 0);
87 qCDebug(qLHWAccel) << " Checking HW context:" << av_hwdevice_get_type_name(type);
88 if (ret == 0) {
89 qCDebug(qLHWAccel) << " Using above hw context.";
90 return hwContext;
91 }
92 qCDebug(qLHWAccel) << " Could not create hw context:" << ret << strerror(-ret);
93 return nullptr;
94}
95
96template<typename CodecFinder>
97std::pair<const AVCodec *, std::unique_ptr<HWAccel>>
98findCodecWithHwAccel(AVCodecID id, const std::vector<AVHWDeviceType> &deviceTypes,
99 CodecFinder codecFinder,
100 const std::function<bool(const HWAccel &)> &hwAccelPredicate)
101{
102 for (auto type : deviceTypes) {
103 const auto codec = codecFinder(id, type, {});
104
105 if (!codec)
106 continue;
107
108 qCDebug(qLHWAccel) << "Found potential codec" << codec->name << "for hw accel" << type
109 << "; Checking the hw device...";
110
111 auto hwAccel = QFFmpeg::HWAccel::create(type);
112
113 if (!hwAccel)
114 continue;
115
116 if (hwAccelPredicate && !hwAccelPredicate(*hwAccel)) {
117 qCDebug(qLHWAccel) << "HW device is available but doesn't suit due to restrictions";
118 continue;
119 }
120
121 qCDebug(qLHWAccel) << "HW device is OK";
122
123 return { codec, std::move(hwAccel) };
124 }
125
126 qCDebug(qLHWAccel) << "No hw acceleration found for codec id" << id;
127
128 return { nullptr, nullptr };
129}
130
131static bool isNoConversionFormat(AVPixelFormat f)
132{
133 bool needsConversion = true;
134 QFFmpegVideoBuffer::toQtPixelFormat(f, &needsConversion);
135 return !needsConversion;
136};
137
138// Used for the AVCodecContext::get_format callback
139AVPixelFormat getFormat(AVCodecContext *codecContext, const AVPixelFormat *suggestedFormats)
140{
141 // First check HW accelerated codecs, the HW device context must be set
142 if (codecContext->hw_device_ctx) {
143 auto *device_ctx = (AVHWDeviceContext *)codecContext->hw_device_ctx->data;
144 std::pair formatAndScore(AV_PIX_FMT_NONE, NotSuitableAVScore);
145
146 // to be rewritten via findBestAVFormat
147 for (int i = 0;
148 const AVCodecHWConfig *config = avcodec_get_hw_config(codecContext->codec, i); i++) {
149 if (!(config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX))
150 continue;
151
152 if (device_ctx->type != config->device_type)
153 continue;
154
155 const bool isDeprecated = (config->methods & AV_CODEC_HW_CONFIG_METHOD_AD_HOC) != 0;
156 const bool shouldCheckCodecFormats = config->pix_fmt == AV_PIX_FMT_NONE;
157
158 auto scoresGettor = [&](AVPixelFormat format) {
159 if (shouldCheckCodecFormats && !isAVFormatSupported(codecContext->codec, format))
160 return NotSuitableAVScore;
161
162 if (!shouldCheckCodecFormats && config->pix_fmt != format)
163 return NotSuitableAVScore;
164
165 auto result = DefaultAVScore;
166
167 if (isDeprecated)
168 result -= 10000;
170 result += 10;
171
172 return result;
173 };
174
175 const auto found = findBestAVFormat(suggestedFormats, scoresGettor);
176
177 if (found.second > formatAndScore.second)
178 formatAndScore = found;
179 }
180
181 const auto &format = formatAndScore.first;
182 if (format != AV_PIX_FMT_NONE) {
183#if QT_CONFIG(wmf)
184 if (format == AV_PIX_FMT_D3D11)
185 QFFmpeg::D3D11TextureConverter::SetupDecoderTextures(codecContext);
186#endif
187#ifdef Q_OS_ANDROID
188 if (format == AV_PIX_FMT_MEDIACODEC)
190#endif
191 qCDebug(qLHWAccel) << "Selected format" << format << "for hw" << device_ctx->type;
192 return format;
193 }
194 }
195
196 // prefer video formats we can handle directly
197 const auto noConversionFormat = findAVFormat(suggestedFormats, &isNoConversionFormat);
198 if (noConversionFormat != AV_PIX_FMT_NONE) {
199 qCDebug(qLHWAccel) << "Selected format with no conversion" << noConversionFormat;
200 return noConversionFormat;
201 }
202
203 qCDebug(qLHWAccel) << "Selected format with conversion" << *suggestedFormats;
204
205 // take the native format, this will involve one additional format conversion on the CPU side
206 return *suggestedFormats;
207}
208
209TextureConverter::Data::~Data()
210{
211 delete backend;
212}
213
214HWAccel::~HWAccel() = default;
215
216std::unique_ptr<HWAccel> HWAccel::create(AVHWDeviceType deviceType)
217{
218 if (auto *ctx = loadHWContext(deviceType))
219 return std::unique_ptr<HWAccel>(new HWAccel(ctx));
220 else
221 return {};
222}
223
224AVPixelFormat HWAccel::format(AVFrame *frame)
225{
226 if (!frame->hw_frames_ctx)
227 return AVPixelFormat(frame->format);
228
229 auto *hwFramesContext = (AVHWFramesContext *)frame->hw_frames_ctx->data;
231 return AVPixelFormat(hwFramesContext->sw_format);
232}
233
234const std::vector<AVHWDeviceType> &HWAccel::encodingDeviceTypes()
235{
236 static const auto &result = deviceTypes("QT_FFMPEG_ENCODING_HW_DEVICE_TYPES");
237 return result;
238}
239
240const std::vector<AVHWDeviceType> &HWAccel::decodingDeviceTypes()
241{
242 static const auto &result = deviceTypes("QT_FFMPEG_DECODING_HW_DEVICE_TYPES");
243 return result;
244}
245
246AVHWDeviceContext *HWAccel::hwDeviceContext() const
247{
248 return m_hwDeviceContext ? (AVHWDeviceContext *)m_hwDeviceContext->data : nullptr;
249}
250
251AVPixelFormat HWAccel::hwFormat() const
252{
254}
255
257{
259 av_hwdevice_get_hwframe_constraints(hwDeviceContextAsBuffer(), nullptr));
260}
261
262std::pair<const AVCodec *, std::unique_ptr<HWAccel>>
263HWAccel::findEncoderWithHwAccel(AVCodecID id, const std::function<bool(const HWAccel &)>& hwAccelPredicate)
264{
265 auto finder = qOverload<AVCodecID, const std::optional<AVHWDeviceType> &,
266 const std::optional<PixelOrSampleFormat> &>(&QFFmpeg::findAVEncoder);
267 return findCodecWithHwAccel(id, encodingDeviceTypes(), finder, hwAccelPredicate);
268}
269
270std::pair<const AVCodec *, std::unique_ptr<HWAccel>>
271HWAccel::findDecoderWithHwAccel(AVCodecID id, const std::function<bool(const HWAccel &)>& hwAccelPredicate)
272{
274 hwAccelPredicate);
275}
276
277AVHWDeviceType HWAccel::deviceType() const
278{
279 return m_hwDeviceContext ? hwDeviceContext()->type : AV_HWDEVICE_TYPE_NONE;
280}
281
282void HWAccel::createFramesContext(AVPixelFormat swFormat, const QSize &size)
283{
284 if (m_hwFramesContext) {
285 qWarning() << "Frames context has been already created!";
286 return;
287 }
288
289 if (!m_hwDeviceContext)
290 return;
291
292 m_hwFramesContext.reset(av_hwframe_ctx_alloc(m_hwDeviceContext.get()));
293 auto *c = (AVHWFramesContext *)m_hwFramesContext->data;
294 c->format = hwFormat();
295 c->sw_format = swFormat;
296 c->width = size.width();
297 c->height = size.height();
298 qCDebug(qLHWAccel) << "init frames context";
299 int err = av_hwframe_ctx_init(m_hwFramesContext.get());
300 if (err < 0)
301 qWarning() << "failed to init HW frame context" << err << err2str(err);
302 else
303 qCDebug(qLHWAccel) << "Initialized frames context" << size << c->format << c->sw_format;
304}
305
306AVHWFramesContext *HWAccel::hwFramesContext() const
307{
308 return m_hwFramesContext ? (AVHWFramesContext *)m_hwFramesContext->data : nullptr;
309}
310
311
313 : d(new Data)
314{
315 d->rhi = rhi;
316}
317
319{
320 if (!frame || isNull())
321 return nullptr;
322
323 Q_ASSERT(frame->format == d->format);
324 return d->backend->getTextures(frame);
325}
326
327void TextureConverter::updateBackend(AVPixelFormat fmt)
328{
329 d->backend = nullptr;
330 if (!d->rhi)
331 return;
332
333 // HW textures conversions are not stable in specific cases, dependent on the hardware and OS.
334 // We need the env var for testing with no textures conversion on the user's side.
335 static const bool disableConversion =
336 qEnvironmentVariableIsSet("QT_DISABLE_HW_TEXTURES_CONVERSION");
337
338 if (disableConversion)
339 return;
340
341 switch (fmt) {
342#if QT_CONFIG(vaapi)
343 case AV_PIX_FMT_VAAPI:
344 d->backend = new VAAPITextureConverter(d->rhi);
345 break;
346#endif
347#ifdef Q_OS_DARWIN
348 case AV_PIX_FMT_VIDEOTOOLBOX:
349 d->backend = new VideoToolBoxTextureConverter(d->rhi);
350 break;
351#endif
352#if QT_CONFIG(wmf)
353 case AV_PIX_FMT_D3D11:
354 d->backend = new D3D11TextureConverter(d->rhi);
355 break;
356#endif
357#ifdef Q_OS_ANDROID
358 case AV_PIX_FMT_MEDIACODEC:
359 d->backend = new MediaCodecTextureConverter(d->rhi);
360 break;
361#endif
362 default:
363 break;
364 }
365 d->format = fmt;
366}
367
368} // namespace QFFmpeg
369
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
AVHWFramesConstraintsUPtr constraints() 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
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)
\inmodule QtGui
Definition qrhi.h:1767
\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:5857
QString toLower() const &
Definition qstring.h:368
EGLContext ctx
const AVCodec * findAVEncoder(AVCodecID codecId, const std::optional< AVHWDeviceType > &deviceType, const std::optional< PixelOrSampleFormat > &format)
Definition qffmpeg.cpp:276
std::pair< Format, AVScore > findBestAVFormat(const Format *fmts, const CalculateScore &calculateScore)
Definition qffmpeg_p.h:174
std::unique_ptr< AVHWFramesConstraints, AVDeleter< decltype(&av_hwframe_constraints_free), &av_hwframe_constraints_free > > AVHWFramesConstraintsUPtr
Definition qffmpeg_p.h:136
AVPixelFormat getFormat(AVCodecContext *codecContext, const AVPixelFormat *suggestedFormats)
bool isHwPixelFormat(AVPixelFormat format)
Definition qffmpeg.cpp:299
static const std::initializer_list< AVHWDeviceType > preferredHardwareAccelerators
QString err2str(int errnum)
Definition qffmpeg_p.h:56
static bool isNoConversionFormat(AVPixelFormat f)
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
constexpr AVScore DefaultAVScore
Definition qffmpeg_p.h:141
Format findAVFormat(const Format *fmts, const Predicate &predicate)
Definition qffmpeg_p.h:165
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:142
static AVBufferRef * loadHWContext(const AVHWDeviceType type)
AVPixelFormat pixelFormatForHwDevice(AVHWDeviceType deviceType)
Definition qffmpeg.cpp:305
static std::vector< AVHWDeviceType > deviceTypes(const char *envVarName)
Combined button and popup list for selecting options.
EGLConfig config
QMediaFormat::AudioCodec codec
#define qWarning
Definition qlogging.h:162
#define Q_LOGGING_CATEGORY(name,...)
#define qCDebug(category,...)
return ret
GLenum GLuint GLintptr GLsizeiptr size
[1]
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
Q_CORE_EXPORT QByteArray qgetenv(const char *varName)
Q_CORE_EXPORT bool qEnvironmentVariableIsSet(const char *varName) noexcept
QVideoFrameFormat::PixelFormat fmt
static QInputDevice::DeviceType deviceType(const UINT cursorType)
QFrame frame
[0]