QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
GstAppSinkAdapter.cc
Go to the documentation of this file.
1#include "GstAppSinkAdapter.h"
3
4#include <QtCore/QMetaObject>
5#include <QtMultimedia/QVideoFrame>
6#include <QtMultimedia/QVideoFrameFormat>
7#include <QtMultimedia/QVideoSink>
8
9#include <gst/app/gstappsink.h>
10#include <gst/video/video-info.h>
11
12QGC_LOGGING_CATEGORY(GstAppSinkAdapterLog, "Video.GstAppSinkAdapter")
13
15 : QObject(parent)
16{
17}
18
23
24bool GstAppSinkAdapter::setup(GstElement *sinkBin, QVideoSink *videoSink)
25{
26 if (!sinkBin || !videoSink) {
27 qCWarning(GstAppSinkAdapterLog) << "setup() called with null arguments";
28 return false;
29 }
30
31 teardown();
32
33 if (!GST_IS_BIN(sinkBin)) {
34 qCWarning(GstAppSinkAdapterLog) << "sinkBin is not a GstBin";
35 return false;
36 }
37
38 _appsink = gst_bin_get_by_name(GST_BIN(sinkBin), "qgcappsink");
39 if (!_appsink) {
40 qCWarning(GstAppSinkAdapterLog) << "Could not find 'qgcappsink' in sink bin";
41 return false;
42 }
43
44 _videoSink = videoSink;
45 _signalId = g_signal_connect(_appsink, "new-sample", G_CALLBACK(onNewSample), this);
46 qCDebug(GstAppSinkAdapterLog) << "Connected to appsink, signal id:" << _signalId;
47 return true;
48}
49
51{
52 if (_appsink && _signalId) {
53 g_signal_handler_disconnect(_appsink, _signalId);
54 _signalId = 0;
55 }
56 gst_clear_object(&_appsink);
57 _videoSink = nullptr;
58}
59
60GstFlowReturn GstAppSinkAdapter::onNewSample(GstElement *appsink, gpointer userData)
61{
62 auto *self = static_cast<GstAppSinkAdapter *>(userData);
63
64 GstSample *sample = gst_app_sink_pull_sample(GST_APP_SINK(appsink));
65 if (!sample) {
66 return GST_FLOW_ERROR;
67 }
68
69 GstBuffer *buffer = gst_sample_get_buffer(sample);
70 GstCaps *caps = gst_sample_get_caps(sample);
71 if (!buffer || !caps) {
72 gst_sample_unref(sample);
73 return GST_FLOW_ERROR;
74 }
75
76 GstVideoInfo videoInfo;
77 if (!gst_video_info_from_caps(&videoInfo, caps)) {
78 qCWarning(GstAppSinkAdapterLog) << "Failed to parse video info from caps";
79 gst_sample_unref(sample);
80 return GST_FLOW_ERROR;
81 }
82
83 if (GST_VIDEO_INFO_FORMAT(&videoInfo) != GST_VIDEO_FORMAT_BGRA) {
84 qCWarning(GstAppSinkAdapterLog) << "Unexpected video format (expected BGRA)";
85 gst_sample_unref(sample);
86 return GST_FLOW_ERROR;
87 }
88
89 const int width = GST_VIDEO_INFO_WIDTH(&videoInfo);
90 const int height = GST_VIDEO_INFO_HEIGHT(&videoInfo);
91 if (width <= 0 || height <= 0) {
92 gst_sample_unref(sample);
93 return GST_FLOW_ERROR;
94 }
95
96 GstMapInfo mapInfo;
97 if (!gst_buffer_map(buffer, &mapInfo, GST_MAP_READ)) {
98 qCWarning(GstAppSinkAdapterLog) << "Failed to map GStreamer buffer";
99 gst_sample_unref(sample);
100 return GST_FLOW_ERROR;
101 }
102
103 const QSize frameSize(width, height);
104 const QVideoFrameFormat format(frameSize, QVideoFrameFormat::Format_BGRA8888);
105 QVideoFrame videoFrame(format);
106
107 if (!videoFrame.map(QVideoFrame::WriteOnly)) {
108 qCWarning(GstAppSinkAdapterLog) << "Failed to map QVideoFrame for writing";
109 gst_buffer_unmap(buffer, &mapInfo);
110 gst_sample_unref(sample);
111 return GST_FLOW_ERROR;
112 }
113
114 const int dstStride = videoFrame.bytesPerLine(0);
115 const int srcStride = GST_VIDEO_INFO_PLANE_STRIDE(&videoInfo, 0);
116 const uchar *src = mapInfo.data;
117 uchar *dst = videoFrame.bits(0);
118
119 const int rowBytes = width * 4; // BGRA = 4 bytes per pixel
120 if (rowBytes > srcStride || rowBytes > dstStride) {
121 qCWarning(GstAppSinkAdapterLog) << "Stride smaller than row size:"
122 << "rowBytes" << rowBytes
123 << "srcStride" << srcStride
124 << "dstStride" << dstStride;
125 videoFrame.unmap();
126 gst_buffer_unmap(buffer, &mapInfo);
127 gst_sample_unref(sample);
128 return GST_FLOW_ERROR;
129 }
130
131 const gsize requiredSize = static_cast<gsize>(height - 1) * srcStride + rowBytes;
132 if (mapInfo.size < requiredSize) {
133 qCWarning(GstAppSinkAdapterLog) << "Buffer too small:" << mapInfo.size << "<" << requiredSize;
134 videoFrame.unmap();
135 gst_buffer_unmap(buffer, &mapInfo);
136 gst_sample_unref(sample);
137 return GST_FLOW_ERROR;
138 }
139
140 if (srcStride == dstStride) {
141 memcpy(dst, src, static_cast<size_t>(height) * srcStride);
142 } else {
143 for (int y = 0; y < height; ++y) {
144 memcpy(dst + y * dstStride, src + y * srcStride, rowBytes);
145 }
146 }
147
148 videoFrame.unmap();
149 gst_buffer_unmap(buffer, &mapInfo);
150 gst_sample_unref(sample);
151
152 // Dispatch to the QVideoSink's owning thread — onNewSample runs on a
153 // GStreamer streaming thread, but QVideoSink is a QObject bound to the
154 // main/Qt thread.
155 if (self->_videoSink) {
156 QMetaObject::invokeMethod(self->_videoSink, [sink = self->_videoSink, frame = std::move(videoFrame)]() {
157 sink->setVideoFrame(frame);
158 }, Qt::QueuedConnection);
159 }
160
161 return GST_FLOW_OK;
162}
struct _GstElement GstElement
#define QGC_LOGGING_CATEGORY(name, categoryStr)
~GstAppSinkAdapter() override
void teardown()
Disconnect the callback (safe to call multiple times).
bool setup(GstElement *sinkBin, QVideoSink *videoSink)
QByteArray format(const QList< LogEntry > &entries, int fmt)