6#include <QtCore/QMetaObject>
7#include <QtCore/QMutexLocker>
10#include <QtMultimedia/QVideoFrame>
11#include <QtMultimedia/QVideoFrameFormat>
12#include <QtMultimedia/QVideoSink>
14#include <gst/app/gstappsink.h>
15#include <gst/video/gstvideometa.h>
16#include <gst/video/video-hdr.h>
17#include <gst/video/video-info.h>
21#include <gst/video/video.h>
22#if GST_CHECK_VERSION(1, 24, 0)
23# include <gst/video/video-info-dma.h>
26#if defined(QGC_HAS_GST_DMABUF_GPU_PATH)
28#include <QtGui/QGuiApplication>
29#include <qpa/qplatformnativeinterface.h>
32#if defined(QGC_HAS_GST_GLMEMORY_GPU_PATH)
35#if defined(QGC_HAS_GST_D3D11_GPU_PATH)
38#if defined(QGC_HAS_GST_D3D12_GPU_PATH)
41#if defined(QGC_HAS_GST_IOSURFACE_GPU_PATH)
44#if defined(QGC_HAS_GST_AHARDWAREBUFFER_GPU_PATH)
54 case GST_VIDEO_COLOR_MATRIX_BT601:
return QVideoFrameFormat::ColorSpace_BT601;
55 case GST_VIDEO_COLOR_MATRIX_BT709:
return QVideoFrameFormat::ColorSpace_BT709;
56 case GST_VIDEO_COLOR_MATRIX_BT2020:
return QVideoFrameFormat::ColorSpace_BT2020;
57 case GST_VIDEO_COLOR_MATRIX_SMPTE240M:
return QVideoFrameFormat::ColorSpace_BT709;
58 case GST_VIDEO_COLOR_MATRIX_FCC:
return QVideoFrameFormat::ColorSpace_BT601;
59 default:
return QVideoFrameFormat::ColorSpace_Undefined;
68 case GST_VIDEO_TRANSFER_BT601:
return QVideoFrameFormat::ColorTransfer_BT601;
69 case GST_VIDEO_TRANSFER_BT2020_10:
70 case GST_VIDEO_TRANSFER_BT2020_12:
71 case GST_VIDEO_TRANSFER_BT709:
return QVideoFrameFormat::ColorTransfer_BT709;
72 case GST_VIDEO_TRANSFER_GAMMA20:
return QVideoFrameFormat::ColorTransfer_BT709;
73 case GST_VIDEO_TRANSFER_SMPTE240M:
return QVideoFrameFormat::ColorTransfer_BT709;
74 case GST_VIDEO_TRANSFER_GAMMA22:
75 case GST_VIDEO_TRANSFER_SRGB:
76 case GST_VIDEO_TRANSFER_ADOBERGB:
return QVideoFrameFormat::ColorTransfer_Gamma22;
77 case GST_VIDEO_TRANSFER_GAMMA18:
return QVideoFrameFormat::ColorTransfer_Gamma22;
78 case GST_VIDEO_TRANSFER_GAMMA28:
return QVideoFrameFormat::ColorTransfer_Gamma28;
79 case GST_VIDEO_TRANSFER_GAMMA10:
return QVideoFrameFormat::ColorTransfer_Linear;
80 case GST_VIDEO_TRANSFER_SMPTE2084:
return QVideoFrameFormat::ColorTransfer_ST2084;
81 case GST_VIDEO_TRANSFER_ARIB_STD_B67:
return QVideoFrameFormat::ColorTransfer_STD_B67;
83 default:
return QVideoFrameFormat::ColorTransfer_Unknown;
90 case GST_VIDEO_FORMAT_BGRA:
return QVideoFrameFormat::Format_BGRA8888;
91 case GST_VIDEO_FORMAT_RGBA:
return QVideoFrameFormat::Format_RGBA8888;
92 case GST_VIDEO_FORMAT_BGRx:
return QVideoFrameFormat::Format_BGRX8888;
93 case GST_VIDEO_FORMAT_RGBx:
return QVideoFrameFormat::Format_RGBX8888;
95 case GST_VIDEO_FORMAT_BGR:
96 case GST_VIDEO_FORMAT_RGB:
return QVideoFrameFormat::Format_Invalid;
97 case GST_VIDEO_FORMAT_ARGB:
return QVideoFrameFormat::Format_ARGB8888;
98 case GST_VIDEO_FORMAT_xRGB:
return QVideoFrameFormat::Format_XRGB8888;
99 case GST_VIDEO_FORMAT_NV12:
return QVideoFrameFormat::Format_NV12;
100 case GST_VIDEO_FORMAT_NV21:
return QVideoFrameFormat::Format_NV21;
101 case GST_VIDEO_FORMAT_I420:
return QVideoFrameFormat::Format_YUV420P;
102 case GST_VIDEO_FORMAT_Y42B:
return QVideoFrameFormat::Format_YUV422P;
103 case GST_VIDEO_FORMAT_YV12:
return QVideoFrameFormat::Format_YV12;
104 case GST_VIDEO_FORMAT_I420_10LE:
return QVideoFrameFormat::Format_YUV420P10;
105 case GST_VIDEO_FORMAT_P010_10LE:
return QVideoFrameFormat::Format_P010;
106 case GST_VIDEO_FORMAT_P016_LE:
return QVideoFrameFormat::Format_P016;
107 case GST_VIDEO_FORMAT_AYUV:
return QVideoFrameFormat::Format_AYUV;
108 case GST_VIDEO_FORMAT_YUY2:
return QVideoFrameFormat::Format_YUYV;
109 case GST_VIDEO_FORMAT_UYVY:
return QVideoFrameFormat::Format_UYVY;
110 case GST_VIDEO_FORMAT_GRAY8:
return QVideoFrameFormat::Format_Y8;
111 case GST_VIDEO_FORMAT_GRAY16_LE:
return QVideoFrameFormat::Format_Y16;
112 default:
return QVideoFrameFormat::Format_Invalid;
118QVideoFrameFormat::ColorRange toQtColorRange(GstVideoColorRange range)
121 case GST_VIDEO_COLOR_RANGE_0_255:
return QVideoFrameFormat::ColorRange_Full;
122 case GST_VIDEO_COLOR_RANGE_16_235:
return QVideoFrameFormat::ColorRange_Video;
123 default:
return QVideoFrameFormat::ColorRange_Unknown;
134 case GST_VIDEO_ORIENTATION_IDENTITY:
135 frame.setRotation(QtVideo::Rotation::None);
136 frame.setMirrored(
false);
138 case GST_VIDEO_ORIENTATION_90R:
139 frame.setRotation(QtVideo::Rotation::Clockwise90);
140 frame.setMirrored(
false);
142 case GST_VIDEO_ORIENTATION_180:
143 frame.setRotation(QtVideo::Rotation::Clockwise180);
144 frame.setMirrored(
false);
146 case GST_VIDEO_ORIENTATION_90L:
147 frame.setRotation(QtVideo::Rotation::Clockwise270);
148 frame.setMirrored(
false);
150 case GST_VIDEO_ORIENTATION_HORIZ:
151 frame.setRotation(QtVideo::Rotation::None);
152 frame.setMirrored(
true);
154 case GST_VIDEO_ORIENTATION_VERT:
155 frame.setRotation(QtVideo::Rotation::Clockwise180);
156 frame.setMirrored(
true);
158 case GST_VIDEO_ORIENTATION_UL_LR:
159 frame.setRotation(QtVideo::Rotation::Clockwise90);
160 frame.setMirrored(
true);
162 case GST_VIDEO_ORIENTATION_UR_LL:
163 frame.setRotation(QtVideo::Rotation::Clockwise270);
164 frame.setMirrored(
true);
167 static bool s_warnedUnhandled =
false;
168 if (!s_warnedUnhandled) {
169 s_warnedUnhandled =
true;
170 qCWarning(GstAppSinkAdapterLog) <<
"Unhandled GstVideoOrientationMethod" << method <<
"— treating as identity";
172 frame.setRotation(QtVideo::Rotation::None);
173 frame.setMirrored(
false);
180void applyColorimetry(QVideoFrameFormat &format,
const GstVideoInfo &info, GstCaps *caps)
182 const GstVideoColorimetry &colorimetry = GST_VIDEO_INFO_COLORIMETRY(&info);
183 QVideoFrameFormat::ColorSpace colorSpace =
toQtColorSpace(colorimetry.matrix);
185 if (colorSpace == QVideoFrameFormat::ColorSpace_Undefined) {
186 const int height = GST_VIDEO_INFO_HEIGHT(&info);
188 colorSpace = (height <= 720) ? QVideoFrameFormat::ColorSpace_BT601
189 : QVideoFrameFormat::ColorSpace_BT709;
192 format.setColorSpace(colorSpace);
194 QVideoFrameFormat::ColorRange range = toQtColorRange(colorimetry.range);
196 if (range == QVideoFrameFormat::ColorRange_Unknown
197 && colorimetry.matrix != GST_VIDEO_COLOR_MATRIX_RGB) {
198 range = QVideoFrameFormat::ColorRange_Video;
200 format.setColorRange(range);
203 GstVideoContentLightLevel cll;
204 bool clipApplied =
false;
205 if (caps && gst_video_content_light_level_from_caps(&cll, caps)
206 && cll.max_content_light_level > 0) {
207 format.setMaxLuminance(
static_cast<float>(cll.max_content_light_level));
211 GstVideoMasteringDisplayInfo masteringInfo;
212 if (caps && gst_video_mastering_display_info_from_caps(&masteringInfo, caps)) {
214 const double maxLuminance =
static_cast<double>(masteringInfo.max_display_mastering_luminance) / 10000.0;
215 if (maxLuminance > 0.0) {
216 format.setMaxLuminance(
static_cast<float>(maxLuminance));
224void applyOrientationAndTiming(QVideoFrame &frame, [[maybe_unused]] GstBuffer *buffer,
225 int streamOrientation)
230#ifdef QGC_HAS_GST_VIDEO_ORIENTATION_META
231 if (GstVideoOrientationMeta *meta = gst_buffer_get_video_orientation_meta(buffer)) {
235 if (streamOrientation !=
static_cast<int>(GST_VIDEO_ORIENTATION_IDENTITY)) {
238 if (GST_BUFFER_PTS_IS_VALID(buffer)) {
240 frame.setStartTime(GST_BUFFER_PTS(buffer) / GST_USECOND);
241 if (GST_BUFFER_DURATION_IS_VALID(buffer)) {
242 frame.setEndTime((GST_BUFFER_PTS(buffer) + GST_BUFFER_DURATION(buffer)) / GST_USECOND);
247void pushFrameQueued(QPointer<QVideoSink> sink, QVideoFrame &&frame)
252 QMetaObject::invokeMethod(sink.data(), [sink, f = std::move(frame)]() {
253 if (sink) sink->setVideoFrame(f);
254 }, Qt::AutoConnection);
264 if (GstVideoCropMeta *crop = gst_buffer_get_video_crop_meta(buffer)) {
265 format.setViewport(QRect(crop->x, crop->y, crop->width, crop->height));
270void GstAppSinkAdapter::_logFrameStats()
const
272 const quint64 cpu = _cpuFrames.load(std::memory_order_relaxed);
273 quint64 totalThisCall = cpu;
274 QString s = QStringLiteral(
"CPU:%1").arg(cpu);
275#if defined(QGC_HAS_GST_DMABUF_GPU_PATH)
276 const quint64 dma = _gpuFrames.load(std::memory_order_relaxed);
277 s += QStringLiteral(
" DMABuf:%1/%2").arg(dma).arg(GstDmaBufVideoBuffer::peekMapFailureCount());
278 totalThisCall += dma;
280#if defined(QGC_HAS_GST_GLMEMORY_GPU_PATH)
281 const quint64 gl = _glFrames.load(std::memory_order_relaxed);
282 s += QStringLiteral(
" GL:%1/%2").arg(gl).arg(GstGlVideoBuffer::peekMapFailureCount());
285#if defined(QGC_HAS_GST_D3D11_GPU_PATH)
286 const quint64 d3d = _d3d11Frames.load(std::memory_order_relaxed);
287 s += QStringLiteral(
" D3D11:%1/%2").arg(d3d).arg(GstD3D11VideoBuffer::peekMapFailureCount());
288 totalThisCall += d3d;
290#if defined(QGC_HAS_GST_D3D12_GPU_PATH)
291 const quint64 d3d12 = _d3d12Frames.load(std::memory_order_relaxed);
292 s += QStringLiteral(
" D3D12:%1/%2").arg(d3d12).arg(GstD3D12VideoBuffer::peekMapFailureCount());
293 totalThisCall += d3d12;
295#if defined(QGC_HAS_GST_IOSURFACE_GPU_PATH)
296 const quint64 ios = _iosurfaceFrames.load(std::memory_order_relaxed);
297 s += QStringLiteral(
" IOSurface:%1/%2").arg(ios).arg(GstIOSurfaceVideoBuffer::peekMapFailureCount());
298 totalThisCall += ios;
300#if defined(QGC_HAS_GST_AHARDWAREBUFFER_GPU_PATH)
301 const quint64 ahwb = _ahwbFrames.load(std::memory_order_relaxed);
302 s += QStringLiteral(
" AHWBuf:%1/%2").arg(ahwb).arg(GstAHardwareBufferVideoBuffer::peekMapFailureCount());
303 totalThisCall += ahwb;
308 QMutexLocker locker(&_stateMutex);
309 if (!_cachedAllocatorName.isEmpty()) {
310 const int w = GST_VIDEO_INFO_WIDTH(&_cachedInfo);
311 const int h = GST_VIDEO_INFO_HEIGHT(&_cachedInfo);
312 const char *fmt = gst_video_format_to_string(GST_VIDEO_INFO_FORMAT(&_cachedInfo));
313 tail = QStringLiteral(
" (alloc=%1 %2x%3 %4")
314 .arg(_cachedAllocatorName).arg(w).arg(h).arg(QLatin1String(fmt));
318 const qint64 nowNs = std::chrono::duration_cast<std::chrono::nanoseconds>(
319 std::chrono::steady_clock::now().time_since_epoch()).count();
320 const qint64 prevNs = _lastStatsAtNs.exchange(nowNs, std::memory_order_relaxed);
321 if (prevNs != 0 && nowNs > prevNs && !tail.isEmpty()) {
324 const double seconds =
static_cast<double>(nowNs - prevNs) / 1e9;
325 const quint64 prevTotal = _lastStatsTotal.exchange(totalThisCall, std::memory_order_relaxed);
326 if (totalThisCall > prevTotal && seconds > 0.0) {
327 const double fps =
static_cast<double>(totalThisCall - prevTotal) / seconds;
328 tail += QStringLiteral(
" ~%1fps").arg(fps, 0,
'f', 1);
330 tail += QLatin1Char(
')');
331 }
else if (!tail.isEmpty()) {
332 _lastStatsTotal.store(totalThisCall, std::memory_order_relaxed);
333 tail += QLatin1Char(
')');
336 qCDebug(GstAppSinkAdapterLog).noquote() <<
"Frame stats —" << s << tail;
352 if (!sinkBin || !videoSink) {
353 qCWarning(GstAppSinkAdapterLog) <<
"setup() called with null arguments";
359 if (!GST_IS_QGC_VIDEO_SINK_BIN(sinkBin)) {
360 qCWarning(GstAppSinkAdapterLog) <<
"sinkBin is not a GstQgcVideoSinkBin";
366 qCWarning(GstAppSinkAdapterLog) <<
"qgcvideosinkbin has no appsink (not constructed?)";
371 QMutexLocker locker(&_stateMutex);
372 _videoSink = videoSink;
375#if defined(QGC_HAS_ANY_GPU_PATH)
378 gboolean binZeroCopy = FALSE;
379 g_object_get(sinkBin,
"gpu-zerocopy", &binZeroCopy, NULL);
380 _gpuPathEnabled = binZeroCopy ? true :
false;
383#if defined(QGC_HAS_GST_DMABUF_GPU_PATH)
384 if (_gpuPathEnabled) {
386 _eglDisplay = EGL_NO_DISPLAY;
387 const QString platform = QGuiApplication::platformName();
388 if (platform == QLatin1String(
"wayland") || platform == QLatin1String(
"wayland-egl")) {
389 if (
auto *ni = QGuiApplication::platformNativeInterface()) {
390 _eglDisplay =
static_cast<EGLDisplay
>(ni->nativeResourceForIntegration(
"egldisplay"));
393 if (_eglDisplay == EGL_NO_DISPLAY) {
394 _eglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
396 if (_eglDisplay == EGL_NO_DISPLAY) {
397 qCWarning(GstAppSinkAdapterLog) <<
"GPU zero-copy requested but EGLDisplay unavailable on platform"
398 << platform <<
"— DMABuf path disabled";
400 qCInfo(GstAppSinkAdapterLog) <<
"DMABuf zero-copy path available on" << platform
401 <<
"— actual path chosen at caps negotiation";
405#if defined(QGC_HAS_GST_D3D11_GPU_PATH)
406 if (_gpuPathEnabled) {
407 qCInfo(GstAppSinkAdapterLog) <<
"D3D11 zero-copy path available — actual path chosen at caps negotiation";
410#if defined(QGC_HAS_GST_D3D12_GPU_PATH)
411 if (_gpuPathEnabled) {
412 qCInfo(GstAppSinkAdapterLog) <<
"D3D12 zero-copy path available — actual path chosen at caps negotiation";
415#if defined(QGC_HAS_GST_IOSURFACE_GPU_PATH)
416 if (_gpuPathEnabled) {
417 qCInfo(GstAppSinkAdapterLog) <<
"IOSurface zero-copy path available — actual path chosen at caps negotiation";
420#if defined(QGC_HAS_GST_AHARDWAREBUFFER_GPU_PATH)
421 if (_gpuPathEnabled) {
422 _ahwbEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
423 if (_ahwbEglDisplay == EGL_NO_DISPLAY) {
424 qCWarning(GstAppSinkAdapterLog) <<
"AHardwareBuffer path: EGLDisplay unavailable";
426 qCInfo(GstAppSinkAdapterLog) <<
"AHardwareBuffer zero-copy path available"
427 <<
"— actual path chosen at caps negotiation";
432 GstAppSinkCallbacks callbacks{};
433 callbacks.new_sample = onNewSample;
435 gst_app_sink_set_callbacks(GST_APP_SINK(_appsink), &callbacks,
this,
nullptr);
440 _appsinkInputFrames.store(0, std::memory_order_relaxed);
441 _flushing.store(
false, std::memory_order_relaxed);
442 if (GstPad *sinkPad = gst_element_get_static_pad(_appsink,
"sink")) {
447 _appsinkProbeId = gst_pad_add_probe(sinkPad,
448 GstPadProbeType(GST_PAD_PROBE_TYPE_BUFFER
449 | GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM
450 | GST_PAD_PROBE_TYPE_EVENT_FLUSH),
451 &GstAppSinkAdapter::appsinkBufferProbe,
this,
nullptr);
452 if (_appsinkProbeId == 0) {
453 qCWarning(GstAppSinkAdapterLog) <<
"gst_pad_add_probe(BUFFER) returned 0 — appsink drop counter disabled";
454 gst_object_unref(sinkPad);
457 _appsinkProbePad = sinkPad;
460 qCWarning(GstAppSinkAdapterLog) <<
"Could not obtain appsink sink pad — drop counter disabled";
463 _telemetryEmitTimer.setInterval(1000);
464 _telemetryEmitTimer.setSingleShot(
false);
466 QObject::disconnect(&_telemetryEmitTimer, &QTimer::timeout,
this,
nullptr);
467 QObject::connect(&_telemetryEmitTimer, &QTimer::timeout,
this, [
this]() {
468 quint64 total = _cpuFrames.load(std::memory_order_relaxed);
469#if defined(QGC_HAS_GST_DMABUF_GPU_PATH)
470 total += _gpuFrames.load(std::memory_order_relaxed);
472#if defined(QGC_HAS_GST_GLMEMORY_GPU_PATH)
473 total += _glFrames.load(std::memory_order_relaxed);
475#if defined(QGC_HAS_GST_D3D11_GPU_PATH)
476 total += _d3d11Frames.load(std::memory_order_relaxed);
478#if defined(QGC_HAS_GST_D3D12_GPU_PATH)
479 total += _d3d12Frames.load(std::memory_order_relaxed);
481#if defined(QGC_HAS_GST_IOSURFACE_GPU_PATH)
482 total += _iosurfaceFrames.load(std::memory_order_relaxed);
484#if defined(QGC_HAS_GST_AHARDWAREBUFFER_GPU_PATH)
485 total += _ahwbFrames.load(std::memory_order_relaxed);
487 if (total != _lastEmittedFrameTotal) {
488 _lastEmittedFrameTotal = total;
493 QMetaObject::invokeMethod(&_telemetryEmitTimer, qOverload<>(&QTimer::start), Qt::QueuedConnection);
495 qCDebug(GstAppSinkAdapterLog) <<
"Installed appsink callbacks";
504 const quint64 periodNs =
static_cast<quint64
>(GST_SECOND / hz);
505 _refreshPeriodNs.store(periodNs, std::memory_order_release);
508#if defined(QGC_GST_BUILD_VERSION_MAJOR) && \
509 (QGC_GST_BUILD_VERSION_MAJOR > 1 || \
510 (QGC_GST_BUILD_VERSION_MAJOR == 1 && QGC_GST_BUILD_VERSION_MINOR >= 24))
511 QMutexLocker locker(&_stateMutex);
513 gst_app_sink_set_max_time(GST_APP_SINK(_appsink),
static_cast<GstClockTime
>(periodNs));
520 if (enabled == _smoothingEnabled.load(std::memory_order_acquire)) {
525 const int hz = (refreshHz >= 1.0 && refreshHz <= 240.0) ?
int(qRound(refreshHz)) : 60;
526 const int periodMs = qMax(1,
int(qRound(1000.0 / hz)));
528 _smoothingClock.start();
529 connect(&_smoothingTickTimer, &QTimer::timeout,
530 this, &GstAppSinkAdapter::_onSmoothingTick, Qt::UniqueConnection);
531 _smoothingTickTimer.setInterval(periodMs);
532 _smoothingTickTimer.setTimerType(Qt::PreciseTimer);
533 _smoothingEnabled.store(
true, std::memory_order_release);
534 QMetaObject::invokeMethod(&_smoothingTickTimer, qOverload<>(&QTimer::start),
535 Qt::QueuedConnection);
536 qCInfo(GstAppSinkAdapterLog) <<
"Smoothing ring on: tick=" << periodMs
537 <<
"ms threshold=" << (kSmoothingThresholdNs / 1000000) <<
"ms capacity="
538 << kSmoothingRingCapacity;
540 _smoothingEnabled.store(
false, std::memory_order_release);
541 QMetaObject::invokeMethod(&_smoothingTickTimer, &QTimer::stop, Qt::QueuedConnection);
542 QMutexLocker lock(&_smoothingMutex);
543 _smoothingRing.clear();
544 _smoothingFirstPtsNs = -1;
548void GstAppSinkAdapter::_deliverFrame(QPointer<QVideoSink> sink, QVideoFrame &&frame, int64_t ptsNs)
550 if (!_smoothingEnabled.load(std::memory_order_acquire)) {
551 pushFrameQueued(sink, std::move(frame));
554 const qint64 nowNs = _smoothingClock.nsecsElapsed();
555 QMutexLocker lock(&_smoothingMutex);
556 if (_smoothingRing.size() >= kSmoothingRingCapacity) {
557 _smoothingRing.removeFirst();
558 const quint64 c = _smoothingDroppedFrames.fetch_add(1, std::memory_order_relaxed) + 1;
559 if ((c & 0x3F) == 1) {
560 qCDebug(GstAppSinkAdapterLog) <<
"Smoothing ring overflow; dropped oldest (total=" << c <<
")";
563 _smoothingRing.append({std::move(frame),
564 ptsNs >= 0 ? ptsNs :
static_cast<int64_t
>(nowNs),
568void GstAppSinkAdapter::_onSmoothingTick()
570 QPointer<QVideoSink> sinkSnapshot;
572 QMutexLocker locker(&_stateMutex);
573 sinkSnapshot = _videoSink;
581 QMutexLocker lock(&_smoothingMutex);
582 if (_smoothingRing.isEmpty()) {
585 if (_smoothingFirstPtsNs < 0) {
586 _smoothingFirstPtsNs = _smoothingRing.first().ptsNs;
587 _smoothingFirstClockNs = _smoothingRing.first().enqueuedNs;
589 const qint64 nowNs = _smoothingClock.nsecsElapsed();
590 const int64_t targetPts = _smoothingFirstPtsNs + (nowNs - _smoothingFirstClockNs);
592 int64_t bestDelta = std::llabs(_smoothingRing[0].ptsNs - targetPts);
593 for (
int i = 1; i < _smoothingRing.size(); ++i) {
594 const int64_t d = std::llabs(_smoothingRing[i].ptsNs - targetPts);
600 if (bestDelta > kSmoothingThresholdNs) {
602 _smoothingFirstPtsNs = -1;
605 chosen = _smoothingRing[bestIdx].frame;
607 for (
int i = bestIdx; i >= 0; --i) {
608 _smoothingRing.removeAt(i);
611 pushFrameQueued(sinkSnapshot, std::move(chosen));
617 QMetaObject::invokeMethod(&_telemetryEmitTimer, &QTimer::stop, Qt::QueuedConnection);
619 if (_smoothingEnabled.exchange(
false, std::memory_order_acq_rel)) {
620 QMetaObject::invokeMethod(&_smoothingTickTimer, &QTimer::stop, Qt::QueuedConnection);
621 QMutexLocker lock(&_smoothingMutex);
622 _smoothingRing.clear();
623 _smoothingFirstPtsNs = -1;
627 QString stats = QStringLiteral(
"CPU:%1").arg(_cpuFrames.load(std::memory_order_relaxed));
628 quint64 totalFrames = _cpuFrames.load(std::memory_order_relaxed);
629#if defined(QGC_HAS_GST_DMABUF_GPU_PATH)
630 const quint64 dmaFailures = GstDmaBufVideoBuffer::takeMapFailureCount();
631 stats += QStringLiteral(
" DMABuf:%1 DMABuf-failures:%2").arg(_gpuFrames.load(std::memory_order_relaxed)).arg(dmaFailures);
632 totalFrames += _gpuFrames.load(std::memory_order_relaxed) + dmaFailures;
633 _gpuFrames.store(0, std::memory_order_relaxed);
635#if defined(QGC_HAS_GST_GLMEMORY_GPU_PATH)
636 const quint64 glFailures = GstGlVideoBuffer::takeMapFailureCount();
637 const quint64 glReuse = GstGlVideoBuffer::takeTextureReuseHits();
638 quint64 glGpuWaits = 0;
639 const quint64 glCpuWaits = GstGlVideoBuffer::takeSyncWaitCounts(glGpuWaits);
640 stats += QStringLiteral(
" GL:%1 GL-failures:%2 GL-reuse:%3 GL-wait[gpu/cpu]:%4/%5")
641 .arg(_glFrames.load(std::memory_order_relaxed))
642 .arg(glFailures).arg(glReuse).arg(glGpuWaits).arg(glCpuWaits);
643 totalFrames += _glFrames.load(std::memory_order_relaxed) + glFailures;
644 _glFrames.store(0, std::memory_order_relaxed);
646#if defined(QGC_HAS_GST_D3D11_GPU_PATH)
647 const quint64 d3dFailures = GstD3D11VideoBuffer::takeMapFailureCount();
648 stats += QStringLiteral(
" D3D11:%1 D3D11-failures:%2").arg(_d3d11Frames.load(std::memory_order_relaxed)).arg(d3dFailures);
649 totalFrames += _d3d11Frames.load(std::memory_order_relaxed) + d3dFailures;
650 _d3d11Frames.store(0, std::memory_order_relaxed);
652#if defined(QGC_HAS_GST_D3D12_GPU_PATH)
653 const quint64 d3d12Failures = GstD3D12VideoBuffer::takeMapFailureCount();
654 stats += QStringLiteral(
" D3D12:%1 D3D12-failures:%2").arg(_d3d12Frames.load(std::memory_order_relaxed)).arg(d3d12Failures);
655 totalFrames += _d3d12Frames.load(std::memory_order_relaxed) + d3d12Failures;
656 _d3d12Frames.store(0, std::memory_order_relaxed);
658#if defined(QGC_HAS_GST_IOSURFACE_GPU_PATH)
659 const quint64 iosFailures = GstIOSurfaceVideoBuffer::takeMapFailureCount();
660 stats += QStringLiteral(
" IOSurface:%1 IOSurface-failures:%2").arg(_iosurfaceFrames.load(std::memory_order_relaxed)).arg(iosFailures);
661 totalFrames += _iosurfaceFrames.load(std::memory_order_relaxed) + iosFailures;
662 _iosurfaceFrames.store(0, std::memory_order_relaxed);
664#if defined(QGC_HAS_GST_AHARDWAREBUFFER_GPU_PATH)
665 const quint64 ahwbFailures = GstAHardwareBufferVideoBuffer::takeMapFailureCount();
666 stats += QStringLiteral(
" AHWBuf:%1 AHWBuf-failures:%2").arg(_ahwbFrames.load(std::memory_order_relaxed)).arg(ahwbFailures);
667 totalFrames += _ahwbFrames.load(std::memory_order_relaxed) + ahwbFailures;
668 _ahwbFrames.store(0, std::memory_order_relaxed);
670 if (totalFrames > 0) {
671 qCInfo(GstAppSinkAdapterLog).noquote() <<
"Adapter teardown —" << stats;
674 _cpuFrames.store(0, std::memory_order_relaxed);
675 _lastEmittedFrameTotal = 0;
680 GstAppSinkCallbacks empty{};
681 gst_app_sink_set_callbacks(GST_APP_SINK(_appsink), &empty,
nullptr,
nullptr);
683 while (GstSample *s = gst_app_sink_try_pull_sample(GST_APP_SINK(_appsink), 0)) {
688 if (_appsinkProbeId != 0 && _appsinkProbePad) {
689 gst_pad_remove_probe(_appsinkProbePad, _appsinkProbeId);
692 gst_clear_object(&_appsinkProbePad);
693 _appsinkInputFrames.store(0, std::memory_order_relaxed);
694 gst_clear_object(&_appsink);
697 QMutexLocker locker(&_stateMutex);
698 _videoSink =
nullptr;
700 if (_cachedCapsKey) {
701 gst_caps_unref(_cachedCapsKey);
702 _cachedCapsKey =
nullptr;
704 _cachedFormat = QVideoFrameFormat();
705 _cachedPixelFormat = QVideoFrameFormat::Format_Invalid;
708#if defined(QGC_HAS_GST_DMABUF_GPU_PATH)
709 _eglDisplay = EGL_NO_DISPLAY;
711#if defined(QGC_HAS_GST_AHARDWAREBUFFER_GPU_PATH)
712 _ahwbEglDisplay = EGL_NO_DISPLAY;
714#if defined(QGC_HAS_ANY_GPU_PATH)
715 _gpuPathEnabled =
false;
717 _active.store(
true, std::memory_order_release);
720 _qosLastPts = GST_CLOCK_TIME_NONE;
721 _qosLastArrivalNs = 0;
722 _pipelineMinLatencyNs = 0;
723 _latencyValid =
false;
724 _latencyRefreshPending.store(
false, std::memory_order_relaxed);
729 const bool was = _active.exchange(active, std::memory_order_acq_rel);
730 if (was == active)
return;
735 QPointer<QVideoSink> sinkSnapshot;
737 QMutexLocker locker(&_stateMutex);
738 sinkSnapshot = _videoSink;
741 pushFrameQueued(sinkSnapshot, QVideoFrame{});
745GstFlowReturn GstAppSinkAdapter::onNewSample(GstAppSink *appsink, gpointer userData)
749 if (!self)
return GST_FLOW_OK;
754 if (self->_flushing.load(std::memory_order_acquire)) {
755 if (GstSample *drop = gst_app_sink_try_pull_sample(appsink, 0)) {
756 gst_sample_unref(drop);
758 return GST_FLOW_FLUSHING;
762 if (!self->_active.load(std::memory_order_acquire)) {
763 if (GstSample *drop = gst_app_sink_try_pull_sample(appsink, 0)) {
764 gst_sample_unref(drop);
769 GstSample *sample = gst_app_sink_pull_sample(appsink);
771 return GST_FLOW_ERROR;
774 GstBuffer *buffer = gst_sample_get_buffer(sample);
775 GstCaps *caps = gst_sample_get_caps(sample);
776 if (!buffer || !caps) {
777 gst_sample_unref(sample);
778 return GST_FLOW_ERROR;
782 QPointer<QVideoSink> sinkSnapshot;
784 QMutexLocker locker(&self->_stateMutex);
785 sinkSnapshot = self->_videoSink;
788 gst_sample_unref(sample);
793 GstVideoInfo localInfo{};
794 QVideoFrameFormat localFormat;
795 int localPixelFormat = 0;
798 QMutexLocker locker(&self->_stateMutex);
799 if (caps == self->_cachedCapsKey) {
800 localInfo = self->_cachedInfo;
801 localFormat = self->_cachedFormat;
802 localPixelFormat = self->_cachedPixelFormat;
804 localPixelFormat = -1;
808 if (localPixelFormat == -1) {
809 GstVideoInfo parsedInfo{};
811#if GST_CHECK_VERSION(1, 24, 0)
812 if (gst_video_is_dma_drm_caps(caps)) {
813 GstVideoInfoDmaDrm drmInfo;
814 gst_video_info_dma_drm_init(&drmInfo);
815 if (!gst_video_info_dma_drm_from_caps(&drmInfo, caps)
816 || !gst_video_info_dma_drm_to_video_info(&drmInfo, &parsedInfo)) {
817 qCWarning(GstAppSinkAdapterLog) <<
"Failed to parse DMA-DRM video info from caps";
818 gst_sample_unref(sample);
819 return GST_FLOW_ERROR;
823 if (!gst_video_info_from_caps(&parsedInfo, caps)) {
824 qCWarning(GstAppSinkAdapterLog) <<
"Failed to parse video info from caps";
825 gst_sample_unref(sample);
826 return GST_FLOW_ERROR;
829 GST_VIDEO_INFO_FORMAT(&parsedInfo));
830 if (pixelFormat == QVideoFrameFormat::Format_Invalid) {
831 const GstVideoFormat fmt = GST_VIDEO_INFO_FORMAT(&parsedInfo);
832 if (self->_lastWarnedFormat.exchange(fmt, std::memory_order_relaxed) != fmt) {
833 qCWarning(GstAppSinkAdapterLog) <<
"Unsupported video format"
834 << gst_video_format_to_string(fmt);
836 gst_sample_unref(sample);
837 return GST_FLOW_ERROR;
839 const int w = GST_VIDEO_INFO_WIDTH(&parsedInfo);
840 const int h = GST_VIDEO_INFO_HEIGHT(&parsedInfo);
841 if (w <= 0 || h <= 0) {
842 gst_sample_unref(sample);
843 return GST_FLOW_ERROR;
845 QVideoFrameFormat parsedFormat(QSize(w, h), pixelFormat);
846 applyColorimetry(parsedFormat, parsedInfo, caps);
847 const int fpsN = GST_VIDEO_INFO_FPS_N(&parsedInfo);
848 const int fpsD = GST_VIDEO_INFO_FPS_D(&parsedInfo);
849 if (fpsN > 0 && fpsD > 0) {
850 parsedFormat.setStreamFrameRate(
static_cast<qreal
>(fpsN) /
static_cast<qreal
>(fpsD));
852#if defined(QGC_GST_BUILD_VERSION_MAJOR) && \
853 (QGC_GST_BUILD_VERSION_MAJOR > 1 || \
854 (QGC_GST_BUILD_VERSION_MAJOR == 1 && QGC_GST_BUILD_VERSION_MINOR >= 24))
855 const quint64 framePeriodNs =
static_cast<quint64
>(GST_SECOND) *
static_cast<quint64
>(fpsD)
856 /
static_cast<quint64
>(fpsN);
857 const quint64 refreshNs = self->_refreshPeriodNs.load(std::memory_order_acquire);
858 const GstClockTime maxTime =
static_cast<GstClockTime
>(qMax(framePeriodNs, refreshNs));
859 gst_app_sink_set_max_time(appsink, maxTime);
865 GstMemory *mem0 = gst_buffer_peek_memory(buffer, 0);
866 allocName = QString::fromUtf8((mem0 && mem0->allocator) ? mem0->allocator->mem_type :
"(none)");
867 qCInfo(GstAppSinkAdapterLog).noquote()
868 <<
"Caps changed — allocator:" << allocName
869 <<
"format:" << gst_video_format_to_string(GST_VIDEO_INFO_FORMAT(&parsedInfo))
870 << GST_VIDEO_INFO_WIDTH(&parsedInfo) <<
"x" << GST_VIDEO_INFO_HEIGHT(&parsedInfo);
874 const int parN = GST_VIDEO_INFO_PAR_N(&parsedInfo);
875 const int parD = GST_VIDEO_INFO_PAR_D(&parsedInfo);
876 if (parN > 0 && parD > 0 && parN != parD) {
877#if defined(QGC_HAS_ANY_GPU_PATH)
878 if (self->_gpuPathEnabled) {
879 qCWarning(GstAppSinkAdapterLog).noquote()
880 <<
"Source has non-square PAR" << parN <<
"/" << parD
881 <<
"— GPU zero-copy renders distorted (no Qt API to compensate)."
882 <<
"Disable GPU zero-copy on this source for correct geometry.";
889 QMutexLocker locker(&self->_stateMutex);
890 if (self->_cachedCapsKey) {
891 gst_caps_unref(self->_cachedCapsKey);
893 self->_cachedCapsKey = caps;
894 self->_cachedInfo = parsedInfo;
895 self->_cachedFormat = parsedFormat;
896 self->_cachedPixelFormat = pixelFormat;
897 self->_cachedAllocatorName = allocName;
900 localInfo = parsedInfo;
901 localFormat = parsedFormat;
902 localPixelFormat = pixelFormat;
905 if (GstMemory *mem0 = gst_buffer_peek_memory(buffer, 0)) {
906 const QString memType = QString::fromUtf8(
907 (mem0->allocator) ? mem0->allocator->mem_type :
"(none)");
908 QMutexLocker locker(&self->_stateMutex);
909 if (memType != self->_cachedAllocatorName) {
910 qCInfo(GstAppSinkAdapterLog).noquote()
911 <<
"Allocator changed mid-stream:" << self->_cachedAllocatorName
913 self->_cachedAllocatorName = memType;
918 const GstVideoInfo &videoInfo = localInfo;
921 if (GST_BUFFER_PTS_IS_VALID(buffer)) {
922 if (GST_BUFFER_FLAG_IS_SET(buffer, GST_BUFFER_FLAG_DISCONT)) {
923 self->_lastDeliveredPtsNs.store(-1, std::memory_order_release);
925 const int64_t pts =
static_cast<int64_t
>(GST_BUFFER_PTS(buffer));
926 const int64_t lastPts = self->_lastDeliveredPtsNs.load(std::memory_order_acquire);
927 if (lastPts >= 0 && pts < lastPts) {
928 const int fpsN = GST_VIDEO_INFO_FPS_N(&videoInfo);
929 const int fpsD = GST_VIDEO_INFO_FPS_D(&videoInfo);
930 const int64_t framePeriodNs = (fpsN > 0 && fpsD > 0)
931 ?
static_cast<int64_t
>(GST_SECOND) * fpsD / fpsN
933 if (lastPts - pts > framePeriodNs) {
934 static std::atomic<quint64> s_ptsRegressionDrops{0};
935 const quint64 c = s_ptsRegressionDrops.fetch_add(1, std::memory_order_relaxed) + 1;
936 if ((c & 0x3F) == 1) {
937 qCWarning(GstAppSinkAdapterLog)
938 <<
"PTS regression — dropping buffer (pts=" << pts
939 <<
"last=" << lastPts <<
"delta=" << (lastPts - pts) <<
"ns; total drops=" << c <<
")";
941 gst_sample_unref(sample);
945 self->_lastDeliveredPtsNs.store(pts, std::memory_order_release);
948#if defined(QGC_HAS_ANY_GPU_PATH)
953 self->_gpuPathEnabled,
954#
if defined(QGC_HAS_GST_DMABUF_GPU_PATH)
959#
if defined(QGC_HAS_GST_AHARDWAREBUFFER_GPU_PATH)
960 self->_ahwbEglDisplay,
966 QVideoFrame gpuFrame(std::move(hwBuf));
967 applyOrientationAndTiming(gpuFrame, buffer,
968 self->_streamOrientation.load(std::memory_order_acquire));
969 gst_sample_unref(sample);
970 switch (matchedPath) {
971#if defined(QGC_HAS_GST_DMABUF_GPU_PATH)
973 const quint64 c = self->_gpuFrames.fetch_add(1, std::memory_order_relaxed) + 1;
974 if ((c & 0xFF) == 0) self->_logFrameStats();
978#if defined(QGC_HAS_GST_GLMEMORY_GPU_PATH)
980 const quint64 c = self->_glFrames.fetch_add(1, std::memory_order_relaxed) + 1;
981 if ((c & 0xFF) == 0) self->_logFrameStats();
985#if defined(QGC_HAS_GST_D3D11_GPU_PATH)
987 const quint64 c = self->_d3d11Frames.fetch_add(1, std::memory_order_relaxed) + 1;
988 if ((c & 0xFF) == 0) self->_logFrameStats();
992#if defined(QGC_HAS_GST_D3D12_GPU_PATH)
994 const quint64 c = self->_d3d12Frames.fetch_add(1, std::memory_order_relaxed) + 1;
995 if ((c & 0xFF) == 0) self->_logFrameStats();
999#if defined(QGC_HAS_GST_IOSURFACE_GPU_PATH)
1001 const quint64 c = self->_iosurfaceFrames.fetch_add(1, std::memory_order_relaxed) + 1;
1002 if ((c & 0xFF) == 0) self->_logFrameStats();
1006#if defined(QGC_HAS_GST_AHARDWAREBUFFER_GPU_PATH)
1008 const quint64 c = self->_ahwbFrames.fetch_add(1, std::memory_order_relaxed) + 1;
1009 if ((c & 0xFF) == 0) self->_logFrameStats();
1015 const int64_t ptsNs = GST_BUFFER_PTS_IS_VALID(buffer)
1016 ?
static_cast<int64_t
>(GST_BUFFER_PTS(buffer)) : -1;
1017 self->_deliverFrame(sinkSnapshot, std::move(gpuFrame), ptsNs);
1018 self->_pushQosUpstream(appsink, buffer);
1024 GstVideoFrame gstFrame;
1026 if (!gst_video_frame_map(&gstFrame,
const_cast<GstVideoInfo *
>(&videoInfo), buffer, GST_MAP_READ)) {
1028 static std::atomic<quint64> s_failCount{0};
1029 const quint64 count = s_failCount.fetch_add(1, std::memory_order_relaxed) + 1;
1030 if ((count & 0x3F) == 1) {
1031 qCWarning(GstAppSinkAdapterLog) <<
"gst_video_frame_map failed; pts="
1032 << (GST_BUFFER_PTS_IS_VALID(buffer) ? GST_BUFFER_PTS(buffer) : 0)
1033 <<
"consecutive=" << count;
1035 gst_sample_unref(sample);
1036 return GST_FLOW_ERROR;
1041 if (!videoFrame.map(QVideoFrame::WriteOnly)) {
1042 qCWarning(GstAppSinkAdapterLog) <<
"Failed to map QVideoFrame for writing";
1043 gst_video_frame_unmap(&gstFrame);
1044 gst_sample_unref(sample);
1045 return GST_FLOW_ERROR;
1048 const int planes = GST_VIDEO_INFO_N_PLANES(&videoInfo);
1049 for (
int p = 0; p < planes; ++p) {
1050 const int dstStride = videoFrame.bytesPerLine(p);
1051 const int compWidth = GST_VIDEO_FRAME_COMP_WIDTH(&gstFrame, p);
1052 const int compPstride = GST_VIDEO_FRAME_COMP_PSTRIDE(&gstFrame, p);
1053 const int srcStride = GST_VIDEO_FRAME_PLANE_STRIDE(&gstFrame, p);
1054 const int planeHeight = GST_VIDEO_FRAME_COMP_HEIGHT(&gstFrame, p);
1055 const int activeRowBytes = compWidth * compPstride;
1056 const uchar *src =
static_cast<const uchar *
>(GST_VIDEO_FRAME_PLANE_DATA(&gstFrame, p));
1057 uchar *dst = videoFrame.bits(p);
1061 if (activeRowBytes > dstStride) {
1063 static bool s_warnedStrideOverflow =
false;
1064 if (!s_warnedStrideOverflow) {
1065 s_warnedStrideOverflow =
true;
1066 qCWarning(GstAppSinkAdapterLog)
1067 <<
"Plane" << p <<
": activeRowBytes" << activeRowBytes
1068 <<
"> dstStride" << dstStride <<
"— skipping frame";
1071 gst_video_frame_unmap(&gstFrame);
1072 gst_sample_unref(sample);
1073 return GST_FLOW_ERROR;
1075 if (srcStride == dstStride && activeRowBytes == srcStride) {
1076 memcpy(dst, src,
static_cast<size_t>(planeHeight) * srcStride);
1078 for (
int y = 0; y < planeHeight; ++y) {
1079 memcpy(dst + y * dstStride, src + y * srcStride, activeRowBytes);
1085 gst_video_frame_unmap(&gstFrame);
1087 applyOrientationAndTiming(videoFrame, buffer,
1088 self->_streamOrientation.load(std::memory_order_acquire));
1089 const quint64 c = self->_cpuFrames.fetch_add(1, std::memory_order_relaxed) + 1;
1090 if ((c & 0xFF) == 0) self->_logFrameStats();
1091 const int64_t ptsNs = GST_BUFFER_PTS_IS_VALID(buffer)
1092 ?
static_cast<int64_t
>(GST_BUFFER_PTS(buffer)) : -1;
1093 gst_sample_unref(sample);
1094 self->_deliverFrame(sinkSnapshot, std::move(videoFrame), ptsNs);
1095 self->_pushQosUpstream(appsink, buffer);
1099void GstAppSinkAdapter::_refreshLatency()
1101 if (!_appsink)
return;
1102 GstQuery *q = gst_query_new_latency();
1105 if (gst_element_query(_appsink, q)) {
1106 gboolean live = FALSE;
1107 GstClockTime minLat = 0, maxLat = 0;
1108 gst_query_parse_latency(q, &live, &minLat, &maxLat);
1109 _pipelineMinLatencyNs = (minLat != GST_CLOCK_TIME_NONE) ? minLat : 0;
1110 _latencyValid =
true;
1111 qCDebug(GstAppSinkAdapterLog) <<
"Pipeline latency refreshed: live=" << live
1112 <<
"min=" << _pipelineMinLatencyNs <<
"ns max=" << maxLat <<
"ns";
1118void GstAppSinkAdapter::_pushQosUpstream(GstAppSink * , GstBuffer *buffer)
1120 if (!_qosUpstreamEnabled)
return;
1121 if (!GST_BUFFER_PTS_IS_VALID(buffer))
return;
1124 if (!_latencyValid || _latencyRefreshPending.exchange(
false, std::memory_order_relaxed)
1125 || (_qosSampleCount > 0 && (_qosSampleCount % kLatencyRefreshInterval) == 0)) {
1129 const GstClockTime pts = GST_BUFFER_PTS(buffer);
1132 const GstClockTime nowNs =
static_cast<GstClockTime
>(
1133 std::chrono::duration_cast<std::chrono::nanoseconds>(
1134 std::chrono::steady_clock::now().time_since_epoch()).count());
1138 if (_qosLastPts == GST_CLOCK_TIME_NONE || _qosLastArrivalNs == 0
1139 || pts <= _qosLastPts) {
1141 _qosLastArrivalNs = nowNs;
1145 const GstClockTime ptsDelta = pts - _qosLastPts;
1146 const GstClockTime arrivalDelta = nowNs - _qosLastArrivalNs;
1149 _qosLastArrivalNs = nowNs;
1151 if (ptsDelta == 0)
return;
1154 const double rate =
static_cast<double>(arrivalDelta) /
static_cast<double>(ptsDelta);
1158 if (rate > _qosAvgRate) {
1159 _qosAvgRate = (rate + 15.0 * _qosAvgRate) / 16.0;
1161 _qosAvgRate = (rate + 3.0 * _qosAvgRate) / 4.0;
1165 if (_qosSampleCount < kQosWarmup)
return;
1166 if ((_qosSampleCount % kQosInterval) != 0)
return;
1170 if (_pipelineMinLatencyNs > 0) {
1172 const GstClockTimeDiff absLateness = GST_CLOCK_DIFF(ptsDelta, arrivalDelta);
1173 if (absLateness <
static_cast<GstClockTimeDiff
>(_pipelineMinLatencyNs)) {
1179 const gdouble proportion = qBound(0.0, _qosAvgRate, 16.0);
1182 const GstClockTimeDiff diff = 0;
1185 if (proportion >= 16.0) {
1186 type = GST_QOS_TYPE_THROTTLE;
1187 }
else if (proportion > 1.0) {
1188 type = GST_QOS_TYPE_UNDERFLOW;
1190 type = GST_QOS_TYPE_OVERFLOW;
1193 GstEvent *
event = gst_event_new_qos(type, proportion, diff, pts);
1195 if (!_appsinkProbePad) {
1196 gst_event_unref(event);
1200 const bool pushed = gst_pad_push_event(_appsinkProbePad, event);
1202 static bool s_qosPushFailed =
false;
1203 if (!s_qosPushFailed) {
1204 s_qosPushFailed =
true;
1205 qCDebug(GstAppSinkAdapterLog) <<
"QoS upstream push failed (silenced after first)";
1213 QMutexLocker locker(&_stateMutex);
1215#if defined(QGC_HAS_GST_DMABUF_GPU_PATH)
1216 count += _gpuFrames.load(std::memory_order_relaxed);
1218#if defined(QGC_HAS_GST_GLMEMORY_GPU_PATH)
1219 count += _glFrames.load(std::memory_order_relaxed);
1221#if defined(QGC_HAS_GST_D3D11_GPU_PATH)
1222 count += _d3d11Frames.load(std::memory_order_relaxed);
1224#if defined(QGC_HAS_GST_D3D12_GPU_PATH)
1225 count += _d3d12Frames.load(std::memory_order_relaxed);
1227#if defined(QGC_HAS_GST_IOSURFACE_GPU_PATH)
1228 count += _iosurfaceFrames.load(std::memory_order_relaxed);
1230#if defined(QGC_HAS_GST_AHARDWAREBUFFER_GPU_PATH)
1231 count += _ahwbFrames.load(std::memory_order_relaxed);
1238 QMutexLocker locker(&_stateMutex);
1239 return _cpuFrames.load(std::memory_order_relaxed);
1244 QMutexLocker locker(&_stateMutex);
1245#if defined(QGC_HAS_ANY_GPU_PATH)
1246 return _gpuPathEnabled ? _cpuFrames.load(std::memory_order_relaxed) : quint64(0);
1252quint64 GstAppSinkAdapter::_deliveredFrames() const noexcept
1254 quint64 count = _cpuFrames.load(std::memory_order_relaxed);
1255#if defined(QGC_HAS_GST_DMABUF_GPU_PATH)
1256 count += _gpuFrames.load(std::memory_order_relaxed);
1258#if defined(QGC_HAS_GST_GLMEMORY_GPU_PATH)
1259 count += _glFrames.load(std::memory_order_relaxed);
1261#if defined(QGC_HAS_GST_D3D11_GPU_PATH)
1262 count += _d3d11Frames.load(std::memory_order_relaxed);
1264#if defined(QGC_HAS_GST_D3D12_GPU_PATH)
1265 count += _d3d12Frames.load(std::memory_order_relaxed);
1267#if defined(QGC_HAS_GST_IOSURFACE_GPU_PATH)
1268 count += _iosurfaceFrames.load(std::memory_order_relaxed);
1270#if defined(QGC_HAS_GST_AHARDWAREBUFFER_GPU_PATH)
1271 count += _ahwbFrames.load(std::memory_order_relaxed);
1278 return _appsinkInputFrames.load(std::memory_order_relaxed);
1283 const quint64 in = _appsinkInputFrames.load(std::memory_order_relaxed);
1284 const quint64 out = _deliveredFrames();
1288 return in > out ? in - out : 0;
1291GstPadProbeReturn GstAppSinkAdapter::appsinkBufferProbe(GstPad *, GstPadProbeInfo *info, gpointer userData)
1294 if (!self)
return GST_PAD_PROBE_OK;
1295 const auto type = GST_PAD_PROBE_INFO_TYPE(info);
1298 if (type & (GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM | GST_PAD_PROBE_TYPE_EVENT_FLUSH)) {
1301 if (GstEvent *event = GST_PAD_PROBE_INFO_EVENT(info)) {
1302 switch (GST_EVENT_TYPE(event)) {
1303 case GST_EVENT_FLUSH_START:
1304 self->_flushing.store(
true, std::memory_order_release);
1306 case GST_EVENT_FLUSH_STOP:
1307 self->_flushing.store(
false, std::memory_order_release);
1309 self->_streamOrientation.store(
static_cast<int>(GST_VIDEO_ORIENTATION_IDENTITY),
1310 std::memory_order_release);
1311 self->_lastDeliveredPtsNs.store(-1, std::memory_order_release);
1313 if (self->_smoothingEnabled.load(std::memory_order_acquire)) {
1314 QMutexLocker lock(&self->_smoothingMutex);
1315 self->_smoothingRing.clear();
1316 self->_smoothingFirstPtsNs = -1;
1319 case GST_EVENT_TAG: {
1320 GstTagList *taglist =
nullptr;
1321 gst_event_parse_tag(event, &taglist);
1322 GstVideoOrientationMethod method = GST_VIDEO_ORIENTATION_IDENTITY;
1323 if (taglist && gst_video_orientation_from_tag(taglist, &method)) {
1324 self->_streamOrientation.store(
static_cast<int>(method),
1325 std::memory_order_release);
1333 return GST_PAD_PROBE_OK;
1335 if (type & GST_PAD_PROBE_TYPE_BUFFER) {
1336 self->_appsinkInputFrames.fetch_add(1, std::memory_order_relaxed);
1338 return GST_PAD_PROBE_OK;
QVideoFrameFormat::PixelFormat toQtPixelFormat(GstVideoFormat fmt)
QVideoFrameFormat::ColorTransfer toQtColorTransfer(GstVideoTransferFunction transfer)
QVideoFrameFormat applyCropMeta(QVideoFrameFormat format, GstBuffer *buffer)
QVideoFrameFormat::ColorSpace toQtColorSpace(GstVideoColorMatrix matrix)
void applyOrientationToFrame(QVideoFrame &frame, GstVideoOrientationMethod method)
QVideoFrameFormat::PixelFormat toQtPixelFormat(GstVideoFormat fmt)
QVideoFrameFormat applyCropMeta(QVideoFrameFormat format, GstBuffer *buffer)
std::unique_ptr< QHwVideoBuffer > makeHwVideoBuffer(GstSample *sample, const GstVideoInfo &info, QVideoFrameFormat format, bool gpuEnabled, void *, void *, HwVideoBufferPath &matchedPath)
HwVideoBufferPath
Identifies which GPU path was chosen; used by the adapter to increment the right counter.
struct _GstElement GstElement
#define QGC_LOGGING_CATEGORY(name, categoryStr)
Bridges a GStreamer appsink to a Qt QVideoSink.
~GstAppSinkAdapter() override
void teardown()
Disconnect the callback (safe to call multiple times).
quint64 gpuFrameCount() const noexcept
void frameCountsChanged()
void setActive(bool active)
void setSmoothingEnabled(bool enabled, qreal refreshHz)
quint64 cpuFrameCount() const noexcept
GstAppSinkAdapter(QObject *parent=nullptr)
Frames that reached the appsink sink pad (counted via pad probe).
void setRefreshRate(qreal hz)
bool setup(GstElement *sinkBin, QVideoSink *videoSink)
quint64 gpuFallbackCount() const noexcept
quint64 appsinkInputFrames() const noexcept
quint64 appsinkDroppedFrames() const noexcept
GstElement * gst_qgc_video_sink_bin_get_appsink(GstQgcVideoSinkBin *self)