Qt 6.x
The Qt SDK
Loading...
Searching...
No Matches
qandroidvideooutput.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
6
7#include <rhi/qrhi.h>
8#include <QtGui/private/qopenglextensions_p.h>
9#include <private/qabstractvideobuffer_p.h>
10#include <private/qvideoframeconverter_p.h>
11#include <private/qplatformvideosink_p.h>
12#include <qvideosink.h>
13#include <qopenglcontext.h>
14#include <qopenglfunctions.h>
15#include <qvideoframeformat.h>
16#include <qthread.h>
17#include <qfile.h>
18
20
22{
23public:
25 {
26 m_tex.reset(rhi->newTexture(QRhiTexture::RGBA8, size, 1));
27 m_tex->createFrom({quint64(handle), 0});
28 }
29
30 QRhiTexture *texture(uint plane) const override
31 {
32 return plane == 0 ? m_tex.get() : nullptr;
33 }
34
35private:
36 std::unique_ptr<QRhiTexture> m_tex;
37};
38
40{
41public:
42 AndroidTextureVideoBuffer(QRhi *rhi, std::unique_ptr<QRhiTexture> tex, const QSize &size)
43 : QAbstractVideoBuffer(QVideoFrame::RhiTextureHandle, rhi)
44 , m_size(size)
45 , m_tex(std::move(tex))
46 {}
47
48 QVideoFrame::MapMode mapMode() const override { return m_mapMode; }
49
50 MapData map(QVideoFrame::MapMode mode) override;
51
52 void unmap() override
53 {
54 m_image = {};
55 m_mapMode = QVideoFrame::NotMapped;
56 }
57
58 std::unique_ptr<QVideoFrameTextures> mapTextures(QRhi *rhi) override
59 {
60 return std::make_unique<QAndroidVideoFrameTextures>(rhi, m_size, m_tex->nativeTexture().object);
61 }
62
63private:
64 QSize m_size;
65 std::unique_ptr<QRhiTexture> m_tex;
66 QImage m_image;
68};
69
71{
72public:
74 : QAbstractVideoBuffer(QVideoFrame::RhiTextureHandle, atvb.rhi())
75 , m_atvb(atvb)
76 {}
77 std::unique_ptr<QVideoFrameTextures> mapTextures(QRhi *rhi) override
78 {
79 return m_atvb.mapTextures(rhi);
80 }
82 MapData map(QVideoFrame::MapMode) override { return {}; }
83 void unmap() override {}
84
85private:
87};
88
90{
92
93 if (m_mapMode == QVideoFrame::NotMapped && mode == QVideoFrame::ReadOnly) {
94 m_mapMode = QVideoFrame::ReadOnly;
97 mapData.nPlanes = 1;
98 mapData.bytesPerLine[0] = m_image.bytesPerLine();
99 mapData.size[0] = static_cast<int>(m_image.sizeInBytes());
100 mapData.data[0] = m_image.bits();
101 }
102
103 return mapData;
104}
105
106static const float g_quad[] = {
107 -1.f, -1.f, 0.f, 0.f,
108 -1.f, 1.f, 0.f, 1.f,
109 1.f, 1.f, 1.f, 1.f,
110 1.f, -1.f, 1.f, 0.f
111};
112
114{
115 static QShader getShader(const QString &name)
116 {
117 QFile f(name);
118 if (f.open(QIODevice::ReadOnly))
119 return QShader::fromSerialized(f.readAll());
120 return {};
121 }
122
123public:
124 TextureCopy(QRhi *rhi, QRhiTexture *externalTex)
125 : m_rhi(rhi)
126 {
127 m_vertexBuffer.reset(m_rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(g_quad)));
128 m_vertexBuffer->create();
129
130 m_uniformBuffer.reset(m_rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 64 + 64 + 4 + 4));
131 m_uniformBuffer->create();
132
135 m_sampler->create();
136
137 m_srb.reset(m_rhi->newShaderResourceBindings());
138 m_srb->setBindings({
141 });
142 m_srb->create();
143
144 m_vertexShader = getShader(QStringLiteral(":/qt-project.org/multimedia/shaders/externalsampler.vert.qsb"));
145 Q_ASSERT(m_vertexShader.isValid());
146 m_fragmentShader = getShader(QStringLiteral(":/qt-project.org/multimedia/shaders/externalsampler.frag.qsb"));
147 Q_ASSERT(m_fragmentShader.isValid());
148 }
149
150 std::unique_ptr<QRhiTexture> copyExternalTexture(QSize size, const QMatrix4x4 &externalTexMatrix);
151
152private:
153 QRhi *m_rhi = nullptr;
154 std::unique_ptr<QRhiBuffer> m_vertexBuffer;
155 std::unique_ptr<QRhiBuffer> m_uniformBuffer;
156 std::unique_ptr<QRhiSampler> m_sampler;
157 std::unique_ptr<QRhiShaderResourceBindings> m_srb;
158 QShader m_vertexShader;
159 QShader m_fragmentShader;
160};
161
162static std::unique_ptr<QRhiGraphicsPipeline> newGraphicsPipeline(QRhi *rhi,
163 QRhiShaderResourceBindings *shaderResourceBindings,
164 QRhiRenderPassDescriptor *renderPassDescriptor,
165 QShader vertexShader,
166 QShader fragmentShader)
167{
168 std::unique_ptr<QRhiGraphicsPipeline> gp(rhi->newGraphicsPipeline());
170 gp->setShaderStages({
171 { QRhiShaderStage::Vertex, vertexShader },
172 { QRhiShaderStage::Fragment, fragmentShader }
173 });
174 QRhiVertexInputLayout inputLayout;
175 inputLayout.setBindings({
176 { 4 * sizeof(float) }
177 });
178 inputLayout.setAttributes({
180 { 0, 1, QRhiVertexInputAttribute::Float2, 2 * sizeof(float) }
181 });
182 gp->setVertexInputLayout(inputLayout);
183 gp->setShaderResourceBindings(shaderResourceBindings);
184 gp->setRenderPassDescriptor(renderPassDescriptor);
185 gp->create();
186
187 return gp;
188}
189
190std::unique_ptr<QRhiTexture> TextureCopy::copyExternalTexture(QSize size, const QMatrix4x4 &externalTexMatrix)
191{
192 std::unique_ptr<QRhiTexture> tex(m_rhi->newTexture(QRhiTexture::RGBA8, size, 1, QRhiTexture::RenderTarget));
193 if (!tex->create()) {
194 qWarning("Failed to create frame texture");
195 return {};
196 }
197
198 std::unique_ptr<QRhiTextureRenderTarget> renderTarget(m_rhi->newTextureRenderTarget({ { tex.get() } }));
199 std::unique_ptr<QRhiRenderPassDescriptor> renderPassDescriptor(renderTarget->newCompatibleRenderPassDescriptor());
200 renderTarget->setRenderPassDescriptor(renderPassDescriptor.get());
201 renderTarget->create();
202
203 QRhiResourceUpdateBatch *rub = m_rhi->nextResourceUpdateBatch();
204 rub->uploadStaticBuffer(m_vertexBuffer.get(), g_quad);
205
206 QMatrix4x4 identity;
207 char *p = m_uniformBuffer->beginFullDynamicBufferUpdateForCurrentFrame();
208 memcpy(p, identity.constData(), 64);
209 memcpy(p + 64, externalTexMatrix.constData(), 64);
210 float opacity = 1.0f;
211 memcpy(p + 64 + 64, &opacity, 4);
212 m_uniformBuffer->endFullDynamicBufferUpdateForCurrentFrame();
213
214 auto graphicsPipeline = newGraphicsPipeline(m_rhi, m_srb.get(), renderPassDescriptor.get(),
215 m_vertexShader, m_fragmentShader);
216
217 const QRhiCommandBuffer::VertexInput vbufBinding(m_vertexBuffer.get(), 0);
218
219 QRhiCommandBuffer *cb = nullptr;
220 if (m_rhi->beginOffscreenFrame(&cb) != QRhi::FrameOpSuccess)
221 return {};
222
223 cb->beginPass(renderTarget.get(), Qt::transparent, { 1.0f, 0 }, rub);
224 cb->setGraphicsPipeline(graphicsPipeline.get());
225 cb->setViewport({0, 0, float(size.width()), float(size.height())});
226 cb->setShaderResources(m_srb.get());
227 cb->setVertexInput(0, 1, &vbufBinding);
228 cb->draw(4);
229 cb->endPass();
230 m_rhi->endOffscreenFrame();
231
233 QOpenGLFunctions *f = ctx->functions();
234 static_cast<QOpenGLExtensions *>(f)->flushShared();
235
236 return tex;
237}
238
240{
241 QMatrix4x4 m = surfaceTexture->getTransformMatrix();
242 // flip it back, see
243 // http://androidxref.com/9.0.0_r3/xref/frameworks/native/libs/gui/GLConsumer.cpp#866
244 // (NB our matrix ctor takes row major)
245 static const QMatrix4x4 flipV(1.0f, 0.0f, 0.0f, 0.0f,
246 0.0f, -1.0f, 0.0f, 1.0f,
247 0.0f, 0.0f, 1.0f, 0.0f,
248 0.0f, 0.0f, 0.0f, 1.0f);
249 m *= flipV;
250 return m;
251}
252
254{
256public:
258
260 {
262 params.shareContext = context;
263 params.fallbackSurface = QRhiGles2InitParams::newFallbackSurface();
264 m_rhi.reset(QRhi::create(QRhi::OpenGLES2, &params));
265 }
266
267public slots:
269 {
270 // Check if 'm_surfaceTexture' is not reset because there can be pending frames in queue.
271 if (m_surfaceTexture) {
272 m_surfaceTexture->updateTexImage();
273 auto matrix = extTransformMatrix(m_surfaceTexture.get());
274 auto tex = m_textureCopy->copyExternalTexture(m_size, matrix);
275 auto *buffer = new AndroidTextureVideoBuffer(m_rhi.get(), std::move(tex), m_size);
278 }
279 }
280
281 void clearFrame() { emit newFrame({}); }
282
283 void setFrameSize(QSize size) { m_size = size; }
284
286 {
287 m_surfaceTexture.reset();
288 m_texture.reset();
289 m_textureCopy.reset();
290 m_rhi.reset();
291 }
292
294 {
295 if (m_surfaceTexture)
296 return m_surfaceTexture.get();
297
298 QOpenGLContext *ctx = rhi
299 ? static_cast<const QRhiGles2NativeHandles *>(rhi->nativeHandles())->context
300 : nullptr;
301 initRhi(ctx);
302
303 m_texture.reset(m_rhi->newTexture(QRhiTexture::RGBA8, m_size, 1, QRhiTexture::ExternalOES));
304 m_texture->create();
305 m_surfaceTexture = std::make_unique<AndroidSurfaceTexture>(m_texture->nativeTexture().object);
306 if (m_surfaceTexture->surfaceTexture()) {
307 connect(m_surfaceTexture.get(), &AndroidSurfaceTexture::frameAvailable, this,
309
310 m_textureCopy = std::make_unique<TextureCopy>(m_rhi.get(), m_texture.get());
311
312 } else {
313 m_texture.reset();
314 m_surfaceTexture.reset();
315 }
316
317 return m_surfaceTexture.get();
318 }
319
320signals:
321 void newFrame(const QVideoFrame &);
322
323private:
324 std::unique_ptr<QRhi> m_rhi;
325 std::unique_ptr<AndroidSurfaceTexture> m_surfaceTexture;
326 std::unique_ptr<QRhiTexture> m_texture;
327 std::unique_ptr<TextureCopy> m_textureCopy;
328 QSize m_size;
329};
330
333 , m_sink(sink)
334{
335 if (!m_sink) {
336 qDebug() << "Cannot create QAndroidTextureVideoOutput without a sink.";
337 m_surfaceThread = nullptr;
338 return;
339 }
340
341 m_surfaceThread = std::make_unique<AndroidTextureThread>();
342 connect(m_surfaceThread.get(), &AndroidTextureThread::newFrame,
343 this, &QAndroidTextureVideoOutput::newFrame);
344
345 m_surfaceThread->start();
346 m_surfaceThread->moveToThread(m_surfaceThread.get());
347}
348
350{
351 QMetaObject::invokeMethod(m_surfaceThread.get(),
353 m_surfaceThread->quit();
354 m_surfaceThread->wait();
355}
356
358{
359 if (m_sink) {
360 auto *sink = m_sink->platformVideoSink();
361 if (sink)
362 sink->setSubtitleText(subtitle);
363 }
364}
365
367{
368 return m_sink->rhi() && m_surfaceCreatedWithoutRhi;
369}
370
372{
373 if (!m_sink)
374 return nullptr;
375
377 QMetaObject::invokeMethod(m_surfaceThread.get(), [&]() {
378 auto rhi = m_sink->rhi();
379 if (!rhi) {
380 m_surfaceCreatedWithoutRhi = true;
381 }
382 else if (m_surfaceCreatedWithoutRhi) {
383 m_surfaceThread->clearSurfaceTexture();
384 m_surfaceCreatedWithoutRhi = false;
385 }
386 surface = m_surfaceThread->createSurfaceTexture(rhi);
387 },
389 return surface;
390}
391
393{
394 if (m_nativeSize == size)
395 return;
396
397 m_nativeSize = size;
398 QMetaObject::invokeMethod(m_surfaceThread.get(),
399 [&](){ m_surfaceThread->setFrameSize(size); },
401}
402
404{
405 m_nativeSize = {};
406 QMetaObject::invokeMethod(m_surfaceThread.get(), [&](){ m_surfaceThread->clearFrame(); });
407}
408
410{
411 if (m_sink)
412 m_sink->platformVideoSink()->setVideoFrame({});
414}
415
416void QAndroidTextureVideoOutput::newFrame(const QVideoFrame &frame)
417{
418 if (m_sink)
419 m_sink->setVideoFrame(frame);
420}
421
423
424#include "qandroidvideooutput.moc"
425#include "moc_qandroidvideooutput_p.cpp"
void newFrame(const QVideoFrame &)
AndroidSurfaceTexture * createSurfaceTexture(QRhi *rhi)
void initRhi(QOpenGLContext *context)
MapData map(QVideoFrame::MapMode mode) override
Independently maps the planes of a video buffer to memory.
QVideoFrame::MapMode mapMode() const override
void unmap() override
Releases the memory mapped by the map() function.
std::unique_ptr< QVideoFrameTextures > mapTextures(QRhi *rhi) override
AndroidTextureVideoBuffer(QRhi *rhi, std::unique_ptr< QRhiTexture > tex, const QSize &size)
QVideoFrame::MapMode mapMode() const override
void unmap() override
Releases the memory mapped by the map() function.
ImageFromVideoFrameHelper(AndroidTextureVideoBuffer &atvb)
MapData map(QVideoFrame::MapMode) override
Independently maps the planes of a video buffer to memory.
std::unique_ptr< QVideoFrameTextures > mapTextures(QRhi *rhi) override
The QAbstractVideoBuffer class is an abstraction for video data. \inmodule QtMultimedia.
QRhi * rhi() const
Returns the QRhi instance.
void setSubtitle(const QString &subtitle)
QAndroidTextureVideoOutput(QVideoSink *sink, QObject *parent=0)
void setVideoSize(const QSize &) override
AndroidSurfaceTexture * surfaceTexture() override
QAndroidVideoFrameTextures(QRhi *rhi, QSize size, quint64 handle)
QRhiTexture * texture(uint plane) const override
\inmodule QtCore
Definition qfile.h:93
\inmodule QtGui
Definition qimage.h:37
qsizetype bytesPerLine() const
Returns the number of bytes per image scanline.
Definition qimage.cpp:1538
qsizetype sizeInBytes() const
Definition qimage.cpp:1526
uchar * bits()
Returns a pointer to the first pixel data.
Definition qimage.cpp:1677
The QMatrix4x4 class represents a 4x4 transformation matrix in 3D space.
Definition qmatrix4x4.h:25
const float * constData() const
Returns a constant pointer to the raw data of this matrix.
Definition qmatrix4x4.h:147
\inmodule QtCore
Definition qobject.h:90
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2823
\inmodule QtGui
static QOpenGLContext * currentContext()
Returns the last context which called makeCurrent in the current thread, or \nullptr,...
The QOpenGLFunctions class provides cross-platform access to the OpenGL ES 2.0 API.
void setSubtitleText(const QString &subtitleText)
virtual void setVideoFrame(const QVideoFrame &frame)
@ Immutable
Definition qrhi.h:837
@ Dynamic
Definition qrhi.h:839
@ VertexBuffer
Definition qrhi.h:843
@ UniformBuffer
Definition qrhi.h:845
\inmodule QtGui
Definition qrhi.h:1614
QPair< QRhiBuffer *, quint32 > VertexInput
Synonym for QPair<QRhiBuffer *, quint32>.
Definition qrhi.h:1643
\inmodule QtGui
\variable QRhiGles2InitParams::format
\inmodule QtGui
Definition qrhi.h:1119
\inmodule QtGui
Definition qrhi.h:1694
void uploadStaticBuffer(QRhiBuffer *buf, quint32 offset, quint32 size, const void *data)
Enqueues updating a region of a QRhiBuffer buf created with the type QRhiBuffer::Immutable or QRhiBuf...
Definition qrhi.cpp:8615
@ ClampToEdge
Definition qrhi.h:1017
static QRhiShaderResourceBinding sampledTexture(int binding, StageFlags stage, QRhiTexture *tex, QRhiSampler *sampler)
Definition qrhi.cpp:5406
static QRhiShaderResourceBinding uniformBuffer(int binding, StageFlags stage, QRhiBuffer *buf)
Definition qrhi.cpp:5292
\inmodule QtGui
Definition qrhi.h:1190
\inmodule QtGui
Definition qrhi.h:883
@ RenderTarget
Definition qrhi.h:886
@ ExternalOES
Definition qrhi.h:894
\inmodule QtGui
Definition qrhi.h:313
void setBindings(std::initializer_list< QRhiVertexInputBinding > list)
Sets the bindings from the specified list.
Definition qrhi.h:317
void setAttributes(std::initializer_list< QRhiVertexInputAttribute > list)
Sets the attributes from the specified list.
Definition qrhi.h:329
\inmodule QtGui
Definition qrhi.h:1767
QRhiBuffer * newBuffer(QRhiBuffer::Type type, QRhiBuffer::UsageFlags usage, quint32 size)
Definition qrhi.cpp:10079
QRhiShaderResourceBindings * newShaderResourceBindings()
Definition qrhi.cpp:10060
@ OpenGLES2
Definition qrhi.h:1772
QRhiSampler * newSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter, QRhiSampler::Filter mipmapMode, QRhiSampler::AddressMode addressU, QRhiSampler::AddressMode addressV, QRhiSampler::AddressMode addressW=QRhiSampler::Repeat)
Definition qrhi.cpp:10228
QRhiTextureRenderTarget * newTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc, QRhiTextureRenderTarget::Flags flags={})
Definition qrhi.cpp:10245
QRhiGraphicsPipeline * newGraphicsPipeline()
Definition qrhi.cpp:10037
static QRhi * create(Implementation impl, QRhiInitParams *params, Flags flags={}, QRhiNativeHandles *importDevice=nullptr)
Definition qrhi.cpp:8129
QRhiTexture * newTexture(QRhiTexture::Format format, const QSize &pixelSize, int sampleCount=1, QRhiTexture::Flags flags={})
Definition qrhi.cpp:10133
const QRhiNativeHandles * nativeHandles()
Definition qrhi.cpp:9708
@ FrameOpSuccess
Definition qrhi.h:1787
\inmodule QtGui
Definition qshader.h:81
static QShader fromSerialized(const QByteArray &data)
Creates a new QShader instance from the given data.
Definition qshader.cpp:510
bool isValid() const
Definition qshader.cpp:313
\inmodule QtCore
Definition qsize.h:25
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:127
The QVideoFrameFormat class specifies the stream format of a video presentation surface.
The QVideoFrame class represents a frame of video data.
Definition qvideoframe.h:26
MapMode
Enumerates how a video buffer's data is mapped to system memory.
Definition qvideoframe.h:36
The QVideoSink class represents a generic sink for video data.
Definition qvideosink.h:22
void setVideoFrame(const QVideoFrame &frame)
Sets the current video frame.
QPlatformVideoSink * platformVideoSink() const
QRhi * rhi() const
Returns the QRhi instance being used to create texture data in the video frames.
TextureCopy(QRhi *rhi, QRhiTexture *externalTex)
std::unique_ptr< QRhiTexture > copyExternalTexture(QSize size, const QMatrix4x4 &externalTexMatrix)
EGLContext ctx
QMap< QString, QString > map
[6]
Combined button and popup list for selecting options.
@ transparent
Definition qnamespace.h:46
@ BlockingQueuedConnection
static void * context
static QMatrix4x4 extTransformMatrix(AndroidSurfaceTexture *surfaceTexture)
static std::unique_ptr< QRhiGraphicsPipeline > newGraphicsPipeline(QRhi *rhi, QRhiShaderResourceBindings *shaderResourceBindings, QRhiRenderPassDescriptor *renderPassDescriptor, QShader vertexShader, QShader fragmentShader)
static const float g_quad[]
#define qDebug
[1]
Definition qlogging.h:160
#define qWarning
Definition qlogging.h:162
GLuint64 GLenum void * handle
GLenum mode
const GLfloat * m
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLfloat GLfloat f
GLenum GLuint buffer
GLuint name
void ** params
GLuint GLenum matrix
GLsizei GLenum GLboolean sink
GLfloat GLfloat p
[1]
static QAbstractVideoBuffer::MapData mapData(const camera_frame_nv12_t &frame, unsigned char *baseAddress)
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
SSL_CTX int(* cb)(SSL *ssl, unsigned char **out, unsigned char *outlen, const unsigned char *in, unsigned int inlen, void *arg)
#define QStringLiteral(str)
#define gp
#define Q_OBJECT
#define slots
#define signals
#define emit
unsigned long long quint64
Definition qtypes.h:56
unsigned int uint
Definition qtypes.h:29
QImage qImageFromVideoFrame(const QVideoFrame &frame, QVideoFrame::RotationAngle rotation, bool mirrorX, bool mirrorY)
if(qFloatDistance(a, b)<(1<< 7))
[0]
QFrame frame
[0]
static bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType, QGenericReturnArgument ret, QGenericArgument val0=QGenericArgument(nullptr), QGenericArgument val1=QGenericArgument(), QGenericArgument val2=QGenericArgument(), QGenericArgument val3=QGenericArgument(), QGenericArgument val4=QGenericArgument(), QGenericArgument val5=QGenericArgument(), QGenericArgument val6=QGenericArgument(), QGenericArgument val7=QGenericArgument(), QGenericArgument val8=QGenericArgument(), QGenericArgument val9=QGenericArgument())
\threadsafe This is an overloaded member function, provided for convenience. It differs from the abov...
IUIAutomationTreeWalker __RPC__deref_out_opt IUIAutomationElement ** parent