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#if QT_CONFIG(opengl)
9#include <QtGui/private/qopenglextensions_p.h>
10#include <qopenglcontext.h>
11#include <qopenglfunctions.h>
12#endif
13#include <private/qhwvideobuffer_p.h>
14#include <private/qvideoframeconverter_p.h>
15#include <private/qplatformvideosink_p.h>
16#include <private/qvideoframe_p.h>
17#include <qvideosink.h>
18#include <qvideoframeformat.h>
19#include <qthread.h>
20#include <qfile.h>
21
23
25{
26public:
27 QAndroidVideoFrameTextures(QRhi *rhi, QSize size, quint64 handle)
28 {
29 m_tex.reset(rhi->newTexture(QRhiTexture::RGBA8, size, 1));
30 m_tex->createFrom({quint64(handle), 0});
31 }
32
33 QRhiTexture *texture(uint plane) const override
34 {
35 return plane == 0 ? m_tex.get() : nullptr;
36 }
37
38private:
39 std::unique_ptr<QRhiTexture> m_tex;
40};
41
43{
44public:
45 AndroidTextureVideoBuffer(std::unique_ptr<QRhiTexture> tex, const QSize &size)
47 m_size(size),
48 m_tex(std::move(tex))
49 {}
50
51 MapData map(QVideoFrame::MapMode mode) override;
52
54 {
55 m_image = {};
56 m_mapMode = QVideoFrame::NotMapped;
57 }
58
59 QVideoFrameTexturesUPtr mapTextures(QRhi &rhi, QVideoFrameTexturesUPtr& /*oldTextures*/) override
60 {
61 return std::make_unique<QAndroidVideoFrameTextures>(&rhi, m_size, m_tex->nativeTexture().object);
62 }
63
64private:
65 QSize m_size;
66 std::unique_ptr<QRhiTexture> m_tex;
67 QImage m_image;
68 QVideoFrame::MapMode m_mapMode = QVideoFrame::NotMapped;
69};
70
72{
73public:
77 QVideoFrameTexturesUPtr mapTextures(QRhi &rhi, QVideoFrameTexturesUPtr& oldTextures) override
78 {
79 return m_atvb.mapTextures(rhi, oldTextures);
80 }
81
82 MapData map(QVideoFrame::MapMode) override { return {}; }
83 void unmap() override {}
84
85private:
87};
88
90{
91 QAbstractVideoBuffer::MapData mapData;
92
93 if (m_mapMode == QVideoFrame::NotMapped && mode == QVideoFrame::ReadOnly) {
94 m_mapMode = QVideoFrame::ReadOnly;
95 m_image = qImageFromVideoFrame(QVideoFramePrivate::createFrame(
96 std::make_unique<ImageFromVideoFrameHelper>(*this),
97 QVideoFrameFormat(m_size, QVideoFrameFormat::Format_RGBA8888)));
98 mapData.planeCount = 1;
99 mapData.bytesPerLine[0] = m_image.bytesPerLine();
100 mapData.dataSize[0] = static_cast<int>(m_image.sizeInBytes());
101 mapData.data[0] = m_image.bits();
102 }
103
104 return mapData;
105}
106
107static const float g_quad[] = {
108 -1.f, -1.f, 0.f, 0.f,
109 -1.f, 1.f, 0.f, 1.f,
110 1.f, 1.f, 1.f, 1.f,
111 1.f, -1.f, 1.f, 0.f
112};
113
115{
116 static QShader getShader(const QString &name)
117 {
118 QFile f(name);
119 if (f.open(QIODevice::ReadOnly))
120 return QShader::fromSerialized(f.readAll());
121 return {};
122 }
123
124public:
125 TextureCopy(QRhi *rhi, QRhiTexture *externalTex)
126 : m_rhi(rhi)
127 {
128 m_vertexBuffer.reset(m_rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(g_quad)));
129 m_vertexBuffer->create();
130
131 m_uniformBuffer.reset(m_rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 64 + 64 + 4 + 4));
132 m_uniformBuffer->create();
133
134 m_sampler.reset(m_rhi->newSampler(QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None,
135 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge));
136 m_sampler->create();
137
138 m_srb.reset(m_rhi->newShaderResourceBindings());
139 m_srb->setBindings({
140 QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, m_uniformBuffer.get()),
141 QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, externalTex, m_sampler.get())
142 });
143 m_srb->create();
144
145 m_vertexShader = getShader(QStringLiteral(":/qt-project.org/multimedia/shaders/externalsampler.vert.qsb"));
146 Q_ASSERT(m_vertexShader.isValid());
147 m_fragmentShader = getShader(QStringLiteral(":/qt-project.org/multimedia/shaders/externalsampler.frag.qsb"));
148 Q_ASSERT(m_fragmentShader.isValid());
149 }
150
151#if QT_CONFIG(opengl)
153#endif
154
155private:
156 QRhi *m_rhi = nullptr;
157 std::unique_ptr<QRhiBuffer> m_vertexBuffer;
158 std::unique_ptr<QRhiBuffer> m_uniformBuffer;
159 std::unique_ptr<QRhiSampler> m_sampler;
160 std::unique_ptr<QRhiShaderResourceBindings> m_srb;
161 QShader m_vertexShader;
162 QShader m_fragmentShader;
163};
164
165#if QT_CONFIG(opengl)
166static std::unique_ptr<QRhiGraphicsPipeline> newGraphicsPipeline(QRhi *rhi,
167 QRhiShaderResourceBindings *shaderResourceBindings,
168 QRhiRenderPassDescriptor *renderPassDescriptor,
169 QShader vertexShader,
170 QShader fragmentShader)
171{
172 std::unique_ptr<QRhiGraphicsPipeline> gp(rhi->newGraphicsPipeline());
173 gp->setTopology(QRhiGraphicsPipeline::TriangleFan);
174 gp->setShaderStages({
175 { QRhiShaderStage::Vertex, vertexShader },
176 { QRhiShaderStage::Fragment, fragmentShader }
177 });
178 QRhiVertexInputLayout inputLayout;
179 inputLayout.setBindings({
180 { 4 * sizeof(float) }
181 });
182 inputLayout.setAttributes({
183 { 0, 0, QRhiVertexInputAttribute::Float2, 0 },
184 { 0, 1, QRhiVertexInputAttribute::Float2, 2 * sizeof(float) }
185 });
186 gp->setVertexInputLayout(inputLayout);
187 gp->setShaderResourceBindings(shaderResourceBindings);
188 gp->setRenderPassDescriptor(renderPassDescriptor);
189 gp->create();
190
191 return gp;
192}
193
194std::unique_ptr<QRhiTexture> TextureCopy::copyExternalGlTexture(QSize size, const QMatrix4x4 &externalTexMatrix)
195{
196 std::unique_ptr<QRhiTexture> tex(m_rhi->newTexture(QRhiTexture::RGBA8, size, 1, QRhiTexture::RenderTarget));
197 if (!tex->create()) {
198 qWarning("Failed to create frame texture");
199 return {};
200 }
201
202 std::unique_ptr<QRhiTextureRenderTarget> renderTarget(m_rhi->newTextureRenderTarget({ { tex.get() } }));
203 std::unique_ptr<QRhiRenderPassDescriptor> renderPassDescriptor(renderTarget->newCompatibleRenderPassDescriptor());
204 renderTarget->setRenderPassDescriptor(renderPassDescriptor.get());
205 renderTarget->create();
206
207 QRhiResourceUpdateBatch *rub = m_rhi->nextResourceUpdateBatch();
208 rub->uploadStaticBuffer(m_vertexBuffer.get(), g_quad);
209
210 QMatrix4x4 identity;
211 char *p = m_uniformBuffer->beginFullDynamicBufferUpdateForCurrentFrame();
212 memcpy(p, identity.constData(), 64);
213 memcpy(p + 64, externalTexMatrix.constData(), 64);
214 float opacity = 1.0f;
215 memcpy(p + 64 + 64, &opacity, 4);
216 m_uniformBuffer->endFullDynamicBufferUpdateForCurrentFrame();
217
218 auto graphicsPipeline = newGraphicsPipeline(m_rhi, m_srb.get(), renderPassDescriptor.get(),
219 m_vertexShader, m_fragmentShader);
220
221 const QRhiCommandBuffer::VertexInput vbufBinding(m_vertexBuffer.get(), 0);
222
223 QRhiCommandBuffer *cb = nullptr;
224 if (m_rhi->beginOffscreenFrame(&cb) != QRhi::FrameOpSuccess)
225 return {};
226
227 cb->beginPass(renderTarget.get(), Qt::transparent, { 1.0f, 0 }, rub);
228 cb->setGraphicsPipeline(graphicsPipeline.get());
229 cb->setViewport({0, 0, float(size.width()), float(size.height())});
230 cb->setShaderResources(m_srb.get());
231 cb->setVertexInput(0, 1, &vbufBinding);
232 cb->draw(4);
233 cb->endPass();
234 m_rhi->endOffscreenFrame();
235
236 QOpenGLContext *ctx = QOpenGLContext::currentContext();
237 QOpenGLFunctions *f = ctx->functions();
238 static_cast<QOpenGLExtensions *>(f)->flushShared();
239
240 return tex;
241}
242
243static QMatrix4x4 extTransformMatrix(AndroidSurfaceTexture *surfaceTexture)
244{
245 QMatrix4x4 m = surfaceTexture->getTransformMatrix();
246 // flip it back, see
247 // http://androidxref.com/9.0.0_r3/xref/frameworks/native/libs/gui/GLConsumer.cpp#866
248 // (NB our matrix ctor takes row major)
249 static const QMatrix4x4 flipV(1.0f, 0.0f, 0.0f, 0.0f,
250 0.0f, -1.0f, 0.0f, 1.0f,
251 0.0f, 0.0f, 1.0f, 0.0f,
252 0.0f, 0.0f, 0.0f, 1.0f);
253 m *= flipV;
254 return m;
255}
256
257#endif
258
260{
262public:
268
275
276 void start()
277 {
278 QThread::start();
279 moveToThread(this);
280 }
281
282#if QT_CONFIG(opengl)
284 {
289 }
290#endif
291
292public slots:
294 {
295 // Check if 'm_surfaceTexture' is not reset and if the current index is the same that
296 // was used for creating connection because there can be pending frames in queue.
298#if QT_CONFIG(opengl)
305#endif
306 }
307 }
308
309 void clearFrame() { emit newFrame({}); }
310
312
320
322 {
324 return m_surfaceTexture.get();
325
326#if QT_CONFIG(opengl)
328 ? static_cast<const QRhiGles2NativeHandles *>(rhi->nativeHandles())->context
329 : nullptr;
330 initRhi(ctx);
331
333 m_texture->create();
338 [this, index] () { this->onFrameAvailable(index); });
339
341
342 } else {
345 }
346
347 return m_surfaceTexture.get();
348#else
349 qCritical()
350 << "Attempting to use Qt Multimedia VideoOutput on a Qt build without OpenGL support. "
351 << "This is not supported by the native Android media backend.";
352 m_rhi.reset(QRhi::create(QRhi::Null, nullptr));
353 return nullptr;
354#endif
355 }
356
357signals:
358 void newFrame(const QVideoFrame &);
359
360private:
361 QAndroidTextureVideoOutput * m_videoOutput;
362 std::shared_ptr<QRhi> m_rhi;
363 std::unique_ptr<AndroidSurfaceTexture> m_surfaceTexture;
364 std::unique_ptr<QRhiTexture> m_texture;
365 std::unique_ptr<TextureCopy> m_textureCopy;
366 QSize m_size;
367};
368
369QAndroidTextureVideoOutput::QAndroidTextureVideoOutput(QVideoSink *sink, QObject *parent)
370 : QAndroidVideoOutput(parent)
371 , m_sink(sink)
372{
373 if (!m_sink) {
374 qDebug() << "Cannot create QAndroidTextureVideoOutput without a sink.";
375 m_surfaceThread = nullptr;
376 return;
377 }
378
379 startNewSurfaceThread();
380}
381
382void QAndroidTextureVideoOutput::startNewSurfaceThread()
383{
384 m_surfaceThread = std::make_shared<AndroidTextureThread>(this);
385 connect(m_surfaceThread.get(), &AndroidTextureThread::newFrame,
386 this, &QAndroidTextureVideoOutput::newFrame);
387 m_surfaceThread->start();
388}
389
391{
392 // Make sure that no more VideFrames will be created by surfaceThread
393 QMetaObject::invokeMethod(m_surfaceThread.get(),
394 &AndroidTextureThread::clearSurfaceTexture, Qt::BlockingQueuedConnection);
395}
396
397void QAndroidTextureVideoOutput::setSubtitle(const QString &subtitle)
398{
399 if (m_sink) {
400 auto *sink = m_sink->platformVideoSink();
401 if (sink)
402 sink->setSubtitleText(subtitle);
403 }
404}
405
407{
408 return m_sink->rhi() && m_surfaceCreatedWithoutRhi;
409}
410
412{
413 if (!m_sink)
414 return nullptr;
415
416 AndroidSurfaceTexture *surface = nullptr;
417 QMetaObject::invokeMethod(m_surfaceThread.get(), [&]() {
418 auto rhi = m_sink->rhi();
419 if (!rhi) {
420 m_surfaceCreatedWithoutRhi = true;
421 }
422 else if (m_surfaceCreatedWithoutRhi) {
423 m_surfaceThread->clearSurfaceTexture();
424 m_surfaceCreatedWithoutRhi = false;
425 }
426 surface = m_surfaceThread->createSurfaceTexture(rhi);
427 },
428 Qt::BlockingQueuedConnection);
429 return surface;
430}
431
433{
434 if (m_nativeSize == size)
435 return;
436
437 m_nativeSize = size;
438 QMetaObject::invokeMethod(m_surfaceThread.get(),
439 [&](){ m_surfaceThread->setFrameSize(size); },
440 Qt::BlockingQueuedConnection);
441}
442
444{
445 m_nativeSize = {};
446 QMetaObject::invokeMethod(m_surfaceThread.get(), [&](){ m_surfaceThread->clearFrame(); });
447}
448
450{
451 if (m_sink)
452 m_sink->platformVideoSink()->setVideoFrame({});
453 QMetaObject::invokeMethod(m_surfaceThread.get(), &AndroidTextureThread::clearSurfaceTexture);
454}
455
456void QAndroidTextureVideoOutput::newFrame(const QVideoFrame &frame)
457{
458 if (m_sink)
459 m_sink->setVideoFrame(frame);
460}
461
462QT_END_NAMESPACE
463
464#include "qandroidvideooutput.moc"
465#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::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:71
TextureCopy(QRhi *rhi, QRhiTexture *externalTex)
Combined button and popup list for selecting options.
static const float g_quad[]