4#include <QtCore/QMetaObject>
5#include <QtMultimedia/QVideoFrame>
6#include <QtMultimedia/QVideoFrameFormat>
7#include <QtMultimedia/QVideoSink>
9#include <gst/app/gstappsink.h>
10#include <gst/video/video-info.h>
26 if (!sinkBin || !videoSink) {
27 qCWarning(GstAppSinkAdapterLog) <<
"setup() called with null arguments";
33 if (!GST_IS_BIN(sinkBin)) {
34 qCWarning(GstAppSinkAdapterLog) <<
"sinkBin is not a GstBin";
38 _appsink = gst_bin_get_by_name(GST_BIN(sinkBin),
"qgcappsink");
40 qCWarning(GstAppSinkAdapterLog) <<
"Could not find 'qgcappsink' in sink bin";
44 _videoSink = videoSink;
45 _signalId = g_signal_connect(_appsink,
"new-sample", G_CALLBACK(onNewSample),
this);
46 qCDebug(GstAppSinkAdapterLog) <<
"Connected to appsink, signal id:" << _signalId;
52 if (_appsink && _signalId) {
53 g_signal_handler_disconnect(_appsink, _signalId);
56 gst_clear_object(&_appsink);
60GstFlowReturn GstAppSinkAdapter::onNewSample(
GstElement *appsink, gpointer userData)
64 GstSample *sample = gst_app_sink_pull_sample(GST_APP_SINK(appsink));
66 return GST_FLOW_ERROR;
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;
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;
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;
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;
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;
103 const QSize frameSize(width, height);
104 const QVideoFrameFormat
format(frameSize, QVideoFrameFormat::Format_BGRA8888);
105 QVideoFrame videoFrame(format);
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;
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);
119 const int rowBytes = width * 4;
120 if (rowBytes > srcStride || rowBytes > dstStride) {
121 qCWarning(GstAppSinkAdapterLog) <<
"Stride smaller than row size:"
122 <<
"rowBytes" << rowBytes
123 <<
"srcStride" << srcStride
124 <<
"dstStride" << dstStride;
126 gst_buffer_unmap(buffer, &mapInfo);
127 gst_sample_unref(sample);
128 return GST_FLOW_ERROR;
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;
135 gst_buffer_unmap(buffer, &mapInfo);
136 gst_sample_unref(sample);
137 return GST_FLOW_ERROR;
140 if (srcStride == dstStride) {
141 memcpy(dst, src,
static_cast<size_t>(height) * srcStride);
143 for (
int y = 0; y < height; ++y) {
144 memcpy(dst + y * dstStride, src + y * srcStride, rowBytes);
149 gst_buffer_unmap(buffer, &mapInfo);
150 gst_sample_unref(sample);
155 if (self->_videoSink) {
156 QMetaObject::invokeMethod(self->_videoSink, [sink = self->_videoSink, frame = std::move(videoFrame)]() {
157 sink->setVideoFrame(frame);
158 }, Qt::QueuedConnection);
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)