Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
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/qhwvideobuffer_p.h>
10#include <private/qvideoframeconverter_p.h>
11#include <private/qplatformvideosink_p.h>
12#include <private/qvideoframe_p.h>
13#include <qvideosink.h>
14#include <qopenglcontext.h>
15#include <qopenglfunctions.h>
16#include <qvideoframeformat.h>
17#include <qthread.h>
18#include <qfile.h>
19
21
23{
24public:
25 QAndroidVideoFrameTextures(QRhi *rhi, QSize size, quint64 handle)
26 {
27 m_tex.reset(rhi->newTexture(QRhiTexture::RGBA8, size, 1));
28 m_tex->createFrom({quint64(handle), 0});
29 }
30
31 QRhiTexture *texture(uint plane) const override
32 {
33 return plane == 0 ? m_tex.get() : nullptr;
34 }
35
36private:
37 std::unique_ptr<QRhiTexture> m_tex;
38};
39
40// QRhiWithThreadGuard keeps QRhi and QThread (that created it) alive to allow proper cleaning
43public:
47protected:
48 std::shared_ptr<QRhi> m_guardRhi;
49private:
50 std::shared_ptr<AndroidTextureThread> m_thread;
51};
52
54{
55public:
56 AndroidTextureVideoBuffer(std::shared_ptr<QRhi> rhi,
57 std::shared_ptr<AndroidTextureThread> thread,
58 std::unique_ptr<QRhiTexture> tex, const QSize &size)
59 : QRhiWithThreadGuard(std::move(rhi), std::move(thread)),
61 m_size(size),
62 m_tex(std::move(tex))
63 {}
64
65 MapData map(QVideoFrame::MapMode mode) override;
66
68 {
69 m_image = {};
70 m_mapMode = QVideoFrame::NotMapped;
71 }
72
73 QVideoFrameTexturesUPtr mapTextures(QRhi &rhi, QVideoFrameTexturesUPtr& /*oldTextures*/) override
74 {
75 return std::make_unique<QAndroidVideoFrameTextures>(&rhi, m_size, m_tex->nativeTexture().object);
76 }
77
78private:
79 QSize m_size;
80 std::unique_ptr<QRhiTexture> m_tex;
81 QImage m_image;
82 QVideoFrame::MapMode m_mapMode = QVideoFrame::NotMapped;
83};
84
86{
87public:
91 QVideoFrameTexturesUPtr mapTextures(QRhi &rhi, QVideoFrameTexturesUPtr& oldTextures) override
92 {
93 return m_atvb.mapTextures(rhi, oldTextures);
94 }
95
96 MapData map(QVideoFrame::MapMode) override { return {}; }
97 void unmap() override {}
98
99private:
101};
102
104{
105 QAbstractVideoBuffer::MapData mapData;
106
107 if (m_mapMode == QVideoFrame::NotMapped && mode == QVideoFrame::ReadOnly) {
108 m_mapMode = QVideoFrame::ReadOnly;
109 m_image = qImageFromVideoFrame(QVideoFramePrivate::createFrame(
110 std::make_unique<ImageFromVideoFrameHelper>(*this),
111 QVideoFrameFormat(m_size, QVideoFrameFormat::Format_RGBA8888)));
112 mapData.planeCount = 1;
113 mapData.bytesPerLine[0] = m_image.bytesPerLine();
114 mapData.dataSize[0] = static_cast<int>(m_image.sizeInBytes());
115 mapData.data[0] = m_image.bits();
116 }
117
118 return mapData;
119}
120
121static const float g_quad[] = {
122 -1.f, -1.f, 0.f, 0.f,
123 -1.f, 1.f, 0.f, 1.f,
124 1.f, 1.f, 1.f, 1.f,
125 1.f, -1.f, 1.f, 0.f
126};
127
129{
130 static QShader getShader(const QString &name)
131 {
132 QFile f(name);
133 if (f.open(QIODevice::ReadOnly))
134 return QShader::fromSerialized(f.readAll());
135 return {};
136 }
137
138public:
139 TextureCopy(QRhi *rhi, QRhiTexture *externalTex)
140 : m_rhi(rhi)
141 {
142 m_vertexBuffer.reset(m_rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(g_quad)));
143 m_vertexBuffer->create();
144
145 m_uniformBuffer.reset(m_rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 64 + 64 + 4 + 4));
146 m_uniformBuffer->create();
147
148 m_sampler.reset(m_rhi->newSampler(QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None,
149 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge));
150 m_sampler->create();
151
152 m_srb.reset(m_rhi->newShaderResourceBindings());
153 m_srb->setBindings({
154 QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, m_uniformBuffer.get()),
155 QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, externalTex, m_sampler.get())
156 });
157 m_srb->create();
158
159 m_vertexShader = getShader(QStringLiteral(":/qt-project.org/multimedia/shaders/externalsampler.vert.qsb"));
160 Q_ASSERT(m_vertexShader.isValid());
161 m_fragmentShader = getShader(QStringLiteral(":/qt-project.org/multimedia/shaders/externalsampler.frag.qsb"));
162 Q_ASSERT(m_fragmentShader.isValid());
163 }
164
165 std::unique_ptr<QRhiTexture> copyExternalTexture(QSize size, const QMatrix4x4 &externalTexMatrix);
166
167private:
168 QRhi *m_rhi = nullptr;
169 std::unique_ptr<QRhiBuffer> m_vertexBuffer;
170 std::unique_ptr<QRhiBuffer> m_uniformBuffer;
171 std::unique_ptr<QRhiSampler> m_sampler;
172 std::unique_ptr<QRhiShaderResourceBindings> m_srb;
173 QShader m_vertexShader;
174 QShader m_fragmentShader;
175};
176
178 QRhiShaderResourceBindings *shaderResourceBindings,
179 QRhiRenderPassDescriptor *renderPassDescriptor,
180 QShader vertexShader,
181 QShader fragmentShader)
182{
183 std::unique_ptr<QRhiGraphicsPipeline> gp(rhi->newGraphicsPipeline());
184 gp->setTopology(QRhiGraphicsPipeline::TriangleFan);
185 gp->setShaderStages({
186 { QRhiShaderStage::Vertex, vertexShader },
187 { QRhiShaderStage::Fragment, fragmentShader }
188 });
189 QRhiVertexInputLayout inputLayout;
190 inputLayout.setBindings({
191 { 4 * sizeof(float) }
192 });
193 inputLayout.setAttributes({
194 { 0, 0, QRhiVertexInputAttribute::Float2, 0 },
195 { 0, 1, QRhiVertexInputAttribute::Float2, 2 * sizeof(float) }
196 });
197 gp->setVertexInputLayout(inputLayout);
198 gp->setShaderResourceBindings(shaderResourceBindings);
199 gp->setRenderPassDescriptor(renderPassDescriptor);
200 gp->create();
201
202 return gp;
203}
204
205std::unique_ptr<QRhiTexture> TextureCopy::copyExternalTexture(QSize size, const QMatrix4x4 &externalTexMatrix)
206{
207 std::unique_ptr<QRhiTexture> tex(m_rhi->newTexture(QRhiTexture::RGBA8, size, 1, QRhiTexture::RenderTarget));
208 if (!tex->create()) {
209 qWarning("Failed to create frame texture");
210 return {};
211 }
212
213 std::unique_ptr<QRhiTextureRenderTarget> renderTarget(m_rhi->newTextureRenderTarget({ { tex.get() } }));
214 std::unique_ptr<QRhiRenderPassDescriptor> renderPassDescriptor(renderTarget->newCompatibleRenderPassDescriptor());
215 renderTarget->setRenderPassDescriptor(renderPassDescriptor.get());
216 renderTarget->create();
217
218 QRhiResourceUpdateBatch *rub = m_rhi->nextResourceUpdateBatch();
219 rub->uploadStaticBuffer(m_vertexBuffer.get(), g_quad);
220
221 QMatrix4x4 identity;
222 char *p = m_uniformBuffer->beginFullDynamicBufferUpdateForCurrentFrame();
223 memcpy(p, identity.constData(), 64);
224 memcpy(p + 64, externalTexMatrix.constData(), 64);
225 float opacity = 1.0f;
226 memcpy(p + 64 + 64, &opacity, 4);
227 m_uniformBuffer->endFullDynamicBufferUpdateForCurrentFrame();
228
229 auto graphicsPipeline = newGraphicsPipeline(m_rhi, m_srb.get(), renderPassDescriptor.get(),
230 m_vertexShader, m_fragmentShader);
231
232 const QRhiCommandBuffer::VertexInput vbufBinding(m_vertexBuffer.get(), 0);
233
234 QRhiCommandBuffer *cb = nullptr;
235 if (m_rhi->beginOffscreenFrame(&cb) != QRhi::FrameOpSuccess)
236 return {};
237
238 cb->beginPass(renderTarget.get(), Qt::transparent, { 1.0f, 0 }, rub);
239 cb->setGraphicsPipeline(graphicsPipeline.get());
240 cb->setViewport({0, 0, float(size.width()), float(size.height())});
241 cb->setShaderResources(m_srb.get());
242 cb->setVertexInput(0, 1, &vbufBinding);
243 cb->draw(4);
244 cb->endPass();
245 m_rhi->endOffscreenFrame();
246
247 QOpenGLContext *ctx = QOpenGLContext::currentContext();
248 QOpenGLFunctions *f = ctx->functions();
249 static_cast<QOpenGLExtensions *>(f)->flushShared();
250
251 return tex;
252}
253
254static QMatrix4x4 extTransformMatrix(AndroidSurfaceTexture *surfaceTexture)
255{
256 QMatrix4x4 m = surfaceTexture->getTransformMatrix();
257 // flip it back, see
258 // http://androidxref.com/9.0.0_r3/xref/frameworks/native/libs/gui/GLConsumer.cpp#866
259 // (NB our matrix ctor takes row major)
260 static const QMatrix4x4 flipV(1.0f, 0.0f, 0.0f, 0.0f,
261 0.0f, -1.0f, 0.0f, 1.0f,
262 0.0f, 0.0f, 1.0f, 0.0f,
263 0.0f, 0.0f, 0.0f, 1.0f);
264 m *= flipV;
265 return m;
266}
267
269{
271public:
277
284
285 void start()
286 {
287 QThread::start();
288 moveToThread(this);
289 }
290
298
299public slots:
301 {
302 // Check if 'm_surfaceTexture' is not reset and if the current index is the same that
303 // was used for creating connection because there can be pending frames in queue.
311 }
312 }
313
314 void clearFrame() { emit newFrame({}); }
315
317
325
353
354signals:
355 void newFrame(const QVideoFrame &);
356
357private:
358 QAndroidTextureVideoOutput * m_videoOutput;
359 std::shared_ptr<QRhi> m_rhi;
360 std::unique_ptr<AndroidSurfaceTexture> m_surfaceTexture;
361 std::unique_ptr<QRhiTexture> m_texture;
362 std::unique_ptr<TextureCopy> m_textureCopy;
363 QSize m_size;
364};
365
366QRhiWithThreadGuard::~QRhiWithThreadGuard() {
367 // It may happen that reseting m_rhi shared_ptr will delete it (if it is the last reference)
368 // QRHI need to be deleted from the thread that created it.
369 QMetaObject::invokeMethod(m_thread.get(), [&]() {m_guardRhi.reset();}, Qt::BlockingQueuedConnection);
370}
371
372QAndroidTextureVideoOutput::QAndroidTextureVideoOutput(QVideoSink *sink, QObject *parent)
373 : QAndroidVideoOutput(parent)
374 , m_sink(sink)
375{
376 if (!m_sink) {
377 qDebug() << "Cannot create QAndroidTextureVideoOutput without a sink.";
378 m_surfaceThread = nullptr;
379 return;
380 }
381
382 startNewSurfaceThread();
383}
384
385void QAndroidTextureVideoOutput::startNewSurfaceThread()
386{
387 m_surfaceThread = std::make_shared<AndroidTextureThread>(this);
388 connect(m_surfaceThread.get(), &AndroidTextureThread::newFrame,
389 this, &QAndroidTextureVideoOutput::newFrame);
390 m_surfaceThread->start();
391}
392
394{
395 // Make sure that no more VideFrames will be created by surfaceThread
396 QMetaObject::invokeMethod(m_surfaceThread.get(),
397 &AndroidTextureThread::clearSurfaceTexture, Qt::BlockingQueuedConnection);
398}
399
400void QAndroidTextureVideoOutput::setSubtitle(const QString &subtitle)
401{
402 if (m_sink) {
403 auto *sink = m_sink->platformVideoSink();
404 if (sink)
405 sink->setSubtitleText(subtitle);
406 }
407}
408
410{
411 return m_sink->rhi() && m_surfaceCreatedWithoutRhi;
412}
413
415{
416 if (!m_sink)
417 return nullptr;
418
419 AndroidSurfaceTexture *surface = nullptr;
420 QMetaObject::invokeMethod(m_surfaceThread.get(), [&]() {
421 auto rhi = m_sink->rhi();
422 if (!rhi) {
423 m_surfaceCreatedWithoutRhi = true;
424 }
425 else if (m_surfaceCreatedWithoutRhi) {
426 m_surfaceThread->clearSurfaceTexture();
427 m_surfaceCreatedWithoutRhi = false;
428 }
429 surface = m_surfaceThread->createSurfaceTexture(rhi);
430 },
431 Qt::BlockingQueuedConnection);
432 return surface;
433}
434
436{
437 if (m_nativeSize == size)
438 return;
439
440 m_nativeSize = size;
441 QMetaObject::invokeMethod(m_surfaceThread.get(),
442 [&](){ m_surfaceThread->setFrameSize(size); },
443 Qt::BlockingQueuedConnection);
444}
445
447{
448 m_nativeSize = {};
449 QMetaObject::invokeMethod(m_surfaceThread.get(), [&](){ m_surfaceThread->clearFrame(); });
450}
451
453{
454 if (m_sink)
455 m_sink->platformVideoSink()->setVideoFrame({});
456 QMetaObject::invokeMethod(m_surfaceThread.get(), &AndroidTextureThread::clearSurfaceTexture);
457}
458
459void QAndroidTextureVideoOutput::newFrame(const QVideoFrame &frame)
460{
461 if (m_sink)
462 m_sink->setVideoFrame(frame);
463}
464
465QT_END_NAMESPACE
466
467#include "qandroidvideooutput.moc"
468#include "moc_qandroidvideooutput_p.cpp"
MapData map(QVideoFrame::MapMode mode) override
Maps the planes of a video buffer to memory.
void unmap() override
Releases the memory mapped by the map() function.
AndroidTextureVideoBuffer(std::shared_ptr< QRhi > rhi, std::shared_ptr< AndroidTextureThread > thread, std::unique_ptr< QRhiTexture > tex, const QSize &size)
QVideoFrameTexturesUPtr mapTextures(QRhi &rhi, QVideoFrameTexturesUPtr &) override
void unmap() override
Releases the memory mapped by the map() function.
QVideoFrameTexturesUPtr mapTextures(QRhi &rhi, QVideoFrameTexturesUPtr &oldTextures) override
ImageFromVideoFrameHelper(AndroidTextureVideoBuffer &atvb)
MapData map(QVideoFrame::MapMode) override
Maps the planes of a video buffer to memory.
void setSubtitle(const QString &subtitle)
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:95
\inmodule QtGuiPrivate \inheaderfile rhi/qrhi.h
Definition qrhi.h:322
std::shared_ptr< QRhi > m_guardRhi
TextureCopy(QRhi *rhi, QRhiTexture *externalTex)
std::unique_ptr< QRhiTexture > copyExternalTexture(QSize size, const QMatrix4x4 &externalTexMatrix)
Combined button and popup list for selecting options.
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[]