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
qffmpegvideobuffer.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#include "private/qvideotexturehelper_p.h"
6#include "private/qmultimediautils_p.h"
9#include <QtCore/qthread.h>
10
11extern "C" {
12#include <libavutil/pixdesc.h>
13#include <libavutil/hdr_dynamic_metadata.h>
14#include <libavutil/mastering_display_metadata.h>
15}
16
17QT_BEGIN_NAMESPACE
18
19using namespace QFFmpeg;
20
21Q_STATIC_LOGGING_CATEGORY(qLcFFmpegVideoBuffer, "qt.multimedia.ffmpeg.videobuffer");
22
23static bool isFrameFlipped(const AVFrame& frame) {
24 for (int i = 0; i < AV_NUM_DATA_POINTERS && frame.data[i]; ++i) {
25 if (frame.linesize[i] < 0)
26 return true;
27 }
28
29 return false;
30}
31
32QFFmpegVideoBuffer::QFFmpegVideoBuffer(AVFrameUPtr frame, AVRational pixelAspectRatio)
34 m_frame(frame.get()),
37{
38 if (frame->hw_frames_ctx) {
39 m_hwFrame = std::move(frame);
40 m_pixelFormat = toQtPixelFormat(HWAccel::format(m_hwFrame.get()));
41 return;
42 }
43
44 m_swFrame = std::move(frame);
45 m_pixelFormat = toQtPixelFormat(AVPixelFormat(m_swFrame->format));
46
48}
49
51
53{
54 Q_ASSERT(m_swFrame);
55
56 const auto actualAVPixelFormat = AVPixelFormat(m_swFrame->format);
57 const auto targetAVPixelFormat = toAVPixelFormat(m_pixelFormat);
58
59 const QSize actualSize(m_swFrame->width, m_swFrame->height);
60 if (actualAVPixelFormat != targetAVPixelFormat || isFrameFlipped(*m_swFrame)
61 || m_size != actualSize) {
62 Q_ASSERT(toQtPixelFormat(targetAVPixelFormat) == m_pixelFormat);
63 // convert the format into something we can handle
64 SwsContextUPtr scaleContext = createSwsContext(actualSize, actualAVPixelFormat, m_size,
65 targetAVPixelFormat, SWS_BICUBIC);
66
67 auto newFrame = makeAVFrame();
68 newFrame->width = m_size.width();
69 newFrame->height = m_size.height();
70 newFrame->format = targetAVPixelFormat;
71 const int status = av_frame_get_buffer(newFrame.get(), 0);
72 if (status < 0) {
73 qCWarning(qLcFFmpegVideoBuffer)
74 << "Failed to allocate frame buffer:" << AVError(status);
75 return;
76 }
77
78 sws_scale(scaleContext.get(), m_swFrame->data, m_swFrame->linesize, 0, m_swFrame->height,
79 newFrame->data, newFrame->linesize);
80 if (m_frame == m_swFrame.get())
81 m_frame = newFrame.get();
82 m_swFrame = std::move(newFrame);
83 }
84}
85
86void QFFmpegVideoBuffer::initTextureConverter(QRhi &rhi)
87{
88 if (!m_hwFrame)
89 return;
90
91 // don't use the result reference here
92 ensureTextureConverter(rhi);
93
94 // the type is to be clarified in the method mapTextures
95 m_type = m_hwFrame && TextureConverter::isBackendAvailable(*m_hwFrame, rhi)
96 ? QVideoFrame::RhiTextureHandle
97 : QVideoFrame::NoHandle;
98}
99
100QFFmpeg::TextureConverter &QFFmpegVideoBuffer::ensureTextureConverter(QRhi &rhi)
101{
102 Q_ASSERT(m_hwFrame);
103
104 HwFrameContextData &frameContextData = HwFrameContextData::ensure(*m_hwFrame);
105 TextureConverter *converter = frameContextData.textureConverterMapper.get(&rhi);
106
107 if (!converter) {
108 bool added = false;
109 std::tie(converter, added) =
110 frameContextData.textureConverterMapper.tryMap(rhi, TextureConverter(rhi));
111 // no issues are expected if it's already added in another thread, however,it's worth to
112 // check it
113 Q_ASSERT(converter && added);
114 }
115
116 return *converter;
117}
118
120{
121 if (!m_hwFrame)
122 return nullptr;
123
124 HwFrameContextData &frameContextData = HwFrameContextData::ensure(*m_hwFrame);
125 return frameContextData.textureConverterMapper.findRhi(
126 [](QRhi &rhi) { return rhi.thread()->isCurrentThread(); });
127}
128
130{
131 return fromAvColorSpace(m_frame->colorspace);
132}
133
135{
136 return fromAvColorTransfer(m_frame->color_trc);
137}
138
140{
141 return fromAvColorRange(m_frame->color_range);
142}
143
145{
146 float maxNits = -1;
147 for (int i = 0; i < m_frame->nb_side_data; ++i) {
148 AVFrameSideData *sd = m_frame->side_data[i];
149 // TODO: Longer term we might want to also support HDR10+ dynamic metadata
150 if (sd->type == AV_FRAME_DATA_MASTERING_DISPLAY_METADATA) {
151 auto *data = reinterpret_cast<AVMasteringDisplayMetadata *>(sd->data);
152 auto maybeLum = QFFmpeg::mul(qreal(10'000.), data->max_luminance);
153 if (maybeLum)
154 maxNits = float(maybeLum.value());
155 }
156 }
157 return maxNits;
158}
159
161{
162 if (!m_swFrame) {
163 Q_ASSERT(m_hwFrame && m_hwFrame->hw_frames_ctx);
164 m_swFrame = makeAVFrame();
165 /* retrieve data from GPU to CPU */
166 int ret = av_hwframe_transfer_data(m_swFrame.get(), m_hwFrame.get(), 0);
167 if (ret < 0) {
168 qWarning() << "Error transferring the data to system memory:" << ret;
169 return {};
170 }
172 }
173
174 m_mode = mode;
175
176 MapData mapData;
177 auto *desc = QVideoTextureHelper::textureDescription(pixelFormat());
178 mapData.planeCount = desc->nplanes;
179 for (int i = 0; i < mapData.planeCount; ++i) {
180 Q_ASSERT(m_swFrame->linesize[i] >= 0);
181
182 mapData.data[i] = m_swFrame->data[i];
183 mapData.bytesPerLine[i] = m_swFrame->linesize[i];
184 mapData.dataSize[i] = mapData.bytesPerLine[i]*desc->heightForPlane(m_swFrame->height, i);
185 }
186
187 if ((mode & QVideoFrame::WriteOnly) != 0 && m_hwFrame) {
188 m_type = QVideoFrame::NoHandle;
189 m_hwFrame.reset();
190 }
191
192 return mapData;
193}
194
196{
197 // nothing to do here for SW buffers.
198 // Set NotMapped mode to ensure map/unmap/mapMode consisteny.
199 m_mode = QVideoFrame::NotMapped;
200}
201
202QVideoFrameTexturesUPtr QFFmpegVideoBuffer::mapTextures(QRhi &rhi, QVideoFrameTexturesUPtr& oldTextures)
203{
204 Q_ASSERT(rhi.thread()->isCurrentThread());
205
206 QVideoFrameTexturesUPtr result = createTexturesFromHwFrame(rhi, oldTextures);
207
208 // update m_type according to the real result
209 m_type = result ? QVideoFrame::RhiTextureHandle : QVideoFrame::NoHandle;
210 return result;
211}
212
213QVideoFrameTexturesUPtr QFFmpegVideoBuffer::createTexturesFromHwFrame(QRhi &rhi, QVideoFrameTexturesUPtr& oldTextures) {
214
215 if (!m_hwFrame)
216 return {};
217
218 // QTBUG-132200:
219 // We aim to set initTextureConverterForAnyRhi=true for as much platforms as we can,
220 // and remove the check after all platforms work fine on CI. If the flag is enabled,
221 // QVideoFrame::toImage can work faster, and we can test hw texture conversion on CI.
222 // Currently, enabling the flag fails some CI platforms.
223 constexpr bool initTextureConverterForAnyRhi = false;
224
225 TextureConverter *converter = initTextureConverterForAnyRhi
226 ? &ensureTextureConverter(rhi)
227 : HwFrameContextData::ensure(*m_hwFrame).textureConverterMapper.get(&rhi);
228
229 if (!converter)
230 return {};
231
232 if (!converter->init(*m_hwFrame))
233 return {};
234
235 const QVideoFrameTextures *oldTexturesRaw = oldTextures.get();
236 if (QVideoFrameTexturesUPtr newTextures = converter->createTextures(*m_hwFrame, oldTextures))
237 return newTextures;
238
239 Q_ASSERT(oldTextures.get() == oldTexturesRaw);
240
241 QVideoFrameTexturesHandlesUPtr oldTextureHandles =
242 oldTextures ? oldTextures->takeHandles() : nullptr;
243 QVideoFrameTexturesHandlesUPtr newTextureHandles =
244 converter->createTextureHandles(*m_hwFrame, std::move(oldTextureHandles));
245
246 if (newTextureHandles) {
247 QVideoFrameTexturesUPtr newTextures = QVideoTextureHelper::createTexturesFromHandles(
248 std::move(newTextureHandles), rhi, m_pixelFormat,
249 { m_hwFrame->width, m_hwFrame->height });
250
251 return newTextures;
252 }
253
254 static thread_local int lastFormat = 0;
255 if (std::exchange(lastFormat, m_hwFrame->format) != m_hwFrame->format) // prevent logging spam
256 qWarning() << " failed to get textures for frame; format:" << m_hwFrame->format;
257
258 return {};
259}
260
262{
263 return m_pixelFormat;
264}
265
267{
268 return m_size;
269}
270
271QVideoFrameFormat::PixelFormat QFFmpegVideoBuffer::toQtPixelFormat(AVPixelFormat avPixelFormat, bool *needsConversion)
272{
273 if (needsConversion)
274 *needsConversion = false;
275
276 switch (avPixelFormat) {
277 default:
278 break;
279 case AV_PIX_FMT_NONE:
280 Q_ASSERT(!"Invalid avPixelFormat!");
281 return QVideoFrameFormat::Format_Invalid;
282 case AV_PIX_FMT_ARGB:
283 return QVideoFrameFormat::Format_ARGB8888;
284 case AV_PIX_FMT_0RGB:
285 return QVideoFrameFormat::Format_XRGB8888;
286 case AV_PIX_FMT_BGRA:
287 return QVideoFrameFormat::Format_BGRA8888;
288 case AV_PIX_FMT_BGR0:
289 return QVideoFrameFormat::Format_BGRX8888;
290 case AV_PIX_FMT_ABGR:
291 return QVideoFrameFormat::Format_ABGR8888;
292 case AV_PIX_FMT_0BGR:
293 return QVideoFrameFormat::Format_XBGR8888;
294 case AV_PIX_FMT_RGBA:
295 return QVideoFrameFormat::Format_RGBA8888;
296 case AV_PIX_FMT_RGB0:
297 return QVideoFrameFormat::Format_RGBX8888;
298
299 case AV_PIX_FMT_YUV422P:
300 return QVideoFrameFormat::Format_YUV422P;
301 case AV_PIX_FMT_YUV420P:
302 return QVideoFrameFormat::Format_YUV420P;
303 case AV_PIX_FMT_YUV420P10:
304 return QVideoFrameFormat::Format_YUV420P10;
305 case AV_PIX_FMT_UYVY422:
306 return QVideoFrameFormat::Format_UYVY;
307 case AV_PIX_FMT_YUYV422:
308 return QVideoFrameFormat::Format_YUYV;
309 case AV_PIX_FMT_NV12:
310 return QVideoFrameFormat::Format_NV12;
311 case AV_PIX_FMT_NV21:
312 return QVideoFrameFormat::Format_NV21;
313 case AV_PIX_FMT_GRAY8:
314 return QVideoFrameFormat::Format_Y8;
315 case AV_PIX_FMT_GRAY16:
316 return QVideoFrameFormat::Format_Y16;
317
318 case AV_PIX_FMT_P010:
319 return QVideoFrameFormat::Format_P010;
320 case AV_PIX_FMT_P016:
321 return QVideoFrameFormat::Format_P016;
322 case AV_PIX_FMT_MEDIACODEC:
323 return QVideoFrameFormat::Format_SamplerExternalOES;
324 }
325
326 if (needsConversion)
327 *needsConversion = true;
328
329 const AVPixFmtDescriptor *descriptor = av_pix_fmt_desc_get(avPixelFormat);
330
331 if (descriptor->flags & AV_PIX_FMT_FLAG_RGB)
332 return QVideoFrameFormat::Format_RGBA8888;
333
334 if (descriptor->comp[0].depth > 8)
335 return QVideoFrameFormat::Format_P016;
336 return QVideoFrameFormat::Format_YUV420P;
337}
338
339AVPixelFormat QFFmpegVideoBuffer::toAVPixelFormat(QVideoFrameFormat::PixelFormat pixelFormat)
340{
341 switch (pixelFormat) {
342 default:
343 case QVideoFrameFormat::Format_Invalid:
344 case QVideoFrameFormat::Format_AYUV:
345 case QVideoFrameFormat::Format_AYUV_Premultiplied:
346 case QVideoFrameFormat::Format_YV12:
347 case QVideoFrameFormat::Format_IMC1:
348 case QVideoFrameFormat::Format_IMC2:
349 case QVideoFrameFormat::Format_IMC3:
350 case QVideoFrameFormat::Format_IMC4:
351 return AV_PIX_FMT_NONE;
352 case QVideoFrameFormat::Format_Jpeg:
353 // We're using the data from the converted QImage here, which is in BGRA.
354 return AV_PIX_FMT_BGRA;
355 case QVideoFrameFormat::Format_ARGB8888:
356 return AV_PIX_FMT_ARGB;
357 case QVideoFrameFormat::Format_ARGB8888_Premultiplied:
358 case QVideoFrameFormat::Format_XRGB8888:
359 return AV_PIX_FMT_0RGB;
360 case QVideoFrameFormat::Format_BGRA8888:
361 return AV_PIX_FMT_BGRA;
362 case QVideoFrameFormat::Format_BGRA8888_Premultiplied:
363 case QVideoFrameFormat::Format_BGRX8888:
364 return AV_PIX_FMT_BGR0;
365 case QVideoFrameFormat::Format_ABGR8888:
366 return AV_PIX_FMT_ABGR;
367 case QVideoFrameFormat::Format_XBGR8888:
368 return AV_PIX_FMT_0BGR;
369 case QVideoFrameFormat::Format_RGBA8888:
370 return AV_PIX_FMT_RGBA;
371 // to be added in 6.8:
372 // case QVideoFrameFormat::Format_RGBA8888_Premultiplied:
373 case QVideoFrameFormat::Format_RGBX8888:
374 return AV_PIX_FMT_RGB0;
375
376 case QVideoFrameFormat::Format_YUV422P:
377 return AV_PIX_FMT_YUV422P;
378 case QVideoFrameFormat::Format_YUV420P:
379 return AV_PIX_FMT_YUV420P;
380 case QVideoFrameFormat::Format_YUV420P10:
381 return AV_PIX_FMT_YUV420P10;
382 case QVideoFrameFormat::Format_UYVY:
383 return AV_PIX_FMT_UYVY422;
384 case QVideoFrameFormat::Format_YUYV:
385 return AV_PIX_FMT_YUYV422;
386 case QVideoFrameFormat::Format_NV12:
387 return AV_PIX_FMT_NV12;
388 case QVideoFrameFormat::Format_NV21:
389 return AV_PIX_FMT_NV21;
390 case QVideoFrameFormat::Format_Y8:
391 return AV_PIX_FMT_GRAY8;
392 case QVideoFrameFormat::Format_Y16:
393 return AV_PIX_FMT_GRAY16;
394
395 case QVideoFrameFormat::Format_P010:
396 return AV_PIX_FMT_P010;
397 case QVideoFrameFormat::Format_P016:
398 return AV_PIX_FMT_P016;
399
400 case QVideoFrameFormat::Format_SamplerExternalOES:
401 return AV_PIX_FMT_MEDIACODEC;
402 }
403}
404
405QT_END_NAMESPACE
QVideoFrameFormat::PixelFormat pixelFormat() const
QVideoFrameFormat::ColorSpace colorSpace() const
void unmap() override
Releases the memory mapped by the map() function.
MapData map(QVideoFrame::MapMode mode) override
Maps the planes of a video buffer to memory.
QVideoFrameFormat::ColorTransfer colorTransfer() const
QVideoFrameFormat::ColorRange colorRange() const
QFFmpegVideoBuffer(AVFrameUPtr frame, AVRational pixelAspectRatio={ 1, 1 })
~QFFmpegVideoBuffer() override
QRhi * associatedCurrentThreadRhi() const override
static bool isFrameFlipped(const AVFrame &frame)
#define qCWarning(category,...)
#define Q_STATIC_LOGGING_CATEGORY(name,...)