diff options
Diffstat (limited to 'src/multimedia/platform/android/common')
6 files changed, 958 insertions, 0 deletions
diff --git a/src/multimedia/platform/android/common/common.pri b/src/multimedia/platform/android/common/common.pri new file mode 100644 index 000000000..67a5116d2 --- /dev/null +++ b/src/multimedia/platform/android/common/common.pri @@ -0,0 +1,10 @@ +INCLUDEPATH += $$PWD + +HEADERS += \ + $$PWD/qandroidglobal_p.h \ + $$PWD/qandroidvideooutput_p.h \ + $$PWD/qandroidmultimediautils_p.h + +SOURCES += \ + $$PWD/qandroidvideooutput.cpp \ + $$PWD/qandroidmultimediautils.cpp diff --git a/src/multimedia/platform/android/common/qandroidglobal_p.h b/src/multimedia/platform/android/common/qandroidglobal_p.h new file mode 100644 index 000000000..45bd22ffb --- /dev/null +++ b/src/multimedia/platform/android/common/qandroidglobal_p.h @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QANDROIDGLOBAL_H +#define QANDROIDGLOBAL_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <private/qtmultimediaglobal_p.h> +#include <QtCore/qglobal.h> +#include <QtCore/qloggingcategory.h> + +QT_BEGIN_NAMESPACE + +Q_DECLARE_LOGGING_CATEGORY(qtAndroidMediaPlugin) + +QT_END_NAMESPACE + +#endif // QANDROIDGLOBAL_H diff --git a/src/multimedia/platform/android/common/qandroidmultimediautils.cpp b/src/multimedia/platform/android/common/qandroidmultimediautils.cpp new file mode 100644 index 000000000..850b3d7ea --- /dev/null +++ b/src/multimedia/platform/android/common/qandroidmultimediautils.cpp @@ -0,0 +1,152 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qandroidmultimediautils_p.h" +#include "qandroidglobal_p.h" + +#include <qlist.h> +#include <QtCore/private/qjni_p.h> +#include <QtCore/private/qjnihelpers_p.h> + +QT_BEGIN_NAMESPACE + +int qt_findClosestValue(const QList<int> &list, int value) +{ + if (list.size() < 2) + return 0; + + int begin = 0; + int end = list.size() - 1; + int pivot = begin + (end - begin) / 2; + int v = list.at(pivot); + + while (end - begin > 1) { + if (value == v) + return pivot; + + if (value > v) + begin = pivot; + else + end = pivot; + + pivot = begin + (end - begin) / 2; + v = list.at(pivot); + } + + return value - v >= list.at(pivot + 1) - value ? pivot + 1 : pivot; +} + +bool qt_sizeLessThan(const QSize &s1, const QSize &s2) +{ + return s1.width() * s1.height() < s2.width() * s2.height(); +} + +QVideoFrame::PixelFormat qt_pixelFormatFromAndroidImageFormat(AndroidCamera::ImageFormat f) +{ + switch (f) { + case AndroidCamera::NV21: + return QVideoFrame::Format_NV21; + case AndroidCamera::YV12: + return QVideoFrame::Format_YV12; + case AndroidCamera::RGB565: + return QVideoFrame::Format_RGB565; + case AndroidCamera::YUY2: + return QVideoFrame::Format_YUYV; + case AndroidCamera::JPEG: + return QVideoFrame::Format_Jpeg; + default: + return QVideoFrame::Format_Invalid; + } +} + +AndroidCamera::ImageFormat qt_androidImageFormatFromPixelFormat(QVideoFrame::PixelFormat f) +{ + switch (f) { + case QVideoFrame::Format_NV21: + return AndroidCamera::NV21; + case QVideoFrame::Format_YV12: + return AndroidCamera::YV12; + case QVideoFrame::Format_RGB565: + return AndroidCamera::RGB565; + case QVideoFrame::Format_YUYV: + return AndroidCamera::YUY2; + case QVideoFrame::Format_Jpeg: + return AndroidCamera::JPEG; + default: + return AndroidCamera::UnknownImageFormat; + } +} + +static bool androidRequestPermission(const QString &key) +{ + using namespace QtAndroidPrivate; + + if (androidSdkVersion() < 23) + return true; + + PermissionsResult res = checkPermission(key); + if (res == PermissionsResult::Granted) // Permission already granted? + return true; + + QJNIEnvironmentPrivate env; + const auto &results = requestPermissionsSync(env, QStringList() << key); + if (!results.contains(key)) { + qCWarning(qtAndroidMediaPlugin, "No permission found for key: %s", qPrintable(key)); + return false; + } + + if (results[key] == PermissionsResult::Denied) { + qCDebug(qtAndroidMediaPlugin, "%s - Permission denied by user!", qPrintable(key)); + return false; + } + + return true; +} + +bool qt_androidRequestCameraPermission() +{ + return androidRequestPermission(QLatin1String("android.permission.CAMERA")); +} + +bool qt_androidRequestRecordingPermission() +{ + return androidRequestPermission(QLatin1String("android.permission.RECORD_AUDIO")); +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/android/common/qandroidmultimediautils_p.h b/src/multimedia/platform/android/common/qandroidmultimediautils_p.h new file mode 100644 index 000000000..205244eb5 --- /dev/null +++ b/src/multimedia/platform/android/common/qandroidmultimediautils_p.h @@ -0,0 +1,74 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QANDROIDMULTIMEDIAUTILS_H +#define QANDROIDMULTIMEDIAUTILS_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <qglobal.h> +#include <qsize.h> +#include "androidcamera_p.h" + +QT_BEGIN_NAMESPACE + +// return the index of the closest value to <value> in <list> +// (binary search) +int qt_findClosestValue(const QList<int> &list, int value); + +bool qt_sizeLessThan(const QSize &s1, const QSize &s2); + +QVideoFrame::PixelFormat qt_pixelFormatFromAndroidImageFormat(AndroidCamera::ImageFormat f); +AndroidCamera::ImageFormat qt_androidImageFormatFromPixelFormat(QVideoFrame::PixelFormat f); + +bool qt_androidRequestCameraPermission(); +bool qt_androidRequestRecordingPermission(); + +QT_END_NAMESPACE + +#endif // QANDROIDMULTIMEDIAUTILS_H diff --git a/src/multimedia/platform/android/common/qandroidvideooutput.cpp b/src/multimedia/platform/android/common/qandroidvideooutput.cpp new file mode 100644 index 000000000..0fa6cb84a --- /dev/null +++ b/src/multimedia/platform/android/common/qandroidvideooutput.cpp @@ -0,0 +1,502 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qandroidvideooutput_p.h" + +#include "androidsurfacetexture_p.h" +#include <QAbstractVideoSurface> +#include <QVideoSurfaceFormat> +#include <qevent.h> +#include <qcoreapplication.h> +#include <qopenglcontext.h> +#include <qopenglfunctions.h> +#include <qopenglshaderprogram.h> +#include <qopenglframebufferobject.h> +#include <QtCore/private/qjnihelpers_p.h> +#include <QtGui/QWindow> +#include <QtGui/QOffscreenSurface> + +QT_BEGIN_NAMESPACE + +static const GLfloat g_vertex_data[] = { + -1.f, 1.f, + 1.f, 1.f, + 1.f, -1.f, + -1.f, -1.f +}; + +static const GLfloat g_texture_data[] = { + 0.f, 0.f, + 1.f, 0.f, + 1.f, 1.f, + 0.f, 1.f +}; + +void OpenGLResourcesDeleter::deleteTextureHelper(quint32 id) +{ + if (id != 0) + glDeleteTextures(1, &id); +} + +void OpenGLResourcesDeleter::deleteFboHelper(void *fbo) +{ + delete reinterpret_cast<QOpenGLFramebufferObject *>(fbo); +} + +void OpenGLResourcesDeleter::deleteShaderProgramHelper(void *prog) +{ + delete reinterpret_cast<QOpenGLShaderProgram *>(prog); +} + +void OpenGLResourcesDeleter::deleteThisHelper() +{ + delete this; +} + +class AndroidTextureVideoBuffer : public QAbstractVideoBuffer +{ +public: + AndroidTextureVideoBuffer(QAndroidTextureVideoOutput *output, const QSize &size) + : QAbstractVideoBuffer(GLTextureHandle) + , m_mapMode(NotMapped) + , m_output(output) + , m_size(size) + , m_textureUpdated(false) + { + } + + virtual ~AndroidTextureVideoBuffer() {} + + MapMode mapMode() const override { return m_mapMode; } + + MapData map(MapMode mode) override + { + MapData mapData; + if (m_mapMode == NotMapped && mode == ReadOnly && updateFrame()) { + m_mapMode = mode; + m_image = m_output->m_fbo->toImage(); + + mapData.nBytes = static_cast<int>(m_image.sizeInBytes()); + mapData.nPlanes = 1; + mapData.bytesPerLine[0] = m_image.bytesPerLine(); + mapData.data[0] = m_image.bits(); + } + + return mapData; + } + + void unmap() override + { + m_image = QImage(); + m_mapMode = NotMapped; + } + + QVariant handle() const override + { + AndroidTextureVideoBuffer *that = const_cast<AndroidTextureVideoBuffer*>(this); + if (!that->updateFrame()) + return QVariant(); + + return m_output->m_fbo->texture(); + } + +private: + bool updateFrame() + { + // Even though the texture was updated in a previous call, we need to re-check + // that this has not become a stale buffer, e.g., if the output size changed or + // has since became invalid. + if (!m_output->m_nativeSize.isValid()) + return false; + + // Size changed + if (m_output->m_nativeSize != m_size) + return false; + + // In the unlikely event that we don't have a valid fbo, but have a valid size, + // force an update. + const bool forceUpdate = !m_output->m_fbo; + + if (m_textureUpdated && !forceUpdate) + return true; + + // update the video texture (called from the render thread) + return (m_textureUpdated = m_output->renderFrameToFbo()); + } + + MapMode m_mapMode; + QAndroidTextureVideoOutput *m_output; + QImage m_image; + QSize m_size; + bool m_textureUpdated; +}; + +QAndroidTextureVideoOutput::QAndroidTextureVideoOutput(QObject *parent) + : QAndroidVideoOutput(parent) + , m_surface(0) + , m_surfaceTexture(0) + , m_externalTex(0) + , m_fbo(0) + , m_program(0) + , m_glDeleter(0) + , m_surfaceTextureCanAttachToContext(QtAndroidPrivate::androidSdkVersion() >= 16) +{ + +} + +QAndroidTextureVideoOutput::~QAndroidTextureVideoOutput() +{ + delete m_offscreenSurface; + delete m_glContext; + clearSurfaceTexture(); + + if (m_glDeleter) { // Make sure all of these are deleted on the render thread. + m_glDeleter->deleteFbo(m_fbo); + m_glDeleter->deleteShaderProgram(m_program); + m_glDeleter->deleteTexture(m_externalTex); + m_glDeleter->deleteThis(); + } +} + +QAbstractVideoSurface *QAndroidTextureVideoOutput::surface() const +{ + return m_surface; +} + +void QAndroidTextureVideoOutput::setSurface(QAbstractVideoSurface *surface) +{ + if (surface == m_surface) + return; + + if (m_surface) { + if (m_surface->isActive()) + m_surface->stop(); + + if (!m_surfaceTextureCanAttachToContext) + m_surface->setProperty("_q_GLThreadCallback", QVariant()); + } + + m_surface = surface; + + if (m_surface && !m_surfaceTextureCanAttachToContext) { + m_surface->setProperty("_q_GLThreadCallback", + QVariant::fromValue<QObject*>(this)); + } +} + +bool QAndroidTextureVideoOutput::isReady() +{ + return m_surfaceTextureCanAttachToContext || QOpenGLContext::currentContext() || m_externalTex; +} + +bool QAndroidTextureVideoOutput::initSurfaceTexture() +{ + if (m_surfaceTexture) + return true; + + if (!m_surface) + return false; + + if (!m_surfaceTextureCanAttachToContext) { + // if we have an OpenGL context in the current thread, create a texture. Otherwise, wait + // for the GL render thread to call us back to do it. + if (QOpenGLContext::currentContext()) { + glGenTextures(1, &m_externalTex); + if (!m_glDeleter) + m_glDeleter = new OpenGLResourcesDeleter; + } else if (!m_externalTex) { + return false; + } + } + + QMutexLocker locker(&m_mutex); + + m_surfaceTexture = new AndroidSurfaceTexture(m_externalTex); + + if (m_surfaceTexture->surfaceTexture() != 0) { + connect(m_surfaceTexture, SIGNAL(frameAvailable()), this, SLOT(onFrameAvailable())); + } else { + delete m_surfaceTexture; + m_surfaceTexture = 0; + if (m_glDeleter) + m_glDeleter->deleteTexture(m_externalTex); + m_externalTex = 0; + } + + return m_surfaceTexture != 0; +} + +void QAndroidTextureVideoOutput::clearSurfaceTexture() +{ + QMutexLocker locker(&m_mutex); + if (m_surfaceTexture) { + delete m_surfaceTexture; + m_surfaceTexture = 0; + } + + // Also reset the attached OpenGL texture + // Note: The Android SurfaceTexture class does not release the texture on deletion, + // only if detachFromGLContext() called (API level >= 16), so we'll do it manually, + // on the render thread. + if (m_surfaceTextureCanAttachToContext) { + if (m_glDeleter) + m_glDeleter->deleteTexture(m_externalTex); + m_externalTex = 0; + } +} + +AndroidSurfaceTexture *QAndroidTextureVideoOutput::surfaceTexture() +{ + if (!initSurfaceTexture()) + return 0; + + return m_surfaceTexture; +} + +void QAndroidTextureVideoOutput::setVideoSize(const QSize &size) +{ + QMutexLocker locker(&m_mutex); + if (m_nativeSize == size) + return; + + stop(); + + m_nativeSize = size; +} + +void QAndroidTextureVideoOutput::stop() +{ + if (m_surface && m_surface->isActive()) + m_surface->stop(); + m_nativeSize = QSize(); +} + +void QAndroidTextureVideoOutput::reset() +{ + // flush pending frame + if (m_surface) + m_surface->present(QVideoFrame()); + + clearSurfaceTexture(); +} + +void QAndroidTextureVideoOutput::onFrameAvailable() +{ + if (!m_nativeSize.isValid() || !m_surface) + return; + + QAbstractVideoBuffer *buffer = new AndroidTextureVideoBuffer(this, m_nativeSize); + QVideoFrame frame(buffer, m_nativeSize, QVideoFrame::Format_ABGR32); + + if (m_surface->isActive() && (m_surface->surfaceFormat().pixelFormat() != frame.pixelFormat() + || m_surface->surfaceFormat().frameSize() != frame.size())) { + m_surface->stop(); + } + + if (!m_surface->isActive()) { + QVideoSurfaceFormat format(frame.size(), frame.pixelFormat(), + QAbstractVideoBuffer::GLTextureHandle); + + m_surface->start(format); + } + + if (m_surface->isActive()) + m_surface->present(frame); +} + +bool QAndroidTextureVideoOutput::renderFrameToFbo() +{ + QMutexLocker locker(&m_mutex); + + if (!m_nativeSize.isValid() || !m_surfaceTexture) + return false; + + QOpenGLContext *shareContext = !m_glContext && m_surface + ? qobject_cast<QOpenGLContext*>(m_surface->property("GLContext").value<QObject*>()) + : nullptr; + + // Make sure we have an OpenGL context to make current. + if (shareContext || (!QOpenGLContext::currentContext() && !m_glContext)) { + // Create Hidden QWindow surface to create context in this thread. + m_offscreenSurface = new QWindow(); + m_offscreenSurface->setSurfaceType(QWindow::OpenGLSurface); + // Needs geometry to be a valid surface, but size is not important. + m_offscreenSurface->setGeometry(0, 0, 1, 1); + m_offscreenSurface->create(); + m_offscreenSurface->moveToThread(m_surface->thread()); + + // Create OpenGL context and set share context from surface. + m_glContext = new QOpenGLContext(); + m_glContext->setFormat(m_offscreenSurface->requestedFormat()); + + auto surface = qobject_cast<QAbstractVideoSurface *>(m_surface->property("videoSurface").value<QObject *>()); + if (!surface) + surface = m_surface; + if (shareContext) + m_glContext->setShareContext(shareContext); + + if (!m_glContext->create()) { + qWarning("Failed to create QOpenGLContext"); + return false; + } + } + + if (m_glContext) + m_glContext->makeCurrent(m_offscreenSurface); + + createGLResources(); + + m_surfaceTexture->updateTexImage(); + + // save current render states + GLboolean stencilTestEnabled; + GLboolean depthTestEnabled; + GLboolean scissorTestEnabled; + GLboolean blendEnabled; + glGetBooleanv(GL_STENCIL_TEST, &stencilTestEnabled); + glGetBooleanv(GL_DEPTH_TEST, &depthTestEnabled); + glGetBooleanv(GL_SCISSOR_TEST, &scissorTestEnabled); + glGetBooleanv(GL_BLEND, &blendEnabled); + + if (stencilTestEnabled) + glDisable(GL_STENCIL_TEST); + if (depthTestEnabled) + glDisable(GL_DEPTH_TEST); + if (scissorTestEnabled) + glDisable(GL_SCISSOR_TEST); + if (blendEnabled) + glDisable(GL_BLEND); + + m_fbo->bind(); + + glViewport(0, 0, m_nativeSize.width(), m_nativeSize.height()); + + m_program->bind(); + m_program->enableAttributeArray(0); + m_program->enableAttributeArray(1); + m_program->setUniformValue("frameTexture", GLuint(0)); + m_program->setUniformValue("texMatrix", m_surfaceTexture->getTransformMatrix()); + + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, g_vertex_data); + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, g_texture_data); + + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + + m_program->disableAttributeArray(0); + m_program->disableAttributeArray(1); + + glBindTexture(GL_TEXTURE_EXTERNAL_OES, 0); + m_fbo->release(); + + // restore render states + if (stencilTestEnabled) + glEnable(GL_STENCIL_TEST); + if (depthTestEnabled) + glEnable(GL_DEPTH_TEST); + if (scissorTestEnabled) + glEnable(GL_SCISSOR_TEST); + if (blendEnabled) + glEnable(GL_BLEND); + + return true; +} + +void QAndroidTextureVideoOutput::createGLResources() +{ + Q_ASSERT(QOpenGLContext::currentContext() != NULL); + + if (!m_glDeleter) + m_glDeleter = new OpenGLResourcesDeleter; + + if (m_surfaceTextureCanAttachToContext && !m_externalTex) { + m_surfaceTexture->detachFromGLContext(); + glGenTextures(1, &m_externalTex); + m_surfaceTexture->attachToGLContext(m_externalTex); + } + + if (!m_fbo || m_fbo->size() != m_nativeSize) { + delete m_fbo; + m_fbo = new QOpenGLFramebufferObject(m_nativeSize); + } + + if (!m_program) { + m_program = new QOpenGLShaderProgram; + + QOpenGLShader *vertexShader = new QOpenGLShader(QOpenGLShader::Vertex, m_program); + vertexShader->compileSourceCode("attribute highp vec4 vertexCoordsArray; \n" \ + "attribute highp vec2 textureCoordArray; \n" \ + "uniform highp mat4 texMatrix; \n" \ + "varying highp vec2 textureCoords; \n" \ + "void main(void) \n" \ + "{ \n" \ + " gl_Position = vertexCoordsArray; \n" \ + " textureCoords = (texMatrix * vec4(textureCoordArray, 0.0, 1.0)).xy; \n" \ + "}\n"); + m_program->addShader(vertexShader); + + QOpenGLShader *fragmentShader = new QOpenGLShader(QOpenGLShader::Fragment, m_program); + fragmentShader->compileSourceCode("#extension GL_OES_EGL_image_external : require \n" \ + "varying highp vec2 textureCoords; \n" \ + "uniform samplerExternalOES frameTexture; \n" \ + "void main() \n" \ + "{ \n" \ + " gl_FragColor = texture2D(frameTexture, textureCoords); \n" \ + "}\n"); + m_program->addShader(fragmentShader); + + m_program->bindAttributeLocation("vertexCoordsArray", 0); + m_program->bindAttributeLocation("textureCoordArray", 1); + m_program->link(); + } +} + +void QAndroidTextureVideoOutput::customEvent(QEvent *e) +{ + if (e->type() == QEvent::User) { + // This is running in the render thread (OpenGL enabled) + if (!m_surfaceTextureCanAttachToContext && !m_externalTex) { + glGenTextures(1, &m_externalTex); + if (!m_glDeleter) // We'll use this to cleanup GL resources in the correct thread + m_glDeleter = new OpenGLResourcesDeleter; + emit readyChanged(true); + } + } +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/android/common/qandroidvideooutput_p.h b/src/multimedia/platform/android/common/qandroidvideooutput_p.h new file mode 100644 index 000000000..dbc53ca44 --- /dev/null +++ b/src/multimedia/platform/android/common/qandroidvideooutput_p.h @@ -0,0 +1,156 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QANDROIDVIDEOOUTPUT_H +#define QANDROIDVIDEOOUTPUT_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <qobject.h> +#include <qsize.h> +#include <qmutex.h> + +QT_BEGIN_NAMESPACE + +class AndroidSurfaceTexture; +class AndroidSurfaceHolder; +class QOpenGLFramebufferObject; +class QOpenGLShaderProgram; +class QAbstractVideoSurface; +class QWindow; +class QOpenGLContext; + +class QAndroidVideoOutput : public QObject +{ + Q_OBJECT +public: + virtual ~QAndroidVideoOutput() { } + + virtual AndroidSurfaceTexture *surfaceTexture() { return 0; } + virtual AndroidSurfaceHolder *surfaceHolder() { return 0; } + + virtual bool isReady() { return true; } + + virtual void setVideoSize(const QSize &) { } + virtual void stop() { } + virtual void reset() { } + +Q_SIGNALS: + void readyChanged(bool); + +protected: + QAndroidVideoOutput(QObject *parent) : QObject(parent) { } +}; + +class OpenGLResourcesDeleter : public QObject +{ + Q_OBJECT +public: + void deleteTexture(quint32 id) { QMetaObject::invokeMethod(this, "deleteTextureHelper", Qt::AutoConnection, Q_ARG(quint32, id)); } + void deleteFbo(QOpenGLFramebufferObject *fbo) { QMetaObject::invokeMethod(this, "deleteFboHelper", Qt::AutoConnection, Q_ARG(void *, fbo)); } + void deleteShaderProgram(QOpenGLShaderProgram *prog) { QMetaObject::invokeMethod(this, "deleteShaderProgramHelper", Qt::AutoConnection, Q_ARG(void *, prog)); } + void deleteThis() { QMetaObject::invokeMethod(this, "deleteThisHelper"); } + +private: + Q_INVOKABLE void deleteTextureHelper(quint32 id); + Q_INVOKABLE void deleteFboHelper(void *fbo); + Q_INVOKABLE void deleteShaderProgramHelper(void *prog); + Q_INVOKABLE void deleteThisHelper(); +}; + +class QAndroidTextureVideoOutput : public QAndroidVideoOutput +{ + Q_OBJECT +public: + explicit QAndroidTextureVideoOutput(QObject *parent = 0); + ~QAndroidTextureVideoOutput() override; + + QAbstractVideoSurface *surface() const; + void setSurface(QAbstractVideoSurface *surface); + + AndroidSurfaceTexture *surfaceTexture() override; + + bool isReady() override; + void setVideoSize(const QSize &) override; + void stop() override; + void reset() override; + + void customEvent(QEvent *) override; + +private Q_SLOTS: + void onFrameAvailable(); + +private: + bool initSurfaceTexture(); + bool renderFrameToFbo(); + void createGLResources(); + + QMutex m_mutex; + void clearSurfaceTexture(); + + QAbstractVideoSurface *m_surface; + QSize m_nativeSize; + + AndroidSurfaceTexture *m_surfaceTexture; + + quint32 m_externalTex; + QOpenGLFramebufferObject *m_fbo; + QOpenGLShaderProgram *m_program; + OpenGLResourcesDeleter *m_glDeleter; + + bool m_surfaceTextureCanAttachToContext; + + QWindow *m_offscreenSurface = nullptr; + QOpenGLContext *m_glContext = nullptr; + + friend class AndroidTextureVideoBuffer; +}; + +QT_END_NAMESPACE + +#endif // QANDROIDVIDEOOUTPUT_H |