3#include <QtCore/QLoggingCategory>
4#include <QtMultimedia/QAbstractVideoBuffer>
8#include <gst/video/video.h>
13#if defined(QGC_HAS_ANY_GPU_PATH)
21class PooledCpuVideoBuffer final :
public QAbstractVideoBuffer
25 : _format(std::move(
format)), _layout(layout), _backing(std::move(backing))
28 ~PooledCpuVideoBuffer()
override
30 if (!_backing.isEmpty()) {
36 MapData map(QVideoFrame::MapMode mode)
override
38 if (mode == QVideoFrame::NotMapped) {
42 data.planeCount = _layout.planeCount;
43 uchar* base =
reinterpret_cast<uchar*
>(_backing.data());
44 for (
int i = 0; i < _layout.planeCount; ++i) {
45 data.data[i] = base + _layout.planeOffset[i];
46 data.bytesPerLine[i] = _layout.bytesPerLine[i];
47 data.dataSize[i] =
static_cast<qsizetype
>(_layout.bytesPerLine[i]) * _layout.height[i];
52 QVideoFrameFormat
format()
const override {
return _format; }
55 QVideoFrameFormat _format;
62class MapThroughGstVideoBuffer final :
public QAbstractVideoBuffer
68 static int maxLive() noexcept
70 static const int s_max = []() ->
int {
71 constexpr int kFloor = 6;
72#if defined(QGC_HAS_ANY_GPU_PATH)
78 return std::clamp(fif * 2, kFloor, 16);
85 static std::atomic<int>& liveCount()
87 static std::atomic<int> s_live{0};
91 MapThroughGstVideoBuffer(GstBuffer* buffer,
const GstVideoInfo& info, QVideoFrameFormat format)
92 : _buffer(gst_buffer_ref(buffer)), _info(info), _format(std::move(
format))
94 liveCount().fetch_add(1, std::memory_order_relaxed);
97 ~MapThroughGstVideoBuffer()
override
100 gst_video_frame_unmap(&_frame);
102 gst_buffer_unref(_buffer);
103 liveCount().fetch_sub(1, std::memory_order_relaxed);
106 MapData map(QVideoFrame::MapMode mode)
override
108 if (mode == QVideoFrame::NotMapped) {
111 if (mode != QVideoFrame::ReadOnly) {
115 if (!gst_video_frame_map(&_frame, &_info, _buffer, GST_MAP_READ)) {
121 data.planeCount = GST_VIDEO_FRAME_N_PLANES(&_frame);
122 for (
int i = 0; i < data.planeCount; ++i) {
123 data.data[i] =
static_cast<uchar*
>(GST_VIDEO_FRAME_PLANE_DATA(&_frame, i));
124 data.bytesPerLine[i] = GST_VIDEO_FRAME_PLANE_STRIDE(&_frame, i);
125 data.dataSize[i] =
static_cast<qsizetype
>(GST_VIDEO_FRAME_PLANE_STRIDE(&_frame, i)) *
126 GST_VIDEO_FRAME_COMP_HEIGHT(&_frame, i);
131 void unmap()
override
134 gst_video_frame_unmap(&_frame);
139 QVideoFrameFormat
format()
const override {
return _format; }
144 QVideoFrameFormat _format;
145 GstVideoFrame _frame = {};
146 bool _mapped =
false;
160 const int n = GST_VIDEO_INFO_N_PLANES(&info);
161 if (n <= 0 || n > 4) {
165 for (
int i = 0; i < n; ++i) {
166 L.
bytesPerLine[i] = GST_VIDEO_INFO_PLANE_STRIDE(&info, i);
167 L.
planeOffset[i] =
static_cast<qsizetype
>(GST_VIDEO_INFO_PLANE_OFFSET(&info, i));
168 L.
height[i] =
static_cast<int>(GST_VIDEO_INFO_COMP_HEIGHT(&info, i));
170 L.
byteSize =
static_cast<qsizetype
>(GST_VIDEO_INFO_SIZE(&info));
174QByteArray CpuVideoFramePool::acquireBacking(
const PlaneLayout& layout, QSize size,
175 QVideoFrameFormat::PixelFormat format)
177 QMutexLocker lock(&_mutex);
178 for (std::size_t i = 0; i < _slots.size(); ++i) {
179 auto& slot = _slots[i];
180 if (slot.size == size && slot.format == format) {
183 while (slot.availableCount > 0) {
184 QByteArray b = std::move(slot.available[--slot.availableCount]);
185 if (b.size() == layout.byteSize) {
186 _hits.fetch_add(1, std::memory_order_relaxed);
193 _misses.fetch_add(1, std::memory_order_relaxed);
195 return QByteArray(layout.byteSize, Qt::Uninitialized);
200 QMutexLocker lock(&_mutex);
201 for (std::size_t i = 0; i < _slots.size(); ++i) {
202 auto& slot = _slots[i];
203 if (slot.size == size && slot.format == format) {
204 if (slot.availableCount < kMaxPerSlot) {
205 slot.available[slot.availableCount++] = std::move(backing);
211 Slot* target =
nullptr;
212 for (std::size_t i = 0; i < _slots.size(); ++i) {
213 auto& slot = _slots[i];
214 if (slot.format == QVideoFrameFormat::Format_Invalid) {
220 target = &_slots[_evictCursor];
221 _evictCursor = (_evictCursor + 1) % kMaxSlots;
225 target->format = format;
226 target->available[0] = std::move(backing);
227 target->availableCount = 1;
236 QByteArray backing = acquireBacking(layout, format.frameSize(), format.pixelFormat());
237 auto buffer = std::make_unique<PooledCpuVideoBuffer>(format, layout, std::move(backing));
238 return QVideoFrame(std::move(buffer));
242 const QVideoFrameFormat& format)
244 if (!buffer || !format.isValid()) {
247 if (MapThroughGstVideoBuffer::liveCount().load(std::memory_order_relaxed) >= MapThroughGstVideoBuffer::maxLive()) {
250 auto buf = std::make_unique<MapThroughGstVideoBuffer>(buffer, info, format);
253 const auto probe = buf->map(QVideoFrame::ReadOnly);
254 if (probe.planeCount <= 0) {
261 const QVideoFrameFormat& format)
263 if (!buffer || !format.isValid()) {
267 GstVideoFrame gstFrame;
268 if (!gst_video_frame_map(&gstFrame, &videoInfo, buffer, GST_MAP_READ)) {
269 static std::atomic<quint64> s_failCount{0};
270 const quint64 count = s_failCount.fetch_add(1, std::memory_order_relaxed) + 1;
271 if ((count & 0x3F) == 1) {
272 qCWarning(CpuVideoFramePoolLog) <<
"copyFromBuffer: gst_video_frame_map failed (count=" << count <<
")";
278 if (!videoFrame.isValid() || !videoFrame.map(QVideoFrame::WriteOnly)) {
279 gst_video_frame_unmap(&gstFrame);
283 const int srcPlanes = GST_VIDEO_INFO_N_PLANES(&videoInfo);
284 if (srcPlanes != videoFrame.planeCount()) {
285 static std::atomic<bool> s_warnedPlaneMismatch{
false};
286 if (!s_warnedPlaneMismatch.exchange(
true, std::memory_order_relaxed)) {
287 qCWarning(CpuVideoFramePoolLog) <<
"copyFromBuffer: plane-count mismatch src" << srcPlanes <<
"dst"
288 << videoFrame.planeCount() <<
"— dropping frame";
291 gst_video_frame_unmap(&gstFrame);
295 for (
int p = 0; p < srcPlanes; ++p) {
296 const int dstStride = videoFrame.bytesPerLine(p);
297 const int srcStride = GST_VIDEO_FRAME_PLANE_STRIDE(&gstFrame, p);
298 const int planeHeight = GST_VIDEO_FRAME_COMP_HEIGHT(&gstFrame, p);
299 const int activeRowBytes =
300 GST_VIDEO_FRAME_COMP_WIDTH(&gstFrame, p) * GST_VIDEO_FRAME_COMP_PSTRIDE(&gstFrame, p);
302 if (activeRowBytes > dstStride) {
303 static std::atomic<bool> s_warnedStrideOverflow{
false};
304 if (!s_warnedStrideOverflow.exchange(
true, std::memory_order_relaxed)) {
305 qCWarning(CpuVideoFramePoolLog) <<
"copyFromBuffer: plane" << p <<
"activeRowBytes" << activeRowBytes
306 <<
"> dstStride" << dstStride <<
"— dropping frame";
309 gst_video_frame_unmap(&gstFrame);
312 const uchar* src =
static_cast<const uchar*
>(GST_VIDEO_FRAME_PLANE_DATA(&gstFrame, p));
313 uchar* dst = videoFrame.bits(p);
317 if (srcStride == dstStride && activeRowBytes == srcStride) {
318 std::memcpy(dst, src,
static_cast<size_t>(planeHeight) * srcStride);
320 for (
int y = 0; y < planeHeight; ++y) {
321 std::memcpy(dst + y * dstStride, src + y * srcStride,
static_cast<size_t>(activeRowBytes));
327 gst_video_frame_unmap(&gstFrame);
#define QGC_LOGGING_CATEGORY(name, categoryStr)
Recycles CPU-backed QVideoFrame storage keyed by (size, pixelFormat) to avoid per-frame malloc churn.
static CpuVideoFramePool & instance()
static QVideoFrame copyFromBuffer(GstBuffer *buffer, const GstVideoInfo &videoInfo, const QVideoFrameFormat &format)
Copy buffer's planes into a pool-allocated QVideoFrame; invalid on stride overflow or unsupported for...
void releaseBacking(QSize size, QVideoFrameFormat::PixelFormat format, QByteArray &&backing)
Return a backing array to the pool; called by PooledCpuVideoBuffer's destructor only.
static PlaneLayout computeLayout(const GstVideoInfo &info)
Destination plane layout mirroring info (strides/offsets from the decoder). planeCount==0 means unsup...
QVideoFrame acquireFrame(const QVideoFrameFormat &format, const GstVideoInfo &info)
Returns a pool-backed frame sized to info, or freshly allocates on a pool miss; invalid if unsupporte...
static std::unique_ptr< QAbstractVideoBuffer > wrapZeroCopy(GstBuffer *buffer, const GstVideoInfo &info, const QVideoFrameFormat &format)
DeviceSnapshot & deviceSnapshot() noexcept
Returns the global snapshot. Atomic fields make individual reads thread-safe.
std::atomic< int > framesInFlight
QRhi::FramesInFlight resource limit (0 = unset)