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