QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
GstAHardwareBufferVideoBuffer.cc
Go to the documentation of this file.
2
3#if defined(QGC_HAS_GST_AHARDWAREBUFFER_GPU_PATH)
4
6
7#include <QtCore/QHash>
8#include <QtCore/QLoggingCategory>
9#include <QtCore/QMutex>
10#include <QtCore/QMutexLocker>
11#include <QtCore/QSize>
12#include <QtGui/QOpenGLContext>
13#include <QtGui/QOpenGLFunctions>
14#include <private/qvideotexturehelper_p.h>
15#include <rhi/qrhi.h>
16
17#include <gst/android/gstandroid.h>
18#include <gst/video/video.h>
19
20#include <GLES2/gl2.h>
21#include <GLES2/gl2ext.h>
22#include <EGL/eglext.h>
23
24#include <android/hardware_buffer.h>
25
26#include <array>
27#include <atomic>
28#include <limits>
29
30QGC_LOGGING_CATEGORY(GstAHWBufLog, "Video.GStreamer.HwBuffers.GstAHWBuf")
31
32namespace {
33
34constexpr int kMaxPlanes = 4;
35
36std::atomic<quint64> s_mapFailureCount{0};
37std::atomic<bool> s_loggedBadBackend{false};
38
39// EGL_ANDROID_image_native_buffer presence cache — one query per EGLDisplay.
40QMutex s_extCacheMutex;
41QHash<EGLDisplay, bool> s_extCache;
42
43bool queryHasNativeBufferExt(EGLDisplay display)
44{
45 QMutexLocker lock(&s_extCacheMutex);
46 auto it = s_extCache.find(display);
47 if (it != s_extCache.end()) {
48 return it.value();
49 }
50 const char *exts = eglQueryString(display, EGL_EXTENSIONS);
51 const bool supported = exts != nullptr
52 && strstr(exts, "EGL_ANDROID_image_native_buffer") != nullptr;
53 s_extCache.insert(display, supported);
54 return supported;
55}
56
57QVideoFrameTexturesUPtr fail()
58{
59 s_mapFailureCount.fetch_add(1, std::memory_order_relaxed);
60 return {};
61}
62
63class FrameTextures final : public QVideoFrameTextures
64{
65public:
66 // AHardwareBuffer is always single-plane external-OES; pixelFormat must be Format_SamplerExternalOES.
67 FrameTextures(QRhi *rhi, QSize size, QVideoFrameFormat::PixelFormat pixelFormat, GLuint name)
68 : _rhi(rhi)
69 , _name(name)
70 {
71 const auto *desc = QVideoTextureHelper::textureDescription(pixelFormat);
72 if (!desc) {
73 qCWarning(GstAHWBufLog) << "no QVideoTextureHelper description for format" << pixelFormat;
74 return;
75 }
76 const QSize planeSize = desc->rhiPlaneSize(size, 0, rhi);
77 // ExternalOES: bind GL_TEXTURE_EXTERNAL_OES + emit SamplerExternalOES — without it OES_EGL_image_external samples black on GLES2.
78 _texture.reset(rhi->newTexture(
79 desc->rhiTextureFormat(0, rhi, QVideoTextureHelper::TextureDescription::FallbackPolicy::Disable),
80 planeSize, 1, QRhiTexture::ExternalOES));
81 if (_texture && !_texture->createFrom({_name, 0})) {
82 qCWarning(GstAHWBufLog) << "QRhiTexture::createFrom failed for AHardwareBuffer plane 0";
83 _texture.reset();
84 }
85 }
86
87 ~FrameTextures() override
88 {
89 releaseGLTextures();
90 }
91
92 void onFrameEndInvoked() override
93 {
94 releaseGLTextures();
95 }
96
97 QRhiTexture *texture(uint plane) const override
98 {
99 return plane == 0 ? _texture.get() : nullptr;
100 }
101
102private:
103 void releaseGLTextures()
104 {
105 if (_released || !_rhi) return;
106 _released = true;
107 _rhi->makeThreadLocalNativeContextCurrent();
108 if (auto *ctx = QOpenGLContext::currentContext()) {
109 ctx->functions()->glDeleteTextures(1, &_name);
110 }
111 }
112
113 QRhi *_rhi = nullptr;
114 GLuint _name = 0;
115 bool _released = false;
116 std::unique_ptr<QRhiTexture> _texture;
117};
118
119} // namespace
120
121GstAHardwareBufferVideoBuffer::GstAHardwareBufferVideoBuffer(GstSample *sample,
122 const GstVideoInfo &videoInfo,
123 const QVideoFrameFormat &format,
124 EGLDisplay eglDisplay)
125 : GstHwVideoBuffer(QVideoFrame::RhiTextureHandle, sample, videoInfo, format)
126 , _eglDisplay(eglDisplay)
127{
128}
129
130GstAHardwareBufferVideoBuffer::~GstAHardwareBufferVideoBuffer() = default;
131
132QAbstractVideoBuffer::MapData GstAHardwareBufferVideoBuffer::map(QVideoFrame::MapMode /*mode*/)
133{
134 return {};
135}
136
137bool GstAHardwareBufferVideoBuffer::validatePlaneHandles() const
138{
139 if (!_sample) return false;
140 GstBuffer *buffer = gst_sample_get_buffer(_sample);
141 if (!buffer) return false;
142 const int memCount = qMin(int(gst_buffer_n_memory(buffer)), kMaxPlanes);
143 if (memCount <= 0) return false;
144 for (int i = 0; i < memCount; ++i) {
145 GstMemory *mem = gst_buffer_peek_memory(buffer, i);
146 if (!mem || !gst_is_ahardware_buffer_memory(mem)) return false;
147 if (!gst_ahardware_buffer_memory_get_buffer(GST_AHARDWARE_BUFFER_MEMORY_CAST(mem))) {
148 return false;
149 }
150 }
151 return true;
152}
153
154QVideoFrameTexturesUPtr GstAHardwareBufferVideoBuffer::mapTextures(QRhi &rhi, QVideoFrameTexturesUPtr & /*old*/)
155{
156 Q_ASSERT(rhi.thread()->isCurrentThread()); // Qt's contract: mapTextures runs on the QRhi (render) thread.
157 // TODO(android-test): needs on-device fixture; no Android CI rig for the test harness here.
158
159 if (!_sample || _eglDisplay == EGL_NO_DISPLAY) {
160 return fail();
161 }
162 if (rhi.backend() != QRhi::OpenGLES2) {
163 if (!s_loggedBadBackend.exchange(true, std::memory_order_relaxed)) {
164 qCWarning(GstAHWBufLog) << "QRhi backend is not OpenGLES2; AHardwareBuffer path unsupported";
165 }
166 return fail();
167 }
168
169 // Bind Qt's GL context on the render thread before any EGL/GL call — without this glBindTexture / glEGLImageTargetTexture2DOES silently no-op into a foreign or null context.
170 rhi.makeThreadLocalNativeContextCurrent();
171
172 // Prefer Qt's actual EGLDisplay over the constructor-passed handle; eglGetDisplay(EGL_DEFAULT_DISPLAY) at construction time can resolve to a different EGLDisplay than the one Qt's context is bound to, which makes EGLImage import succeed but sampling return black.
173 EGLDisplay eglDpy = eglGetCurrentDisplay();
174 if (eglDpy == EGL_NO_DISPLAY) {
175 eglDpy = _eglDisplay;
176 }
177
178 GstBuffer *buffer = gst_sample_get_buffer(_sample);
179 if (!buffer) return fail();
180
181 GstMemory *mem0 = gst_buffer_peek_memory(buffer, 0);
182 if (!mem0 || !gst_is_ahardware_buffer_memory(mem0)) {
183 return fail();
184 }
185
186 if (!queryHasNativeBufferExt(eglDpy)) {
187 static bool s_warned = false;
188 if (!s_warned) {
189 s_warned = true;
190 qCWarning(GstAHWBufLog) << "EGL_ANDROID_image_native_buffer unavailable; AHardwareBuffer path disabled";
191 }
192 return fail();
193 }
194
195 static const auto eglGetNativeClientBufferANDROID_ =
196 reinterpret_cast<PFNEGLGETNATIVECLIENTBUFFERANDROIDPROC>(
197 eglGetProcAddress("eglGetNativeClientBufferANDROID"));
198 static const auto eglCreateImageKHR_ =
199 reinterpret_cast<PFNEGLCREATEIMAGEKHRPROC>(
200 eglGetProcAddress("eglCreateImageKHR"));
201 static const auto eglDestroyImageKHR_ =
202 reinterpret_cast<PFNEGLDESTROYIMAGEKHRPROC>(
203 eglGetProcAddress("eglDestroyImageKHR"));
204 static const auto glEGLImageTargetTexture2DOES_ =
205 reinterpret_cast<PFNGLEGLIMAGETARGETTEXTURE2DOESPROC>(
206 eglGetProcAddress("glEGLImageTargetTexture2DOES"));
207
208 if (!eglGetNativeClientBufferANDROID_ || !eglCreateImageKHR_
209 || !eglDestroyImageKHR_ || !glEGLImageTargetTexture2DOES_) {
210 qCWarning(GstAHWBufLog) << "Required EGL/GL proc addresses unavailable";
211 return fail();
212 }
213
214 const auto *nativeHandles = static_cast<const QRhiGles2NativeHandles *>(rhi.nativeHandles());
215 if (!nativeHandles || !nativeHandles->context) {
216 qCWarning(GstAHWBufLog) << "QRhi exposes no GL context";
217 return fail();
218 }
219
220 AHardwareBuffer *ahwb = gst_ahardware_buffer_memory_get_buffer(
221 GST_AHARDWARE_BUFFER_MEMORY_CAST(mem0));
222 if (!ahwb) {
223 qCWarning(GstAHWBufLog) << "gst_ahardware_buffer_memory_get_buffer returned null";
224 return fail();
225 }
226
227 EGLClientBuffer clientBuffer = eglGetNativeClientBufferANDROID_(ahwb);
228 if (!clientBuffer) {
229 qCWarning(GstAHWBufLog) << "eglGetNativeClientBufferANDROID returned null";
230 return fail();
231 }
232
233 const EGLint attribs[] = { EGL_NONE };
234 EGLImageKHR image = eglCreateImageKHR_(eglDpy, EGL_NO_CONTEXT,
235 EGL_NATIVE_BUFFER_ANDROID,
236 clientBuffer, attribs);
237 if (image == EGL_NO_IMAGE_KHR) {
238 qCWarning(GstAHWBufLog) << "eglCreateImageKHR failed, err=" << Qt::hex << eglGetError();
239 return fail();
240 }
241
242 // AHardwareBuffer is single-plane; GL_TEXTURE_EXTERNAL_OES requires Format_SamplerExternalOES.
243 GLuint name = 0;
244 QOpenGLFunctions functions(nativeHandles->context);
245 functions.glGenTextures(1, &name);
246 functions.glBindTexture(GL_TEXTURE_EXTERNAL_OES, name);
247 glEGLImageTargetTexture2DOES_(GL_TEXTURE_EXTERNAL_OES, image);
248 eglDestroyImageKHR_(eglDpy, image);
249
250 auto textures = std::make_unique<FrameTextures>(&rhi, _format.frameSize(),
251 QVideoFrameFormat::Format_SamplerExternalOES, name);
252 if (!textures->texture(0)) {
253 qCWarning(GstAHWBufLog) << "createFrom failed for plane 0 (SamplerExternalOES)";
254 functions.glDeleteTextures(1, &name);
255 return fail();
256 }
257 return textures;
258}
259
260quint64 GstAHardwareBufferVideoBuffer::takeMapFailureCount()
261{
262 return s_mapFailureCount.exchange(0, std::memory_order_relaxed);
263}
264
265quint64 GstAHardwareBufferVideoBuffer::peekMapFailureCount()
266{
267 return s_mapFailureCount.load(std::memory_order_relaxed);
268}
269
270#endif // QGC_HAS_GST_AHARDWAREBUFFER_GPU_PATH
#define QGC_LOGGING_CATEGORY(name, categoryStr)
QByteArray format(const QList< LogEntry > &entries, int fmt)