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
41{
42public:
43 AndroidTextureVideoBuffer(std::unique_ptr<QRhiTexture> tex, const QSize &size)
45 m_size(size),
46 m_tex(std::move(tex))
47 {}
48
49 MapData map(QVideoFrame::MapMode mode) override;
50
52 {
53 m_image = {};
54 m_mapMode = QVideoFrame::NotMapped;
55 }
56
57 QVideoFrameTexturesUPtr mapTextures(QRhi &rhi, QVideoFrameTexturesUPtr& /*oldTextures*/) override
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
70{
71public:
75 QVideoFrameTexturesUPtr mapTextures(QRhi &rhi, QVideoFrameTexturesUPtr& oldTextures) override
76 {
77 return m_atvb.mapTextures(rhi, oldTextures);
78 }
79
80 MapData map(QVideoFrame::MapMode) override { return {}; }
81 void unmap() override {}
82
83private:
85};
86
88{
89 QAbstractVideoBuffer::MapData mapData;
90
91 if (m_mapMode == QVideoFrame::NotMapped && mode == QVideoFrame::ReadOnly) {
92 m_mapMode = QVideoFrame::ReadOnly;
93 m_image = qImageFromVideoFrame(QVideoFramePrivate::createFrame(
94 std::make_unique<ImageFromVideoFrameHelper>(*this),
95 QVideoFrameFormat(m_size, QVideoFrameFormat::Format_RGBA8888)));
96 mapData.planeCount = 1;
97 mapData.bytesPerLine[0] = m_image.bytesPerLine();
98 mapData.dataSize[0] = static_cast<int>(m_image.sizeInBytes());
99 mapData.data[0] = m_image.bits();
100 }
101
102 return mapData;
103}
104
105static const float g_quad[] = {
106 -1.f, -1.f, 0.f, 0.f,
107 -1.f, 1.f, 0.f, 1.f,
108 1.f, 1.f, 1.f, 1.f,
109 1.f, -1.f, 1.f, 0.f
110};
111
113{
114 static QShader getShader(const QString &name)
115 {
116 QFile f(name);
117 if (f.open(QIODevice::ReadOnly))
118 return QShader::fromSerialized(f.readAll());
119 return {};
120 }
121
122public:
123 TextureCopy(QRhi *rhi, QRhiTexture *externalTex)
124 : m_rhi(rhi)
125 {
126 m_vertexBuffer.reset(m_rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(g_quad)));
127 m_vertexBuffer->create();
128
129 m_uniformBuffer.reset(m_rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 64 + 64 + 4 + 4));
130 m_uniformBuffer->create();
131
132 m_sampler.reset(m_rhi->newSampler(QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None,
133 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge));
134 m_sampler->create();
135
136 m_srb.reset(m_rhi->newShaderResourceBindings());
137 m_srb->setBindings({
138 QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, m_uniformBuffer.get()),
139 QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, externalTex, m_sampler.get())
140 });
141 m_srb->create();
142
143 m_vertexShader = getShader(QStringLiteral(":/qt-project.org/multimedia/shaders/externalsampler.vert.qsb"));
144 Q_ASSERT(m_vertexShader.isValid());
145 m_fragmentShader = getShader(QStringLiteral(":/qt-project.org/multimedia/shaders/externalsampler.frag.qsb"));
146 Q_ASSERT(m_fragmentShader.isValid());
147 }
148
149 std::unique_ptr<QRhiTexture> copyExternalTexture(QSize size, const QMatrix4x4 &externalTexMatrix);
150
151private:
152 QRhi *m_rhi = nullptr;
153 std::unique_ptr<QRhiBuffer> m_vertexBuffer;
154 std::unique_ptr<QRhiBuffer> m_uniformBuffer;
155 std::unique_ptr<QRhiSampler> m_sampler;
156 std::unique_ptr<QRhiShaderResourceBindings> m_srb;
157 QShader m_vertexShader;
158 QShader m_fragmentShader;
159};
160
162 QRhiShaderResourceBindings *shaderResourceBindings,
163 QRhiRenderPassDescriptor *renderPassDescriptor,
164 QShader vertexShader,
165 QShader fragmentShader)
166{
167 std::unique_ptr<QRhiGraphicsPipeline> gp(rhi->newGraphicsPipeline());
168 gp->setTopology(QRhiGraphicsPipeline::TriangleFan);
169 gp->setShaderStages({
170 { QRhiShaderStage::Vertex, vertexShader },
171 { QRhiShaderStage::Fragment, fragmentShader }
172 });
173 QRhiVertexInputLayout inputLayout;
174 inputLayout.setBindings({
175 { 4 * sizeof(float) }
176 });
177 inputLayout.setAttributes({
178 { 0, 0, QRhiVertexInputAttribute::Float2, 0 },
179 { 0, 1, QRhiVertexInputAttribute::Float2, 2 * sizeof(float) }
180 });
181 gp->setVertexInputLayout(inputLayout);
182 gp->setShaderResourceBindings(shaderResourceBindings);
183 gp->setRenderPassDescriptor(renderPassDescriptor);
184 gp->create();
185
186 return gp;
187}
188
189std::unique_ptr<QRhiTexture> TextureCopy::copyExternalTexture(QSize size, const QMatrix4x4 &externalTexMatrix)
190{
191 std::unique_ptr<QRhiTexture> tex(m_rhi->newTexture(QRhiTexture::RGBA8, size, 1, QRhiTexture::RenderTarget));
192 if (!tex->create()) {
193 qWarning("Failed to create frame texture");
194 return {};
195 }
196
197 std::unique_ptr<QRhiTextureRenderTarget> renderTarget(m_rhi->newTextureRenderTarget({ { tex.get() } }));
198 std::unique_ptr<QRhiRenderPassDescriptor> renderPassDescriptor(renderTarget->newCompatibleRenderPassDescriptor());
199 renderTarget->setRenderPassDescriptor(renderPassDescriptor.get());
200 renderTarget->create();
201
202 QRhiResourceUpdateBatch *rub = m_rhi->nextResourceUpdateBatch();
203 rub->uploadStaticBuffer(m_vertexBuffer.get(), g_quad);
204
205 QMatrix4x4 identity;
206 char *p = m_uniformBuffer->beginFullDynamicBufferUpdateForCurrentFrame();
207 memcpy(p, identity.constData(), 64);
208 memcpy(p + 64, externalTexMatrix.constData(), 64);
209 float opacity = 1.0f;
210 memcpy(p + 64 + 64, &opacity, 4);
211 m_uniformBuffer->endFullDynamicBufferUpdateForCurrentFrame();
212
213 auto graphicsPipeline = newGraphicsPipeline(m_rhi, m_srb.get(), renderPassDescriptor.get(),
214 m_vertexShader, m_fragmentShader);
215
216 const QRhiCommandBuffer::VertexInput vbufBinding(m_vertexBuffer.get(), 0);
217
218 QRhiCommandBuffer *cb = nullptr;
219 if (m_rhi->beginOffscreenFrame(&cb) != QRhi::FrameOpSuccess)
220 return {};
221
222 cb->beginPass(renderTarget.get(), Qt::transparent, { 1.0f, 0 }, rub);
223 cb->setGraphicsPipeline(graphicsPipeline.get());
224 cb->setViewport({0, 0, float(size.width()), float(size.height())});
225 cb->setShaderResources(m_srb.get());
226 cb->setVertexInput(0, 1, &vbufBinding);
227 cb->draw(4);
228 cb->endPass();
229 m_rhi->endOffscreenFrame();
230
231 QOpenGLContext *ctx = QOpenGLContext::currentContext();
232 QOpenGLFunctions *f = ctx->functions();
233 static_cast<QOpenGLExtensions *>(f)->flushShared();
234
235 return tex;
236}
237
238static QMatrix4x4 extTransformMatrix(AndroidSurfaceTexture *surfaceTexture)
239{
240 QMatrix4x4 m = surfaceTexture->getTransformMatrix();
241 // flip it back, see
242 // http://androidxref.com/9.0.0_r3/xref/frameworks/native/libs/gui/GLConsumer.cpp#866
243 // (NB our matrix ctor takes row major)
244 static const QMatrix4x4 flipV(1.0f, 0.0f, 0.0f, 0.0f,
245 0.0f, -1.0f, 0.0f, 1.0f,
246 0.0f, 0.0f, 1.0f, 0.0f,
247 0.0f, 0.0f, 0.0f, 1.0f);
248 m *= flipV;
249 return m;
250}
251
253{
255public:
261
268
269 void start()
270 {
271 QThread::start();
272 moveToThread(this);
273 }
274
282
283public slots:
285 {
286 // Check if 'm_surfaceTexture' is not reset and if the current index is the same that
287 // was used for creating connection because there can be pending frames in queue.
295 }
296 }
297
298 void clearFrame() { emit newFrame({}); }
299
301
309
337
338signals:
339 void newFrame(const QVideoFrame &);
340
341private:
342 QAndroidTextureVideoOutput * m_videoOutput;
343 std::shared_ptr<QRhi> m_rhi;
344 std::unique_ptr<AndroidSurfaceTexture> m_surfaceTexture;
345 std::unique_ptr<QRhiTexture> m_texture;
346 std::unique_ptr<TextureCopy> m_textureCopy;
347 QSize m_size;
348};
349
350QAndroidTextureVideoOutput::QAndroidTextureVideoOutput(QVideoSink *sink, QObject *parent)
351 : QAndroidVideoOutput(parent)
352 , m_sink(sink)
353{
354 if (!m_sink) {
355 qDebug() << "Cannot create QAndroidTextureVideoOutput without a sink.";
356 m_surfaceThread = nullptr;
357 return;
358 }
359
360 startNewSurfaceThread();
361}
362
363void QAndroidTextureVideoOutput::startNewSurfaceThread()
364{
365 m_surfaceThread = std::make_shared<AndroidTextureThread>(this);
366 connect(m_surfaceThread.get(), &AndroidTextureThread::newFrame,
367 this, &QAndroidTextureVideoOutput::newFrame);
368 m_surfaceThread->start();
369}
370
372{
373 // Make sure that no more VideFrames will be created by surfaceThread
374 QMetaObject::invokeMethod(m_surfaceThread.get(),
375 &AndroidTextureThread::clearSurfaceTexture, Qt::BlockingQueuedConnection);
376}
377
378void QAndroidTextureVideoOutput::setSubtitle(const QString &subtitle)
379{
380 if (m_sink) {
381 auto *sink = m_sink->platformVideoSink();
382 if (sink)
383 sink->setSubtitleText(subtitle);
384 }
385}
386
388{
389 return m_sink->rhi() && m_surfaceCreatedWithoutRhi;
390}
391
393{
394 if (!m_sink)
395 return nullptr;
396
397 AndroidSurfaceTexture *surface = nullptr;
398 QMetaObject::invokeMethod(m_surfaceThread.get(), [&]() {
399 auto rhi = m_sink->rhi();
400 if (!rhi) {
401 m_surfaceCreatedWithoutRhi = true;
402 }
403 else if (m_surfaceCreatedWithoutRhi) {
404 m_surfaceThread->clearSurfaceTexture();
405 m_surfaceCreatedWithoutRhi = false;
406 }
407 surface = m_surfaceThread->createSurfaceTexture(rhi);
408 },
409 Qt::BlockingQueuedConnection);
410 return surface;
411}
412
414{
415 if (m_nativeSize == size)
416 return;
417
418 m_nativeSize = size;
419 QMetaObject::invokeMethod(m_surfaceThread.get(),
420 [&](){ m_surfaceThread->setFrameSize(size); },
421 Qt::BlockingQueuedConnection);
422}
423
425{
426 m_nativeSize = {};
427 QMetaObject::invokeMethod(m_surfaceThread.get(), [&](){ m_surfaceThread->clearFrame(); });
428}
429
431{
432 if (m_sink)
433 m_sink->platformVideoSink()->setVideoFrame({});
434 QMetaObject::invokeMethod(m_surfaceThread.get(), &AndroidTextureThread::clearSurfaceTexture);
435}
436
437void QAndroidTextureVideoOutput::newFrame(const QVideoFrame &frame)
438{
439 if (m_sink)
440 m_sink->setVideoFrame(frame);
441}
442
443QT_END_NAMESPACE
444
445#include "qandroidvideooutput.moc"
446#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
\inmodule QtGuiPrivate \inheaderfile rhi/qrhi.h
Definition qrhi.h:323
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[]