QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
GStreamerFrameMap.cc
Go to the documentation of this file.
1#include "GStreamerFrameMap.h"
2
3#include <QtMultimedia/QAbstractVideoBuffer>
4#include <QtMultimedia/QVideoFrame>
5#include <atomic>
6#include <gst/gst.h>
7#include <gst/video/gstvideometa.h>
8#include <gst/video/video-hdr.h>
9
10#include "GstQgcVideoFormats.h"
12#include "QGCLoggingCategory.h"
13
14QGC_LOGGING_CATEGORY(GStreamerFrameMapLog, "Video.GStreamer.FrameMap")
15
16QVideoFrameFormat::ColorSpace toQtColorSpace(GstVideoColorMatrix matrix)
17{
18 switch (matrix) {
19 case GST_VIDEO_COLOR_MATRIX_BT601:
20 return QVideoFrameFormat::ColorSpace_BT601;
21 case GST_VIDEO_COLOR_MATRIX_BT709:
22 return QVideoFrameFormat::ColorSpace_BT709;
23 case GST_VIDEO_COLOR_MATRIX_BT2020:
24 return QVideoFrameFormat::ColorSpace_BT2020;
25 case GST_VIDEO_COLOR_MATRIX_SMPTE240M:
26 // Matches Qt qgst.cpp convention (shared D65 white point), not full colorimetric equivalence.
27 return QVideoFrameFormat::ColorSpace_AdobeRgb;
28 // Qt groups FCC with UNKNOWN/RGB and leaves it Undefined (qgst.cpp); the
29 // resolution heuristic below then picks BT601/BT709.
30 default:
31 return QVideoFrameFormat::ColorSpace_Undefined;
32 }
33}
34
35QVideoFrameFormat::ColorTransfer toQtColorTransfer(GstVideoTransferFunction transfer)
36{
37 // Mirrors Qt's qgst.cpp QGstCaps::formatAndVideoInfo() (cross-checked Qt 6.10.3).
38 switch (transfer) {
39 case GST_VIDEO_TRANSFER_BT601:
40 return QVideoFrameFormat::ColorTransfer_BT601;
41 case GST_VIDEO_TRANSFER_BT2020_10:
42 case GST_VIDEO_TRANSFER_BT2020_12:
43 case GST_VIDEO_TRANSFER_BT709:
44 return QVideoFrameFormat::ColorTransfer_BT709;
45 case GST_VIDEO_TRANSFER_GAMMA20:
46 return QVideoFrameFormat::ColorTransfer_BT709; // best fit per Qt
47 // SMPTE 240M uses a ~2.2 power curve, not BT.709 piecewise-linear; Qt groups it with Gamma22.
48 case GST_VIDEO_TRANSFER_SMPTE240M:
49 case GST_VIDEO_TRANSFER_GAMMA22:
50 case GST_VIDEO_TRANSFER_SRGB:
51 case GST_VIDEO_TRANSFER_ADOBERGB:
52 return QVideoFrameFormat::ColorTransfer_Gamma22;
53 case GST_VIDEO_TRANSFER_GAMMA18:
54 return QVideoFrameFormat::ColorTransfer_BT709; // matches Qt qgst.cpp GAMMA18 mapping
55 case GST_VIDEO_TRANSFER_GAMMA28:
56 return QVideoFrameFormat::ColorTransfer_Gamma28;
57 case GST_VIDEO_TRANSFER_GAMMA10:
58 return QVideoFrameFormat::ColorTransfer_Linear;
59 case GST_VIDEO_TRANSFER_SMPTE2084:
60 return QVideoFrameFormat::ColorTransfer_ST2084;
61 case GST_VIDEO_TRANSFER_ARIB_STD_B67:
62 return QVideoFrameFormat::ColorTransfer_STD_B67;
63 // GST_VIDEO_TRANSFER_LOG100 / LOG316 have no Qt equivalent — leave as Unknown
64 default:
65 return QVideoFrameFormat::ColorTransfer_Unknown;
66 }
67}
68
69QVideoFrameFormat::PixelFormat toQtPixelFormat(GstVideoFormat fmt)
70{
71 for (const auto& e : GstQgc::kVideoFormatTable) {
72 if (e.gst == fmt) {
73 return e.qt;
74 }
75 }
76 return QVideoFrameFormat::Format_Invalid;
77}
78
79QVideoFrameFormat::ColorRange toQtColorRange(GstVideoColorRange range)
80{
81 switch (range) {
82 case GST_VIDEO_COLOR_RANGE_0_255:
83 return QVideoFrameFormat::ColorRange_Full;
84 case GST_VIDEO_COLOR_RANGE_16_235:
85 return QVideoFrameFormat::ColorRange_Video;
86 default:
87 return QVideoFrameFormat::ColorRange_Unknown;
88 }
89}
90
91// Operates on the GstVideoOrientationMethod enum, which is in <gst/video/video.h>'s
92// always-present subset — independent of QGC_HAS_GST_VIDEO_ORIENTATION_META.
93void applyOrientationToFrame(QVideoFrame& frame, GstVideoOrientationMethod method)
94{
95 switch (method) {
96 case GST_VIDEO_ORIENTATION_IDENTITY:
97 frame.setRotation(QtVideo::Rotation::None);
98 frame.setMirrored(false);
99 break;
100 case GST_VIDEO_ORIENTATION_90R:
101 frame.setRotation(QtVideo::Rotation::Clockwise90);
102 frame.setMirrored(false);
103 break;
104 case GST_VIDEO_ORIENTATION_180:
105 frame.setRotation(QtVideo::Rotation::Clockwise180);
106 frame.setMirrored(false);
107 break;
108 case GST_VIDEO_ORIENTATION_90L:
109 frame.setRotation(QtVideo::Rotation::Clockwise270);
110 frame.setMirrored(false);
111 break;
112 case GST_VIDEO_ORIENTATION_HORIZ:
113 frame.setRotation(QtVideo::Rotation::None);
114 frame.setMirrored(true);
115 break;
116 case GST_VIDEO_ORIENTATION_VERT:
117 frame.setRotation(QtVideo::Rotation::Clockwise180);
118 frame.setMirrored(true);
119 break;
120 case GST_VIDEO_ORIENTATION_UL_LR:
121 frame.setRotation(QtVideo::Rotation::Clockwise90);
122 frame.setMirrored(true);
123 break;
124 case GST_VIDEO_ORIENTATION_UR_LL:
125 frame.setRotation(QtVideo::Rotation::Clockwise270);
126 frame.setMirrored(true);
127 break;
128 default:
129 static std::atomic<bool> s_warnedUnhandled{false};
130 if (!s_warnedUnhandled.exchange(true, std::memory_order_relaxed)) {
131 qCWarning(GStreamerFrameMapLog)
132 << "Unhandled GstVideoOrientationMethod" << method << "— treating as identity";
133 }
134 frame.setRotation(QtVideo::Rotation::None);
135 frame.setMirrored(false);
136 break;
137 }
138}
139
140void applyOrientationAndTiming(QVideoFrame& frame, [[maybe_unused]] GstBuffer* buffer, int streamOrientation)
141{
142 // Per-buffer meta wins (per-frame override) when gst-video exports it; stream-level fallback
143 // works on every install.
144#ifdef QGC_HAS_GST_VIDEO_ORIENTATION_META
145 if (GstVideoOrientationMeta* meta = gst_buffer_get_video_orientation_meta(buffer)) {
146 applyOrientationToFrame(frame, meta->orientation);
147 } else
148#endif
149 if (streamOrientation != static_cast<int>(GST_VIDEO_ORIENTATION_IDENTITY)) {
150 applyOrientationToFrame(frame, static_cast<GstVideoOrientationMethod>(streamOrientation));
151 }
152 if (GST_BUFFER_PTS_IS_VALID(buffer)) {
153 // GstClockTime is ns; QVideoFrame timestamps are µs.
154 frame.setStartTime(GST_BUFFER_PTS(buffer) / GST_USECOND);
155 if (GST_BUFFER_DURATION_IS_VALID(buffer)) {
156 frame.setEndTime((GST_BUFFER_PTS(buffer) + GST_BUFFER_DURATION(buffer)) / GST_USECOND);
157 } else {
158 // No duration: collapse the interval so consumers never see a stale/zero endTime.
159 frame.setEndTime(GST_BUFFER_PTS(buffer) / GST_USECOND);
160 }
161 }
162}
163
164void applyColorimetry(QVideoFrameFormat& format, const GstVideoInfo& info, GstCaps* caps)
165{
166 const GstVideoColorimetry& colorimetry = GST_VIDEO_INFO_COLORIMETRY(&info);
167 QVideoFrameFormat::ColorSpace colorSpace = toQtColorSpace(colorimetry.matrix);
168 // Live RTSP sources often omit colorimetry caps; match Qt's renderer fallback
169 // (qvideotexturehelper.cpp): height > 576 is HD/BT.709, otherwise SD/BT.601.
170 if (colorSpace == QVideoFrameFormat::ColorSpace_Undefined) {
171 const int height = GST_VIDEO_INFO_HEIGHT(&info);
172 if (height > 0) {
173 colorSpace = (height > 576) ? QVideoFrameFormat::ColorSpace_BT709 : QVideoFrameFormat::ColorSpace_BT601;
174 }
175 }
176 format.setColorSpace(colorSpace);
177 format.setColorTransfer(toQtColorTransfer(colorimetry.transfer));
178 QVideoFrameFormat::ColorRange range = toQtColorRange(colorimetry.range);
179 // H.264/H.265 omit VUI range but encode limited per spec — else Qt skips its limited->full offset.
180 // Only infer for a known YUV matrix: an UNKNOWN matrix tells us nothing, and RGB is always full-range.
181 const bool knownYuvMatrix =
182 (colorimetry.matrix == GST_VIDEO_COLOR_MATRIX_BT601) || (colorimetry.matrix == GST_VIDEO_COLOR_MATRIX_BT709) ||
183 (colorimetry.matrix == GST_VIDEO_COLOR_MATRIX_BT2020) ||
184 (colorimetry.matrix == GST_VIDEO_COLOR_MATRIX_SMPTE240M) || (colorimetry.matrix == GST_VIDEO_COLOR_MATRIX_FCC);
185 if (range == QVideoFrameFormat::ColorRange_Unknown && knownYuvMatrix) {
186 range = QVideoFrameFormat::ColorRange_Video;
187 }
188 format.setColorRange(range);
189
190 // Prefer MaxCLL (tighter tone-mapping target) over mastering-display max-luminance.
191 GstVideoContentLightLevel cll;
192 bool clipApplied = false;
193 if (caps && gst_video_content_light_level_from_caps(&cll, caps) && cll.max_content_light_level > 0) {
194 format.setMaxLuminance(static_cast<float>(cll.max_content_light_level));
195 clipApplied = true;
196 }
197 if (!clipApplied) {
198 GstVideoMasteringDisplayInfo masteringInfo;
199 if (caps && gst_video_mastering_display_info_from_caps(&masteringInfo, caps)) {
200 // GstVideoMasteringDisplayColorVolume max_luma is in 0.0001 cd/m².
201 const double maxLuminance = static_cast<double>(masteringInfo.max_display_mastering_luminance) / 10000.0;
202 if (maxLuminance > 0.0) {
203 format.setMaxLuminance(static_cast<float>(maxLuminance));
204 }
205 }
206 }
207}
208
209// QQuickVideoOutput computes sample rect as viewport/frameSize (qquickvideooutput.cpp:498);
210// externalTextureMatrix is only used for Format_SamplerExternalOES, so can't crop standard formats.
211QVideoFrameFormat applyCropMeta(QVideoFrameFormat format, GstBuffer* buffer)
212{
213 if (GstVideoCropMeta* crop = gst_buffer_get_video_crop_meta(buffer)) {
214 format.setViewport(QRect(crop->x, crop->y, crop->width, crop->height));
215 }
216 return format;
217}
218
219MappedFrame mapSampleToFrame(GstBuffer* buffer, [[maybe_unused]] GstCaps* caps, const GstVideoInfo& info,
220 const QVideoFrameFormat& format, [[maybe_unused]] const HwVideoBufferContext& hwContext,
221 [[maybe_unused]] HwResolvedPathCache* pathCache) noexcept
222{
223 MappedFrame out;
224#if defined(QGC_HAS_ANY_GPU_PATH)
226 if (hwContext.gpuEnabled) {
227 // GPU-only: GstHwVideoBuffer holds the sample for the frame's lifetime; makeHwVideoBuffer
228 // takes its own ref, so drop ours immediately after.
229 if (GstSample* sample = gst_sample_new(buffer, caps, nullptr, nullptr)) {
230 auto hwBuf = makeHwVideoBuffer(sample, info, format, hwContext, matchedPath, pathCache);
231 gst_sample_unref(sample);
232 if (hwBuf) {
233 out.frame = QVideoFrame(std::move(hwBuf));
235 out.gpuPath = matchedPath;
236 return out;
237 }
238 }
239 // HW selection failed though GPU was requested: signal the demotion. show_frame owns the
240 // once-per-epoch latch and telemetry so this mapper stays free of the telemetry singleton.
241 out.demoted = true;
242 }
243 out.gpuPath = matchedPath;
244#endif
245 if (auto cpuBuf = CpuVideoFramePool::wrapZeroCopy(buffer, info, format)) {
246 out.frame = QVideoFrame(std::move(cpuBuf));
247 } else {
248 out.frame = CpuVideoFramePool::copyFromBuffer(buffer, info, format);
249 }
251 return out;
252}
QVideoFrameFormat::PixelFormat toQtPixelFormat(GstVideoFormat fmt)
QVideoFrameFormat::ColorTransfer toQtColorTransfer(GstVideoTransferFunction transfer)
QVideoFrameFormat applyCropMeta(QVideoFrameFormat format, GstBuffer *buffer)
Apply video crop meta to format's viewport. Pass-through when no crop meta present.
void applyColorimetry(QVideoFrameFormat &format, const GstVideoInfo &info, GstCaps *caps)
MappedFrame mapSampleToFrame(GstBuffer *buffer, GstCaps *caps, const GstVideoInfo &info, const QVideoFrameFormat &format, const HwVideoBufferContext &hwContext, HwResolvedPathCache *pathCache) noexcept
QVideoFrameFormat::ColorSpace toQtColorSpace(GstVideoColorMatrix matrix)
Sample-to-frame helpers for qgcqvideosink's show_frame; pure functions, streaming-thread safe.
void applyOrientationAndTiming(QVideoFrame &frame, GstBuffer *buffer, int streamOrientation)
void applyOrientationToFrame(QVideoFrame &frame, GstVideoOrientationMethod method)
Apply rotation + mirror flags derived from GstVideoOrientationMethod.
QVideoFrameFormat::ColorRange toQtColorRange(GstVideoColorRange range)
std::unique_ptr< QHwVideoBuffer > makeHwVideoBuffer(GstSample *sample, const GstVideoInfo &info, QVideoFrameFormat format, const HwVideoBufferContext &context, HwVideoBufferPath &matchedPath, HwResolvedPathCache *cache)
HwVideoBufferPath
Identifies which GPU path was chosen; used by the adapter to increment the right counter.
#define QGC_LOGGING_CATEGORY(name, categoryStr)
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...
static std::unique_ptr< QAbstractVideoBuffer > wrapZeroCopy(GstBuffer *buffer, const GstVideoInfo &info, const QVideoFrameFormat &format)
constexpr VideoFormatEntry kVideoFormatTable[]
Platform context for the factory; encapsulates EGL handles so callers don't need path-specific ifdefs...
QVideoFrame frame
enum MappedFrame::Source source